• Уважаемые гости и новички, приветствуем Вас на нашем форуме
    Здесь вы можете найти ответы практически на все свои вопросы о серии игр «Готика» (в том числе различных модах на нее), «Ведьмак», «Ризен», «Древние свитки», «Эра дракона» и о многих других играх. Можете также узнать свежие новости о разработке новых проектов, восхититься творчеством наших форумчан, либо самим показать, что вы умеете. Ну и наконец, можете обсудить общие увлечения или просто весело пообщаться с посетителями «Таверны».

    Чтобы получить возможность писать на форуме, оставьте сообщение в этой теме.
    Удачи!

В разработке... триггеры на юнион

MW 7


Модостроитель
Регистрация
26 Мар 2004
Сообщения
2.000
Благодарности
968
Баллы
295
Традиционно я начну со слов благодарности Gratt который лишает нас костылей.​

Для работы Триггеров потребуется последняя версия юнион вкоторую входит zparser. Ссылка на тему с юнион Gothic ½ - Union (патч для Gothic)

где нибудь в файле скриптов пропишете класс триггера и две инстанции
Daedalus:
//класс триггера
class C_Trigger
{
  var int Delay;
  var int Enabled;
  var int AIVariables[16];
};
 
instance SelfTrigger (C_Trigger) {};
instance FirstTrigger (C_Trigger) {};

Локальныйглобальный
в локальном триггере может быть записано 1-3 НПС
  1. NPC, который при вызове функции будет помещаться в self.
  2. NPC, который при вызове функции будет помещаться в other.
  3. NPC, который при вызове функции будет помещаться в victim.
в глобальном триггере нет записанных НПС
AI_StartTriggerScriptEx("NPC_Trigger_Attribute_TimeBonus", 1, hero, null, null)AI_StartTriggerScriptEx("NPC_Trigger_Attribute_TimeBonus", 1, null, null, null)
AI_StartTriggerScript("NPC_Trigger_Attribute_TimeBonus", 1)
подробнее Gothic ½ - Расширение возможностей парсера | zParserExtender [плагин для Union]
 
Последнее редактирование:

MW 7


Модостроитель
Регистрация
26 Мар 2004
Сообщения
2.000
Благодарности
968
Баллы
295
Баффы от зелий

Баффы будем реализовывать на примере зелий повышающих на время атрибуты НПС.
Инстанции зелий
Пропишем инстанции новых зелий. В инстанции зелий я указываю параметры для баффа.
Daedalus:
instance ItPo_Bonus_MANA(C_Item)
{
    ...
    ...
 
    on_state[0] = UseItPo_TimeBonus;

    cond_atr[1] = ATR_MANA;           // атрибут который меняем
 
    TEXT[1]     = NAME_Bonus_MANA;      COUNT[1] =     MANA_Bonus_Elixier;     // на сколько меняется атрибут
    TEXT[2]     = NAME_Sec_Duration;    COUNT[2] =     Time_Bonus_MANA;        // сколько секунд длится эффект
};
функцию use*** делаю общую для всех зелий, беря данные для баффа уже из инстанции зелья.
Daedalus:
    func void UseItPo_TimeBonus()
    {
        Start_Trigger_Attribute_TimeBonus(self, item.cond_atr[1], item.COUNT[1], item.COUNT[2]);
    };

функции запускающей триггер
Первое на что стоит обратить внимание это что триггер может быть глобальный, то есть тот который действует во всех мирах и локальный который "привязан" к НПС и действует в конкретном мире. По рекомендации Гратта будем использовать для героя глобальные триггеры. Поэтому я сделаю проверку ссылки на НПС, если окажется что slf это герой я просто обнулю ссылку, сделав её "пустой".
Daedalus:
if (Npc_IsPlayer(slf))
    {
        // Обнуляем slf  --> Триггер будет глобальным
        slf = Hlp_GetNpc(-1);
    };
далее ставим запуск триггера на "старт" и задаём ему наши свойства:
Daedalus:
trigger = AI_StartTriggerScriptEx("NPC_Trigger_Attribute_TimeBonus", 1, slf, null, null);
trigger.AIVariables[0] = time;           // число повторов(секунд) триггера
trigger.AIVariables[1] = attribute;      // атрибут который будем изменять
trigger.AIVariables[2] = value;          // значение на которое будем менять атрибута
приведу полный код функции запуска. в ней есть закомментированный фрагмент который я использовал в прошлой версии.
Daedalus:
func void Start_Trigger_Attribute_TimeBonus(var c_npc slf,var int attribute,var int value,var int time)
{
    var C_Trigger trigger;
 
    /*
    if (Npc_IsPlayer(slf))
    {
        // Глобальный Триггер для Героя
        trigger = AI_StartTriggerScript("Hero_Trigger_Attribute_TimeBonus", 1); // Global trigger
    }
    else
    {
        // локальный триггер для других НПС
        trigger = AI_StartTriggerScriptEx("NPC_Trigger_Attribute_TimeBonus", 1, slf, null, null); // local trigger
    };
    */
 
    if (Npc_IsPlayer(slf))
    {
        // Обнуляем slf  --> Триггер будет глобальным
        slf = Hlp_GetNpc(-1);
    };
    trigger = AI_StartTriggerScriptEx("NPC_Trigger_Attribute_TimeBonus", 1, slf, null, null);

    //trigger.Delay = 1000;                       // частота вызова триггера. 1000 это раз в секунду. Буду устанавливать внутри NPC_Trigger_Attribute_TimeBonus
     trigger.AIVariables[0] = time;           // число повторов(секунд) триггера
    trigger.AIVariables[1] = attribute;      // атрибут который будем изменять
     trigger.AIVariables[2] = value;          // значение на которое будем менять атрибута
 
    Hlp_PrintConsole(Str_Format(" --> NPC_Trigger_Attribute_TimeBonus slf = %s; time = %i", slf.name, time));
};

функция цикл loop
Важно(!)
ничего не мешает сделать две разные функции, одну для героя, другую для прочих НПС(если это вообще нужно). скорее всего это даже будет правильным.
Ввиду того что в данном примере используется общий цикл луп( и для локального и для глобального триггеров) нужно будет добавить проверка на "self". в глобальном триггере "self" будет пустой.
Daedalus:
func int NPC_Trigger_Attribute_TimeBonus()
{
    var C_Npc npc;
    if (Hlp_IsValidNpc(self))     { npc = Hlp_GetNpc(self);  }
    else                          { npc = Hlp_GetNpc(hero);  };

в первой итерации я делаю проверку на то не запускается ли триггер повторно, это можно сделать проверив соотв. аивиир у НПС или создав у него уникальный предмет. при повторном запуске триггера можно что нибудь сделать с НПС, а триггер уничтожить. так же в первой итерации я устанавливаю частоту вызова триггера, повышаю атрибут и создаю уникальный предмет-маркер.
Daedalus:
    // первая итерация
    if (SelfTrigger.Delay < 1000)
    {
        if Npc_GetInvItem(npc,ItPo_Bonus_Active)
        {
            // у НПС есть ItPo_Bonus_Active, значит это повторный вызов Триггера
            B_KillNPCItPo(npc);
            return LOOP_END;
        };
 
        SelfTrigger.Delay = 1000; // раз в секунду
        CreateInvItem(npc,ItPo_Bonus_Active);
        B_RaiseAttributeTimeBonus(npc,SelfTrigger.AIVariables[1],SelfTrigger.AIVariables[2]);
    };

При выходе из триггера, я удаляю уникальный предмет и изменяю атрибуты НПС.
Daedalus:
    // выход
    if (SelfTrigger.AIVariables[0] <= 0)
    {
        Npc_RemoveInvItem(npc,ItPo_Bonus_Active);
        B_RaiseAttributeTimeBonus(npc,SelfTrigger.AIVariables[1], -SelfTrigger.AIVariables[2]);
        return LOOP_END;
    };
уменьшение кол-во запусков триггера и LOOP_CONTINUE
Daedalus:
    SelfTrigger.AIVariables[0] -= 1;        // счётчик времени

    return LOOP_CONTINUE;
};

вот в принципе и всё. файлы работающего проекта в скрепке.
 

Вложения

  • 2023_12_03_Trigger_Attribute_TimeBonus.zip
    4,7 KB · Просмотры: 4
Последнее редактирование:

MW 7


Модостроитель
Регистрация
26 Мар 2004
Сообщения
2.000
Благодарности
968
Баллы
295
Давайте сразу же в качестве примера сделаем какой просто триггер, который будем нам решать сюжетную задачу.

Лорд Андре и Гильдия воров
Итак, герой докладывает Андре о том что нашёл гильдию воров, после чего Лорд Андре даёт герою пару дней на ликвидацию гильдии. Будем прописывать запуск триггер прям из функции диалога
Daedalus:
func void DIA_Andre_DGRunning_Verrat()
{ 
      ...
  
    // запуск триггера
    var C_Trigger trigger;
    trigger = AI_StartTriggerScriptEx("Trigger_Andre_DGRunning_Verrat_loop", 1, self, null, null);
    trigger.Delay = 60000;                  // частота вызова триггера. раз в минуту.
    trigger.AIVariables[0] = Wld_GetDay();  // в триггер записываем текущий день
};

в самой функции триггера делаем простую проверку на то прошло ли уже нужное кол-во дней, после чего отключаем триггер :)
Код:
func int Trigger_Andre_DGRunning_Verrat_loop()
{
    // self это Андре
  
    if (SelfTrigger.AIVariables[0] <= (Wld_GetDay() - 2))
    {
        ...
        return LOOP_END;
    };
    return LOOP_CONTINUE;
};

вот и всё:) рабочий пример в скрепке
 

Вложения

  • Trigger_Andre_DGRunning_Verrat_loop.zip
    1,8 KB · Просмотры: 1

MW 7


Модостроитель
Регистрация
26 Мар 2004
Сообщения
2.000
Благодарности
968
Баллы
295
простенький триггер для "душного" платья из мода Велая
Daedalus:
const string PRINT_LUFT_RENGARU = "Воздух: ";
var int  DRESS_LUFT_counter;

instance ITMI_RENGARU_DRESS(C_ITEM)
{
    NAME = "Хлам Ренгару";
    MAINFLAG = ITEM_KAT_ARMOR;
    FLAGS = 0;
    VALUE = 25;
    WEAR = WEAR_TORSO;
    ON_EQUIP = EQUIP_KEINELUFT;
    ON_UNEQUIP = UNEQUIP_KEINELUFT;
    VISUAL = "ItAr_VLKBabe.3DS";
    VISUAL_CHANGE = "Armor_VlkBabe_H.asc";
    VISUAL_SKIN = 0;
    MATERIAL = MAT_LEATHER;
    DESCRIPTION = NAME;
    TEXT[5] = NAME_VALUE;   COUNT[5] = VALUE;
};

func void EQUIP_KEINELUFT()
{
    PRINTSCREEN("Платье туговато...", -1, -1, FONT_SCREEN, 2);
    
    var C_Trigger trigger;
    trigger = AI_StartTriggerScriptEx("EQUIP_KEINELUFT_loop", 1, self, null, null);
    trigger.Delay = 900; 
    DRESS_LUFT_counter = 20;
};

func void UNEQUIP_KEINELUFT()
{
    DRESS_LUFT_counter = 0;
};

func void B_GetLuftString2024(var int TIMELEFT, var int new)
{
    var string print_msg;
    
    if (new == true)     { print_msg = PRINT_LUFT_RENGARU; };
    
    if TIMELEFT > 0
    {
        print_msg = CONCATSTRINGS(print_msg, "I");
        //B_GetLuftString2014(TIMELEFT - 3, false);
        B_GetLuftString2024(TIMELEFT - 1, false);
    }
    else
    {
        PRINTSCREEN(print_msg, 15, 15, FONT_SCREEN, 1);
    };
};

func int EQUIP_KEINELUFT_loop()
{   
    if (DRESS_LUFT_counter <= 0)    { return LOOP_END; }; // выход
    
    //if SelfTrigger.AIVariables[0] >1;
    if DRESS_LUFT_counter > 1
    {
        //SelfTrigger.AIVariables[0] -= 1;
        DRESS_LUFT_counter -= 1;
    }
    else
    {
        NPC_CHANGEATTRIBUTE(self, ATR_HITPOINTS, -1);
    };
    
    B_GetLuftString2024(DRESS_LUFT_counter,true);
    //B_GetLuftString2024(SelfTrigger.AIVariables[0],true);

    return LOOP_CONTINUE;
};
 

MW 7


Модостроитель
Регистрация
26 Мар 2004
Сообщения
2.000
Благодарности
968
Баллы
295
горение

Пожалуй вопрос горения выпаривает мозг с выхода Готики 1. Горение я делал по разному, но на триггерах получилось сделать проще всего.

В функции C_CanNpcCollideWithSpell для нужных спеллов добавим запуск триггера
Daedalus:
func int C_CanNpcCollideWithSpell(var int spellType)
{

    ...
 
//----- Feuer -----
 
    if (spellType     == SPL_ChargeFireball)
    || (spellType      == SPL_InstantFireball)    
    || (spellType      == SPL_Firerain)    
    || (spellType     == SPL_Firebolt)    
    || (spellType     == SPL_Firestorm)
    || (spellType   == SPL_Pyrokinesis)
    || (spellType    == SPL_Deathbolt)
    || (spellType     == SPL_Deathball)
    {
        ...
        start_Trigger_MagicBurn2024(self,other,victim);    
        return COLL_DOEVERYTHING;
    };
...
...

что бы высчитать урон запишем жизни жертвы в триггер до того как заклинание их отнимет
Daedalus:
func void start_Trigger_MagicBurn2024(var c_npc slf,var c_npc  oth, var c_npc  vct)
{
        //Hlp_PrintConsole(Str_Format("   start_Trigger_MagicBurn2024[%s %i]" , slf.name, slf.id));
        var C_Trigger trigger;
        trigger = AI_StartTriggerScriptEx("Trigger_MagicBurn", 1, slf, oth, vct);
        trigger.AIVariables[0] = slf.attribute[ATR_HITPOINTS];  // кол-во жизней до того как закл их отнимет
        trigger.AIVariables[1] = 1;            // урон за повтор
        //trigger.AIVariables[2] = ***;  // число повторов триггера
};

остаётся написать сам триггер. приведу мой черновик, в котором полно лишнего кода :)
Daedalus:
func int Trigger_MagicBurn()
{ 
    var int timer2;  
 
    if (SelfTrigger.Delay != 100)
    {
        //PlayEffect_MAGICBURN(self);     
        SelfTrigger.Delay = 100; // 10 вызовов в секунду
        //timer2 = 40;    // 45 много
     
        //Wld_PlayEffect("VOB_BURN", self, self, 0, 0, DAM_FIRE, FALSE );
        if (Npc_IsDead(self) == true)
        {
            return LOOP_END;
        };
    
       SelfTrigger.AIVariables[0] -= self.attribute[ATR_HITPOINTS]; // ВЫСЧИТЫВАЕМ УРОН
       SelfTrigger.AIVariables[2] = SelfTrigger.AIVariables[0];
     
        if (Npc_IsDead(self) == false)
        && (C_NpcIsHuman(self) == true)
        && (SelfTrigger.AIVariables[0] > 30)    //
        //&&    !Npc_IsInState(self, ZS_MagicBurnHuman)
        //&&    !Npc_IsInState(self, ZS_Unconscious)
        //&&    !C_BodystateContains(self,BS_SWIM)   // проверка выше
        //&&     !C_BodystateContains(self,BS_DIVE)   // проверка выше
        //&&    self.aivar[AIV_ItemFreq] <= S_FIRE_VICTIM    // горит три секунды
        {
            Npc_ClearAIQueue(self);
            AI_StartState(self, ZS_MagicBurnHuman, 0, "");
            Hlp_PrintConsole("          Trigger_MagicBurn  --> ZS_MagicBurnHuman");
        }; 
    };
 
    SelfTrigger.AIVariables[2]  -=1;

 
    /*
    if timer == timer2 * 1
    || timer == timer2 * 2
    || timer == timer2 * 3
    || timer == timer2 * 4
    {
        Wld_PlayEffect("VOB_BURN_CHILD1", self, self, 0, 0, 0, FALSE );
    };
    */



 
    if (SelfTrigger.AIVariables[2] <= 0)    { return LOOP_END;   };
 
    if    C_BodystateContains(self,BS_SWIM)
    {
        Npc_StopAni    (self, "S_FIRE_VICTIM");    // falls der
        Hlp_PrintConsole("  Trigger_MagicBurn_LOOP_END --> C_BodystateContains(self,BS_SWIM)");
        return    LOOP_END;
    };
 
    if    C_BodystateContains(self,BS_DIVE)
    {
        Npc_StopAni    (self, "S_FIRE_VICTIM");    // falls der
        Hlp_PrintConsole("  Trigger_MagicBurn_LOOP_END --> C_BodystateContains(self,BS_DIVE");
        return    LOOP_END;
    };

     Npc_ChangeAttribute(self, ATR_HITPOINTS, - SelfTrigger.AIVariables[1]);

    if (self.attribute[ATR_HITPOINTS] <= 0)
    //|| (Npc_IsDead(self) == true)
    {
        //B_DeathXP2020(self,hero);
        B_GiveXP(self.level * XP_PER_LEVEL_DEAD);   // новый формат начисления опыта?
        Npc_StopAni    (self, "S_FIRE_VICTIM");    // falls der
        /*
        AI_StopFX(self, "VOB_BURN");
        AI_StopFX(self, "VOB_BURN_CHILD1");
        AI_StopFX(self, "VOB_BURN_CHILD2");
        AI_StopFX(self, "VOB_BURN_CHILD3");
        AI_StopFX(self, "VOB_BURN_CHILD4");
        AI_StopFX(self, "VOB_BURN_CHILD5");
        AI_StopFX(self, "VOB_BURN_CHILD6");
        */
        Wld_PlayEffect("spellFX_Firespell_HUMANSMOKE", self, self, 0, 0, 0, FALSE );
        Hlp_PrintConsole(Str_Format("      Trigger_MagicBurn[%s] --> return LOOP_END" , self.name)); 
        return Loop_end;
    };
 
    //Hlp_msg = Str_Format("      Trigger_MagicBurn[%s] :: Таймер %i :: Жизни %i", self.name, timer,self.attribute[ATR_HITPOINTS]);
    //Hlp_PrintConsole(Hlp_msg); 
 
    Hlp_msg = Str_Format("  Trigger_MagicBurn[%s] :: AIVariables[2] %i :: Жизни %i  --> LOOP_CONTINUE", self.name, SelfTrigger.AIVariables[2],self.attribute[ATR_HITPOINTS]);
    Hlp_PrintConsole(Hlp_msg);

    return LOOP_CONTINUE;
};
такой вот вполне рабочий вариант, который безусловно требует доведение до ума под те или иные задачи. поуму в триггер нужно так же добавить проверку на то не горит ли уже жертва и в таком случае отключать старый триггер. хотя кто то может считать , что наоборот, жертва должна гореть бодрее :)
кому то может казаться что жертва должна гореть долго и получать за единицу времени мало ущерба, а кто то захочет сделать горение короткое , но поднять ущерб за единицу времени. всё это можно настроить в рамках функции триггера.
 
Сверху Снизу