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

Использование предметов актором

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

@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
Ссылка на комментарий

init() вызывается каждый раз при загрузке сейва? Я почему-то думал, что только при старте новой игры.


Вообще, про динамическое подключение чего угодно куда попало будет отдельная тема.

О, вот этого я подожду, а то никак приемлемый функционал не напишу.

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

Когда то давно, почти 2 года назад, как оказалось, я наверное изобрел велосипед, но тогда он мне казался весьма интересным в плане познания скриптов. По сути добавляет в какой-нибудь класс (хотя можно сделать и для просто функции) кастомный функционал. Реализация весьма проста - обычная обертка. Ради интереса вот. Это как раз динамичная штука, там есть функция pcon - это вывод в лог. В остальном все просто.

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

ТЧ 1.0004. SAP и Trans mod

github

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

 

 

Денис рассматривает коллбэки получения и потери предметов, take_item_from_box(), on_trade(), on_item_take(), как узнать куда положили или кому передали предмет.

Я правильно понимаю круг вопросов? Если вписывается, то нужно добавить это в текст, если нет - то создать отдельную статейку. либо расширить круг вопросов в рамках этой статьи.

Просто помните, мы говорили, что, конечно, одно цепляется за другое, но так мы никогда не остановимся? Вот этого и стоит опасаться. :)

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

Не, здесь точно закончили. Другие вопросы - отдельными темами.

 

 

Кстати, добавочка, но вообще к теме имеющая отношение отдаленное:

 

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

Так изящно, как с дропом, похоже, не получается - парент меняется до того, как, и для game- и для серверного объекта.

 

Печаль...

 

 

2 Shadows: на вкус и цвет. Кому что надо.

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

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

Не проще ли, колбек на дроп из тушки?
Ссылка на комментарий

А вот мои поделки.

 

 

local actor_id 	= 0
local idnoparent	= 65535

local con = get_console()
local function log(arg)
	con:execute("load ~ "..string.sub(tostring(arg),1 , 200))
	--con:execute("flush")
end

--------------------------------------------------------------
function init_item(obj)
	local new_binder = bind_item(obj)
	obj:bind_object(new_binder)
end

class "bind_item" (object_binder)
function bind_item:__init(obj) super(obj)
end

function bind_item:net_spawn(data)
	if not object_binder.net_spawn(self, data) then
		return false
	end
	self.sect = self.object:section()
	
	log("bind_item:net_spawn(data) section-("..self.sect..")id-("..self.object:id()..")time-("..time_global()..")")
	return true
end

function bind_item:update(delta)
	object_binder.update(self, delta)
	local parent = self.object:parent()
	if parent then
		self.parent_id = parent:id()
	else
		self.parent_id = idnoparent
	end
	-- Здесь может быть код
end

function bind_item:net_destroy()
	if self.parent_id == actor_id and level.main_input_receiver() then
		log("bind_item:net_destroy()activate item!!! section-("..self.sect..")id-("..self.object:id()..")time-("..time_global()..")")
		-- Здесь располагаем код или функцию активации предмета.
	end
	object_binder.net_destroy(self)
end

 

 

Вероятно код не совсем оптимален, но я такой юзаю для активации разных предметов. Преимущество: не зависимость, кинул скрипт в папку скрипты, и всё работает, хорошо для глобальных модов, и мелких то же.

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

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

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

Сразу ограничения:

1. Просто забросить скрипт - недостаточно. Надо еще в конфиге предмета прописывать. А там может быть прописано нечто иное.

2. net_destroy() - это не только использование, но и удаление, и просто уход в офлайн.

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

 

P.S. Но таки да, я, например, для того же детектора артов не нашел лучшего варианта, чем биндер, который заносит оные арты в табличку при net_spawn().

Изменено пользователем Dennis_Chikin
Ссылка на комментарий
1. Просто забросить скрипт - недостаточно. Надо еще в конфиге предмета прописывать. А там может быть прописано нечто иное.

Ну это само собой.

 

 

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

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

Хорошо бы в правленых движках этот колбек (use_item) на сам биндер предмета повесить.

ЗЫ

Ах да, ещё, если инвентарь открыт, и какой нибудь скрипт зачистит инвентарь ГГ (удалит этот предмет), то скрипт ложно сработает.

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

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

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

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

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

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

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

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

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

Войти

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

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

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

AMK-Team.ru

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