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

Это, разумеется, читать и использовать все равно ни кто не будет, но поскольку однотипные вопросы, задаваемые неизвестным телепатам, слегка поддостали, хоть душу отведу.

 

Исторически сложилось, что разные события, случающиеся с актором, обрабатываются в bind_stalker скрипт.

Опять же, исторически, в нем есть несколько коллбэков, отвечающих за получение и потерю предметов:

 

function actor_binder:take_item_from_box( box, item )

function actor_binder:on_trade( item, sell_bye, money )

function actor_binder:on_item_take( item )

function actor_binder:on_item_drop( item )

function actor_binder:on_use_object( item )

 

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

 

function actor_binder:reinit()
	log( "info", "reinit" )
	object_binder.reinit( self )
	pstor = {}
	st = { ["pstor"] = pstor }
	self.st = st
	db.storage[0] = st

	self.next_restrictors_update_time = -10000
	actor:set_callback( callback.inventory_info,	self.info_callback,	self )
	actor:set_callback( callback.article_info,	self.article_callback,	self )
	actor:set_callback( callback.on_item_take,	self.on_item_take,	self )
	actor:set_callback( callback.on_item_drop,	self.on_item_drop,	self )
	actor:set_callback( callback.trade_sell_buy_item,self.on_trade,	self )

	actor:set_callback( callback.task_state,	self.task_callback,	self )
	actor:set_callback( callback.level_border_enter,self.level_border_enter,self )
	actor:set_callback( callback.level_border_exit,	self.level_border_exit,	self )
	actor:set_callback( callback.take_item_from_box,self.take_item_from_box,self )
	actor:set_callback( callback.use_object,	self.on_use_object,	self )
	actor:set_callback( callback.death,		self.death_callback,	self )

	log( "info", "reinit, done" )
end

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

 

Снимаются колбэки так:

 

function actor_binder:net_destroy()
	local log_level = smart_terrain.get_log_level() or 100
	if log_level < 20 and log_level >= 2 then
		smart_terrain.disable_log()
		if se_stalker.disable_log then se_stalker.disable_log() end
		if se_monster.disable_log then se_monster.disable_log() end
		xr_gulag.disable_log()
	end

	local remove_from_ranking = actor_stats.remove_from_ranking
	if remove_from_ranking then remove_from_ranking( 0 ) end
	sr_light.clean_up()

	actor:set_callback( callback.inventory_info )	-- чистим колл-бэки ( устанавливаются в nil )
	actor:set_callback( callback.article_info )
	actor:set_callback( callback.on_item_take )
	actor:set_callback( callback.on_item_drop )
	actor:set_callback( callback.trade_sell_buy_item )

	actor:set_callback( callback.task_state )
	actor:set_callback( callback.level_border_enter )
	actor:set_callback( callback.level_border_exit )
	actor:set_callback( callback.take_item_from_box )
	actor:set_callback( callback.use_object )
	actor:set_callback( callback.death )

	if psy_antenna then
		psy_antenna:destroy()
		sr_psy_antenna.psy_antenna = false
	end

	xr_sound.stop_all_sound_object()
	db.del_actor( actor )
	_G.actor = nil
	object_binder.net_destroy( self )
end

- надо это, чтобы итра не вылетала при уничтожении объекта (в данном случае - при завершении/перезагрузке).

 

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

 

Да, в случае очистки коллбэков магическое nil, типа actor:set_callback( callback.article_info, nil ) - вроде, не требуется. По крайней мере я ни каких негативных эффектов пока не увидел. Но, возможно, ошибаюсь, и эффекты просто пока не вылезли.

А вообще за код вида "== nil" и "~= nil" надо давать сразу 35 лет строгого расстрела. Вот чтоб каждое утро в 6 часов не взирая на погоду выводили, и расстреливали.

 

Почему здесь везде actor а не db.actor ? По тому что код пал жертвой рефакторинга, и актор опрделяется раз и навсегда в глобальном пространстве:

function actor_binder:__init( npc ) super( npc )
	_G.actor = self.object
	db.add_actor( actor )
	self.is_saved = false
end
Изменено пользователем Murarius
  • Спасибо 2
  • Полезно 1
Ссылка на комментарий

 

 

А вообще за код вида "== nil" и "~= nil" надо давать сразу 35 лет строгого расстрела.

Это еще почему?

ИМХО Тема не совсем верно названа, по смыслу скорее "Использование событий актора" и дополнить не только потерей\получением предмета, но и всеми остальными событиями, на которые можно подписаться.

ТЧ 1.0004. SAP и Trans mod

github

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

По тому что требуют явно определять переменные и явно возвращать из вызываемых функций это самый nil.

 

Да, а рассматривать в этой теме on_hit(), on_death() и прочие апдейты я не буду. Только операции с предметами.

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

По тому что не срабатывает. Неделю назад в ШМ обсуждалось. Впрочем, сейчас воспроизвести не удалось. Но движок другой - для забугра, а не для одноэса.

 

 

Продолжаем разговор. ©

 

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() будет отдельная телега.

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

 

 

эта функция нам пригодится.

 

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

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

Для неписей, внезапно, нужна function actor_binder:on_item_drop( item ).

Как ни странно. Вот там все будет.

 

А с этой, например, удобно считать предметы, или те же деньги, которые перешли к кому-либо после открытия окна торговли. То есть, мы можем, например, переоткрывать лавочку для обмена предметов, притащенных по спецзаказу, на товары "из под прилавка". Вместо того, чтобы плодить 100500 строк "бартера" в диалогах, или забивать нетпакет мусором из task_manager.ltx.

 

И, кстати, все не доходят руки посмотреть, кто у перемествшегося предмета - parent, и когда именно он меняется. Собственно, по тому и не доходят, что on_drop хватает.

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

И, кстати, все не доходят руки посмотреть, кто у перемествшегося предмета - parent, и когда именно он меняется.

На дропе? Для какого объекта вызывали колбек, тот и парент.
Ссылка на комментарий

На трэйде.

 

А вот на дропе, внезапно, как раз таки нет, из чего и проистекает весьма много вкусностей.

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

А вот на дропе, внезапно, как раз таки нет, из чего и проистекает весьма много вкусностей.

Ссылка на инвентарь, в котором лежит предмет обнуляется аккурат перед вызовом колбека для владельца.

 

На трэйде.

Скорее всего парент уже актор, вызов колбека происходит в самом конце функции трансфера.
Ссылка на комментарий

Продолжаем разговор...

 

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, кстати, тоже надо отсюда убрать.

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

повесил на этот вызов 100500 if ... then ...

Стоит лишь отсечь стартовый спавн предметов в актора, например через проверку device().precache_frame и проблема будет решена.

 

Переписывавем традиционно:

А толку, если такая таблица актуальна только при старте игры по сути? Тогда уж на дропе убирай из таблицы выкинутую вещь. Впрочем, чем не нравится получение предмета через actor:object(section) (или через ту же итерацию по инвентарю) - я хз.
Ссылка на комментарий

А теперь самое вкусное. Я так до сих пор и не понял, зачем люди вешают все-все на

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

 

На этом, в общем-то, все.

И немедленно выпил. ©

Изменено пользователем 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

На этом, в общем-то, все.

И немедленно выпил. ©

 

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

Мне одному кажется, что в отредактированном варианте что-то пропало ?

Ну и удаление разбивки читабельность отнюдь не улучшает.

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

local sect = item:section()
-- log( "info", "on_item_lost: %s", sect )

local id = item:id()
if inv_used[id] then inv_used[id] = nil -- использован
-- log( "info", "on_item_lost: %s, used", sect )
else
local obj = sim:object( id )
if obj then
if ( obj.parent_id or 65535 ) == 65535 then -- выброшен
-- log( "info", "on_item_lost: %s, drop", sect )
for i = 1, on_drop_n do on_drop_t.any[i]( item ) end
if on_drop_t[sect] then
for i, f in ipairs( on_drop_t[sect] ) do f( item ) end
end
-- else log( "info", "on_item_lost: %s, new parent: %s", sect, obj.parent_id )
end
-- else log( "info", "on_item_lost: %s, deleted", sect )
end end
Код все же лучше давать в псевдо-коде. Тогда предыдущий кусок будет гораздо понятней

if item_used(item) then return end
if item_released(item) then
  signals.on_item_release(item)
elseif item_has_parent(item) then
  signals.on_transfer_item(item)
else
  signals.on_drop_item_to_ground(item)
end
Еще в примере описка походу: on_drop_t.any( item ) end

И в одном случае:

for i, f in ipairs( on_drop_t[sect] ) do f( item ) end
а в другом:

local t = on_take_t[item:section()]
if t then
for i = 1, #t do t[i]( item ) end
Ссылка на комментарий

local on_drop_t = { ["any"] = {} } -- вполне честная таблица из inv_manager.script
кусок кода с for i = 1, on_drop_n do on_drop_t.any( item ) end -- оттуда же.
Вызываются все функции, добавленные на выпадание любого предмета на землю.

по for i, f in ipairs( on_drop_t[sect] ) do f( item ) end -- это вызов всех функций, которые добавлены для конкретного предмета.

 

В принципе кусок не из этой серии, добавлен для иллюстрации возможностей. Из рабочего скрипта.

Да, наверное, лучше переписать в псевдокоде. Если кому заняться нечем. ;)

Но не signals все-же.

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

 

 

Мне одному кажется, что в отредактированном варианте что-то пропало ?

По-моему, ничего.

 

 

Ну и удаление разбивки читабельность отнюдь не улучшает.

Это о чем? Я вроде не удалял ничего. Вот редактор при копипасте иногда чудеса вытворяет с лишними строками и пробелами - это может быть...

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

Можно вопрос, а в самих скриптах где мы интить будем свои функции разве не нужно будет писать if then 100500 раз? Посыл то какой, автономность? Так xr_s (xStream), m_main (Artos), ogse_signals (malandrinus)... Я конечно не программист, но у тебя вроде быстрее работа, это основная цель написания своей системы что-ли? Или я не понял чего?

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

Да, bind_stalker.script, как и все остальное, мы изменяем один раз в жизни, и никогда больше не трогаем.
 
Есть апи, которое из любого добавляемого скрипта дергается при ините, и вот там добавляется все, что надо.
При этом отработав, скажем, на локаци X квест Y больше никогда ничего не добавляется, и естественно, не проверяется.
Ну а выборка из таблицы выигрывает уже при 5-7 if даже без сравнения строк.
 
Вот, например, инициализация escape_dialog.script из NLC/солянки, квест с ночной звездой:

 

t_take = {
{ have_a_art, "af_night_star", "kvest_art_started", "test_quest_art_vziat" }
}

function init()
	for i, v in ipairs( t_take ) do
		if ( ( not v[3] ) or actor:has_info( v[3] ) )
		 and ( ( not v[4] ) or actor:dont_has_info( v[4] ) ) then
			bind_stalker.add_on_take( v[1], v[2] )
	end	end
	return true
end

 

- это оно сюда вообще из amk.script переехало.

А вот по-злобнее будет:

 

t_take = {	-- f, item, level, has_info, dont_has_info
{ pda1,		"kostya_pda",	"l01_escape",	"kostya_taynik1_start",		"kostya_pda_have" },
{ esc_art_have,	"af_gold_fish", "l01_escape",	"kostya_art_start",		"kostya_art_have" },
{ pda2,		"kostya_pda",	"l02_garbage",	"kostya_svalka_taynik_start",	"kostya_svalka_taynik_have" },
{ pda3,		"kostya_pda",	"l04u_labx18",	"kostya_x18_taynik_start",	"kostya_x18_taynik_have" },
{ pda5,		"kostya_pda",	"l03_agroprom",	"kostya_agroprom_taynik_start",	"kostya_agroprom_taynik_have" },
{ pda4,		"kostya_pda",	"l08_yantar",	"kostya_yantar_taynik_start",	"kostya_yantar_taynik_have" },
{ pda6,		"kostya_pda",	"l07_military",	"kostya_as_taynik_start",	"kostya_as_taynik_have" },
{ pda7,		"kostya_pda",	"l10_radar",	"kostya_radar_taynik_start",	"kostya_radar_taynik_have" },
{ pda9,		"kostya_pda9",	"l10u_bunker",	"kostya_x10_taynik_start",	"kostya_x10_taynik_have" },
{ kostya_doc,	"kostya_documents","l10u_bunker","kostya_x10_taynik_start",	"kostya_documents_have" },
{ pda8,		"kostya_pda",	"l11_pripyat",	"kostya_sacharov_start",	"kostya_pripyat_taynik_have" },
{ pda_vasilyev,	"pda_vasilyev",	"l08u_brainlab","kostya_sacharov_pda_start",	"kostya_sacharov_pda_have" },
}

function init()
	local lname = level.name()
	for i, v in ipairs( t_take ) do
		if lname == v[3] and actor:has_info( v[4] ) and actor:dont_has_info( v[5] ) then
			bind_stalker.add_on_take( v[1], v[2] )
	end	end
	return true
end

 


Раньше все это висело всю игру, причем на любой чих, да еще сначала вызовом через 3 скрипта, а потом уже получение секции и проверка.

 

P.S. На самом деле это примерно то самое, что ПЫС имели в виду под схемами. Только предельно упрощенное, и по тому - работающее как задумано, а не как получилось. ;)

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

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

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

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

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

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

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

Войти

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

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

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

AMK-Team.ru

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