Разбор Bind или новая замена Infoportions — S.T.A.L.K.E.R. Inside Wiki

Разбор Bind или новая замена Infoportions

Материал из S.T.A.L.K.E.R. Inside Wiki

Перейти к: навигация, поиск

Описание статьи

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

Что такое Bind и как его используют?

Итак, разберу сначала такую штуку, как bind. Для меня Bind - это повторение одного и того же действия в режиме alife (во время игры), производимого для определённого объекта, пока он не перейдёт в офлайн. Не работает во время паузы. Итак, как же используется бинд? Начну с самых верхов. У нас есть какая-нибудь секция. По умолчанию там присутствует такая строка:

script_binding

И она ничему не равна. Как бы сказали скрипты: nil. Но есть такие секции, которым присвоены определённые значения script_binding. Например:

[actor]:common_ph_friction_params_on_npc_death
GroupControlSection		= spawn_group
$spawn 					= "actor"
$ed_icon				= ed\ed_actor
$player 				= on
$prefetch 				= 16
cform                   = skeleton
class                   = O_ACTOR
money					= 40;
rank					= 3;
script_binding			= bind_stalker.actor_init


Мы видим, что актор (собственно, сам Меченый или, если выражаться правильней, Главный Герой, ГГ) имеет скриптовый бинд. Теперь люди, знакомые со скриптом bind_stalker, могли заметить там функцию:

function actor_init    (npc)
	npc:bind_object(actor_binder(npc))
end

Разберём её. Прямо с первой строки.

  • actor_init - название функции
  • npc - тот объект, у которого в секции имеется script_binding, будет каждый раз при переходе в онлайн, биндиться тем самым скриптом, указаным в script_binding. Т.к. всё вертится вокруг актора, то получается так, что актор всегда в онлайне, не считая его удаления (при выходе/перезагрузке или при переходе на др. локацию)

Вторая строка:

  • bind_object - т.к. npc уже обсуждали, то скажу так: объект, получаемый скриптом через скриптовый бинд (на выходе npc) является объектом, к которому обращение идёт через двоеточие (game_object*). Например, можно написать npc:position(). Для примера можно вставить такой код:
function actor_init    (npc)
	get_console():execute(npc:game_vertex_id())
	npc:bind_object(actor_binder(npc))
end

Я его не проверял, но, по идее, он должен вывести в консоль текущий гейм-вертекс актора. Будет это выглядеть как-то так:

! Unknown command:  666

Это так будет, если гейм-вертекс актора будет аццкий - 666.

Итак... С обращением разобрались. Теперь разберём само обращение bind_object. Это обращение зашито в dll (скорее всего, в xrGame) и про него можно посмотреть в скрипте lua_help.script:

function bind_object(object_binder*);

Сразу перейдём к следующим после bind_object словам, не забывая про object_binder* из lua_help.script

  • actor_binder(npc) - это то, с помощью чего мы обращаемся. Если поднапряжёмся и вспомним, что было друмя строками выше, то поймём, что actor_binder(npc) - это и есть object_binder*. Но это не полное объяснение. Ещё надо добавить, что actor_binder - это класс в том же скрипте - bind_stalker, который носит назввание actor_binder. Как же он выглядит, это класс бинда:
class "actor_binder" (object_binder)

А npc в скобках - это object_binder, который указан в скобках класса.

"actor_binder" (object_binder)

Итак, мы разобрали построчно функцию actor_init, но ещё не знаем, чем же различаются класс actor_binder и функция actor_init, начинающая этот бинд. Так вот в чём различия:

  • actor_binder - это класс, который постоянно повторяется (биндится)
  • actor_init - это вызываемая из секции конфига функция, которая начинает бинд, но она выполняется только один раз - при переходе объекта в онлайн и ждёт перехода объекта в режим офлайн или удаления этого объекта из игры. После того, как объект пропадает, функция отключается и при его следующем появлении в онлайне, выполняется снова.

Но и бинд не длится вечно. Как я уже говорил, Bind - это повторение одного и того же действия в режиме alife (во время игры), производимого для определённого объекта, пока он не перейдёт в офлайн. Сам переход в офлайн происходит через net_destroy. Это я называю функцией класса. Теперь рассмотрим работу функций класса.

Я пока только начал подробное изучение биндеров и могу ошибиться в порядке выполнения функций класса типа bind. 
Тем не менее, у меня уже есть свой порядок выполнения функций класса bind.
  • Сначала выполняется __init.
  • Заетм идёт, load. Если сохранялись какие-то изменения.
  • Потом происходит net_spawn
  • Потом идёт update - бинд.
  • Потом при сохранении происходит save.
  • Затем net_destroy при офлайн переходе.
  • Потом по идее net_spawn и reinit. Дальше всё с третьего шага (не уверен, что второй шаг тоже пропускается)

На этапе net_destroy и net_spawn происходит множество других функций класса bind вроде take_item_from_box (колбэк на взятие предмета в инвентарь из inventory_box - тайника), on_item_drop (колбэк при выбрасывании предмета) и т.п. Тем не менее, некоторые функции класса (кроме главных, которые я перечислял в этапах) могут быть использованы на других этапах, но на update обычно происходят проверки. Не буду сейчас останавливаться на колбэках и перейду к следующему этапу - разбор сохранения.

Разбор сохранений

На самом деле сохранения я буду разбирать прямо сейчас. очень хочу что-нибудь полегче. Например, treasure_manager. Посмотрим, что тут у нас...

function CTreasure:save(p)
	--' Сохраняем размер таблицы
	local size = 0
	for k,v in pairs(self.treasure_info) do
		size = size + 1
	end
	p:w_u16(size)
	for k,v in pairs(self.treasure_info) do
		p:w_u16(v.target)
		p:w_bool(v.active)
		p:w_bool(v.done)
	end
end

По идее это save сохраняет всю указанную информацию объекте в sav файл сохранения в виде нет-пакета. Судя по некоторым строкам, которые, по идее могут кое-что прояснять... Особенно при вылете :) Например, "SAVE FILE IS CORRUPT". Что же, попробую теперь разобрать загрузку:

--' Загружаем уровень сложности
local game_difficulty = reader:r_u8()
local load_treasure_manager = false      
if game_difficulty >= 128 then           
	game_difficulty = game_difficulty - 128
	load_treasure_manager = true           
end                                      
get_console():execute("g_game_difficulty "..game_difficulty_by_num[game_difficulty])
 

Тут явная загрузка уровня сложности. Слишком явная, судя по комментарию... Хватит лирики... Разбираем!

  • Сначала задаём переменной game_difficulty одну из переменных пакета. В данном случае переменная числовая.
  • Присваиваем переменной load_treasure_manager булёвое (логическое) значение false.
  • Проверяем, что если game_difficulty больше или равен 128, то:
    • Вычитаем из game_difficulty 128
    • Присваиваем переменной load_treasure_manager значение true
  • Устанавливаем через консоль уровень сложности.

Потом можно заметить:

 
if load_treasure_manager == true then
	treasure_manager.load(reader)      
end
 

Это значит, что в будущем переменная load_treasure_manager будет играть особенную роль. По идее она отвечает за загрузку менеджера тайников. Точное значение сказать не смогу.

Итак, какие же значения можно присваивать пакетным данным? Об этом сказано в ранее упоминаемом скрипте lua_help.script. А там... Лучше не буду писать, а то места много надо. Сами посмотрите. Я укажу лишь несколько значений:

  • Булёвое. r_bool и w_bool
  • Строковое. r_stringZ и w_stringZ
  • Числовое. r_u8 и w_u8
Я не знаю точно, можно ли ставить (плавающую) запятую в числе, поэтому лучше берите целые числа где-то в области от -107 до 107

Практика

Итак. Теперь попробую попрактиковаться... Вы со мной... для начала изменим функции класса bind с именами до такого вида (полностью заменяйте текст функции, которую я вам даю:

 
function actor_binder:net_spawn(data)
	printf("actor net spawn")		
 
	level.show_indicators()
 
	self.bCheckStart = true
	self.weapon_hide = false -- спрятано или нет оружие при разговоре.
	weapon_hide = false -- устанавливаем глобальный дефолтовый флаг.
 
	if object_binder.net_spawn(self,data) == false then
		return false
	end
 
	db.add_actor(self.object)
 
	if self.st.disable_input_time == nil then
		level.enable_input()
	end
 
	self.weather_manager:reset()
--	game_stats.initialize ()
 
	if(actor_stats.add_to_ranking~=nil)then
		actor_stats.add_to_ranking(self.object:id())
	end
 
	--' Загружаем настройки дропа
	death_manager.init_drop_settings()
 
	--' В случае новой игры у нас этой переменной не будет. Иначе она загрузится из load
	if db.storage["rekongstor_boolean"] == nil then
	db.storage["rekongstor_boolean"] = true
	end
 
 
	if db.storage["rekongstor_boolean"] == true then
		alife():create("af_medusa",vector():set(0,0,0),1,1,self.object:id())
		db.storage["rekongstor_boolean"] = false
	end
 
	return true
end
 

Лучше редактировать сначала load, а потом save, т.к. по любому сначала идёт загрузка...

 
function actor_binder:load(reader)
	printf("actor_binder:load(): self.object:name()='%s'", self.object:name())
	object_binder.load(self, reader)
	printf("actor_binder:object_binder.load(): self.object:name()='%s'", self.object:name())
 
	--' Загружаем уровень сложности
	local game_difficulty = reader:r_u8()
 
	local load_treasure_manager = false      
	if game_difficulty >= 128 then           
		game_difficulty = game_difficulty - 128
		load_treasure_manager = true           
	end                                      
 
 
	get_console():execute("g_game_difficulty "..game_difficulty_by_num[game_difficulty])
 
	if reader:r_eof() then
		abort("SAVE FILE IS CORRUPT")
	end
 
	local stored_input_time = reader:r_u8()
	if stored_input_time == true then
		self.st.disable_input_time = utils.r_CTime(reader)
	end
 
	--' проходит прежде всего. если данных в пакете нет, то по идее должен быть nil
		db.storage["rekongstor_boolean"] = reader:r_bool()
 
	xr_logic.pstor_load_all(self.object, reader)
	self.weather_manager:load(reader)
 
	sr_psy_antenna.load(reader)
 
	if load_treasure_manager == true then
		treasure_manager.load(reader)      
	end                                  
 
 
	task_manager.load(reader)
	self.actor_detector:load(reader)	
end
 

А теперь save:

 
function actor_binder:save(packet)
 
	local save_treasure_manager = true
 
	printf("actor_binder:save(): self.object:name()='%s'", self.object:name())
	object_binder.save(self, packet)
 
	--' Сохраняем уровень сложности
	if save_treasure_manager == true then
		packet:w_u8(level.get_game_difficulty() + 128)
	else
		packet:w_u8(level.get_game_difficulty())
	end
 
 
	--' Сохраняем данные об отключенном вводе
	if self.st.disable_input_time == nil then
		packet:w_bool(false)
	else
		packer:w_bool(true)
		utils.w_CTime(packet, self.st.disable_input_time)
	end
 
	--' save по-любому будет после новой игры. поэтому переменная у нас будет либо дефолтная (при net_spawn), либо загруженная (при load)
	packet:w_bool(db.storage["rekongstor_boolean"])
 
	xr_logic.pstor_save_all(self.object, packet)
	self.weather_manager:save(packet)
 
	sr_psy_antenna.save( packet )
 
	if save_treasure_manager == true then
		treasure_manager.save(packet)      
	end                                  
 
	task_manager.save(packet)
	self.actor_detector:save(packet)	
end
 

Я специально оставил комментарии, чтобы было легче разобраться в действиях. А теперь объяснения:

	--' проходит прежде всего. если данных в пакете нет, то по идее должен быть nil
		db.storage["rekongstor_boolean"] = reader:r_bool()

Тут происходит загрузка данных из сохранённого пакета в переменную, которая существует на протяжении игры. Точно не знаю, когда она прерывается, но это не имеет особого значения.

  • Я не уверен, но сначала происходит load. Если он не нашёл данные в пакете, то они равны nil. В этом случае переменная принимает данные из пакета.
  • Load может и не происходить, но и так данные будут равны nil, потому что им ничто не присвоено. А в этом случае просто load не происходит и переменная никак не трогается.
	--' В случае новой игры у нас этой переменной не будет. Иначе она загрузится из load
	if db.storage["rekongstor_boolean"] == nil then
	db.storage["rekongstor_boolean"] = true
	end
 
 
	if db.storage["rekongstor_boolean"] == true then
		alife():create("af_medusa",vector():set(0,0,0),1,1,self.object:id())
		db.storage["rekongstor_boolean"] = false
	end

Сначала происходит Load, но его я уже объяснил. Сейчас я объясняю net_spawn.

Первое действие - проверка на существование переменной. Если мы начали новую игру, то тогда переменная равна nil, т.к. load её ещё не знает и мы её ещё не объявляли. А если переменной не существует, то мы ей объявляем значение true и она уже не nil, а принимает логическое значение.

Второе действие. Если у переменной логическое значение равно true, то спавним артефакт медуза в инвентарь ГГ и объявляем значение false. Если было значение false, то не спавним. Как это работает?

  • Мы начали новую игру
  • (Возможно, идёт загрузка пакета и переменной, которая имеет значение nil. Мы опять присваиваем значение nil, т.е. ничего не меняется.)
  • Идёт проверка. Т.к. переменная = nil, то ей присваивается true.
  • Идёт проверка. Т.к. переменная = true, То спавним артефакт (а вообще, можно произвести любое действие) и присваиваем переменной значение false
      • Больше ничего не происходит - т.е. идёт бинд update
  • Мы сохраняем игру (переходим на др. уровень)
  • Идёт сохранение всех переменных, в том числе и нашей, а она равна false
  • Загружаем игру (загрузка др. уровня)
  • Идёт загрузка пакета и переменной, которая имеет значение nil. Мы присваиваем значение false, т.е. то, которое было сохранено.
  • Идёт проверка. Т.к. переменная = false, то ей ничего нового не присваивается (действие присваивания пропускается)
  • Идёт проверка. Т.к. переменная = false, игра обходит действие и не происходит спавна и присвоения переменной false.


Вот и всё. У меня скрипт нормально работает и спавнится только один артефакт. А теперь спросите, зачем это нужно? Значит вспоминаем не любимые многими модмейкерами инфопоршни. Именно с ними многие мучались при создании квестов. Сейчас я буду пробовать использование этих новых переменных, которым я пока дал название Info_Scripts в различных направлениях. Сейчас я знаю, что инфопоршни можно использовать с диалогами, квестами, энциклопедией и самими скриптами. В данном примере мы рассмотрели запрет на повторение действия без инфопоршня, но это было намного сложнее, нежели если бы мы создали инфопоршень. Тем не менее, это подробный разбор для отдельного случая. В последующих изменениях будет улучшена система выдачи этого добра и будет всё упрощено, в лучшем случае, до одной строчки. Но сначала надо создать скрипт, обрабатывающий всю эту информацию.

Различия и сходства Infoportion и Infoscript

Чем отличается Infoportion от Infoscript:

  • Получается из xml файла
  • Относительно лёгкое использование в диалогах
  • Относительно лёгкое использование в стандартных заданиях
  • Помимо булёвых значений, может содержать информацию для выдачи статьи энциклопедии и/или задания
  • Сложное использование в нестандартных заданиях, но есть возможность диалогов

Чем отличается Infoscript от Infoportion:

  • Получается из переменной скрипта
  • Помимо булёвого значения, может принимать строковые и числовые
  • Дальше не проверенная, но теоретически возможная информация:
    • Сложное использование в диалогах (изучается сложность, направление на лёгкость - за основу dialog_manager.script) или лёгкий аналог
    • Сложное использование в стандартных заданиях (изучается сложность, направление на лёгкость за основу task_manager.script)
    • Не может выдать статью в энциклопедию (на проверке ключи и другие, если найду и эти не помогут set_article_key и set_article_id)
  • Относительно лёгкое (при использовании упрощённого скрипта) использование при создании нестандартных квестов, но диалоги, скорее всего, придётся заменить

Основное отличие infoportions от infoscripts в том, что состояние infoportions привязывается к объекту игры, и при удалении объекта, infoportion сбрасывается. При этом infoscripts всегда привязаны к объекту actor.

В чём их сходства?

  • Оба могут использоваться в качестве булёвого значения
  • Оба загружаются из файла сохранения (обычная переменная на такое не способна)
  • Оба могут использоваться в нестандартных заданиях
  • Оба могут использоваться в стандартных заданиях и диалогах

Проблема со стандартными заданиями и диалогами

Есть такие теги, как infoportion_complete и infoportion_fail. Они оба обращаются к отдельно взятому инфопоршню. В диалогах почти также - has_info и dont_has_info. Это всё проверки на существование у игрока инфопоршня. Если такой будет, то задание либо выполняется, либо проваливается. Диалог/реплика либо присутствует, либо отсутствует. Но есть и такие теги, как precondition. Он, если вернёт false, то диалог или его реплика будут отсутствовать, если true, то наоборот. В заданиях теги такие - function_complete и function_fail. Они отвечают за возвращение функции true и false. При выполнении условий (возвращение функцией true), задание выполняется или, соответственно, проваливается. Но они обращаются лишь к функции, а вот какой инфоскрипт мы хотим взять, он умалчивает. Поэтому, пока придётся обходиться проверкой наличия инфоскрипта отдельно - через конкретное содержание функции. Но я попробую найти зацепки у диалогов и/или заданий, через которые можно получить уникальную информацию и послать её в функцию. Но это уже мои проблемы.

Что такое нестандартные задания?

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

  • Мы в зоне выдачи задания?
  • Мы согласились на выполнение задания?
  • Нам выдали задание?
  • Мы в зоне выполнения подзадания / у нас есть предмет для выполнения подзадания?
  • Мы выполнили подзадание?
  • Мы в зоне выполнения задания?
  • Наше подзадание выполнено?
  • Закончить выполнение задания.
    • Выдать награду в виде денег, ранга или любой другой вещи, которые можно придумать.


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

Заключение

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

Ждите новых статей по инфоскриптам!

Я думаю, что эта статья достойна обсуждения!

Дополнения

  • Возможно, получится создать аналог диалогу с помощью dialog_manager.script
  • То же самое и для заданий! Скорее всего, можно будет использовать некоторые возможности task_maneger.script, чтобы занести задание в КПК.
  • Сейчас появилась возможность, что выдать статью возможно - произвёл поиск по всем скриптам по article. Нашёл set_article_key и set_article_id. Буду пробовать.
  • Ещё сейчас начну проверку по АМК скриптам. Там вроде что-то тоже упоминалось про сохранение и загрузку переменных.

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

Ссылки

Скрипт bind_stalker с одиночной выдачей "Медузы"
*.RAR (4087 Б)
*.SCRIPT (15476 Б)

Автор

Rekongstor ~19:30-22:54, 9 января 2011 (UTC)

Другие места
LANGUAGE