Теорія
У скриптах є одна єдина функція, що відповідає за спавн об'єктів:
alife ( ) :create ( section,position,levelvertex,gamevertex )
Строго кажучи, їх дві: create і create_ammo, але відмінності між ними не суттєві.
Перший параметр - секція в конфігураціях, що описує об'єкт, наприклад "bolt", "medkit" - це прості секції, простих об'єктів, а є об'єкти, які переходять в онлайн/оффлайн, це неписи, монстри і так далі, наприклад mil_killer_respawn_2 - сповнюється снайпер угруповання кілерів.
З позицією, думаю, пояснювати не треба, тільки існує нюанс - висота це Y, а не Z.
Задати позицію можна такою конструкцією: vector():set(x,y,z), де x, y та z - координати точки на рівні, де спавним об'єкт.
Вертекс у грі Сталкер не те саме, що вертекс у тривимірній графіці. Vertex - певна зона малого розміру, за якою закріплений індивідуальний номер у межах рівня або цілої гри. level_vertex визначає зону на рівні, через яку можна пройти NPC. game_vertex визначають зони вільного ходіння, тобто. під керівництвом ІІ, а також між рівнями. Вертекси мають одне з першорядних значень. Точність положення вказується через точку спавна. Наприклад, можна отримати вертекс найближчий до актора - db.actor:level_vertex_id()
game_vertex_id потрібен для того, щоб вказати на якій картці об'єкта.
Відповідно, щоб спати щось на іншій карті, достатньо вказати game_vertex_id у четвертому параметрі. Наприклад:
db.actor:game_vertex_id ( )
Отже, щоб, наприклад, спати болт під ногами актора, пишемо:
alife ( ) :create ( "bolt" , db.actor:position ( ) , db.actor:level_vertex_id ( ) , db.actor:game_vertex_id ( ) )
Чому 1, а чи не level_vertex_id? Перевірено - різниці особливої немає, який level_vertex_id, хоча в деяких випадках треба прописувати валідний вертекс, а то предмет може просто спати не там, де планувалося... Але здебільшого все проходить нормально і з одиницею (ігнорування level_vertex_id може призводити до провалювання вироблених предметів/персонажів під землю). А ось game_vertex вирішує все – він вказує на якому рівні спавнити предмет, тому його треба вказувати. Теоретично можна просто знайти для кожного рівня по одному game_vertex'у та використовувати їх у скриптах. Насправді game_vertex показує, який фрагмент карти використовується (вся карта розбита на шматочки, що мають наскрізну нумерацію по всіх рівнях, і game_vertex вибирає потрібний) відповідно неправильне використання.
Крім того, є ще один параметр - ID об'єкта, якщо вказати ID NPC або актора - то предмет спав у нього в інвентарі.
Приклад (співаємо артефакт Медуза в інвентарі актора):
alife ( ) :create ( "af_medusa" , db.actor:position ( ) , 1 , db.actor:game_vertex_id ( ) , db.actor:id ( ) )
Функція спавна повертає серверний об'єкт, тобто ні NPC, ні монстра ні щось інше.
Серверний об'єкт дозволяє свіжоствореного NPC або схованку затарити різними кермами/артефактами. Наприклад, ось так створимо перед входом до Сидоровича боргівця і засунемо в нього пачку набоїв:
local obj local a = vector ( ) -- Задаємо тип змінної local dir = db.actor:direction ( ) ax = -243.61 - координата X ay = -19.52 - висота Y az = -127.17 - координата Z obj = alife ( ) :create ( "bar_dolg_respawn_3" , a, 13193 , 8 , 65535 ) alife ( ) :create_ammo ( "ammo_9x18_fmj" obj.position, obj.m_level_vertex_id, obj.m_game_vertex_id, obj.id, 20 ) - число патронів
До речі, create_ammo - практично те саме, що і create, різниця в тому, що create_ammo призначена спеціально для спавна патронів і дозволяє створювати неповні пачки патронів. Можливо, є ще якісь відмінності. Варто врахувати, що самі автори гри сповнять набої виключно через create_ammo. Imp 22:38, 23 липня 2007 (EEST)
Просто мінімальний набір - координати, ID, секція, а з нього (серверного об'єкта) зазвичай потрібен тільки ID, тому що по ID можна отримати цей серверний об'єкт:
( alife ( ) :object ( id ) )
Його можна використовувати, щоб поставити мітку, наприклад, але я його особисто використовую для інших цілей - спавн складних об'єктів, конкретно - NPC.
Наприклад, треба вирішити наступне завдання - треба створити найманця, змінити йому угруповання і змінити його інвентар, ну і в навантаження - зробити другом для гравця.
У певний момент спалений об'єкт переходить онлайн, в цей момент викликається callback – net_spawn.
Що ми робимо? Звіряємо ID онлайн об'єкта зі збереженим ID!
Якщо вони збігаються, наприклад:
if obj:id ( ) ==saved_id then ...
Важливо те, що серверний об'єкт ID - це параметр, а в онлайнового об'єкта ID виходить за допомогою функції. Це важливо, бо можна прогоріти.
Отже, ми зловили нашого кілера за ID.
Далі все дуже просто – викликаємо команди для спавна гауса та патронів до нього в інвентарі NPC (див. вище), змінюємо угруповання спеціальною функцією, і робимо його другом.
Навіщо такі складнощі? Просто в офлайні NPC як би не існує, є лише опосередкована згадка про нього, і, плюс, всі ці функції працюють саме з об'єктом типу "NPC", а не з серверними об'єктами.
Простіша функція спауна в один рядок:
alife ( ) :create ( section,position,levelvertex,gamevertex )
section -- це секція предмета position -- це його позиція (Через vector()) Брати в грі за допомогою консольної команди rs_stats 1 або rs_cam_pos 1 (Току в ЧН) levelvertex - нехай одно 1 gamevertex - його Game Vertex, визначає рівень де буде спаун. GV можна взяти або скриптом, або через розпакований all.spawn.
Приклад:
alife ( ) : create ( "stalker" , vector ( ) : set ( 0 , 0 , 0 ) , 1 , 135 ) - спауніт сталкера на водокачці (гра ЧН, на рівні Болото).
Практика (частина 1)
Невеликий відступ:
Чому краще робити спавн скриптом, а не через той же xrSpawner? Програма xrSpawner, при всіх своїх перевагах, має один недолік, а саме – редагує спавн через файл all.spawn, що призводить до:
- Неможливості поєднати два моди, такі спавн використовують
- Необхідності щоразу розпочинати нову гру
При спавне через скрипт ситуація інша: в переважній більшості випадків, раніше збережені ігри працюватимуть, що не може не тішити 🙂
Отже, визначимося із квестом.
Завдання: після розмови із Сидоровичем спавним зомбі на території фабрики у першій локації. Для того, щоб не пошкодити оригінальний сюжет гри, завдання видаватиметься після проходження квесту з флешкою Шустрого, тому що з'явись там зомбі одночасно з бандитами та Шустрим... я думаю, результат бою вирішено наперед 🙂
Реалізація: Намагатимуся описати всі дії максимально докладно, буквально по кроках. Насамперед запустіть гру 🙂
У консолі введіть команду:
rs_stats on або rs_stats 1
Тим самим ми вмикаємо виведення інформації на екран. Далі вводимо ще одну команду:
demo_record 1
І «летимо» на фабрику. Нам потрібно вибрати місце для спавна об'єктів і цей режим якнайкраще підходить для реалізації задуманого. Поміщаємо камеру в точці передбачуваного спавна і записуємо координати - у мене вийшло 115, -6, -16.
Для виходу з режиму demo_record натискаємо Esc, в консолі пишемо rs_stats off або rs_stats 0 (прибираємо виведення інформації).
Інший спосіб отримання тих же відомостей - прийти в потрібне місце і запустити скрипт, який видасть всі потрібні координати. Я користуюся наступним скриптом (викликаю загальновідомим способом, через main_menu):
function main_menu : main_cheat_f3 ( ) -- Видамо повідомлення про наше місце розташування _ _ _ _ _ a = db.actor:position ( ) -- Наше положення в координатах vid = db.actor:level_vertex_id ( ) gvid = db.actor:game_vertex_id ( ) text = "Позиція: \\ nX=" ..ax. " \\ nY=" ..ay. " \ nZ=" ..az. \\ nlevel_vertex= " ..vid.. " \\ ngame_vertex_id = " ..gvid news_manager.send_tip ( db.actor, text, nil , nil , 30000 ) end
В результаті не потрібно експериментувати, ми одразу отримуємо все, в тому числі і level_vertex та game_vertex. Imp 22:38, 23 липня 2007 (EEST)
Виходимо з гри, йдемо в папку зі встановленою грою і створюємо каталог gamedata (передбачається, що "ліпимо" свій "мод" на "чисту" гру, без встановлених модів, і маємо розпаковані ресурси гри в папці, скажімо, gamedata source).
У папці gamedata створюємо папку config, а в ній - папку creatures. Скопіюємо з оригінальної папки файл m_zombie.ltx та відкриємо його на редагування.
У файлах гри є 5 моделей цивільних зомбі: файли zombi_1.ogf, zombi_1_ghost.ogf, zombi_2.ogf, zombi_trup.ogf, zombi_trup_2.ogf.
Повернем у гру їх усіх 🙂
Вже є секції:
[zombie_weak]:m_zombie_e, [zombie_normal]:m_zombie_e, [zombie_strong]:m_zombie_e та [zombie_immortal]:zombie_strong.
Два останні типи використовують ту саму модель zombi_trup.ogf, хм... непорядок, виправляємо. Остання секція виглядає тепер так:
[ zombie_immortal ] :zombie_strong $ spawn = "monsters\zombies\zombie_immortal" visual = monsters\zombi\zombi_trup_2 panic_threshold = 0.05
Додамо п'яту модель.
Для цього наприкінці файлу створимо секцію:
[ zombie_ghost ] :zombie_strong
Це означає, що наш п'ятий зомбі успадковує всі параметри zombie_strong, ми додамо лише візуальне уявлення.
Пишемо далі:
$ spawn = "monsters\zombies\zombie_ghost" visual = monsters\zombi\zombi_1_ghost
Всі. Зберігаємо зміни та закриваємо файл.
2. Пишемо скрипт спавна. У папці gamedata створюємо нову папку scripts, у ній створюємо новий текстовий документ і називаємо його esc_zombie.script.
Відступ третій:
При написанні статті використовувався оригінальний скрипт zombie_story.script з horror-mod'а. Концепція спавна перенесена практично без змін, тому на авторство цього способу спавна я аж ніяк не претендую 🙂
Отже, відкриваємо наш порожній файл на редагування, першим рядком оголошуємо змінну, в якій зберігаються наші зомбі:
local zombie_types = { "zombie_weak" , "zombie_normal" , "zombie_strong" , "zombie_immortal" , "zombie_ghost" }
Далі пишемо функцію:
function spawn_zombies ( position, total ) local zombie_index - тип зомбі з масиву zombie_types local new_pos, x_offset, z_offset - оголошуємо змінні for zombie_index= 1 , total do - крутимо цикл стільки разів, скільки задає змінна total x_offset = math . random ( 5 ) - випадкове (рандомне) x від 1 до 5 z_offset = math . random ( 5 ) -- випадкове (рандомне) z від 1 до 5 new_pos = position -- передаємо координати у функцію new_pos.x = new_pos.x + x_offset -- додаємо до зазначеної нами координати x отримане вище рандомне x new_pos.z = new_pos .z + z_offset -- додаємо до вказаної нами координати z отримане вище рандомне z -- Нижче, власне і викликається функція спавна випадкового типу зомбі zombie_types[math.random(5)] прив'язаного до наших координат alife ( ) :create ( zombie_types [ math . random ( 5 ) ] ,new_pos,db.actor:level_vertex_id ( ) ,db.actor:game_vertex_id ( ) ) end end
І останнє:
function zombie_story_1 ( actor, npc ) -- десять зомбі на фабриці (Кордон) local spawn_point = vector ( ) :set ( 115 , -6 , -16 ) -- тут вказуємо координати, -- вибрані нами для спавна, коли «літали» камерою :) spawn_zombies ( spawn_point, 10 ) -- власне виклик попередньої функції -- з передачею їй координат та кількості об'єктів end
Всі. Зберігаємо та закриваємо файл.
Продовжуємо розмову 🙂
Щоб гра не вилітала після того, як ми додали новий тип монстрів, їх потрібно додати у файл xr_statistic.script. Отже, скопіюємо цей файл із папки ресурсів гри scripts у нашу папку до файлу esc_zombie.script та відкриємо на редагування.
Додамо в local killCountProps до монстрів рядок:
zombie_weak = 1 , zombie_normal = 2 , zombie_strong = 3 , zombie_immortal = 4 , zombie_ghost = 5
У local sect_alias після рядка:
zombied_novice = 1 , zombied_experienced = 2 , zombied_veteran = 3 , zombied_master = 4 ,
Допишемо ці рядки:
zombie_weak = "zombie_weak" , zombie_normal = "zombie_normal" , zombie_strong = "zombie_strong" , zombie_immortal = "zombie_immortal" , zombie_ghost = "zombie_ghost" ,
А нижче в monster_classes рядок:
[ clsid.zombie_s ] = "zombie"
У функцію getNpcType(npc) додаємо конструкцію:
elseif npc:character_community ( ) == "zombie" then community = "zombie"
Зберігаємо зміни та закриваємо файл.
Все буде працювати на ура, допоки ми не спробуємо обшукати вбитого зомбі. Як тільки ми це зробимо, гра вилетить приблизно такою помилкою.
Expression: fatal error Function: CInifile::r_string File : D:\xray-svn\xrCore\Xr_ini.cpp Line : 351 Description : <no expression> Arguments : Cant find variable icon in [ zombie_weak ]
Все правильно - гра не знає, яку іконку нам показувати для зомбі. Іконки монстрів зберігаються у файлі ui_npc_monster.dds. Тут є два варіанти:
- Якщо дружите з Фотошопом, відредагувати цей файл (намалювати, додати іконки);
- Взяти готовий з будь-якого мода, природно, з дозволу авторів мода. Зараз ми пропустимо цей аспект і надамо нашим зомбі іконки контролера 🙂
Повернемося до файлу m_zombie.ltx і в розділ [m_zombie_e]:monster_base впишемо параметр
icon = ui_npc_monster_kontroler
Всі. Вильотів не буде.
3. Тема цієї статті не передбачає докладного опису того, як зробити новий діалог. На початку статті я згадав джерело, де можна знайти вичерпну інформацію щодо створення діалогів, можу також навести приклад статтю зі створення діалогів від BAC9-FLCL.
Нам потрібно просто перевірити працездатність скриптового спавна, тому я наведу просто сам змінений діалог з файлу dialogs_escape.xml:
<dialog id="escape_trader_talk_info"> ……… ……… ……… <phrase id="999"> <text>escape_trader_talk_info_999</text> <next>7770</next> <next>9991</next> <next>9992</next> <next>9993</next> <next>9994</next> <next>9995</next> <next>9996</next> </phrase> <phrase id="9992"> <text>escape_trader_talk_info_9992</text> <next>99922</next> </phrase> <phrase id="99922"> <text>escape_trader_talk_info_99922</text> <next>9996</next> <next>9995</next> </phrase> <phrase id="9993"> <text>escape_trader_talk_info_9993</text> <next>99933</next> </phrase> <phrase id="9995"> <text>escape_trader_talk_info_9995</text> </phrase> <phrase id="3121"> <text>escape_trader_talk_info_3121</text> <next>9996</next> <next>9995</next> </phrase> <phrase id="3131"> <text>escape_trader_talk_info_3131</text> <next>9996</next> <next>9995</next> </phrase> <phrase id="41"> <text>escape_trader_talk_info_41</text> <next>9996</next> <next>9995</next> </phrase> <!------Наш диалог: Начало-------> <phrase id="7770"> <text>escape_trader_talk_info_7770</text> <next>7771</next> </phrase> <phrase id="7771"> <text>escape_trader_talk_info_7771</text> <next>7772</next> <next>7773</next> </phrase> <phrase id="7772"> <text>escape_trader_talk_info_7772</text> <next>7777</next> </phrase> <phrase id="7773"> <text>escape_trader_talk_info_7773</text> <next>7779</next> </phrase> <phrase id="7779"> <text>escape_trader_talk_info_7779</text> <next>9996</next> <next>9995</next> </phrase> <phrase id="7777"> <text>escape_trader_talk_info_7777</text> <action>esc_zombie.zombie_story_1</action> <next>9996</next> <next>9995</next> </phrase> <!------Наш диалог: Конец-------> <phrase id="51"> <text>escape_trader_talk_info_51</text> <next>9996</next> <next>9995</next> </phrase> ……… ……… ……… </dialog>
Також пов'язаний з ним файл stable_dialogs_escape.xml. На самому початку файлу пишемо наступне:
<string id = "escape_trader_talk_info_7770" > <text > Пригод ніяких не було? </text > </string > <string id = "escape_trader_talk_info_7771" > <text > Так знаєш... Начебто тихо все у нас. Хоча, ось, згадав! Говорили мені днями, що на фабриці, ну, там, де бандюки б'ються постійно, бачили якихось чи то людей, чи привидів... Чи мало що п'яну здасться - я і сказав цим панікерам, мовляв, закушувати треба! Хех, блін, алкаші... </text > </string > <string id = "escape_trader_talk_info_7772" > <text > Тож мені по будь-якому повз фабрику тупотіти - заодно і подивлюся на цих "людей-примар". </text > </string > <string id = "escape_trader_talk_info_7773" > <text > Так я якось не збирався в той бік... </text > </string > <string id = "escape_trader_talk_info_7779" > <text > Ну, дивися сам, все одно будь обережний. </text > </string > <string id = "escape_trader_talk_info_7777" > <text > Ага. Сходи, провітрися. Потім зайдеш, розкажеш, що там і як. </text > </string > <string id = "esc_bridge_soldiers_start_11" > <text > Тут прохід заборонено, сталкер. </text > </string >
Все. Можна запускати гру, йти на Кордон, після розмови з Сидоровичем, залежно від обраного Міченим рішення, біжимо на фабрику і дивимося самі 🙂