Перейти к контенту
Dennis_Chikin

Прозекторская

Рекомендуемые сообщения

"Вскрытие показало, что больной умер от вскрытия."

Тема для "крупной формы", то есть, на уровне скриптов целиком или больших частей оных скриптов. "Что у него внутри, зачем оно там, и что с этим можно сделать ?"

Изменено пользователем Dennis_Chikin
Ссылка на комментарий

Пост к вот этому: 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

Изменено пользователем Dennis_Chikin
  • Спасибо 1
  • Полезно 1

...в конце концов, важен лишь, машинный код.

СТАЛКЕР только для ПК!

Ссылка на комментарий
К использованию рекомендуются интегрированные системы компонентов, включающие систему хранения, событий и таймеры. Именно все три, поскольку они друг на друге строятся.

 

А можно назвать логические причины, почему эти три системы (все три) друг на друге строятся (почему они обязаны друг на друге строиться, причины?).

Я например, поразмыслив, этот момент так и не понял. Система хранения и таймеры - ну ладно еще допустим взаимосвязаны. Хотя я бы сказал не факт. А третье...

 

 

Если по факту раз n секунд надо лишь проверить, не наступило ли собственно время для события х. То есть, сравниваем нашу константу с game_time(). При чем здесь таймеры ?

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

 

DC: Можно я твой вопрос про таймеры перефразирую немножко иначе. Вот так: Для чего нужно ООП? для каких целей уместно применять, для каких нет. и почему. Сформулируй свой вариант ответа на такой вопрос?

Кстати ответ уважаемого маландринуса, на этот же вопрос, мне тоже любопытен.

Изменено пользователем Zander_driver

Мод, где не бывает одинаковых путей - Судьба Зоны. (Лучшее, что у меня получилось на X-Ray) На базе модифицированного движка OGSR Engine.

Бывший мододел на X-Ray / Начинающий игродел на Unreal Engine. Программист.

AMD Ryzen 9 7950X (16 ядер, 5.7ГГц); RTX 3080; 128 ГБ DDR5; Arctic Liquid Freezer II-420; 3 ТБ SSD PCIe 4.0; 4ТБ HDD.

Ссылка на комментарий

почему эти три системы (все три) друг на друге строятся

Ну они конечно иерархически подчинены. Самая независимая - система событий (сигналов, ивентов и т.п.). Система хранения уже зависит от событий, поскольку использует события для инициализации себя в нужные моменты. Таймеры зависимы и от событий (в основном от апдейтов) и от системы хранения (если сохраняемые). Мои таймеры тесно используют особенности моей системы событий. Песочницу от 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

 

Ссылка на комментарий

У себя уже давно использую оригинальный пысовский метод level.add_call() - как раз для таких "быстрых" таймеров. Каждый новый вызов этого метода, по сути и является созданием экземпляра класса и всё действо помещается в одну строку луашного кода. Но при такой компактности, конечно, и наворотов меньше чем у malandrinus'a - например из входных параметров только: имя таймера, время (таймаут), функция, которая будет вызвана по окончанию таймера и аргументы, переданные в эту функцию. То есть это именно таймер, с одним лишь условием - временем. Сохранение между сейвами так же не предусмотрено, но лично для моих нужд, пока этого хватает.

Изменено пользователем Shadows
Ссылка на комментарий

 

 

использую оригинальный пысовский метод level.add_call()

Это не ООП, а типичный процедурный подход, поскольку отсутствует инкапсуляция кода и данных. Ответь на простой вопрос, как ты при этом будешь хранить данные, которые надо "донести" от места/момента запуска до места/момента срабатывания? В принципе конечно можно использовать замыкания, но как по мне - это эрзац и вообще не ООП, а элемент функционального программирования.

 

 

 

Получается, что приведенные методы (ну кроме конструктора), должны быть виртуальными

Если вкратце, то в Lua, в силу прототипного ООП, все методы заведомо виртуальные, поскольку определяются ссылками, хранящимися прямо в таблице/юзердате (что аналогично таблице виртуальных методов C++).

 

Плагины Total Commander для работы с игровыми архивами:

Архиваторный плагин (для работы с одиночным архивом): link1 link2

Системный плагин (для распаковки установленной игры): link1 link2

 

Ссылка на комментарий

 

 

Это не ООП, а типичный процедурный подход, поскольку отсутствует инкапсуляция кода и данных.

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

 

 

 

Ответь на простой вопрос, как ты при этом будешь хранить данные, которые надо "донести" от места/момента запуска до места/момента срабатывания?

Передаю внутрь функции, которая будет вызвана после отработки первой функции.

Ссылка на комментарий
из входных параметров только: имя таймера, время (таймаут), функция, которая будет вызвана по окончанию таймера и аргументы, переданные в эту функцию.

я так понял, что ты сделал некую обёртку для level.add_call(), позволяющую хранить данные до вызова функции?

 

Я то как раз сделал наоборот. Специально для "процедурщиков" написал обёртку над классом таймера, чтобы запускать в процедурно-ориентированном стиле. Правда только для сохраняемых таймеров. В итоге, можно и так сделать

ogse_st_mgr.start_timer(timer_name, <задержка>, <имя функции>, <аргументы>)

Но это естественно сильно ограничивает поле применения. Аргументы не могут быть любого типа, срабатывает только по задержке. Хотя конечно написать ещё одну обёртку с дополнительной функцией-условием тоже можно, но как по мне - это уже извраты и лучше использовать собственно класс таймера.

Изменено пользователем malandrinus
 

Плагины Total Commander для работы с игровыми архивами:

Архиваторный плагин (для работы с одиночным архивом): link1 link2

Системный плагин (для распаковки установленной игры): link1 link2

 

Ссылка на комментарий

 

 

я так понял, что ты сделал некую обёртку для level.add_call(), позволяющую хранить данные до вызова функции?

Можно и так сказать. Передаваемые аргументы хранятся в таблице, которая передается во вторую функцию, после чего эта таблица там распаковывается. Таким образом, можно передать любое количество аргументов и любого типа.

 

 

 

Но это естественно сильно ограничивает поле применения. Аргументы не могут быть любого типа, срабатывает только по задержке. Хотя конечно написать ещё одну обёртку с дополнительной функцией-условием тоже можно, но как по мне - это уже извраты и лучше использовать собственно класс таймера.

Ну безусловно, твои таймеры (точнее, я бы назвал это "отложенные события") это отличный пример возможностей ООП, однако из моей практики применения, мне были нужны именно таймеры (с временным условием), а следовательно такой подход вполне устраивает. Тут надо смотреть из конкретной практики применения и писать функционал под потребности применяющего.

Ссылка на комментарий

{}
Да, это все лирика была.

Вообще-то, планировал еще и расписывать: как, что и зачем сделано, но... В общем, попытаюсь.

В принципе, планировалось все по порядку описывать, с пляской от собственно _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 - это время от загрузки игры.

Изменено пользователем Dennis_Chikin
Ссылка на комментарий

 

 

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 ядер, 5.7ГГц); RTX 3080; 128 ГБ DDR5; Arctic Liquid Freezer II-420; 3 ТБ SSD PCIe 4.0; 4ТБ HDD.

Ссылка на комментарий

У меня там всего 600 байт лежит. Из которых еще 300 подлежит сносу.
При том, что легким движением руки максимальный объем расширяется до бесконечности.

Ну так и почему бы не pstor тогда ?

P.S. Пф ! Можно подумать, что есть всего один мод на 13 Гб... Ну пусть не на 13, а на 14...

 

(Это хвост про таймеры, которые не нужны. Бурное обсуждение "обо всем" перехало во флудилку.)

Изменено пользователем Dennis_Chikin
Ссылка на комментарий

Гм, методически неверно было с самого начала выкладывать что попало куда попало без объяснений, но, впрочем, объяснения были даны, про то что на форуме было отдельное спрятанное от большинства подполье, где предполагалось понаписать всякого разного, а потом все писатели куда-то делись, и в мир вышло вот это самое что попало и как попало.

 

И, да, по-хорошему бы надо и к этим кускам, каждому, во-первых добавить пояснения про то, что это такое, зачем, что внутри, и как это едят, а еще перетащить сюда же дискусси из других тем. Но... в общем, некогда и лень.

 

Однако, не лень добавить еще один очередной малоосмысленный кусок чего-то странного. Точнее, не то, чтоб не лень, а "за державу обидно". Обидно в общем-то следующе: в свое время я поимел немало дивного секса с кучей монстротаблиц в "солянке", и был этим самым сексом, э-эээ, удовлетворен более, чем хотелось. До полного осознания смысла фразы "много хорошо - тоже плохо". А еще кроме монстротаблиц в десятках экземпляров одного и того же, было 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. Да, тот код, который я время-от времени куда-нибудь выкладываю, именно на эти таблицы и опирается. Так что, опять же, если кого интересуют какие мои поделия, то вот можно глянуть сюда на предмет всяких "недостающих" частей.

Изменено пользователем Dennis_Chikin
  • Согласен 1
Ссылка на комментарий

Для поиска: диалоги, 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. разбор потрохов, надеюсь, воспоследует. Как руки дойдут.

Изменено пользователем Dennis_Chikin
  • Спасибо 1
Ссылка на комментарий

 

 

Нужны диалоги на скриптовом гуе

А диалоги, оформленные как в Mass Effect, не хочешь? ;)

А то ведь будут, запросто.

Мод, где не бывает одинаковых путей - Судьба Зоны. (Лучшее, что у меня получилось на X-Ray) На базе модифицированного движка OGSR Engine.

Бывший мододел на X-Ray / Начинающий игродел на Unreal Engine. Программист.

AMD Ryzen 9 7950X (16 ядер, 5.7ГГц); RTX 3080; 128 ГБ DDR5; Arctic Liquid Freezer II-420; 3 ТБ SSD PCIe 4.0; 4ТБ HDD.

Ссылка на комментарий

Я б этот массэффект еще видел...

Впрочем, оформление может быть любым - основной-то вопрос в создании/изменении всяких мегабайтных wawka_dialog и иже с ними. Которые даже редакторы-то нормально пережевать не могут.

 

2 abramcumner: так они именно что в xml. 180 файлов, 6 мег в gameplay, и еще 4 в text\rus.

Изменено пользователем Dennis_Chikin
Ссылка на комментарий

Для поиска: netpacket, нетпакеты, хранилище

Поскольку возник такой вопрос, выкладываю здесь для интересующейся общественности se_stor от @Artos (версия от 09.09.2013, последняя из опубликованных автором):
https://yadi.sk/d/AlA809oPehaqN
Авторские ссылки все устарели, а в середине темы «Ищу файлы ...» не каждый догадается посмотреть.


Перенесу, пожалуй, сюда. Ибо внутри темы на 3 сотни страниц очень не сразу найти можно. dc

Изменено пользователем Dennis_Chikin
  • Спасибо 1
  • Полезно 2
Ссылка на комментарий

xr_logic

 

Раз пошла такая пьянка, закину сюда свою недоделку: или кто что нужное найдет, или недоделанное поможет допилить. Ну и вообще как затравка для анатомирования и препарирования: разберем, будет руки и желание дойдут - что и зачем где делается.

 

https://dl.dropboxusercontent.com/u/27871782/xr_logic.script

  • Нравится 1
Ссылка на комментарий

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 и -info3info2 прочитан не будет, т.к. перед ним нет никакого знака.

На самом деле это ошибка. И, считаю, такие записи нужно пресекать.

 

Что и получилось при использовании шаблона  "(%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]+)"
Изменено пользователем Nazgool
  • Нравится 2
Ссылка на комментарий

Сдается мне, что в parse_infop надо ловить неалфавитный символ + строку (включая точку).

Как-то так. А потом ругаться, если непечатный символ не определится как осмысленный. И мне не нравится elseif-простыня, ПЕРЕД которой дергается parse_func_params.

 

Кажется, должно быть решение изящнее.

 

abort_syntax_error_in_cond - там в оригинале таких шедевров... Причем не работают - ВСЕ. По разным причинам, но не работают - дают глухой висяк + через непредсказуемое время после - наше любимое stack overflow/memory...

Ссылка на комментарий

Создайте аккаунт или авторизуйтесь, чтобы оставить комментарий

Комментарии могут оставлять только зарегистрированные пользователи

Создать аккаунт

Зарегистрировать новый аккаунт в нашем сообществе. Это несложно!

Зарегистрировать новый аккаунт

Войти

Есть аккаунт? Войти.

Войти
  • Недавно просматривали   0 пользователей

    Ни один зарегистрированный пользователь не просматривает эту страницу.

AMK-Team.ru

×
×
  • Создать...