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

    Чтобы получить возможность писать на форуме, оставьте сообщение в этой теме.
    Удачи!
  • Друзья, доброго времени суток! Спешите принять участие в конкурсе "Таинственные миры" 2024!
    Ждем именно вас!

    Ссылка на конкурсную тему - тык

Важно Создание скриптовой зацикленной функции при помощи Trigger-Script (ZEN)

Myxomop

Почетный форумчанин
Регистрация
28 Май 2005
Сообщения
3.235
Благодарности
2.575
Баллы
455
Решил переписать в отдельный развернутый урок, старый и частично поврежденный после переезда форума ответ.

Создание скриптовой зацикленной функции при помощи Trigger-Script (ZEN)

-----------------------------------

Зачем это нужно?
С помощью этой циклической связки скриптов игры и триггера мира (ZEN) можно в реальном времени отслеживать текущие параметры и состояния ГГ, ставить на них условия и возможные реакции, а также много чего другого.

Принцип действия.
После запуска игры (модификации) из скриптов игры вызывается триггер, расположенный в мире (ZEN) игры, который в свою очередь через установленный промежуток времени снова вызывает скриптовую функцию и выполняет все что в ней написано. И так по кругу, т.е. зациклено.

-----------------------------------

Работа в Spacer.
Создание скриптового триггера в Spacer.

Запускаем Spacer и грузим в него свой ZEN.

Входим в режим обзора (F3) и летим куда-нибудь в центр мира.
(Триггер можно разместить в ЛЮБОМ месте мира, например в небе, его все равно видно не будет.)

Выходим из режима обзора и заходим в панель Objects, выделяем вкладку Create.

Далее раскрываем: zCTriggerBase (abstract) -> zCTrigger и выделяем oCTriggerScript как на скриншоте ниже:

trig1.jpg


После этого на свободном пространстве мира жмем правой кнопкой мыши и в появившемся контекстном меню жмем на Insert[oCTriggerScript].

trig2.jpg


Если все получилось, панель Objects поменяет вкладку на Modify, в которой и будем настраивать наш свежесозданный Trigger-Script.

trig3.jpg


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

Рассмотрим более детально.

vobName: CYCLE_TRIGGER //Имя триггера.
Должно в точности соответствовать вызываемому триггеру из скриптовой функции B_CYCLE_FUNCTION, которая будет описана ниже.

ActivationFilter: //Фильтр срабатывания.
Поставить все значения в TRUE, иначе не будет работать, все остальные параметры в рамке тоже по скриншоту.

FireBehavior: //Скорость реакции
fireDelaySec:0.1 //Время ответа триггера для вызова скриптовой функции, т.е. время цикла, в данный момент 1/10 секунды.
sendUntrigger:TRUE

scriptFunc:B_CYCLE_FUNCTION //Собственно, сама вызываемая зацикленная функция, в скриптах может быть только типа void

После не забываем сохранить ZEN! :)

-----------------------------------

Работа со скриптами.
Создание функции, вызываемой триггером, можно разместить где-угодно, но не после startup.d

func void B_CYCLE_FUNCTION()
{
if(hero.attribute[ATR_HITPOINTS_MAX] == 0) //Проверка, загружен ли ГГ в мир игры. Если игра еще не загрузила ГГ в игру, значение максимальной жизни ГГ будет равно 0 и вызов триггера будет происходить до выполнения своих нужных функций. Это проверка необходима, т.к. если ГГ еще не загружен в игру и функция начнет полную отработку, возможен вылет из игры.
{
Wld_SendTrigger("CYCLE_TRIGGER"); //Вызов триггера c именем CYCLE_TRIGGER для зацикливания всей функции.
return; //Выход из функции.
};

//Если ГГ уже загружен в игру, дальше идет полная отработка цикла, куда можно вставлять множество своих нужных функций...

MyFunction1();
MyFunction2();
MyFunction3();

Wld_SendTrigger("CYCLE_TRIGGER"); //Вызов триггера c именем CYCLE_TRIGGER для зацикливания всей функции.
};

----

Запуск скриптовой функции из startup.d

func void init_world() //функция инициализации мира, срабатывает при каждой загрузке мира, в том числе из сохранения игры, где world является именем WORLD.ZEN.
{
B_CYCLE_FUNCTION(); //вызов описанной ранее скриптовой функции при инициализации мира.
};

Обновил скриншоты и немного дополнил комментарии для лучшего понимания работы цикла.
 
Последнее редактирование модератором:

Myxomop

Почетный форумчанин
Регистрация
28 Май 2005
Сообщения
3.235
Благодарности
2.575
Баллы
455
Бонус, к вышеописанному. Возможно кому-нить пригодится.

Собственно обычные часы :D
Определяет внутриигровое время с точностью до минуты с возможностью вывода на экран. А кому нужно может и добавлять время.

Принцип работы:
Фунция часов B_GetTime вставленная в зацикленный триггер-скрипт по очереди перебирает все значения часов и минут и если находит текщее, то выводит на экран. Чем меньше время цикла триггер-скрипта, тем точнее определяют время часы. Рекомендуемые значения ответа триггера 1 секунда или меньше.

Daedalus:
//Глобальные переменные.
var int time_hour; //Перебор часов.
var int time_minutes; //Перебор минут.

func void B_GetTime()
{
    var string concatText;
 
    var int t;
    var int m;
 
    if(Wld_IsTime(time_hour,0,time_hour,59)) //Если текущее значение time_hour находится в пределах текущего времени игры.
    {
        t = time_hour; //t = текущему часу в игре.
    };
    if(Wld_IsTime(t,time_minutes,t,time_minutes + 2))  //Если текущее значение time_minutes находится в пределах текущего часа и между границами минут в игре.
    {
        m = time_minutes + 1;  //m = текущей минуте.
    };
 
    time_hour += 1; //Перебор всех часов
    time_minutes += 1;  //Перебор всех минут
 
    if(time_hour >= 24)
    {
        time_hour = 0;
    };
    if(time_minutes >= 60)
    {
        time_minutes = 0;
    };
 
    //Построение индикатора вида ЧЧ:ММ
    concatText = "";
    if(t < 10) //Если значение часов меньше 10, например 9:00
    {
        concatText = ConcatStrings(concatText, "0"); //Перед цифрой часов добавляется 0 (нуль), 09:00
    };
    concatText = ConcatStrings(concatText, IntToString(t));
 
    concatText = ConcatStrings(concatText,":");
 
    if(m < 10) //Если значение минут меньше 10
    {
        concatText = ConcatStrings(concatText, "0"); //Перед цифрой минут добавляется 0 (нуль)
    };
    concatText = ConcatStrings(concatText, IntToString(m));
    PrintScreen(concatText,50,90,FONT_ScreenSmall,1);
};
 

Phantom95

Участник форума
Регистрация
31 Июл 2014
Сообщения
2.226
Благодарности
1.907
Баллы
370
Небольшое дополнение, чтоб часы отображались в правом верхнем углу нужно прописать в функции PrintScreen числа "96" и "1" заместо "50" и "90" соответственно
 

Kreol Nekr

Участник форума
Регистрация
21 Ноя 2014
Сообщения
229
Благодарности
109
Баллы
190
Обьясните пожалуйста, какой триггер и какая у него должна быть функция, чтобы он срабатывал после взятия квеста в определенное время и появлялась запись в дневнике?
Я поставил скриптовый зацикленный триггер,и написал ему вот такую функцию:

func void EVT_GLOBALTRIGGER_FUNC()
{
if((hero.attribute[ATR_HITPOINTS_MAX] == 0) && (MIS_REGADARK == LOG_Running) && Wld_IsTime(0,0,2,0))
{
B_LogEntry(TOPIC_REGADARK,"Я дождался ночи, но кажется все спокойно... Думаю, стоит подойти к Реге и поинтересоваться как ей спится.");
}
else
{
Wld_SendTrigger("GLOBAL_TRIGGER_1");
return;
};
Wld_SendTrigger("GLOBAL_TRIGGER_1");
};
Но он не срабатывает ни при загрузке мира, ни при взятии квеста ни при наступлении 12 часов.
 

Vlad_Torop

Участник форума
Регистрация
2 Май 2014
Сообщения
868
Благодарности
498
Баллы
230
На вскидку:
В условии срабатывания твоего триггера стоит hero.attribute[ATR_HITPOINTS_MAX] == 0,это значит что он сработает если ГГ не загружен в мир.Да и сам скрипт выстроен не правильно.
И вообще,как я понимаю,этот триггер нужен тебе лишь на время действия миссии,так что в глобальной зацикленной функции нет необходимости.Проще создать обычный триггер-скрипт,и запустить его в диалоге,где берется миссия.
А сам скрипт триггера можно переделать так:
func void EVT_GLOBALTRIGGER_FUNC()
{
if(MIS_REGADARK == LOG_Running) && Wld_IsTime(0,0,2,0))
{
B_LogEntry(TOPIC_REGADARK,"Я дождался ночи, но кажется все спокойно... Думаю, стоит подойти к Реге и поинтересоваться как ей спится.");
}
else
{
Wld_SendTrigger("GLOBAL_TRIGGER_1");
};
 
Последнее редактирование:

Kreol Nekr

Участник форума
Регистрация
21 Ноя 2014
Сообщения
229
Благодарности
109
Баллы
190
На вскидку:
В условии срабатывания твоего триггера стоит hero.attribute[ATR_HITPOINTS_MAX] == 0,это значит что он сработает если ГГ не загружен в мир.Да и сам скрипт выстроен не правильно.
И вообще,как я понимаю,этот триггер нужен тебе лишь на время действия миссии,так что в глобальной зацикленной функции нет необходимости.Проще создать обычный триггер-скрипт,и запустить его в диалоге,где берется миссия.
А сам скрипт триггера можно переделать так:
func void EVT_GLOBALTRIGGER_FUNC()
{
if(MIS_REGADARK == LOG_Running) && Wld_IsTime(0,0,2,0))
{
B_LogEntry(TOPIC_REGADARK,"Я дождался ночи, но кажется все спокойно... Думаю, стоит подойти к Реге и поинтересоваться как ей спится.");
}
else
{
Wld_SendTrigger("GLOBAL_TRIGGER_1");
};
Ну спасибо, попробуем... Только вот не совсем понял, как его к диалогу привязать?
 

Vlad_Torop

Участник форума
Регистрация
2 Май 2014
Сообщения
868
Благодарности
498
Баллы
230

Myxomop

Почетный форумчанин
Регистрация
28 Май 2005
Сообщения
3.235
Благодарности
2.575
Баллы
455
Еще одно дополнение к циклическому триггеру.

Проверка использования в игре актуальных сохранений для Г2-НВ, дабы "обезопасить" пользователя от возможных багов при использовании сохранений от несовместимых версий мода.

В Startup.d пишем глобальные константу и переменную:
Код:
const int mod_version_start = 22052017; //Не изменяемая константа отвечающая за текущую версию мода, например в виде: число, месяц, год.
var int mod_version_save; //Переменная, которой присваивается текущая константа при первой загрузке локации.

Код:
func void startup_global()
{
    Game_InitGerman();
    mod_version_save = mod_version_start; //Присваиваем переменной значение константы при первом запуске игры.
};

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

Код:
if(mod_version_save != mod_version_start) //Если версия сохранений отличается от версии мода.
{
 //Просим начать новую игру, или другие различные наказания :D
};
 

Myxomop

Почетный форумчанин
Регистрация
28 Май 2005
Сообщения
3.235
Благодарности
2.575
Баллы
455
Очередное полезное дополнение к циклическому триггеру.

Позволяет бороться с читерским "выбиванием" эликсиров во время их приема при получении удара в толпе монстров.

---------------------------------------------------------------------------------
Скрипт обновлен 31.05.2017.
- Добавлен тип эликсира, теперь в теле триггера используется один скрипт для ВСЕХ эликсиров.
---------------------------------------------------------------------------------

Рассмотрим на примере эликсира силы:
Daedalus:
var int ItPo_Quantity; //создаем глобальную переменную, отвечающую за количество эликсиров силы в инвентаре.
var int ItPo_Type; //создаем глобальную переменную, отвечающую за тип употребляемого эликсира.

instance ItPo_Perm_Str(C_Item)
{
    name = NAME_TRANK;
    mainflag = ITEM_KAT_POTIONS;
    flags = ITEM_MULTI;
    value = VALUE_STRELIXIER;
    visual = "ItPo_Perm_STR.3ds";
    material = MAT_GLAS;
    on_state[0] = useitpo_perm_str;
    scemeName = "POTIONFAST";
    wear = WEAR_EFFECT;
    effect = "SPELLFX_ITEMGLIMMER";
    description = "Эликсир силы";
    text[1] = NAME_Bonus_Str;
    count[1] = STR_ELIXIER;
    text[5] = NAME_Value;
    count[5] = VALUE_STRELIXIER;
};

func void useitpo_perm_str()
{
    B_RaiseAttribute(self,ATR_STRENGTH,STR_ELIXIER);
    if(self.id == 0) //Эликсир выпил ГГ
    {
        ItPo_Type = ItPo_Perm_Str; //Присваиваем переменной тип выпиваемого эликсира.
        ItPo_Quantity = Npc_HasItems(self,ItPo_Perm_Str);   //После приема эликсира присваиваем переменной количество оставшихся эликсиров в инвентаре ГГ.
    };
};

В Startup.d
Daedalus:
func void startup_global() //Встроенная функция движка Г2-НВ запускается один раз при старте новой игры.
{
   Game_InitGerman();
  ItPo_Type= -1;
  ItPo_Quantity = -1; //Присваиваем переменной количества эликсиров силы отрицательное значение, это нужно для правильной работы функции в теле триггер-скрипта.
};

В теле триггер-скрипта прописываем следующее.
Daedalus:
  if(!C_BodyStateContains(hero,BS_ITEMINTERACT)) //Если ГГ не взаимодействует с предметом (закончил пить) - Не обязательное условие.
  {
     if(ItPo_Quantity>= 0) //Если переменная не имеет отрицательного значения (если был выпит хоть один эликсир, если последний - значение будет равно нулю)
     {
       if(Npc_HasItems(hero,ItPo_Type) > ItPo_Quantity) //Если в инвентаре ГГ эликсиров силы больше чем должно быть.
        {
          Npc_RemoveInvItem(hero,ItPo_Type); //Удалить из инвентаря один эликсир (т.к. функция зацикленная, с каждым циклом будут удалены все лишние "выбитые" бутылки)
        };
         if(Npc_HasItems(hero,ItPo_Type) == ItPo_Quantity) //Если количество эликсиров стало соответствовать.
        {
         ItPo_Quantity=-1; //Выключаем функцию установкой отрицательного значения до следующего приема.
        };
     };
  };


Ну и аналогичным способом нужно будет окучить все ценные эликсиры, а можно и косяки болотника заодно! ;)
 
Последнее редактирование модератором:
Сверху Снизу