DukeKAn, не соглашаешься потому, что это писал ты, а мнение новичков ты вряд ли увидишь, т.к. не понимают они то о чём здесь написано, как следствие высказать мысль не о чём. Если целью всё же было объяснить принцип работы класса object_binder, то и сделай его описание начиная с теории, а не говоря о ней в конце, когда большинство новичков не дочитывают до этого момента ибо не понимают о чём речь. KoSShак, в ТЧ, например, у актора нет колбека на хит.
Хит каллбек есть, если не знаешь не говори. Я использовал его раньше но потом откозался. Так что я лучше знаю...
Ты задолбал выпендриваться скриптер фигов. Каллбек у меня есть, но он старый. В Dmx моде есть более сокращеный.
if last_health > db.actor.health + 0.008 then dmx_hit_callback(last_health - db.actor.health, db.actor:who_hit_name(), db.actor:who_hit_section_name()) end last_health = db.actor.health end
function dmx_hit_callback(amount, who_name, who_section_name) if who_section_name == "zone_mine_field" or who_section_name == "zone_mine_electra" then amk.start_timer("remove_mine_timer", 2, who_name) end end
А по поводу ПДА функции можно прикрепить и через делта апдейт.
KoSShак, ты задолбал пороть чушь. Если ты не можешь отличить коллбек от функции с аналогичным названием, скриптеры тут не при чём.
В ТЧ и ЧН используется просто проверка на уровень здоровья. За ЗП не скажу, не копал ни разу. А коллбек не висит в апдейте, а вызывается на хит по актёру.
А то, что показал ты, вообще бред какой-то. К теме это имеет мизерное отношение, т.к. функций таких море в любом моде. Есть каллбек, так покажи его, а нет, так согласись, что был не прав.
Все действия НПС в игре, которые составляют a-life, выполняются движком. Однако, движок в некоторых случаях использует скриптовые условия и выполняет некоторые действия исходя из этих условий. Более того, возможно прописать даже необходимые действий при выполнении этих условий. Связка условий и действий в сталкере называется «модель поведения».
Процесс создания модели поведения состоит из шагов: 1. Создание условий (которые называются Evaluators) 2. Создания действий (называются Actions) 3. Связывание между собой условий и действий при их выполнении 4. Регистрация модели поведения 5. Активация/деактивация модели поведения
Для того, чтобы вся конструкция обладала гибкостью и лёгкой расширяемостью, условия и действия сделаны классами. Соответственно, для создания своей схемы, нужно написать новые классы условий и действий, наследующиеся от базовых.
Движок игры постоянно проверяет все зарегистрированные условия, и в зависимости от их выполнения или невыполнения (а также приоритета) выполняет некоторые действия, которые связаны с этими условиями. Так, если ранить НПС (выполнилось условие наличия рядом врага), то он встанет, перезарядит оружие и откроет огонь в ответ (выполнилось соответствующее действие). Если же он убьёт врага, и рядом врагов тоже не будет, ему будет назначено другое действие, к примеру – сидеть у костра или встать обратно в дозор. Однако, если в схемах поведения есть логическая ошибка (не синтаксическая), то возможна ситуация, когда движок просто не может выбрать нужную схему из-за их конфликта. В этом случае игра вылетает с пустым логом и отследить ошибку бывает довольно сложно (специально для таких случаев, рекомендую прикупить хороший бубен, освящённый у шамана).
Также, надо понимать, что вовсе не все схемы скриптовые, например боёвка – движковая. Изменить такие схемы простыми скриптами не получится.
Добавлено (15.01.2014, 23:25) --------------------------------------------- Пример в оригинале.
Как пример, разберём скрипт xr_kamp.script.
В этом скрипте можно выделить 4 из 5 необходимых шагов для создания модели поведения. 1 шаг: создание условий. В скрипте это строчки между Evaluators и Actions.
Эвалуатор конца посиделок у костра
Код
class "evaluator_kamp_end" (property_evaluator) – класс условия наследуется от стандартного function evaluator_kamp_end:__init(name, storage) super (nil, name) self.a = storage -- st хранит таблицу состояний модели поведения end
метод evaluate() вызывается постоянно. Именно этот метод возвращает true или false, в зависимости от которых выполняется/не выполняется какое-то действие
Код
function evaluator_kamp_end:evaluate() return not xr_logic.is_active(self.object, self.a) end --'Эвалуатор, находится ли НПС на заданной позиции (у костра) class "evaluator_on_position" (property_evaluator) function evaluator_on_position:__init(name, storage) super (nil, name) self.a = storage end function evaluator_on_position:evaluate() if self.object:level_vertex_id() == self.a.pos_vertex then return true end return false end
2 шаг: создание действий
Действие «Идти к костру»
Код
class "action_go_position" (action_base) function action_go_position:__init (npc_name,action_name,storage) super (nil,action_name) self.a = storage end function action_go_position:initialize() action_base.initialize(self) -- self.object:set_node_evaluator() -- self.object:set_path_evaluator() self.object:set_desired_position() self.object:set_desired_direction() end function action_go_position:execute () action_base.execute (self) … end function action_go_position:finalize () action_base.finalize (self) end
Действие посиделок у костра
Код
class "action_wait" (action_base) function action_wait:__init (npc_name,action_name,storage) super (nil,action_name) self.a = storage end function action_wait:initialize() action_base.initialize(self) -- self.object:set_node_evaluator() -- self.object:set_path_evaluator() self.object:set_desired_position() self.object:set_desired_direction()
kamps[self.a.center_point]:increasePops(self.object) end function action_wait:activate_scheme() end
А вот метод execute() вызывается постоянно, именно этот метод отвечает за необходимые действия.
Код
function action_wait:execute() … end function action_wait:finalize() – вызывается при уничтожении объекта Action kamps[self.a.center_point]:decreasePops(self.object) action_base.finalize (self) end function action_wait:deactivate(npc) --вызывается при деактивации схемы kamps[self.a.center_point]:removeNpc(npc) end function action_wait:death_callback(npc) – коллбэк на смерть НПС kamps[self.a.center_point]:removeNpc(npc) end function action_wait:net_destroy(npc) – коллбэк на выход НПС в оффлайн kamps[self.a.center_point]:removeNpc(npc) end
Класс "CKampManager" напрямую не относится к схемам поведения, а выполняет служебные действия вроде поворота лицом к костру и пр.
3 шаг: связывание условий и действий
Код
---------------------------------------------------------------------------------------------------------------------- --Kamp binder ---------------------------------------------------------------------------------------------------------------------- function add_to_binder(object, ini, scheme, section, storage) local operators = {} – массив операторов (действий) local properties = {}—массив условий
local manager = object:motivation_action_manager() -- дальше идёт задание элементам массивов уникальных численных идентификаторов.
Вот теперь связываем конкретные действия с условиями. Здесь действие «Сидеть у костра» будет выполняться, если следующие условия выполняются одновременно 1. НПС жив (property_alive, true) 2. НПС в безопасности (property_danger,false) 3. Рядом нет врагов (property_enemy, false) 4. Рядом нет аномалий (property_anomaly,false) 5. НПС на нужном месте (properties["on_position”]) -, у костра, смотрит куда нужно
И ожидаемый эффект этого действия – что условие «kamp_end» станет истинным
Снова связываем условия и действия. Для действия «action_go_position» (Идти к костру) необходимо выполнение следующих условий 1. НПС жив (property_alive, true) 2. НПС в безопасности (property_danger,false) 3. Рядом нет врагов (property_enemy, false) 4. Рядом нет аномалий (property_anomaly,false) 5. НПС НЕ на нужном месте (ещё не у костра)
Ожидаемый эффект- условие «На позиции» станет истиной
Выполняется методом function set_scheme(npc, ini, scheme, section, gulag_name) Метода для деактивации схемы нет
Пропущенный 4 шаг выполняется вне схемы (регистрация схемы). Выполняется он в modules.script
Добавлено (15.01.2014, 23:26) --------------------------------------------- Пишем свою схему:
Попробуем написать свою схему. Идея: развить a-life - реализовать торговлю сталкеров между собой. Пусть на уровне будет один торговец, который подаёт объявление в сеть, и есть несколько НПС, которые подписались на объявление. Задача подписавшихся – идти к текущему местоположению торговца и при приближении у нему на расстояние меньше 2 метров – торговать.
Торговец и список подписавшихся назначаются «снаружи», т.е. схема за это не ответственна, а отвечает лишь за саму торговлю.
Т.к. пример учебный, то схему сделаем максимально простой – 1 эвалуатор (условие) и 1 действие.
Напишем свой эвалуатор. Постоянно будем проверять, не появился ли на уровне торговец.
class "evaluator_want_trade" (property_evaluator) function evaluator_want_trade:__init(name, storage) super (nil, name) self.a = storage end
-- как я уже говорил, этот метод вызывается постоянно (как апдейт), и когда он вернёт true, то будет запущено соответствующее действие
В примерах постоянно используется конструкции вида self.object:метод() self.object в данном случае – НПС (его т.н. user_data, или клиентский объект)
Код
function evaluator_want_trade:evaluate()
-- нет торговца - нет торговли if trader == nil then return false end
-- если НПС один из подписавшихся – пусть запускается действие торговли if is_subscribed(self.object:id()) then return true end
return false end
В какой-то момент, наступит такая ситуация, что на уровне появится торговец и список подписавшихся на торговлю НПС. Тогда должно выполняться такое действие: пусть НПС (если он подписавшийся) идёт к торговцу торговать. Если не подписавшийся, то НПС будет выполнять другие схемы, потому что для него не сработает эвалуатор, описанный выше.
-- Action (действие) торговли. Для торговца - просто обновление координат, для подписавшихся - перемещение к торговцу и сама торговля class "action_want_trade" (action_base) function action_want_trade:__init (npc_name,action_name,storage) super (nil,action_name) self.a = storage end
function action_want_trade:initialize() action_base.initialize(self) self.object:set_desired_position() self.object:set_desired_direction() end
function action_want_trade:activate_scheme() end
function action_want_trade:execute() action_base.execute (self)
-- если подписавшийся на объявление нашёл торговца, то пусть торгуют, иначе - пусть топает к торговцу ножками if distance_between(self.object , trader) < 2 then db.actor:give_game_news("\\n%c[255,255,0,0]"..self.object:character_name().." торгует c "..trader:character_name(), "ui\\ui_iconsTotal", Frect():set(50,40,83,47), 0, 20*1000) -- здесь нужно выполнить саму торговлю, т.е. перемещение денег и предметов от одного к другому -- торговля выполнена, сбрасываем все обязательства reset_trade() else -- Сбрасываем текущие анимации self.object:clear_animations() -- Задаём параметры движения self.object:set_detail_path_type(move.line) self.object:set_body_state(move.standing) self.object:set_movement_type(move.run) self.object:set_path_type(game_object.level_path) -- Эксперименты показали, что эта функция устанавливает скорость движения (anim.danger - --инимальная скорость, anim.free - нормальная, anim.panic - максимальная) self.object:set_mental_state(anim.free) -- Повышаем зоркость NPC self.object:set_sight(look.danger, nil, 0) self.object:remove_all_restrictions() -- здесь хитренько. Посылаем в ближайший подходящий вертекс к тому, где находится торговец. utils.send_to_nearest_accessible_vertex(self.object, trader:level_vertex_id() ) --sdb.actor:give_game_news("\\n%c[255,0,255,0]"..self.object:character_name().." идёт к "..trader:character_name(), "ui\\ui_iconsTotal", Frect():set(50,40,83,47), 0, 20*1000) end
end function action_want_trade:finalize() end function action_want_trade:deactivate(npc) end
-- если торговец погиб – то сбросить торговлю. Если погиб подписавшийся – то просто удалить его из списка подписавшихся function action_want_trade:death_callback(npc) if npc:id() == trader:id() then reset_trade() else table.remove(npc) end end function action_want_trade:net_destroy(npc) end
2 шага мы выполнили, написали условие и действие. Теперь необходимо привязать к действию наше и другие условия.
Условия для запуска действия торговли будут такими: 1. НПС жив (property_alive, true) 2. НПС в безопасности (property_danger,false) 3. Рядом нет врагов (property_enemy, false) 4. Рядом нет аномалий (property_anomaly,false) 5. НПС – один из подписавшихся
Код
---------------------------------------------------------------------------------------------------------------------- -- binder ---------------------------------------------------------------------------------------------------------------------- function add_to_binder(object, ini, scheme, section, storage) local operators = {} local properties = {}
local manager = object:motivation_action_manager()
-- Застолбим идентификаторы для эвалуаторов и экшнов properties["want_trade"] = property_want_trade
operators["trade"] = property_want_trade + 1
-- Присвоим эвалуаторам идентификаторы, что застолбили выше manager:add_evaluator (properties["want_trade"], this.evaluator_want_trade ("want_trade", storage, "want_trade"))
-- Actions (действия) + их условия и последствия local action = this.action_want_trade(object:name(),"action_trade", storage) action:add_precondition (world_property(stalker_ids.property_alive, true)) -- НПС жив action:add_precondition (world_property(stalker_ids.property_danger,false)) -- НПС не в опасности action:add_precondition (world_property(stalker_ids.property_enemy, false)) -- НПС не видит рядом врага action:add_precondition (world_property(stalker_ids.property_anomaly,false)) -- НПС не рядом с аномалией
xr_motivator.addCommonPrecondition(action) action:add_precondition (world_property(properties["want_trade"], true)) -- хочет торговать action:add_effect (world_property(properties["want_trade"], false)) -- эффект действий - не хочет торговать manager:add_action (operators["trade"], action) -- добавляем к менеджеру наше действие торговли xr_logic.subscribe_action_for_events(object, storage, action)
-- запрещаем действовать а-лайфу, если мы хотим (*) подать объявление, или готовы откликнуться на уже существующее -- (*) = (танцевать, как завещал нам дедушка Ленин) action = manager:action (xr_actions_id.alife) action:add_precondition (world_property(properties["want_trade"], false))
end
Напишем методы для активации/деактивации модели
Код
------------------------------------------------------------------------------------------------------------- -- Активация/деактивация схемы. ------------------------------------------------------------------------------------------------------------- function disable_trade(npc,scheme) local st = db.storage[npc:id()][scheme] if st then st.enabled = false end end
function set_trade(npc,ini,scheme) local st=xr_logic.assign_storage_and_bind(npc, ini, scheme, "xr_stalkers_trade") st.enabled=true end
В коде также используются некоторые служебные функции. Также, для теста были написаны функции назначения торговца и подписавшихся. Добавим их в начало скрипта
Код
---------------------------------------------------------------------------------------------- -------- Настройки ---------------------------------------------------------------------------------------------- -- переменная максимального количества подписчиков local max_subs_npcs_count = 6
-- стартовый идентификатор (остальные получим прибавлением к этому) local property_want_trade = xr_evaluators_id.stohe_kamp_base + 7
-- переменная для хранения клиентского объекта торговца local trader
-- массив для хранения НПС, которые откликнулись на объявление local subscribed_npcs = {}
-- для получения размера таблицы (или массива) function get_size(table)
local size = 0 for key, value in pairs(table) do if key ~= nil and value ~= nil then size = size + 1 end end return size end
-- сброс торговли в исходное состояние function reset_trade() subscribed_npcs = {} trader = nil end
-- подписался ли непись к торговле function is_subscribed(id) local size = get_size(subscribed_npcs) if size == 0 then return false end for i = 1, size do if subscribed_npcs[i] then if subscribed_npcs[i]:id() == id then return true end end end return false end
---------------------------------------------------------------------------------------------- -------- Тест и отладка ----------------------------------------------------------------------------------------------
-- TEST устанавливает торговцем далёкого от ГГ сталкера (расстояние - не меньше 50 м) function set_far_trader()
if trader == nil then for a=1,65534 do local sobj = alife():object(a) if sobj and IsStalker(sobj) then local obj = level.object_by_id(sobj.id) if obj ~= nil then -- если сталкер существует
if get_size(subscribed_npcs) < max_subs_npcs_count then -- если ещё не заполнили подписчиков table.insert(subscribed_npcs,obj) db.actor:give_game_news("\\n%c[255,0,255,0]"..obj:character_name().." откликнулся на предложение ", "ui\\ui_iconsTotal", Frect():set(50,40,83,47), 0, 20*1000) else -- подписчики заполнены - добавим торговца if distance_between(obj, db.actor) > 50 then trader = obj db.actor:give_game_news("\\n%c[255,0,0,255]"..trader:character_name().." : продам что-нибудь", "ui\\ui_iconsTotal", Frect():set(50, 40,83,47), 0, 20*1000) return end end
end end end else reset_trade() end
end
-- TEST устанавливает торговцем произвольного сталкера. function set_random_trader()
if trader == nil then for a=1,65534 do local sobj = alife():object(a) if sobj and IsStalker(sobj) then local obj = level.object_by_id(sobj.id) if obj ~= nil then -- если сталкер существует if get_size(subscribed_npcs) < max_subs_npcs_count then -- если ещё не заполнили подписчиков table.insert(subscribed_npcs,obj) db.actor:give_game_news("\\n%c[255,0,255,0]"..obj:character_name().." откликнулся на предложение ", "ui\\ui_iconsTotal", Frect():set(50,40,83,47), 0, 20*1000) else -- подписчики заполнены - добавим торговца trader = obj db.actor:give_game_news("\\n%c[255,0,0,255]"..trader:character_name().." : продам что-нибудь", "ui\\ui_iconsTotal", Frect():set(50, 40,83,47), 0, 20*1000) return end
end end end else reset_trade() end
end
-- TEST функция выводит схемы сталкеров function get_schemes() for a=1,65534 do local obj = alife():object(a) if obj and IsStalker(obj) then local t = level.object_by_id(obj.id) if t then local s = db.storage[t:id()] if s and s.active_scheme then db.actor:give_game_news("\\n%c[255,0,0,255]"..t:character_name().." - "..s.active_scheme, "ui\\ui_iconsTotal", Frect():set(50,40,83,47), 0, 20*1000) end end end end end
-- TEST выводит имена тех НПС, кто в списке подписавшихся и кто торговец function print_subs_npcs()
if trader then db.actor:give_game_news("\\n%c[255,255,0,255] торговец - "..trader:character_name(), "ui\\ui_iconsTotal", Frect():set(50,40,83,47), 0, 20*1000) end
local size = get_size(subscribed_npcs) if size == 0 then return false end
for i = 1, size do if subscribed_npcs[i] then db.actor:give_game_news("\\n%c[255,255,0,255]"..subscribed_npcs[i]:character_name(), "ui\\ui_iconsTotal", Frect():set(50,40,83,47), 0, 20*1000) end end
end
Теперь необходимо зарегистрировать схему в modules.script. Перед строкой «Загрузка модулей монстров» вставим следующие строки
Код
if xr_stalkers_traders then load_scheme("xr_stalkers_traders","xr_stalkers_traders",stype_stalker) end
Примечание. Конструкции вида if название_файла_скрипта then позволяют проверять, существует ли файл скрипта, и нет ли в нём ошибок. Это позволяет, в нашем случае, убрать схему из мода, просто удалив её файл скрипта.
Для того, чтобы схема запускалась автоматом, её можно прописать в xr_logic.script В функцию enable_generic_schemes(ini, npc, stype, section) перед строкой elseif stype == modules.stype_mobile then вставляем
Код
if xr_stalkers_traders then xr_stalkers_traders.set_trade(npc,ini,"xr_stalkers_traders") end
В функцию disable_generic_schemes(npc, stype) перед строкой elseif stype == modules.stype_mobile then вставляем
Код
if xr_stalkers_traders then xr_stalkers_traders.disable_trade(npc,"xr_stalkers_traders") end
Для назначения торговца и подписавшихся используем функции в начале скрипта. Вызываем их из главного меню, для этого в функции ui_main_menu. main_menu:OnKeyboard(dik, keyboard_action) после строки if keyboard_action == ui_events.WINDOW_KEY_PRESSED then вставим
Код
if level.present() and (db.actor ~= nil) and db.actor:alive() then if dik == DIK_keys.DIK_NUMPAD1 then xr_stalkers_traders.set_random_trader() end end
if level.present() and (db.actor ~= nil) and db.actor:alive() then if dik == DIK_keys.DIK_NUMPAD2 then xr_stalkers_traders.get_schemes() end end
if level.present() and (db.actor ~= nil) and db.actor:alive() then if dik == DIK_keys.DIK_NUMPAD3 then xr_stalkers_traders.print_subs_npcs() end end
if level.present() and (db.actor ~= nil) and db.actor:alive() then if dik == DIK_keys.DIK_NUMPAD4 then xr_stalkers_traders.set_far_trader() end end
Тогда нажатие в меню 1 - назначит произвольного торговца 2 – вывести схемы сталкеров 3 – вывести имя торговца и подписавшихся 4 – установить далёкого от ГГ НПС торговцем
Если сейчас попробовать схему в действии, то нажав в главном меню 4, находясь в деревне на кордоне, мы увидим, что НПС рядом с костром начинают танцевать возле костра. Случается это потому, что для них одновременно истинными становятся условия «рассесться у костра» и «пойти торговать». В итоге для них постоянно выполняется переключение между соответствующими действиями, и НПС крутятся на месте. Чтобы НПС всё-таки пошли к торговцу, в данном случае мы добавим для действия «сидеть у костра» и «идти к костру» предусловие того, что НПС не хочет в данный момент торговать.
Найдём в xr_kamp.script строчки xr_motivator.addCommonPrecondition(action) и после них добавим
«xr_evaluators_id.stohe_kamp_base + 7» - это как раз численный идентификатор, который относится к нашему предусловию «хочет торговать».
Запускаем игру, назначаем торговца (лучше дальнего НПС от ГГ, кнопка 4 в главном меню), и смотрим, как подписавшиеся НПС побежали к торговцу. Как первый из них добежит, пишет объявление в сеть и все подписавшиеся топают обратно.
Что ж, пример действия схем готов, теперь разберём некоторые недостатки, которые не позволяют назвать ещё эту схему модом. 1. Торговли-то и не происходит, НПС просто отписываются в сеть, что подошли к ГГ. Перемещение предметов и денег ещё нужно дописать. 2. Торговец не пишет, что хочет конкретно хочет продать. 3. Отношение здесь 1 ко многим (1 торговец, много подписавшихся). В реальности, разумеется, объявление могут подать много торговцев одновременно и у каждого много подписавшихся, которые ещё и подписываются на многих торговцев. 4. Для теста подают объявление и подписываются случайные НПС. Разумеется, надо написать дельный скрипт для управления торговцами и подписавшимися. 5. Не учитываются отношения торговца и НПС. Так что бывают печальные случаи, когда НПС бегут к торговцу на блокпост. Разумеется, гиблая затея.
В следующий раз разберём что-нибудь попроще) Для тех, кто не хочет мучиться со вставкой фрагментов кода, ссылка на готовую схему. Яндекс диск
Принимаются предложения по улучшению/исправлению/дополнению урока
Сообщение отредактировал DukeKAn - Пятница, 17.01.2014, 14:46
stalker-MiX, Чего не знаю-того не знаю. Просто подключить - сильно сомневаюсь, если только самому писать что-то сильно похожее на неё конкретно под ЗП.
Добавлено (15.01.2014, 23:37) --------------------------------------------- LaRento, Возможно, никогда даже не лез в папки ЗП
stalker-MiX, посиделки, как я помню, как раз через аним поинты и делаются. Но чтобы эта схема заработала, в логике нужно поставить чтобы аним поинт использовал схему посиделок. И если НПС попадает в камп зону, то у него срабатывает эта схема и он рассказывает анекдоты и играет на гитаре.
Сообщение отредактировал aleksn09 - Четверг, 16.01.2014, 00:23