Теория
В скриптах есть одна единственная функция, отвечающая за спавн объектов:
alife():create(section,position,levelvertex,gamevertex)
Строго говоря, их две: create create_ammo но различия между ними не существенны. Imp 22:45, 23 июля 2007 (EEST)
Первый параметр - секция в конфигурациях, описывающая объект, например "bolt","med_kit" - это простые секции, простых объектов а есть объекты, которые переходят в онлайн/оффлайн, это неписи, монстры и так далее, например mil_killer_respawn_2 - спавнится снайпер группировки киллеров.
С позицией, думаю объяснять не надо, только существует нюанс - высота это Y, а не Z. Задать позицию можно такой конструкцией vector():set(x,y,z), где x, y и z - координаты точки на уровне, где спавним объект.
Дальше сложнее, так как сам толком сформулировать не могу.
Начнем от простого к сложному. На каждом уровне много объектов, все объекты состоят из полигонов, у каждого полигона есть вершины – вертексы.
Именно они и должны здесь указываться, зачем - не особо понимаю, скорее всего для точного позиционирования объекта. Например, можно получить вертекс ближайший к актору - db.actor:level_vertex()
Дальше идет гораздо более интересный параметр game_vertex, это почти то же самое, что и level_vertex, но (!) это глобальные величины! Если level_vertex считается для уровня, то game_vertex - для всей игры, и нужен он для того, чтобы указать на какой карте спавнить объект (более вразумительного объяснения я не нашел).
Соответственно, чтобы заспавнить что-нибудь на другой карте, достаточно указать game_vertex в четвертом параметре Например:
db.actor:game_vertex()
Итак, чтобы, например, заспавнить болт под ногами актора, пишем:
alife():create("bolt",db.actor():position(),1,db.actor:game_vertex())
Почему 1, а не level_vertex? Проверено - разницы особой нет, какой level_vertex, хотя в некоторых случаях надо прописывать валидный вертекс, а то предмет может просто заспавнится не там, где планировалось... Но по большей части все проходит нормально и с единицей. (Игнорирование level_vertex может приводить к проваливанию произведенных предметов/персонажей под землю.) А вот game_vertex решает все - он указывает на каком уровне спавнить предмет, поэтому его надо указывать. Теоретически можно просто найти для каждого уровня по одному game_vertex'у и использовать их в скриптах. На самом деле game_vertex показывает какой фрагмент карты используется (вся карта разбита на кусочки имеющие сквозную нумерацию по всем уровням и game_vertex выбирает нужный) соответсвенно неправильное использование черевато....
Кроме того - есть еще один параметр - ID объекта, если указать ID NPC или актора - то предмет заспавнится у него в инвентаре.
Пример (спавним артефакт Медуза в инвентаре у актора):
alife():create("af_medusa", db.actor():position(), 1, db.actor:game_vertex(), db.actor:id())
Функция спавна возвращает серверный объект, то есть ни NPC, ни монстра ни что-либо еще.
Серверный обьект позволяет свеже созданного NPC или тайник затарить разными рулезами/артефактами. Например, вот так создадим перед входом к Сидоровичу долговца и засунем в него пачку патронов:
local obj
local a = vector() -- Задаем тип переменной
local dir = db.actor:direction()
a.x = -243.61 -- координата X
a.y = -19.52 -- высота Y
a.z = -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,
ob j.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", а не с серверными объектами.
Практика (часть 1)
1. Чтобы не повторяться в описании создания нового квеста, просто изучите статью по созданию квестов от Fr3nzy – лучшей статьи на эту тему я просто не видел
Мы просто свяжем все воедино и научимся спавнить объекты из скрипта.
Небольшое отступление:
Почему предпочтительнее делать спавн скриптом, а не через тот же 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()
-- Выдадим сообщение о нашем местоположении
local text
local vid
local gvid
local a = vector() -- Тип переменной
local text
a = db.actor:position() -- Наше положение в координатах
vid = db.actor:level_vertex_id()
gvid = db.actor:game_vertex_id()
text = "Позиция:\\nX= "..a.x.."\\nY= "..a.y.."\\nZ= "..a.z.."\\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 zomby_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
В local sect_alias строчку:
zombie_weak = "zombie_weak", zombie_normal = "zombie_normal", zombie_strong = "zombie_strong"
А ниже в 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 : Can't 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>
Все. Можно запускать игру, идти на Кордон, после разговороа с Сидоровичем, в зависимости от выбранного Меченным решения, бежим на фабрику и … смотрим сами :)