Murarius 10 533 Опубликовано 1 Января 2015 Поделиться Опубликовано 1 Января 2015 @Dennis_Chikin ИСПОЛЬЗОВАНИЕ ПРЕДМЕТОВ АКТОРОМ Какие коллбэки отвечают за получение и потерю предметов? Как эти коллбэки ставятся и снимаются? Для чего нужны и как работают take_item_from_box(), on_trade(), on_item_take()? Как узнать, куда положили или кому передали предмет? Ответы на эти вопросы - в статье ниже. Исторически сложилось, что разные события, случающиеся с актором, обрабатываются в bind_stalker скрипт. Опять же, исторически, в нем есть несколько коллбэков, отвечающих за получение и потерю предметов: function actor_binder:take_item_from_box( box, item ) function actor_binder:on_trade( item, sell_bye, money ) function actor_binder:on_item_take( item ) function actor_binder:on_item_drop( item ) function actor_binder:on_use_object( item ) По идее, на этом статью можно было бы и закончить, поскольку все вполне прозрачно из названий функций, но для тех, кому лень смотреть скрипты, добавлю, что ставятся они вот как-то так: function actor_binder:reinit() log( "info", "reinit" ) object_binder.reinit( self ) pstor = {} st = { ["pstor"] = pstor } self.st = st db.storage[0] = st self.next_restrictors_update_time = -10000 actor:set_callback( callback.inventory_info, self.info_callback, self ) actor:set_callback( callback.article_info, self.article_callback, self ) actor:set_callback( callback.on_item_take, self.on_item_take, self ) actor:set_callback( callback.on_item_drop, self.on_item_drop, self ) actor:set_callback( callback.trade_sell_buy_item,self.on_trade, self ) actor:set_callback( callback.task_state, self.task_callback, self ) actor:set_callback( callback.level_border_enter,self.level_border_enter,self ) actor:set_callback( callback.level_border_exit, self.level_border_exit, self ) actor:set_callback( callback.take_item_from_box,self.take_item_from_box,self ) actor:set_callback( callback.use_object, self.on_use_object, self ) actor:set_callback( callback.death, self.death_callback, self ) log( "info", "reinit, done" ) end Здесь и далее приводимые куски кода пали жертвой рефакторинга, и у вас это может быть иначе. Снимаются колбэки так: function actor_binder:net_destroy() local log_level = smart_terrain.get_log_level() or 100 if log_level < 20 and log_level >= 2 then smart_terrain.disable_log() if se_stalker.disable_log then se_stalker.disable_log() end if se_monster.disable_log then se_monster.disable_log() end xr_gulag.disable_log() end local remove_from_ranking = actor_stats.remove_from_ranking if remove_from_ranking then remove_from_ranking( 0 ) end sr_light.clean_up() actor:set_callback( callback.inventory_info ) -- чистим колл-бэки ( устанавливаются в nil ) actor:set_callback( callback.article_info ) actor:set_callback( callback.on_item_take ) actor:set_callback( callback.on_item_drop ) actor:set_callback( callback.trade_sell_buy_item ) actor:set_callback( callback.task_state ) actor:set_callback( callback.level_border_enter ) actor:set_callback( callback.level_border_exit ) actor:set_callback( callback.take_item_from_box ) actor:set_callback( callback.use_object ) actor:set_callback( callback.death ) if psy_antenna then psy_antenna:destroy() sr_psy_antenna.psy_antenna = false end xr_sound.stop_all_sound_object() db.del_actor( actor ) _G.actor = nil object_binder.net_destroy( self ) end - надо это, чтобы игра не вылетала при уничтожении объекта (в данном случае - при завершении/перезагрузке). Далее будет в порядке графомании расписано, что с этими коллбэками вообще можно делать, а заодно - как их стоило бы переписать для вящего удобства и чтобы перестать уже насиловать процессор. Да, в случае очистки коллбэков магическое nil, типа actor:set_callback( callback.article_info, nil ) вроде не требуется. По крайней мере, я никаких негативных эффектов пока не увидел. Но, возможно, ошибаюсь, и эффекты просто пока не вылезли. А вообще за код вида "== nil" и "~= nil" надо давать сразу 35 лет строгого расстрела. Вот чтоб каждое утро в 6 часов, невзирая на погоду, выводили и расстреливали, поскольку требуется явно определять переменные и явно возвращать из вызываемых функций это самый nil Почему здесь везде actor а не db.actor? Потому что код пал жертвой рефакторинга, и актор определяется раз и навсегда в глобальном пространстве: function actor_binder:__init( npc ) super( npc ) _G.actor = self.object db.add_actor( actor ) self.is_saved = false end *** function actor_binder:take_item_from_box( box, item ) function actor_binder:on_trade( item, sell_bye, money ) function actor_binder:on_item_take( item ) С первым все понятно. Срабатывает при взятии предметов из ящиков. На входе - объект ящика и объект собственно предмета. Кстати, если сюда прицепить телепортацию актора на другой конец локации - ящик уйдет в офлайн, и объект станет невалидным. Со всеми вытекающими. Впрочем, знаменитый вылет с буквами e случается, по-моему, еще раньше. Да, если при этом уничтожать, скажем, взятую из ящика гранату, будет примерно то же самое. И вообще, прежде чем делать что-либо дальше с ящиком или предметом - закройте ящик через level.hide_indicators() или подождите, пока игрок его сам закроет. Что же с ней можно сделать еще - это переписать простыни о 100500 строк всяких странных if ... then ... и вызовы 100500 скриптов, которые в нее обычно суют, следующим образом: local t_takebox, t_takebox_sid, t_takebox_any = {}, {}, {} function add_on_take_box( f, sect ) -- ( функция, true - проверка на sid ящика ) if sect then if sect == true then table_insert( t_takebox_sid, f ) elseif t_takebox[sect] then table_insert( t_takebox[sect], f ) else t_takebox[sect] = { f } end else table_insert( t_takebox_any, f ) end end function actor_binder:take_item_from_box( box, item ) local sid = box:story_id() if sid then -- dc: а что, nil бывает ? for i, v in ipairs( t_takebox_sid ) do v( box, item ) end end if item and t_takebox[item:section()] then for i, v in ipairs( t_takebox[item:section()] ) do v( box, item ) end end for i, v in ipairs( t_takebox_any ) do v( box, item ) end if level.map_has_object_spot( box:id(), "crlc_big" ) ~= 0 then -- amk.on_item_take_from_box level.map_remove_object_spot( box:id(), "crlc_big" ) end end - и в табличку помещать ваши сиды для ящиков, секции шмоток и функции, которые нужно дергать. Можно делать руками, а можно - из самих подключаемых скриптов. Впрочем, про это будет совсем другая тема. on_trade() - обычно здесь бывает что-то типа function actor_binder:on_trade( item, sell_bye, money ) if sell_bye then game_stats.money_trade_update( money ) else game_stats.money_trade_update( -money ) end end, и в общем-то больше ничего не нужно. Но если, например, переписывать "квесты" типа "принеси мне 100500 AK-100, а я дам тебе за это фантик от конфеты - можешь его облизать" - эта функция нам пригодится. А вот про on_item_take() - отдельная телега. on_item_take() вызывается, когда какой-либо предмет появляется в инвентаре актора. "Появляется" - здесь - ключевое слово. То есть, неважно как. То есть, первый вызов случается тогда, когда у загрузившегося актора (а он грузится всегда первым) предметы инвентаря начинают входить в онлайн. Извращение, конечно, зато мы можем составить свою табличку предметов, вместо медленного и печального перебора инвентаря. Хотя по разным причинам на практике так никто не делает. Зато, имеем медленную и печальную загрузку, когда игрок привык таскать на себе по полтонны хлама, а аффтар мода, традиционно, повесил на этот вызов 100500 if ... then ... Переписывавем традиционно: local on_take_t, on_take_any, on_take_n = {}, {}, 0 function add_on_take( f, sect ) -- ( функция, nil ) - для любых секций, либо ( f, секция ) if sect then local t = on_take_t[sect] if t then table_insert( t, f ) else on_take_t[sect] = { f } end else on_take_n = on_take_n + 1; on_take_any[on_take_n] = f end end function actor_binder:on_item_take( item ) -- log( "info", "on_item_take" ) ltasks_proceed() for i = 1, on_take_n do on_take_any[i]( item ) end local t = on_take_t[item:section()] if t then for i = 1, #t do t[i]( item ) end end if level.map_has_object_spot( item:id(), "red_location" ) ~= 0 then level.map_remove_object_spot( item:id(), "red_location" ) end -- log( "info", "on_item_take, done" ) end вызов level_tasks, кстати, тоже надо отсюда убрать. *** А теперь самое вкусное. Я так до сих пор и не понял, зачем люди вешают все-все на function actor_binder:on_item_drop( item ), потом пишут секцию, или, что еще хуже, id этого item в pstor актора, а потом запускают какие-то аццкие таймеры с аццкими проверками на неизвестно что для неизвестных объектов, у которых id случайно совпало с сохраненным, когда есть function actor_binder:on_use_object( item ), срабатывающий именно на использование. Но, кстати, да, on_item_drop() при этом тоже срабатывает. А еще он срабатывает, например, на уход предмета в оффлайн, так что если коллбэк для уничтожаемого актора не обнулить при net_destroy(), результат будет немного предсказуем. С использованием все понятно. А вот как узнать, куда положили или кому передали предмет? Или просто выбросили? Или он по какой-то мистической причине просто исчез? А элементарно (если мы отслеживаем еще и использование через on_use_object(), и ведем табличку, скажем inv_used[]): local sect = item:section() -- log( "info", "on_item_lost: %s", sect ) local id = item:id() if inv_used[id] then inv_used[id] = nil -- использован -- log( "info", "on_item_lost: %s, used", sect ) else local obj = sim:object( id ) if obj then if ( obj.parent_id or 65535 ) == 65535 then -- выброшен -- log( "info", "on_item_lost: %s, drop", sect ) for i = 1, on_drop_n do on_drop_t.any[i]( item ) end if on_drop_t[sect] then for i, f in ipairs( on_drop_t[sect] ) do f( item ) end end -- else log( "info", "on_item_lost: %s, new parent: %s", sect, obj.parent_id ) end -- else log( "info", "on_item_lost: %s, deleted", sect ) end end - это кусочек менеджера инвентаря, который отслеживает выбрасывание предметов, и, в свою очередь, запускает всякие варки артефактов и прочие нанопульты. В принципе, сюда же можно запихать и всякое переодевание и перевооружение неписей, но не нужно, поскольку если про парламент, как не место для дискуссий - еще спорно, то Зона - совершенно точно - не для стриптиза. Впрочем, это совсем другая тема. А пока просто следует запомнить, что таймеры - не нужны. Кстати, вот полный скрипт менеджера, с учетом пояса, перепаковки чего попало во что угодно, и еще всяким странным: https://dl.dropboxusercontent.com/u/27871782/inv_manager.script Да, сами функции использования и потери мы традиционно переписываем так: local on_drop_t, on_drop_any, on_drop_n = {}, {}, 0 function add_on_drop( f, sect ) -- ( функция, nil ) - для любых секций, либо ( f, секция ) if sect then local t = on_drop_t[sect] if t then table_insert( t, f ) else on_drop_t[sect] = { f } end else on_drop_n = on_drop_n + 1; on_drop_any[on_drop_n] = f end end function actor_binder:on_item_drop( item ) -- log( "info", "on_item_drop" ) ltasks_proceed() for i = 1, on_drop_n do on_drop_any[i]( item ) end local t = on_drop_t[item:section()] if t then for i = 1, #t do t[i]( item ) end end -- log( "info", "on_item_drop, done" ) end local on_use_t, on_use_n = { ["any"] = {} }, 0 function add_on_use( f, sect ) -- ( функция, nil ) - для любых секций, либо ( f, секция ) if sect then local t = on_use_t[sect] if t then t[#t + 1] = f else on_use_t[sect] = { f } end else on_use_n = on_use_n + 1; on_use_t.any[on_use_n] = f end end function actor_binder:on_use_object( item ) local sect = item:section() log( "info", "on_use, item: %s", sect ) for i = 1, on_use_n do on_use_t.any[i]( item ) end if on_use_t[sect] then for i, f in ipairs( on_use_t[sect] ) do f( item ) end end end На этом, в общем-то, все. И немедленно выпил. © Рецензия: @lsclon В статье автор описывает весьма оригинальное использование динамических таблиц для проверок. Я бы сказала, что эта статья - хорошая с большой буквы. 1 2 Литературка (избранное): "Координаты избушки" (2023) --- "Колобок времени" (2019) --- "Пиво и жлоб" (2018) --- "Лекарство против морщин" (2013) --- "Когда все пройдет" (2013) Креатив (бесперспективное): Dominanta --- Сон на земле Досвиданьице (слезное): Смена администратора (2024) Ссылка на комментарий
Карлан 1 049 Опубликовано 11 Ноября 2014 Поделиться Опубликовано 11 Ноября 2014 (изменено) init() вызывается каждый раз при загрузке сейва? Я почему-то думал, что только при старте новой игры. Вообще, про динамическое подключение чего угодно куда попало будет отдельная тема. О, вот этого я подожду, а то никак приемлемый функционал не напишу. Изменено 11 Ноября 2014 пользователем Карлан Ссылка на комментарий
Desertir 202 Опубликовано 11 Ноября 2014 Поделиться Опубликовано 11 Ноября 2014 (изменено) Когда то давно, почти 2 года назад, как оказалось, я наверное изобрел велосипед, но тогда он мне казался весьма интересным в плане познания скриптов. По сути добавляет в какой-нибудь класс (хотя можно сделать и для просто функции) кастомный функционал. Реализация весьма проста - обычная обертка. Ради интереса вот. Это как раз динамичная штука, там есть функция pcon - это вывод в лог. В остальном все просто. Изменено 11 Ноября 2014 пользователем Desertir 1 ТЧ 1.0004. SAP и Trans mod github Ссылка на комментарий
Murarius 10 533 Опубликовано 11 Ноября 2014 Поделиться Опубликовано 11 Ноября 2014 @Desertir, не отклоняемся ли от темы? Может, в отдельную перенести? Или это здесь очень даже к месту? Литературка (избранное): "Координаты избушки" (2023) --- "Колобок времени" (2019) --- "Пиво и жлоб" (2018) --- "Лекарство против морщин" (2013) --- "Когда все пройдет" (2013) Креатив (бесперспективное): Dominanta --- Сон на земле Досвиданьице (слезное): Смена администратора (2024) Ссылка на комментарий
Desertir 202 Опубликовано 11 Ноября 2014 Поделиться Опубликовано 11 Ноября 2014 Ну раз начали говорить про динамику... можно создать тему и вколотить туда. ТЧ 1.0004. SAP и Trans mod github Ссылка на комментарий
Murarius 10 533 Опубликовано 11 Ноября 2014 Поделиться Опубликовано 11 Ноября 2014 Денис рассматривает коллбэки получения и потери предметов, take_item_from_box(), on_trade(), on_item_take(), как узнать куда положили или кому передали предмет. Я правильно понимаю круг вопросов? Если вписывается, то нужно добавить это в текст, если нет - то создать отдельную статейку. либо расширить круг вопросов в рамках этой статьи. Просто помните, мы говорили, что, конечно, одно цепляется за другое, но так мы никогда не остановимся? Вот этого и стоит опасаться. Литературка (избранное): "Координаты избушки" (2023) --- "Колобок времени" (2019) --- "Пиво и жлоб" (2018) --- "Лекарство против морщин" (2013) --- "Когда все пройдет" (2013) Креатив (бесперспективное): Dominanta --- Сон на земле Досвиданьице (слезное): Смена администратора (2024) Ссылка на комментарий
Dennis_Chikin 3 658 Опубликовано 11 Ноября 2014 Автор Поделиться Опубликовано 11 Ноября 2014 (изменено) Не, здесь точно закончили. Другие вопросы - отдельными темами. Кстати, добавочка, но вообще к теме имеющая отношение отдаленное: Чтобы узнать, из чьей тушки взяли предмет - используем коллбэк на use в скрипте тушки. Так изящно, как с дропом, похоже, не получается - парент меняется до того, как, и для game- и для серверного объекта. Печаль... 2 Shadows: на вкус и цвет. Кому что надо. Изменено 2 Января 2015 пользователем Dennis_Chikin Солянка обезжиренная, диетическая, полезные советы по "солянке", текущий тестовый патч Ссылка на комментарий
RayTwitty 509 Опубликовано 22 Ноября 2014 Поделиться Опубликовано 22 Ноября 2014 Чтобы узнать, из чьей тушки взяли предмет - используем коллбэк на use в скрипте тушки.Не проще ли, колбек на дроп из тушки? Ссылка на комментарий
НаноБот 742 Опубликовано 29 Января 2015 Поделиться Опубликовано 29 Января 2015 А вот мои поделки. local actor_id = 0 local idnoparent = 65535 local con = get_console() local function log(arg) con:execute("load ~ "..string.sub(tostring(arg),1 , 200)) --con:execute("flush") end -------------------------------------------------------------- function init_item(obj) local new_binder = bind_item(obj) obj:bind_object(new_binder) end class "bind_item" (object_binder) function bind_item:__init(obj) super(obj) end function bind_item:net_spawn(data) if not object_binder.net_spawn(self, data) then return false end self.sect = self.object:section() log("bind_item:net_spawn(data) section-("..self.sect..")id-("..self.object:id()..")time-("..time_global()..")") return true end function bind_item:update(delta) object_binder.update(self, delta) local parent = self.object:parent() if parent then self.parent_id = parent:id() else self.parent_id = idnoparent end -- Здесь может быть код end function bind_item:net_destroy() if self.parent_id == actor_id and level.main_input_receiver() then log("bind_item:net_destroy()activate item!!! section-("..self.sect..")id-("..self.object:id()..")time-("..time_global()..")") -- Здесь располагаем код или функцию активации предмета. end object_binder.net_destroy(self) end Вероятно код не совсем оптимален, но я такой юзаю для активации разных предметов. Преимущество: не зависимость, кинул скрипт в папку скрипты, и всё работает, хорошо для глобальных модов, и мелких то же. ...в конце концов, важен лишь, машинный код. СТАЛКЕР только для ПК! Ссылка на комментарий
Dennis_Chikin 3 658 Опубликовано 29 Января 2015 Автор Поделиться Опубликовано 29 Января 2015 (изменено) Сразу ограничения: 1. Просто забросить скрипт - недостаточно. Надо еще в конфиге предмета прописывать. А там может быть прописано нечто иное. 2. net_destroy() - это не только использование, но и удаление, и просто уход в офлайн. 3. проверять парента на апдейте - это медленно и печально. Коллбэки срабатывают именно тогда, когда он действительно используется/передается и т.д. P.S. Но таки да, я, например, для того же детектора артов не нашел лучшего варианта, чем биндер, который заносит оные арты в табличку при net_spawn(). Изменено 29 Января 2015 пользователем Dennis_Chikin Солянка обезжиренная, диетическая, полезные советы по "солянке", текущий тестовый патч Ссылка на комментарий
НаноБот 742 Опубликовано 29 Января 2015 Поделиться Опубликовано 29 Января 2015 (изменено) 1. Просто забросить скрипт - недостаточно. Надо еще в конфиге предмета прописывать. А там может быть прописано нечто иное. Ну это само собой. 3. проверять парента на апдейте - это медленно и печально. Коллбэки срабатывают именно тогда, когда он действительно используется/передается и т.д. В основном это для всяких приборов, которые обычно в единичном экземпляре. На счёт быстродействия, вроде не сколько микросекунд выполняется, общем если предметов не много, то это лучшей вариант, ну а если их 100500, то так лучше не надо, например для всяких аптечек не годится, а для пультов очень да же годится. Хорошо бы в правленых движках этот колбек (use_item) на сам биндер предмета повесить. ЗЫ Ах да, ещё, если инвентарь открыт, и какой нибудь скрипт зачистит инвентарь ГГ (удалит этот предмет), то скрипт ложно сработает. Изменено 29 Января 2015 пользователем НаноБот ...в конце концов, важен лишь, машинный код. СТАЛКЕР только для ПК! Ссылка на комментарий
Рекомендуемые сообщения
Создайте аккаунт или авторизуйтесь, чтобы оставить комментарий
Комментарии могут оставлять только зарегистрированные пользователи
Создать аккаунт
Зарегистрировать новый аккаунт в нашем сообществе. Это несложно!
Зарегистрировать новый аккаунтВойти
Есть аккаунт? Войти.
Войти