Сповіщення
Очистити все

Спавн через скрипт


Ранг:
Майстер
Роль:
Гість
Записи:
752
Приєднався:
2 роки тому
 

Теорія

У скриптах є одна єдина функція, що відповідає за спавн об'єктів:

 
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 >
 

Все. Можна запускати гру, йти на Кордон, після розмови з Сидоровичем, залежно від обраного Міченим рішення, біжимо на фабрику і дивимося самі 🙂


   
Цитата