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) Ссылка на комментарий
Dennis_Chikin 3 658 Опубликовано 8 Ноября 2014 Поделиться Опубликовано 8 Ноября 2014 (изменено) Это, разумеется, читать и использовать все равно ни кто не будет, но поскольку однотипные вопросы, задаваемые неизвестным телепатам, слегка поддостали, хоть душу отведу. Исторически сложилось, что разные события, случающиеся с актором, обрабатываются в 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 часов не взирая на погоду выводили, и расстреливали. Почему здесь везде actor а не db.actor ? По тому что код пал жертвой рефакторинга, и актор опрделяется раз и навсегда в глобальном пространстве: function actor_binder:__init( npc ) super( npc ) _G.actor = self.object db.add_actor( actor ) self.is_saved = false end Изменено 1 Января 2015 пользователем Murarius 2 1 Солянка обезжиренная, диетическая, полезные советы по "солянке", текущий тестовый патч Ссылка на комментарий
Desertir 202 Опубликовано 8 Ноября 2014 Поделиться Опубликовано 8 Ноября 2014 А вообще за код вида "== nil" и "~= nil" надо давать сразу 35 лет строгого расстрела. Это еще почему? ИМХО Тема не совсем верно названа, по смыслу скорее "Использование событий актора" и дополнить не только потерей\получением предмета, но и всеми остальными событиями, на которые можно подписаться. ТЧ 1.0004. SAP и Trans mod github Ссылка на комментарий
Dennis_Chikin 3 658 Опубликовано 8 Ноября 2014 Автор Поделиться Опубликовано 8 Ноября 2014 (изменено) По тому что требуют явно определять переменные и явно возвращать из вызываемых функций это самый nil. Да, а рассматривать в этой теме on_hit(), on_death() и прочие апдейты я не буду. Только операции с предметами. Изменено 8 Ноября 2014 пользователем Dennis_Chikin Солянка обезжиренная, диетическая, полезные советы по "солянке", текущий тестовый патч Ссылка на комментарий
Desertir 202 Опубликовано 8 Ноября 2014 Поделиться Опубликовано 8 Ноября 2014 При чем тут сравнение с nil? И кто требует? ТЧ 1.0004. SAP и Trans mod github Ссылка на комментарий
Dennis_Chikin 3 658 Опубликовано 8 Ноября 2014 Автор Поделиться Опубликовано 8 Ноября 2014 (изменено) По тому что не срабатывает. Неделю назад в ШМ обсуждалось. Впрочем, сейчас воспроизвести не удалось. Но движок другой - для забугра, а не для одноэса. Продолжаем разговор. © 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() будет отдельная телега. Изменено 8 Ноября 2014 пользователем Dennis_Chikin 1 Солянка обезжиренная, диетическая, полезные советы по "солянке", текущий тестовый патч Ссылка на комментарий
Карлан 1 049 Опубликовано 8 Ноября 2014 Поделиться Опубликовано 8 Ноября 2014 эта функция нам пригодится. Интересно посмотреть как ее нормально использовать, если бы в нее непись передавался - другое дело, а так я ее только через жопу использовать смог, но тем не менее хоть что-то. Ссылка на комментарий
Dennis_Chikin 3 658 Опубликовано 8 Ноября 2014 Автор Поделиться Опубликовано 8 Ноября 2014 Для неписей, внезапно, нужна function actor_binder:on_item_drop( item ). Как ни странно. Вот там все будет. А с этой, например, удобно считать предметы, или те же деньги, которые перешли к кому-либо после открытия окна торговли. То есть, мы можем, например, переоткрывать лавочку для обмена предметов, притащенных по спецзаказу, на товары "из под прилавка". Вместо того, чтобы плодить 100500 строк "бартера" в диалогах, или забивать нетпакет мусором из task_manager.ltx. И, кстати, все не доходят руки посмотреть, кто у перемествшегося предмета - parent, и когда именно он меняется. Собственно, по тому и не доходят, что on_drop хватает. Солянка обезжиренная, диетическая, полезные советы по "солянке", текущий тестовый патч Ссылка на комментарий
RayTwitty 492 Опубликовано 8 Ноября 2014 Поделиться Опубликовано 8 Ноября 2014 И, кстати, все не доходят руки посмотреть, кто у перемествшегося предмета - parent, и когда именно он меняется.На дропе? Для какого объекта вызывали колбек, тот и парент. Ссылка на комментарий
Dennis_Chikin 3 658 Опубликовано 8 Ноября 2014 Автор Поделиться Опубликовано 8 Ноября 2014 На трэйде. А вот на дропе, внезапно, как раз таки нет, из чего и проистекает весьма много вкусностей. Солянка обезжиренная, диетическая, полезные советы по "солянке", текущий тестовый патч Ссылка на комментарий
RayTwitty 492 Опубликовано 8 Ноября 2014 Поделиться Опубликовано 8 Ноября 2014 А вот на дропе, внезапно, как раз таки нет, из чего и проистекает весьма много вкусностей.Ссылка на инвентарь, в котором лежит предмет обнуляется аккурат перед вызовом колбека для владельца. На трэйде.Скорее всего парент уже актор, вызов колбека происходит в самом конце функции трансфера. Ссылка на комментарий
Dennis_Chikin 3 658 Опубликовано 9 Ноября 2014 Автор Поделиться Опубликовано 9 Ноября 2014 (изменено) Продолжаем разговор... 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, кстати, тоже надо отсюда убрать. Изменено 9 Ноября 2014 пользователем Dennis_Chikin Солянка обезжиренная, диетическая, полезные советы по "солянке", текущий тестовый патч Ссылка на комментарий
RayTwitty 492 Опубликовано 9 Ноября 2014 Поделиться Опубликовано 9 Ноября 2014 повесил на этот вызов 100500 if ... then ...Стоит лишь отсечь стартовый спавн предметов в актора, например через проверку device().precache_frame и проблема будет решена. Переписывавем традиционно:А толку, если такая таблица актуальна только при старте игры по сути? Тогда уж на дропе убирай из таблицы выкинутую вещь. Впрочем, чем не нравится получение предмета через actor:object(section) (или через ту же итерацию по инвентарю) - я хз. Ссылка на комментарий
Dennis_Chikin 3 658 Опубликовано 10 Ноября 2014 Автор Поделиться Опубликовано 10 Ноября 2014 (изменено) А теперь самое вкусное. Я так до сих пор и не понял, зачем люди вешают все-все на 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 На этом, в общем-то, все. И немедленно выпил. © Изменено 10 Ноября 2014 пользователем Dennis_Chikin Солянка обезжиренная, диетическая, полезные советы по "солянке", текущий тестовый патч Ссылка на комментарий
Murarius 10 533 Опубликовано 11 Ноября 2014 Поделиться Опубликовано 11 Ноября 2014 Денис, смотри. Условно разделил на три части. Правильно ли, что на три? Правильно ли вообще разделил? К остальным академикам вопрос: не хотите ли что-то добавить в рамках темы? Напомню, Денис рассматривает коллбэки получения и потери предметов, 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 На этом, в общем-то, все. И немедленно выпил. © Литературка (избранное): "Координаты избушки" (2023) --- "Колобок времени" (2019) --- "Пиво и жлоб" (2018) --- "Лекарство против морщин" (2013) --- "Когда все пройдет" (2013) Креатив (бесперспективное): Dominanta --- Сон на земле Досвиданьице (слезное): Смена администратора (2024) Ссылка на комментарий
Dennis_Chikin 3 658 Опубликовано 11 Ноября 2014 Автор Поделиться Опубликовано 11 Ноября 2014 Мне одному кажется, что в отредактированном варианте что-то пропало ? Ну и удаление разбивки читабельность отнюдь не улучшает. Солянка обезжиренная, диетическая, полезные советы по "солянке", текущий тестовый патч Ссылка на комментарий
abramcumner 1 157 Опубликовано 11 Ноября 2014 Поделиться Опубликовано 11 Ноября 2014 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Код все же лучше давать в псевдо-коде. Тогда предыдущий кусок будет гораздо понятней if item_used(item) then return end if item_released(item) then signals.on_item_release(item) elseif item_has_parent(item) then signals.on_transfer_item(item) else signals.on_drop_item_to_ground(item) end Еще в примере описка походу: on_drop_t.any( item ) endИ в одном случае: for i, f in ipairs( on_drop_t[sect] ) do f( item ) end а в другом: local t = on_take_t[item:section()] if t then for i = 1, #t do t[i]( item ) end Ссылка на комментарий
Dennis_Chikin 3 658 Опубликовано 11 Ноября 2014 Автор Поделиться Опубликовано 11 Ноября 2014 (изменено) local on_drop_t = { ["any"] = {} } -- вполне честная таблица из inv_manager.scriptкусок кода с for i = 1, on_drop_n do on_drop_t.any( item ) end -- оттуда же.Вызываются все функции, добавленные на выпадание любого предмета на землю.по for i, f in ipairs( on_drop_t[sect] ) do f( item ) end -- это вызов всех функций, которые добавлены для конкретного предмета. В принципе кусок не из этой серии, добавлен для иллюстрации возможностей. Из рабочего скрипта. Да, наверное, лучше переписать в псевдокоде. Если кому заняться нечем. Но не signals все-же. Изменено 11 Ноября 2014 пользователем Dennis_Chikin Солянка обезжиренная, диетическая, полезные советы по "солянке", текущий тестовый патч Ссылка на комментарий
Murarius 10 533 Опубликовано 11 Ноября 2014 Поделиться Опубликовано 11 Ноября 2014 Мне одному кажется, что в отредактированном варианте что-то пропало ? По-моему, ничего. Ну и удаление разбивки читабельность отнюдь не улучшает. Это о чем? Я вроде не удалял ничего. Вот редактор при копипасте иногда чудеса вытворяет с лишними строками и пробелами - это может быть... Литературка (избранное): "Координаты избушки" (2023) --- "Колобок времени" (2019) --- "Пиво и жлоб" (2018) --- "Лекарство против морщин" (2013) --- "Когда все пройдет" (2013) Креатив (бесперспективное): Dominanta --- Сон на земле Досвиданьице (слезное): Смена администратора (2024) Ссылка на комментарий
Карлан 1 049 Опубликовано 11 Ноября 2014 Поделиться Опубликовано 11 Ноября 2014 Можно вопрос, а в самих скриптах где мы интить будем свои функции разве не нужно будет писать if then 100500 раз? Посыл то какой, автономность? Так xr_s (xStream), m_main (Artos), ogse_signals (malandrinus)... Я конечно не программист, но у тебя вроде быстрее работа, это основная цель написания своей системы что-ли? Или я не понял чего? Ссылка на комментарий
Dennis_Chikin 3 658 Опубликовано 11 Ноября 2014 Автор Поделиться Опубликовано 11 Ноября 2014 (изменено) Да, bind_stalker.script, как и все остальное, мы изменяем один раз в жизни, и никогда больше не трогаем. Есть апи, которое из любого добавляемого скрипта дергается при ините, и вот там добавляется все, что надо.При этом отработав, скажем, на локаци X квест Y больше никогда ничего не добавляется, и естественно, не проверяется.Ну а выборка из таблицы выигрывает уже при 5-7 if даже без сравнения строк. Вот, например, инициализация escape_dialog.script из NLC/солянки, квест с ночной звездой: t_take = { { have_a_art, "af_night_star", "kvest_art_started", "test_quest_art_vziat" } } function init() for i, v in ipairs( t_take ) do if ( ( not v[3] ) or actor:has_info( v[3] ) ) and ( ( not v[4] ) or actor:dont_has_info( v[4] ) ) then bind_stalker.add_on_take( v[1], v[2] ) end end return true end - это оно сюда вообще из amk.script переехало.А вот по-злобнее будет: t_take = { -- f, item, level, has_info, dont_has_info { pda1, "kostya_pda", "l01_escape", "kostya_taynik1_start", "kostya_pda_have" }, { esc_art_have, "af_gold_fish", "l01_escape", "kostya_art_start", "kostya_art_have" }, { pda2, "kostya_pda", "l02_garbage", "kostya_svalka_taynik_start", "kostya_svalka_taynik_have" }, { pda3, "kostya_pda", "l04u_labx18", "kostya_x18_taynik_start", "kostya_x18_taynik_have" }, { pda5, "kostya_pda", "l03_agroprom", "kostya_agroprom_taynik_start", "kostya_agroprom_taynik_have" }, { pda4, "kostya_pda", "l08_yantar", "kostya_yantar_taynik_start", "kostya_yantar_taynik_have" }, { pda6, "kostya_pda", "l07_military", "kostya_as_taynik_start", "kostya_as_taynik_have" }, { pda7, "kostya_pda", "l10_radar", "kostya_radar_taynik_start", "kostya_radar_taynik_have" }, { pda9, "kostya_pda9", "l10u_bunker", "kostya_x10_taynik_start", "kostya_x10_taynik_have" }, { kostya_doc, "kostya_documents","l10u_bunker","kostya_x10_taynik_start", "kostya_documents_have" }, { pda8, "kostya_pda", "l11_pripyat", "kostya_sacharov_start", "kostya_pripyat_taynik_have" }, { pda_vasilyev, "pda_vasilyev", "l08u_brainlab","kostya_sacharov_pda_start", "kostya_sacharov_pda_have" }, } function init() local lname = level.name() for i, v in ipairs( t_take ) do if lname == v[3] and actor:has_info( v[4] ) and actor:dont_has_info( v[5] ) then bind_stalker.add_on_take( v[1], v[2] ) end end return true end Раньше все это висело всю игру, причем на любой чих, да еще сначала вызовом через 3 скрипта, а потом уже получение секции и проверка. P.S. На самом деле это примерно то самое, что ПЫС имели в виду под схемами. Только предельно упрощенное, и по тому - работающее как задумано, а не как получилось. Вообще, про динамическое подключение чего угодно куда попало будет отдельная тема. Изменено 11 Ноября 2014 пользователем Dennis_Chikin 1 Солянка обезжиренная, диетическая, полезные советы по "солянке", текущий тестовый патч Ссылка на комментарий
Рекомендуемые сообщения
Создайте аккаунт или авторизуйтесь, чтобы оставить комментарий
Комментарии могут оставлять только зарегистрированные пользователи
Создать аккаунт
Зарегистрировать новый аккаунт в нашем сообществе. Это несложно!
Зарегистрировать новый аккаунтВойти
Есть аккаунт? Войти.
Войти