Dennis_Chikin 3 658 Опубликовано 30 Января 2015 Автор Поделиться Опубликовано 30 Января 2015 (изменено) "Вскрытие показало, что больной умер от вскрытия."Тема для "крупной формы", то есть, на уровне скриптов целиком или больших частей оных скриптов. "Что у него внутри, зачем оно там, и что с этим можно сделать ?" Изменено 30 Января 2015 пользователем Dennis_Chikin Солянка обезжиренная, диетическая, полезные советы по "солянке", текущий тестовый патч Ссылка на комментарий
НаноБот 735 Опубликовано 24 Января 2015 Поделиться Опубликовано 24 Января 2015 (изменено) Пост к вот этому: http://www.amk-team.ru/forum/index.php?showtopic=13078&p=893616 просто слегка перекидываю кой-чего кое-куда. dc Странный стиль программирования, т.е. форматирования, моск слегка зависает когда видишь несколько end-ов в одной строке. В общем, жизнь показала что алгоритм удара, в оригинальном скрипте в принципе инвалидный и глючный, заменил своим из скрипта "гранаты с УДЗ", работает без ошибок и быстрей. function track_item(delta) local item = thrown_id and lobj_by_id( thrown_id ) local pos, ps if item then pos = item:position() -- if check_on_level( pos ) then if level.get_bounding_volume().min.y < pos.y then ps = item:get_physics_shell() else local obj = sim:object( thrown_id ) -- улетел за пределы уровня, удаляем if obj then sim:release( obj, true ) end end end if ps and ( thrown_upd >= global_time_ms ) then -- v_lin, v_ang = vector(), vector() -- странное и загадочное, но иначе не работает ps:get_linear_vel(v_lin) if not lv_lin or (throw_time and time_global() < throw_time + 200) then -- предохранитель lv_lin = vector():set(v_lin) --, vector():set( v_ang ) return end -- ловим ускорения (отрицательное) local vel = vector() vel.x = v_lin.x - lv_lin.x vel.y = v_lin.y - lv_lin.y vel.z = v_lin.z - lv_lin.z -- так быстрей local acc = vel:magnitude()/(delta * 0.001) --log("скорость предмета-("..lvel_mag..")ускорение-("..acc..")ID-("..id..")time-("..time_global()..")") if acc > udar then -- основной показатель удара - динамическое ускорение (торможение) local sect = item:section() if string_find( sect, "explosive" ) then item:explode() elseif t_af[sect] then af_activate( sect, thrown_id ) end -- все, упали else lv_lin:set( v_lin )--; lv_ang:set( v_ang ) return -- продолжаем лететь end end thrown_id, thrown_upd = false, 0 lv_lin = false end Главное можно силу удара регулировать, а то ветка, и арт или СВУ срабатывают, так же если в верх выстрелить, то в верхней точки так же иногда срабатывает. Странно что кираг с маландринусом до такого просто алгоритма не додумались. Ах да, там не сколько не обычное вычисление разницы векторов, странно но так гораздо быстрей, ну их в болото со своей векторной арифметикой. https://yadi.sk/d/XBIXdIv6eDTNo Не бага, а фича. Они изменение траектории ловят. Но, да, наверное, не нужно. Форматирование - так выделяется "законченный" блок. Когда часто пользуешься - удобнее. P.S. комментарии перенес прямо в этот пост, для компактности. dc Изменено 30 Января 2015 пользователем Dennis_Chikin 1 1 ...в конце концов, важен лишь, машинный код. СТАЛКЕР только для ПК! Ссылка на комментарий
Zander_driver 10 333 Опубликовано 27 Января 2015 Поделиться Опубликовано 27 Января 2015 (изменено) К использованию рекомендуются интегрированные системы компонентов, включающие систему хранения, событий и таймеры. Именно все три, поскольку они друг на друге строятся. А можно назвать логические причины, почему эти три системы (все три) друг на друге строятся (почему они обязаны друг на друге строиться, причины?). Я например, поразмыслив, этот момент так и не понял. Система хранения и таймеры - ну ладно еще допустим взаимосвязаны. Хотя я бы сказал не факт. А третье... Если по факту раз n секунд надо лишь проверить, не наступило ли собственно время для события х. То есть, сравниваем нашу константу с game_time(). При чем здесь таймеры ? Если я правильно понимаю, в этом вся суть таймера и состоит. висим/лежим где-то в памяти, и наблюдаем за окружающими событиями, дожидаясь когда наша хранимая константа совпадет/превысит с игровым временем, или с чем нибудь еще. Горбатость реализации среди прочего определяется по уместности подбора частоты обновлений, с которой этот "таймер" осматривает окружающую его действительность... но это лирика уже. DC: Можно я твой вопрос про таймеры перефразирую немножко иначе. Вот так: Для чего нужно ООП? для каких целей уместно применять, для каких нет. и почему. Сформулируй свой вариант ответа на такой вопрос? Кстати ответ уважаемого маландринуса, на этот же вопрос, мне тоже любопытен. Изменено 27 Января 2015 пользователем Zander_driver Мод, где не бывает одинаковых путей - Судьба Зоны. (Лучшее, что у меня получилось на X-Ray) На базе модифицированного движка OGSR Engine. Бывший мододел на X-Ray / Начинающий игродел на Unreal Engine. Программист. AMD Ryzen 9 7950X (16 ядер, 32 потока, 5.75 ГГц); RTX 3080; 128 ГБ DDR5; Arctic Liquid Freezer II-420; 3 ТБ SSD PCIe 4.0; 4ТБ HDD. Ссылка на комментарий
Malandrinus 615 Опубликовано 28 Января 2015 Поделиться Опубликовано 28 Января 2015 почему эти три системы (все три) друг на друге строятся Ну они конечно иерархически подчинены. Самая независимая - система событий (сигналов, ивентов и т.п.). Система хранения уже зависит от событий, поскольку использует события для инициализации себя в нужные моменты. Таймеры зависимы и от событий (в основном от апдейтов) и от системы хранения (если сохраняемые). Мои таймеры тесно используют особенности моей системы событий. Песочницу от xStream таким же образом будет использовать тяжеловато. Соответственно, компоненты xStream связаны примерно таким же образом и так же неотделимы друг от друга. "Для чего нужно ООП?" - вот понятия не имею. Сперва, а что вообще ООП? Классически считается, что ООП - это три таких идеи: инкапсуляция, наследование, полиморфизм. Попробую показать на примере своих таймеров. Инкапсуляция - это объединение в одном флаконе состояния и действий (или иными словами данных и операций над этими данными). Вот есть задача выполнить некое действие в некий отдалённый момент, не сейчас. Для этого по идее можно вызвать функцию, и наработаны способы вызвать её "не сейчас, а потом". Но в придачу к вызову функции надо ещё и донести до этого вызова некие данные, с которыми надо что-то в тот момент сделать. Конечно и это можно сделать подручными средствами, но ООП предоставляет готовый механизм - инкапсуляцию данных и кода в одну сущность - объект. Это оформляется немного по разному в разных языках, но ноги растут из древней struct языка СИ, или record языка паскаль, или используются универсальные таблицы, как в Lua. Короче, такая штука с полями-данными и полями-функциями. Это удобно, позволяет ограничить область видимости данных, более структурно оформить код и ещё много чего по мелочи. Обычно во всех языках традиционно используется неявный первый аргумент (this, self и т.п.) функций-членов, чтобы связать данные и операции над этими данными. Получился такой класс (это несохраняемый таймер, пример с упрощениями): class "quick_timer" function quick_timer:__init() -- конструктор таймера end function quick_timer:start(priority) -- Запуск таймера end function quick_timer:condition() -- условие end function quick_timer:action() -- действие end Я не затрагиваю здесь реализацию, только интерфейс. Методов там гораздо больше, чем показано. Конструктор срабатывает при создании объекта класса, метод start - запускает. Запуск таймера с одновременным созданием экземпляра класса будет выглядеть так: quick_timer(time):start(true) Роли остальных методов такие: condition - метод, который проверяет некое условие, какое там задам. Когда метод возвращает true, таймер заканчивает свою работу и вызывает метод action, который делает то, что там напишу. Теперь наследование и полиморфизм. Я выше запихал в один флакон мои данные и функции, но неясно, а что это мне дало. Сейчас объясню. То, что приведено выше, - базовый класс таймера, который только обеспечивает функционал собственно таймера. Хотя это и приличный объём кода, однако вместо общего условия и действия там стоят заглушки, т.е. методы condition и action - пустые. Чтобы сделать что-то полезное, мне надо создать новый класс на основе этого и там переопределить эти методы (т.е. собственно задать условие срабатывания и действие по срабатыванию). Пример, таймер, который ждёт выхода в онлайн какого-либо объекта: class "wait_object_online" (ogse_qt.quick_timer) function wait_object_online:__init(id) -- аргумент - id объекта, за которым следим self.obj_id = id -- запомнили объект, за которым следим end function wait_object_online:condition() -- наше кастомное условие return level.object_by_id(self.obj_id) -- сработает по появлению клиентского объекта end function wait_object_online:action() -- наше кастомное действие -- здесь что-то делаем по факту выхода объекта в онлайн end -- Использование: wait_object_online(some_id):start(false) сразу об аргументе метода start. Это приоритет таймера. false - проверки с низким приоритетом, все выстраиваются в одну очередь и проверяется по одной за очередной апдейт актора. true - проверяется на каждом апдейте актора. В данном случае решили, что задержка срабатывания не критична. Хочу обратить внимание, что показанный выше код уже совершенно не упрощён. Вот так просто сделать и использовать таймер на основе уже написанного базового. Здесь и видим в действии идею наследования и полиморфизма. Мы унаследовали новый таймер от старого, при этом весь его функционал перешёл в новый класс. А то, что надо - заменили на свои методы, т.е. переопределили иными словами. Полиморфизм же заключается в том, что имеющийся там код базового класса "не замечает", что функции condition и action - новые и вызывает их как свои. Таким образом, я решил мою задачу вызвать нужное действие в некий отдалённый момент и при этом донести до этого действия некие данные (id объекта в данном случае). При этом, код создания нового таймера целиком сосредоточен в одном месте (в идеале в том же модуле, где таймер запускается), компактен и требует минимальных действий: определить условие срабатывания и действие. Все необходимые для отложенного действия данные хранятся там же и не вываливаются в глобальную область. Надеюсь, что-то объяснил. Вообще же, кроме технических и синтаксических аспектов ООП есть ещё целая наука, называемая объектно-ориентированный дизайн. Ну или не наука, но в общем определённое умение. Задача ООД - собственно решить, что является объектом, какие у него данные/методы, как он связан с другими объектами. На эту тему книги есть, например Гради Буч - Объектно-ориентированный анализ и проектирование с примерами приложений 1 2 Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Ссылка на комментарий
RayTwitty 492 Опубликовано 28 Января 2015 Поделиться Опубликовано 28 Января 2015 (изменено) У себя уже давно использую оригинальный пысовский метод level.add_call() - как раз для таких "быстрых" таймеров. Каждый новый вызов этого метода, по сути и является созданием экземпляра класса и всё действо помещается в одну строку луашного кода. Но при такой компактности, конечно, и наворотов меньше чем у malandrinus'a - например из входных параметров только: имя таймера, время (таймаут), функция, которая будет вызвана по окончанию таймера и аргументы, переданные в эту функцию. То есть это именно таймер, с одним лишь условием - временем. Сохранение между сейвами так же не предусмотрено, но лично для моих нужд, пока этого хватает. Изменено 28 Января 2015 пользователем Shadows Ссылка на комментарий
Malandrinus 615 Опубликовано 28 Января 2015 Поделиться Опубликовано 28 Января 2015 использую оригинальный пысовский метод level.add_call() Это не ООП, а типичный процедурный подход, поскольку отсутствует инкапсуляция кода и данных. Ответь на простой вопрос, как ты при этом будешь хранить данные, которые надо "донести" от места/момента запуска до места/момента срабатывания? В принципе конечно можно использовать замыкания, но как по мне - это эрзац и вообще не ООП, а элемент функционального программирования. Получается, что приведенные методы (ну кроме конструктора), должны быть виртуальными Если вкратце, то в Lua, в силу прототипного ООП, все методы заведомо виртуальные, поскольку определяются ссылками, хранящимися прямо в таблице/юзердате (что аналогично таблице виртуальных методов C++). Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Ссылка на комментарий
RayTwitty 492 Опубликовано 28 Января 2015 Поделиться Опубликовано 28 Января 2015 Это не ООП, а типичный процедурный подход, поскольку отсутствует инкапсуляция кода и данных. Ну так я и не говорил, что в скриптах мы будем видеть какой-то класс, всё создается внутри движка и там используется. Ответь на простой вопрос, как ты при этом будешь хранить данные, которые надо "донести" от места/момента запуска до места/момента срабатывания? Передаю внутрь функции, которая будет вызвана после отработки первой функции. Ссылка на комментарий
Malandrinus 615 Опубликовано 28 Января 2015 Поделиться Опубликовано 28 Января 2015 (изменено) из входных параметров только: имя таймера, время (таймаут), функция, которая будет вызвана по окончанию таймера и аргументы, переданные в эту функцию. я так понял, что ты сделал некую обёртку для level.add_call(), позволяющую хранить данные до вызова функции? Я то как раз сделал наоборот. Специально для "процедурщиков" написал обёртку над классом таймера, чтобы запускать в процедурно-ориентированном стиле. Правда только для сохраняемых таймеров. В итоге, можно и так сделать ogse_st_mgr.start_timer(timer_name, <задержка>, <имя функции>, <аргументы>) Но это естественно сильно ограничивает поле применения. Аргументы не могут быть любого типа, срабатывает только по задержке. Хотя конечно написать ещё одну обёртку с дополнительной функцией-условием тоже можно, но как по мне - это уже извраты и лучше использовать собственно класс таймера. Изменено 28 Января 2015 пользователем malandrinus Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Ссылка на комментарий
RayTwitty 492 Опубликовано 28 Января 2015 Поделиться Опубликовано 28 Января 2015 я так понял, что ты сделал некую обёртку для level.add_call(), позволяющую хранить данные до вызова функции? Можно и так сказать. Передаваемые аргументы хранятся в таблице, которая передается во вторую функцию, после чего эта таблица там распаковывается. Таким образом, можно передать любое количество аргументов и любого типа. Но это естественно сильно ограничивает поле применения. Аргументы не могут быть любого типа, срабатывает только по задержке. Хотя конечно написать ещё одну обёртку с дополнительной функцией-условием тоже можно, но как по мне - это уже извраты и лучше использовать собственно класс таймера. Ну безусловно, твои таймеры (точнее, я бы назвал это "отложенные события") это отличный пример возможностей ООП, однако из моей практики применения, мне были нужны именно таймеры (с временным условием), а следовательно такой подход вполне устраивает. Тут надо смотреть из конкретной практики применения и писать функционал под потребности применяющего. Ссылка на комментарий
Dennis_Chikin 3 658 Опубликовано 28 Января 2015 Автор Поделиться Опубликовано 28 Января 2015 (изменено) {}Да, это все лирика была.Вообще-то, планировал еще и расписывать: как, что и зачем сделано, но... В общем, попытаюсь.В принципе, планировалось все по порядку описывать, с пляской от собственно _g, но можно и "с конца". Итак, собственно, с конца и пойдем: function init() local t = actor_data.get_pstor() -- хранилище данных актора if t.timers then for k, v in pairs( t.timers ) do timer_n = timer_n + 1 timer_t[timer_n] = { k, v[1], v[2] } end end timer_sort = true for k, v in pairs( { --["blowout"] = amk_mod.Blowout_pp, --["test"] = amk_mod.Run_Blowout_pp, --["blowout_ss"] = amk_mod.blowout_scary_sounds, --["blow_shift"] = amk_mod.Run_Blowout_pp, --["item_transform"] = amk_mod.item_transform, ["timer_test"] = timer_test } ) do timer_f[k] = v end bind_stalker.task_add( "amk_timers.check_timers", 200, check_timers ) bind_stalker.add_on_save( on_save ) timer_start( "timer_test", 1 ) return true end init() -- инициализация. Цепляем тогда, когда появился актор. Раньше просто смысла нет. Что делаем:Из pstor берем табличку таймеров в формате { имя таймера, время срабатывания от момента старта, данные }Сохранение pstor слегка переделано (см. прозекторскую), так что преобразование таблиц в строки и обратно дополнительно делать не нужно. Хранится все довольно компактно, самих таймеров и данных реально мало. Сиречь, ни каких дополнительных хранилищ тоже не нужно.Далее осталась табличка для руками забиваемых функций - атавизм от "13Гб мода" . Поскольку все закомментировано - очевидно, что уже не нужна.bind_stalker.task_add() - вешаем на апдейт актора (раз в 200 ms, чаще нет смысла), bind_stalker.add_on_save() - добавляем функцию, вызываемую при сохранении. (Да, руки дойдут - распишу, но вообще по аналогии с коллбэками по предметам - чтобы по 50 скриптов не трогать при подключении нового). function on_save() -- в таблицах таймеров сохраняется разница времени сработки и текущего local t = {} local d for i = 1, timer_n do d = timer_t[i] t[ d[1] ] = { d[2] - game_time_ms, d[3] } end d = actor_data.get_pstor() d.timers = t end on_save() -- вот та самая функция, вызываемая при сохранении. Что и зачем:нам не нужно знать, сколько времени прошло от начала игры. Нам нужно знать, через какое время сработает. Поэтому храним не 64бит-впемя, а дельту. Сколько осталось до срабатывания. Вот эту дельту и вычисляем. function check_timers() -- проверка таймеров if timer_sort then table_sort( timer_t, function( a, b ) return b[2] < a[2] end ) timer_sort = false --_util.list_tbl( timer_t, "timers" ) --log( "info", "check_timers, n: %s, time: %s", timer_n, game_time ) end local t, f while timer_n ~= 0 do -- идём с хвоста по таймерам с наименьшим временем срабатывания t = timer_t[timer_n] -- t[1] - ключ, t[2] - время акции, t[3] - данные -- log( "info", "check_timers, at: %s, time: %s", t[2], game_time ) if game_time_ms >= t[2] then -- пора запускать Берлагу f = timer_f[ t[1] ] if f then f( t[3] ) table_remove( timer_t, timer_n ) -- отработал свое timer_n = timer_n - 1 end else break -- таблица отсортирована, так что остальные - ждут end end end check_timers() -- проверка таймеров. То самое, что добавляли на апдейт. В принципе, все прозрачно. Время срабатывания подошло - вызываем и удаляем. Практически как в оригинале. Только что табличка отсортирована по времени, то, что должно сработать раньше - в хвосте. Перебираем не все, а прекращаем проверку на первом же вхождении, которое "еще не пора". local time_factor = sys_ini:r_float( "alife", "time_factor") or 5 local time_factor1000 = time_factor * 1000 local timer_t, timer_n = {}, 0 -- таймеры local timer_f = {} -- функции таймеров local timer_sort = true -- нужна сортировка function has_timer( key ) local i = timer_n while i ~= 0 do if timer_t[i][1] == key then return i, timer_t[i][2], timer_t[i][3] end i = i - 1 end end function timer_get_f( key ) return timer_f[key] end function timer_add( key, f ) if key then timer_f[key] = f end end function timer_start( key, delay, prm ) -- старт таймера в реальном времени table_insert( timer_t, { key, game_time_ms + ( delay or 0.2 ) * time_factor1000, prm } ) timer_n = timer_n + 1 timer_sort = true return true end local ms_m = 60 * 1000 local ms_h = ms_m * 60 local ms_d = ms_h * 24 function timer_g_start( key, dd, hh, mm, prm ) -- старт таймера в игровом времени (минуты) table_insert( timer_t, { key, game_time_ms + ( dd or 0 ) * ms_d + ( hh or 0 ) * ms_h + ( mm or 0 ) * ms_m, prm } ) timer_n = timer_n + 1 timer_sort = true return true end function timer_stop( key ) -- удаление таймера if timer_sort then table_sort( timer_t, function( a, b ) return b[2] < a[2] end ) timer_sort = false end local t local i = timer_n while i ~= 0 do -- идём с хвоста по таймерам с наименьшим временем срабатывания t = timer_t[i] -- t[1] - ключ, t[2] - время акции, t[3] - данные if t[1] == key then -- нашли, удаляем table_remove( timer_t, i ) timer_n = timer_n - 1 end i = i - 1 end end timer_stop() -- удаление таймера, если вдруг стал не нужен. Не дожидаясь сработки.timer_start() и timer_g_start() -- запуск таймера для реального и игрового времени соответственно. Реальное здесь сразу преобразуется в игровое, по тому что смысла на самом деле нет хранить и считать по-разному. Все события игры привязаны так или иначе к игровому.timer_add() -- добавление новой функции, которую будем вызывать по таймеру, из сторонних скриптов по инициативе этих скриптов. То есть, все а же система, что и с bind_stalker.task_add() и т.д.has_timer() и timer_get_f() -- соответственно, проверка на то, что функция уже добавлена, и что таймер запущен. В основном, на случай, когда одинаковая функция требуется более чем одному скрипту. Ну и некоторые сценарные события можно контролировать - например, ждем, пока произойдет одно, прежде чем добавить другое.Собственно, вот, такая вот процедурщина. По тому что именно на процедурщину заточено железо и среда.Пример использования - в скрипте сна можно найти. По тому они в одной теме и лежат. Собственно, один из 2-х случаев из упомянутого 13Гб мода. 2 Shadows: а вот ООП здесь почему-то оказалось ну вот совершенно незачем. P.P.S. да, загадочная переменная game_time_ms - это время от загрузки игры. Изменено 30 Января 2015 пользователем Dennis_Chikin Солянка обезжиренная, диетическая, полезные советы по "солянке", текущий тестовый патч Ссылка на комментарий
Zander_driver 10 333 Опубликовано 28 Января 2015 Поделиться Опубликовано 28 Января 2015 function init()local t = actor_data.get_pstor() -- хранилище данных актора if t.timers then Уже после этого места потерял желание читать дальше. За хранение всякой левой всячины в псторе актора, на кострах сжигать разве не пора еще? из упомянутого 13Гб мода Кажись, свой я еще не выкладывал. Мод, где не бывает одинаковых путей - Судьба Зоны. (Лучшее, что у меня получилось на X-Ray) На базе модифицированного движка OGSR Engine. Бывший мододел на X-Ray / Начинающий игродел на Unreal Engine. Программист. AMD Ryzen 9 7950X (16 ядер, 32 потока, 5.75 ГГц); RTX 3080; 128 ГБ DDR5; Arctic Liquid Freezer II-420; 3 ТБ SSD PCIe 4.0; 4ТБ HDD. Ссылка на комментарий
Dennis_Chikin 3 658 Опубликовано 29 Января 2015 Автор Поделиться Опубликовано 29 Января 2015 (изменено) У меня там всего 600 байт лежит. Из которых еще 300 подлежит сносу.При том, что легким движением руки максимальный объем расширяется до бесконечности.Ну так и почему бы не pstor тогда ?P.S. Пф ! Можно подумать, что есть всего один мод на 13 Гб... Ну пусть не на 13, а на 14... (Это хвост про таймеры, которые не нужны. Бурное обсуждение "обо всем" перехало во флудилку.) Изменено 30 Января 2015 пользователем Dennis_Chikin Солянка обезжиренная, диетическая, полезные советы по "солянке", текущий тестовый патч Ссылка на комментарий
Dennis_Chikin 3 658 Опубликовано 11 Апреля 2015 Автор Поделиться Опубликовано 11 Апреля 2015 (изменено) Гм, методически неверно было с самого начала выкладывать что попало куда попало без объяснений, но, впрочем, объяснения были даны, про то что на форуме было отдельное спрятанное от большинства подполье, где предполагалось понаписать всякого разного, а потом все писатели куда-то делись, и в мир вышло вот это самое что попало и как попало. И, да, по-хорошему бы надо и к этим кускам, каждому, во-первых добавить пояснения про то, что это такое, зачем, что внутри, и как это едят, а еще перетащить сюда же дискусси из других тем. Но... в общем, некогда и лень. Однако, не лень добавить еще один очередной малоосмысленный кусок чего-то странного. Точнее, не то, чтоб не лень, а "за державу обидно". Обидно в общем-то следующе: в свое время я поимел немало дивного секса с кучей монстротаблиц в "солянке", и был этим самым сексом, э-эээ, удовлетворен более, чем хотелось. До полного осознания смысла фразы "много хорошо - тоже плохо". А еще кроме монстротаблиц в десятках экземпляров одного и того же, было 100500 чтений конфигов с разной степени этой самой пессимизации. В общем, все это было выковыряно, и собрано в набор "табличных" скиптов, а также написана пара скриптов "кэширующих". Не в смысле "держать все конфиги в памяти", по тому как этим прекрасно занимается движок, а убрать откуда попало кучу строк со странными вызовами и странными преобразованиями. Ну так вот, смотрю я сейчас на некоторые вполне "современные" изделия, и снова становится обидно и мучительно больно за бесцельно прожитые годы. Итак, выкладывается набор, который может быть кому пригодится, а нет, так хоть на пару мыслей наведет. И, да, я знаю, что "у всех уже сделано", и про "на лесоповал, чтоб не смели". Можно про это не повторяться. https://dl.dropboxusercontent.com/u/27871782/%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D0%BE%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20%D1%81%D0%BE%D0%BB%D1%8F%D0%BD%D0%BE%D1%87%D0%BD%D1%8B%D0%B5.zip Основная часть этого - именно таблицы, позволяющие по какому-либо свойству объекта убедиться, что это именно тот объект, который нам нужен. Заменяют простыни "if .. then ... elseif ... end" на 100500 строк. Ну, то есть, ничего принципиально нового, но собрано в разные часто используемые комбинации. Да, в этой теме я уже упоминал про class_registrator, и по-хорошему надо бы доработать все это дело, по тому как часть таблиц явно должна заполняться оттуда, а не руками, и наоборот, данные браться из таблиц, чтоб не дублировать набивание руками. Из того, что заслуживает некоторого количества отдельных слов: _tbl_protected.script - исторически сложилось, что делается куча проверок для противоестественого интеллекта неписей и прочих уборщиков, чтобы не уничтожали квестовые и уникальные предметы. По-хорошему, это бы надо всего 2 проверки: на флажок quest_item в конфиге (который препятствует продаже или выбрасыванию ценного предмета), и на уникальный story_id. Но, опять таки исторически сложилось, что конфиги - священны и неприкосновенны, и конфигурист пишет там то, что левой пятке захотелось, а потом скриптами мы пытаемся все это как-то разобрать. Но, там где используется protected_items.script - скрипт этот, мягко говоря, тоже странен. Куча неочевидных странных и кривоработающих проверок десятками способов. На практике мне оказалось как-то проще сделать таблицы свойств, и поверять наличие нужного свойства в нужной таблице. Да, в процессе еще вынесено куча "одноразовых секций", поскольку элементарно пересчитывается в конкретный объект непосредственно из логики ситуаций. Но это уже другой отдельный вопрос. Далее, _tbl_outfit.scipt: как понятно из названия, позволяет получить свойства костюмов из визуалов неписей, и наоборот. Заменяет 5 разных солянкоскриптов, делающих примерно одно и то же, требующих одновременных правок, и, кстати, все с ошибками (ну или конфиги с ошибками, но от того виснуть и бредить все это дело не не перестает). В обшем, смотрите комментарии. И, наконец, xl_imgr.script - это банальное "кэширование конфигов". Когда вместо упомянутых 100500 строк чтения конфигов мы просто берем нужное свойство из таблицы по секции конструкцией ( t[sect] or get_prm(sect) ).prm Так реально помогает, а память даже и экономится. На данный момент кэшируются только те свойства тех предметов, с регулярным опросом которых я сталкивался. Вот примерно так. P.S. Да, тот код, который я время-от времени куда-нибудь выкладываю, именно на эти таблицы и опирается. Так что, опять же, если кого интересуют какие мои поделия, то вот можно глянуть сюда на предмет всяких "недостающих" частей. Изменено 11 Апреля 2015 пользователем Dennis_Chikin 1 Солянка обезжиренная, диетическая, полезные советы по "солянке", текущий тестовый патч Ссылка на комментарий
Dennis_Chikin 3 658 Опубликовано 29 Апреля 2015 Автор Поделиться Опубликовано 29 Апреля 2015 (изменено) Для поиска: диалоги, dialog_manager, адаптация Все-таки, пожалуй, выложу свой вариант этого самого менеджера, раз спрашивают. https://dl.dropboxusercontent.com/u/27871782/dialog_manager.script Очень коротко: xr_logic.parse_condlist1 здесь - то же самое, что xr_logic.parse_condlist, но на входе - только строка, которую следует разобрать. actor - то же, что db.actor, вызов init() делается в любой удобный момент, например - из reinit() актора, но перед load(), из _g.script здесь вызывать ничего не надо. В db.scipt нужна строка: ver = script_server_object_version() or -1 -- метка весии игры Это и есть вся "адаптация". Основной смысл всего действа - зачистка мусора. Несколько слов про сохранение: вообще оно здесь нафиг не нужно, поскольку сохраниться изнутри диалога все равно нельзя, и информация о выбранной фразе все равно пропадает. Но оставлено, поскольку лень было чистить. Версия движка определяется ОДИН раз, поэтому определять script-версию непися в мотиваторе - не нужно, и передавать сюда - тоже не нужно. Проверки на версию для каждой фразы не нужны, опять же, поскольку версия определена при ините, и все id сразу сформированы в соответствии с ней. То, что откомментировано как "устарело" - оно устарело и не нужно, кроме как для других скриптов, которые зачем-то могут пытаться это дергать. А вообще, опять же, не нужен совсем. Нужны диалоги на скриптовом гуе: это избавит от сложных конструкций и вылетов на забытых треугольных скобках и прочих опечатках. Плюс можно оперативно генерировать диалоги применительно к ситуации, а не хранить кучу заранее набитого ненужного, десятками и сотнями мегабайт. p.s. разбор потрохов, надеюсь, воспоследует. Как руки дойдут. Изменено 29 Апреля 2015 пользователем Dennis_Chikin 1 Солянка обезжиренная, диетическая, полезные советы по "солянке", текущий тестовый патч Ссылка на комментарий
Zander_driver 10 333 Опубликовано 29 Апреля 2015 Поделиться Опубликовано 29 Апреля 2015 Нужны диалоги на скриптовом гуе А диалоги, оформленные как в Mass Effect, не хочешь? А то ведь будут, запросто. Мод, где не бывает одинаковых путей - Судьба Зоны. (Лучшее, что у меня получилось на X-Ray) На базе модифицированного движка OGSR Engine. Бывший мододел на X-Ray / Начинающий игродел на Unreal Engine. Программист. AMD Ryzen 9 7950X (16 ядер, 32 потока, 5.75 ГГц); RTX 3080; 128 ГБ DDR5; Arctic Liquid Freezer II-420; 3 ТБ SSD PCIe 4.0; 4ТБ HDD. Ссылка на комментарий
Dennis_Chikin 3 658 Опубликовано 29 Апреля 2015 Автор Поделиться Опубликовано 29 Апреля 2015 (изменено) Я б этот массэффект еще видел... Впрочем, оформление может быть любым - основной-то вопрос в создании/изменении всяких мегабайтных wawka_dialog и иже с ними. Которые даже редакторы-то нормально пережевать не могут. 2 abramcumner: так они именно что в xml. 180 файлов, 6 мег в gameplay, и еще 4 в text\rus. Изменено 29 Апреля 2015 пользователем Dennis_Chikin Солянка обезжиренная, диетическая, полезные советы по "солянке", текущий тестовый патч Ссылка на комментарий
Kirgudu 1 187 Опубликовано 11 Июня 2015 Поделиться Опубликовано 11 Июня 2015 (изменено) Для поиска: netpacket, нетпакеты, хранилищеПоскольку возник такой вопрос, выкладываю здесь для интересующейся общественности se_stor от @Artos (версия от 09.09.2013, последняя из опубликованных автором):https://yadi.sk/d/AlA809oPehaqNАвторские ссылки все устарели, а в середине темы «Ищу файлы ...» не каждый догадается посмотреть.Перенесу, пожалуй, сюда. Ибо внутри темы на 3 сотни страниц очень не сразу найти можно. dc Изменено 11 Июля 2015 пользователем Dennis_Chikin 1 2 Инструмент Ссылка на комментарий
Dennis_Chikin 3 658 Опубликовано 29 Июля 2015 Автор Поделиться Опубликовано 29 Июля 2015 xr_logic Раз пошла такая пьянка, закину сюда свою недоделку: или кто что нужное найдет, или недоделанное поможет допилить. Ну и вообще как затравка для анатомирования и препарирования: разберем, будет руки и желание дойдут - что и зачем где делается. https://dl.dropboxusercontent.com/u/27871782/xr_logic.script 1 Солянка обезжиренная, диетическая, полезные советы по "солянке", текущий тестовый патч Ссылка на комментарий
Nazgool 250 Опубликовано 30 Июля 2015 Поделиться Опубликовано 30 Июля 2015 (изменено) try_switch_to_another_section можно сократить малость. Так код читать легче. Заодно и убрать вспомогательную функцию "cond_name" можно function try_switch_to_another_section(npc, st, actor) if not actor then abort("try_switch_to_another_section(): error in implementation of scheme '%s': actor is nil", st.scheme) end local l = st.logic if not l then abort("Can't find script switching information in storage, scheme '%s'", st.active_scheme) end local sw = false for _, c in pairs(l) do local name = string.match(c.name, '[%w_]+') if name == "on_actor_dist_le" then sw = see_actor(npc) and distance_between(actor, npc) <= c.v1 elseif name == "on_actor_dist_le_nvis" then sw = distance_between(actor, npc) <= c.v1 elseif name == "on_actor_dist_ge" then sw = see_actor(npc) and distance_between(actor, npc) > c.v1 elseif name == "on_actor_dist_ge_nvis" then sw = distance_between(actor, npc) > c.v1 elseif name == "on_signal" then sw = st.signals and st.signals[c.v1] elseif name == "on_info" then sw = true elseif name == "on_timer" then sw = time_global() >= db.storage[npc:id()].activation_time + c.v1 elseif name == "on_game_timer" then sw = game.get_game_time():diffSec(db.storage[npc:id()].activation_game_time) >= c.v1 elseif name == "on_actor_in_zone" then sw = utils.npc_in_zone(actor, db.zone_by_name[c.v1]) elseif name == "on_actor_not_in_zone" then sw = not utils.npc_in_zone(actor, db.zone_by_name[c.v1]) elseif name == "on_npc_in_zone" then sw = utils.npc_in_zone(level.object_by_id(c.npc:id()), db.zone_by_name[c.v2]) elseif name == "on_npc_not_in_zone" then sw = not utils.npc_in_zone(level.object_by_id(c.npc:id()), db.zone_by_name[c.v2]) elseif name == "on_actor_inside" then sw = utils.npc_in_zone(actor, npc) elseif name == "on_actor_outside" then sw = not utils.npc_in_zone(actor, npc) else abort("try_switch_to_another_section: invalid condition: [%s] (%s)", c.name, npc:name()) end if sw then return switch_to_section(npc, st, pick_section_from_condlist(actor, npc, c.condlist)) end end return false end С parse_infop тоже особо ничего не сделаешь. Там вообще паттерн интересный - "([%-%+%~%=%!])([^%-%+%~%=%!%s]+)" Тут дело такое. Я поставил такой шаблон - "(%S)([^%-%+%~%=%!%s]+)" И вылетел. Но КАК !!! Короче. Первый паттерн продолжает чтение данных, если какой-то из захватов не был найден. Напр. есть запись - "+info1 info2 -info3" Будут прочитаны +info1 и -info3. info2 прочитан не будет, т.к. перед ним нет никакого знака. На самом деле это ошибка. И, считаю, такие записи нужно пресекать. Что и получилось при использовании шаблона "(%S)([^%-%+%~%=%!%s]+)" Еще не нашел где именно, но где-то нашлась запись 'npc_rank(novice)' без знака, которая разбилась на : sign - 'n' infop_name - 'pc_rank(novice)' что и привело к, необходимому имхо, вылету. Что и как нужно пока не знаю, поэтому оставил старый function parse_infop(rslt, str) if str then local infop_n = 1 for sign, infop_name in string.gmatch(str, "([%-%+%~%=%!])([^%-%+%~%=%!%s]+)") do -- парсим параметры функций local func, param = infop_name:match('^(.-)(%b())') if param then param = parse_func_params(param:match('%((.-)%)')) infop_name = func or infop_name end if sign == "+" then rslt[infop_n] = { name = infop_name, required = true } elseif sign == "-" then rslt[infop_n] = { name = infop_name, required = false } elseif sign == "~" then rslt[infop_n] = { prob = tonumber(infop_name) } elseif sign == "=" then rslt[infop_n] = { func = infop_name, expected = true, params = param} elseif sign == "!" then rslt[infop_n] = { func = infop_name, expected = false, params = param} else abort("function 'parse_infop' --> section '%s': field '%s'",section, field) end infop_n = infop_n + 1 end end end Ну и наконец вызов abort_syntax_error_in_cond(npc, section, field) - это шедевр. Передается 'npc', которого и в помине тут нет, а в функции abort_syntax_error_in_cond из ничего хотят получить id !? .... Ага Нашел где 'npc_rank(novice)' без знака (это в оригинале). Исправил и уже не вылетает )) файл treasure_manager.ltx [esc_secret_box_bridge] ... condlist = {=actor_on_level(l01_escape) npc_rank(novice)} 3 ... и [esc_secret_village_rucksack] ... condlist = {npc_rank(novice)} 3 ... Так что у себя однозначно ставлю шаблон "(%S)([^%-%+%~%=%!%s]+)" Изменено 30 Июля 2015 пользователем Nazgool 2 Ссылка на комментарий
Dennis_Chikin 3 658 Опубликовано 30 Июля 2015 Автор Поделиться Опубликовано 30 Июля 2015 Сдается мне, что в parse_infop надо ловить неалфавитный символ + строку (включая точку). Как-то так. А потом ругаться, если непечатный символ не определится как осмысленный. И мне не нравится elseif-простыня, ПЕРЕД которой дергается parse_func_params. Кажется, должно быть решение изящнее. abort_syntax_error_in_cond - там в оригинале таких шедевров... Причем не работают - ВСЕ. По разным причинам, но не работают - дают глухой висяк + через непредсказуемое время после - наше любимое stack overflow/memory... Солянка обезжиренная, диетическая, полезные советы по "солянке", текущий тестовый патч Ссылка на комментарий
Рекомендуемые сообщения
Создайте аккаунт или авторизуйтесь, чтобы оставить комментарий
Комментарии могут оставлять только зарегистрированные пользователи
Создать аккаунт
Зарегистрировать новый аккаунт в нашем сообществе. Это несложно!
Зарегистрировать новый аккаунтВойти
Есть аккаунт? Войти.
Войти