Разбор Bind или новая замена Infoportions — различия между версиями
Материал из S.T.A.L.K.E.R. Inside Wiki
(→Дополнения) |
(→Различия и сходства Infoportion и Infoscript) |
||
(не показаны 5 промежуточные версии 1 участника) | |||
Строка 315: | Строка 315: | ||
* Относительно лёгкое (при использовании упрощённого скрипта) использование при создании нестандартных квестов, но диалоги, скорее всего, придётся заменить | * Относительно лёгкое (при использовании упрощённого скрипта) использование при создании нестандартных квестов, но диалоги, скорее всего, придётся заменить | ||
+ | Основное отличие infoportions от infoscripts в том, что состояние infoportions привязывается к объекту игры, и при удалении объекта, infoportion сбрасывается. | ||
+ | При этом infoscripts всегда привязаны к объекту actor. | ||
В чём их сходства? | В чём их сходства? | ||
Строка 352: | Строка 354: | ||
* Сейчас появилась возможность, что выдать статью возможно - произвёл поиск по всем скриптам по article. Нашёл set_article_key и set_article_id. Буду пробовать. | * Сейчас появилась возможность, что выдать статью возможно - произвёл поиск по всем скриптам по article. Нашёл set_article_key и set_article_id. Буду пробовать. | ||
* Ещё сейчас начну проверку по АМК скриптам. Там вроде что-то тоже упоминалось про сохранение и загрузку переменных. | * Ещё сейчас начну проверку по АМК скриптам. Там вроде что-то тоже упоминалось про сохранение и загрузку переменных. | ||
+ | ----- | ||
+ | * Проверил АМК скрипты. Там присутствует загрузка переменных, но, в основном, роль у них одна - сохранение какой-нибудь стадии для скриптовых событий (выброса) или простое присваивание скриптам значения, которое должно сохраниться. У инфоскриптов, я бы сказал, аналогичное действие, но другая функция. Они существуют заместо стандартных инфопоршней и должны контролировать и облегчать создание диалогов и заданий, контролировать выдачу статей энциклопедии или простого каких-то действий, схожих с перечисленными. | ||
==Ссылки== | ==Ссылки== | ||
Строка 359: | Строка 363: | ||
==Автор== | ==Автор== | ||
− | [[Участник:Rekongstor|Rekongstor]] | + | [[Участник:Rekongstor|Rekongstor]] ~19:30-22:54, 9 января 2011 (UTC) |
[[Категория:Скрипты]] | [[Категория:Скрипты]] |
Текущая версия на 10:02, 22 июня 2011
Содержание
Описание статьи
В этой статье я расскажу о бинде объекта, о последовательности этапов класса бинда, о сохранении и загрузке переменных и на основе всех этих фактов, сделаю переменную, которая будет в некотором смысле напоминать инфопоршень.
Что такое 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)