Под прицелом. Сканер акторов и тактики основа.

Пришёл, увидел, пристрелил. Руки марать не захотел, поэтому приказал своим архаровцам сотворить чернуху, да. Поэтому качаем карту, и давайте подурачимся, ибо то, что я припёр вам сегодня…)))))

В одном из прошлых туториалов я обещал показать, как работают вот эти штуки. Полностью ли я в них разобрался? Нет. Поверхностное знание языка и гуглопереводчик помогают, но статьи о функциях Pointer контринтуитивны. Но зато родился механизм, который работает, и сейчас я им поделюсь. Спойлер: оба механизма связаны с тем, что игрок видит в прицеле, а значит, так или иначе может это контролировать. И перед чтением этой статьи настоятельно рекомендую прочитать ЭТУ.

Также призываю всех, кто знает о данном функционале больше, предлагать улучшения, рекомендации, и вообще стать соавторами статьи.

Сканируем видимых акторов.

script 1 ENTER {
Thing_ChangeTID(0, 999);
ACS_Execute(3, 0);
ACS_Execute(2, 0);
}
script 2 (void){
SetActivator(999, AAPTR_GET_LINETARGET);
if (GetActorProperty(0, APROP_Health)>0) {
Delay(35);
PrintBold(s:"Class: ", s:GetActorClass(0), s:"\n HP: ", i:GetActorProperty(0, APROP_Health), s:" \nSpeed: ", i:GetActorProperty(0, APROP_Speed)>>8);
}
else
Delay(1);
Restart;
}

Первый скрипт мало того, что запускается автоматически и запускает ещё два, так он же делает важное — присваивает игроку ТИД. Потому что без него не работает первая строка второго скрипта:

SetActivator(999, AAPTR_GET_LINETARGET); — строка делает активатором того, кто в прицеле у игрока 999.

if (GetActorProperty(0, APROP_Health)>0) — это условие, при котором будет выводиться информация на экран. Проверяет оно здоровье того, кто у нас в прицеле. Если эту строчку (и else) убрать из скрипта, он будет работать, но перед глазами будет всегда висеть результат сканирования пустоты

Delay(35); — вроде очевидная вещь, но почему секунда? Да просто меньше будет спама в консоли от этого скрипта, да и реализьму поболее, поскольку сканирование не мгновенное. Поставьте задержку в соответствие с вашими взглядами.

PrintBold(s:»Class: «, s:GetActorClass(0), s:»\n HP: «, i:GetActorProperty(0, APROP_Health), s:» \nSpeed: «, i:GetActorProperty(0, APROP_Speed)>>8); — тут я сделал именно «принт» для простоты, но вы можете использовать и HudMessage. Посмотрите, главное, что внутри:

s:»Class: «, s:GetActorClass(0) — пишет словом (потому что s:) слово «Class:» (не забудьте пробел после двоеточия, а то сольётся в одно слово) и сам класс актора, на которого мы нацелились (тоже словом, поскольку s:).

s:»\n HP: «, — пишем на СЛЕДУЮЩЕЙ СТРОКЕ (ибо \n) буквы «HP: «, и снова не забудьте пробел после двоеточия, иначе сольётся в одно слово.

i:GetActorProperty(0, APROP_Health) — выводит значение здоровья актора числом (ибо i:)

s:» \nSpeed: «, i:GetActorProperty(0, APROP_Speed)>>8); — до кучи выводит значение скорости передвижения монстра. Обратите внимание на сдвиг >>8, об этой штуке я уже рассказывал. Просто скорость возвращается в виде конского целочисленного значения.

else Delay(1); Restart; — если не будет этого, скрипт не будет работать циклично, то есть он лишь единожды проверит прицел и всё.

Теперь, наводясь на бочки и монстров, мы можем видеть их название, здоровье и скорость. По тому же принципу можно добавить сбор другой инфы, например, ТИД вывести вместо скорости, либо вообще убрать скорость. Чтобы узнать, какую инфу об акторе может вернуть функция, просто начните писать APROP или СЮДА загляните.

Как я уже писал выше, можете предложить улучшения, которые будут дописаны в статью от вашего имени. А я предлагаю перейти…

К РАССТРЕЛУ!

Расстрелу напарниками выбранного монстряка.

То, что написано ниже, является основой для превращения Doom в тактический шутер, не создавая никаких дополнительных модов.

Script 3 (void)
{
if (GetPlayerInput(-1, INPUT_BUTTONS) == BT_use)
{print(s:"ATTACK");
Delay(1);
ACS_Execute(4, 0);
}
else{
Delay(1);
}
Restart;
}

Script 4 (void)
{
int oldTID, target;
if(SetActivator(0,AAPTR_PLAYER_GETTARGET))
{
oldTID = ActivatorTID(); 
target = UniqueTID(); 
Thing_ChangeTID(0, target);
SetActorFlag(target, "BRIGHT", TRUE);
SetActivator(111); 
SetPointer(AAPTR_TARGET, target);
Thing_hate(111, target, 5);
SetActorFlag(111, "NOTARGETSWITCH", TRUE);
Delay(30);
SetActorFlag(target, "BRIGHT", FALSE);
Thing_ChangeTID(target,oldTID);
}
}

Третий скрипт можно перевести, как: если нажатая игроком клавиша означает «использовать», то мы видим надпись «В АТАКУ» и запускаем четвёртый сценарий, в противном случае не делаем ничего, и перезапускаем вкрипт через минимальную задержку в ожидании следующего действия. Интересен скрипт 4, который я спёр из Вики и слегка переработал.

int oldTID, target; — задаём 2 переменные, чтобы запомнить тид нашей цели (oldTID) и присвоить новый (target)

if(SetActivator(0,AAPTR_PLAYER_GETTARGET)) — это условие, при котором последующие действия будут направлены на нашу цель. AAPTR_PLAYER_GETTARGET в нашем случае работает так же, как AAPTR_GET_LINETARGET, они во втором скрипте взаимозаменяемы. Разница, вроде, в том, что LINETARGET может брать цель, выбранную монстром, но у нас тут другой случай.

oldTID = ActivatorTID(); target = UniqueTID(); — эти строки присваивают нашим переменным значение и скрипт запоминает их. UniqueTID() — эта штука присваивает нашей цели некий случайный тэг, которого больше нет ни у кого, дабы наши архаровцы (о них ниже) могли распознать конкретную цель.

Thing_ChangeTID(0, target); — присваиваем цели, на которую натравим напарников, тид target, который мы сгенерировали строкой ранее.

SetActorFlag(target, «BRIGHT», TRUE); а вот эта функция очень полезна. При создании акторов в декорейте мы можем задавать им поведение и свойства при помощи флагов. Но это не на всю жизнь. Видите ли, мы можем менять их скриптом на лету. Конкретно здесь я делаю целевого актора полностью ярким, чтобы мы видели, на кого нацелились. Как вариант — можно изменить стиль его рендера на полупрозрачный или перекрасить в другие цвета функцией SetConversation, но я остановился на этом. Полная яркость спрайта.

SetActivator(111); — а вот теперь в бой вступают наши рядовые с тидом 111

SetPointer(AAPTR_TARGET, target); — наша цель должна стать их целью! (Если кто-то может перевести на нормальный русский язык текст статьи — буду премного благодарен, ибо то, что я могу там прочитать, не укладывается ни в какое понимание, уж простите.)

Thing_hate(111, target, 5); — эту строку тоже пришлось добавить из-за следующей. Она позволяет переключать акторов-напарников на другую цель. Синтаксис означает: акторы с тидом 111 ненавидят и стремятся уничтожить цель target по протоколу 5, который заставляет монстров накидываться на цель сразу и не обращать внимание на игрока. Без этой строчки наши архаровцы не прекращают гасить того, кого выбрали (в т.ч. и сами), потому что:

SetActorFlag(111, «NOTARGETSWITCH», TRUE); — мы присваиваем им флаг «не менять цель до тех пор, пока выбранная цель не мертва». То есть если вы заставите их валить Кибердемона, то смыслом их жизни вплоть до смерти будет он и только он.

Delay(30); SetActorFlag(target, «BRIGHT», FALSE); Thing_ChangeTID(target,oldTID); — ну а через секунду после нашей команды мы сбрасываем яркость монстра на дефолтную (иначе так и будет ярким, и всякие while APROP_Health > 0 почему-то не помогли). Также монстру вернётся его прежний тид, поскольку он мог быть ещё нужен для чего-то другого.

Ну что, понятно, что происходит? На нашей карте расставлены монстры, бочки, пушки и несколько морпехов, дружественных нам. У последних тэг 111. Наводясь на объекты (но не на то, что можно подобрать, как патроны и оружие), мы с задержкой получаем информацию об объекте или субъекте. Если теперь мы нажмём кнопочку «использовать» (ту самую, которой щёлкаем по выключателям, двери открываем и так далее), глядя на монстра или бочку, то наши морпехи будут расстреливать цель, игнорируя всех остальных до тех пор, пока цель не уничтожена.

Вот так можно реализовать принцип «делегируй или умри», а руководствуясь данным принципом, можно попробовать ставить морпехов на какие-нибудь позиции.

Сам пока не делал, но приблизительная теория того, как это реализовать. Создаём актора в виде, например, стрелочки, который должен иметь флаги shootable и solid и НЕ иметь флага noblockmap, и пусть у него будет класс position. Задаём в скрипте захвата клавиш такое условие, при котором, если класс актора соответствует нашей стрелочке (position), наш напарник должен к ней ThingSet_Goal, а для этого на ту же клетку ставим патруль-точку с тидом, как у стрелки. После этой команды уберём флаги со стрелки, чтобы морпех туда вообще встал. И пусть до следующей команды скорость этого морпеха станет нулевой — скомандуем это тогда, когда он достигнет точки и, к примеру, окажется в некоем секторе. Всё, чувак на позиции и никуда не денется оттуда до самой смерти. Дайте условие, чтобы при щелчке на самого напарника (для начала надо, чтобы при наведении считывался, например, его тид) восстанавливалась его скорость, а другим скриптом при покидании сектора восстанавливалась стрелка.

Кстати, при щелчке на самих морпехов вы не сможете заставить их атаковать друг друга, ибо дружественные дружественных не трогают почему-то. Но это равносильно команде «ОТСТАВИТЬ!», они отпустят свою цель, чуть тупанут и начнут первого попавшегося врага RIP AND TEAR!!!!!!

P.S.: если не хотите, чтобы каждый раз при нажатии Use выскакивала надпись Attack, перенесите её куда-нибудь в скрипт 4, попутно сменив Print на Printbold, иначе вашу надпись увидит только NPC, но, ручаюсь, никому не расскажет... 
avatar