У цій статті я розповім про бінд об'єкта, про послідовність етапів класу бінда, про збереження та завантаження змінних і на основі всіх цих фактів, зроблю змінну, яка в певному сенсі нагадуватиме інфопоршень.
Що таке 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 ) 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 ..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
Я не знаю точно, чи можна ставити (плаваючу) кому в числі, тому краще беріть цілі числа десь в області від -10 7 до 10 7
Практика
Отже. Тепер спробую попрактикуватися... Ви зі мною... спочатку змінимо функції класу 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 the 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. Спробую.
- Ще зараз почну перевірку щодо АМК скриптів. Там наче щось теж згадувалося про збереження та завантаження змінних.
- Перевірив АМК скрипти. Там є завантаження змінних, але, в основному, роль у них одна - збереження якоїсь стадії для скриптових подій (викиду) або просте надання скриптам значення, яке має зберегтися. У інфоскриптів, я б сказав, аналогічна дія, але інша функція. Вони існують замість стандартних инфопоршней і мають контролювати і полегшувати створення діалогів і завдань, контролювати видачу статей енциклопедії чи простого якихось дій, схожих з переліченими.