Как писать скрипты в сталкер
action : initialize () – вызывается в момент включения схемы по GOAP .
Инициализировать переменные здесь НЕЛЬЗЯ. Нужно провести установку callback -ов, которые будет использовать скрипт. В самом конце функции нужно вызвать acton : reset _ scheme ()
action : reset _ scheme () – вызывается в момент включения схемы другим скриптом ( gulag и т.п.), также вызывается из initialize при включении схемы по GOAP
Вся инициализация должна производиться в reset _ scheme (), а не в initialize ()!
action : finalize () – вызывается в момент выключения схемы по GOAP . Снять проставленные в initialize callback -и.
function add _ to _ binder ( npc , char _ ini ) – биндит схему, только если char _ ini == nil (это значит, что биндинг запрошен другим скриптом), или существует секция с именем скрипта в custom _ data , которая передана как char _ ini .
function set _ scriptname (параметры) – настройка параметров скрипта. Параметры, вместо которых передан nil, берутся из customdata персонажа .
Работа с customdata :
Чтобы не переписывать парсинг заново в каждом скрипте, можно воспользоваться функциями из xr _ utils :
function conf_get_bool(char_ini, section, field, override, object, mandatory)
function conf_get_string(char_ini, section, field, override, object, mandatory)
function conf_get_number(char_ini, section, field, override, object, mandatory)
st.enabled = utils.conf_get_bool(char_ini, "guard", "enabled", enable, object, true)
Пример использования можно посмотреть в xr _ guard .
Описание параметров – в xr _ utils в месте, где определена функция.
За перемещение отвечает movement manager .
Пример использования – xr_ walker . script .
1) В _init создается экземпляр movement manager- а:
2) В initialize грузим информацию о путях из их имен:
3) В reset_scheme инициализируем movement manager:
function move_mgr:reset(path_walk, path_walk_info, path_look, path_look_info,
team, mode, move_cb_info)
team – команда для синхронизации нескольких персонажей (произвольная текстовая строка)
mode – таблица, поля которой задают начальный режим перемещения:
move _ cb _ info – таблица, поля которой задают информацию о callback-методе, который будет вызван, если персонаж прибыл в точку, в которой установлено значение ret:
obj – ссылка на объект класса, которому принадлежит функция, или nil, если функция определена вне класса.
func – ссылка на функцию, которая будет вызвана.
Поля team , mode , move _ cb задавать не обязательно.
Переключение скорости и режима перемещения до прибытия в первый вейпоинт.
Для того, чтобы сменить режим движения до прибытия в первый вейпоинт пути (например, переключиться с ходьбы на бег при каких-то условиях), выполните в своем скрипте в любой момент после reset-а:
// ВНИМАНИЕ – вызывать update_movement_state при last_index
// не равном nil недопустимо !
if self.move_mgr.last_index == nil then
self.move_mgr.danger = true / false
self.move_mgr.crouch = true / false
Примечание: в большинстве случаев переключать режим перемещения не понадобится. Рекомендуется задавать стартовый режим перемещения с помощью параметра mode функции reset(), а не менять его вышеописанным способом.
Использование callback:
Функция-callback может быть как свободной функцией, так и членом класса.
Прототип свободной функции имеет следующий вид:
function my_callback(self, mode, number)
Прототип функции класса :
function class:my_callback(mode, number)
Здесь в качестве первого параметра ( self ) будет передано значение поля obj таблицы move_cb_info, т.е. фактически это объект, которому принадлежит функция-callback.
Параметр mode может быть одним из:
move _ mgr . arrival _ before _ rotation – если поле ret было задано в path _ walk и коллбек был вызван сразу же по прибытию персонажа в точку, ДО поворота.
move _ mgr . arrival _ after _ rotation – если поле ret было задано в path _ look и коллбек был вызван после прибытия в точку path _ walk и поворота в соответствующую точку path_look (в которой был задан ret).
number – значение поля ret.
В коллбеке можно прервать движение персонажа (остановить его, но нежелательно сбрасывать пути) и выполнять свои custom действия (апдейты movement manager-а при этом вызывать не нужно по понятным причинам). Для прерывания нормальной работы схемы с целью вмешательства в перемещение, нужно вернуть значение true из callback-а, после чего перестать вызывать апдейты. Если это не сделать – значения, которые Вы установите персонажу в коллбеке могут быть сбиты схемой сразу же по возврату из Вашего коллбека! Чтобы продолжить затем движение по маршруту, вызовите:
self . move _ mgr : update _ movement _ state () – это включит бег или ходьбу (в зависимости от настроек последней точки, в которой был персонаж)
После чего продолжайте вызывать апдейты movement manager-а как обычно.
Перед вызовом update _ movement _ state , можно также явно задать режим перемещения:
self.move_mgr.danger = true / false
self.move_mgr.crouch = true / false
Если коллбек выполняет задачи, никак не влияющие на перемещение и персонажа в целом (например, просто ставит info portion), то из коллбека нужно вернуть nil или false. Вызов коллбека тогда останется для схемы незамеченным.
Взаимодействие path_walk c path_look:
Прийдя на точку path_walk, где установлена какая-то комбинация флажков, сталкер найдет такую же комбинацию флажков в path_look и посмотрит в эту точку. Если же ни один флажок не установлен, сталкер пойдет дальше не останавливаясь.
Проверка текущего состояния персонажа:
Часто с персонажем должны взаимодействовать другие персонажи, например, follower-ы командира должны знать его состояние.
Движется ли персонаж в данный момент можно узнать, опросив у move_mgr переменную moving.
if self.move_mgr.moving then движется end
При этом, если moving == true, можно узнать подробности о перемещении:
if self.move_mgr.crouch then идет в присяде end
if self.move_mgr.danger then находится в состоянии danger end
Если moving == false, то :
if self.move_mgr.standing_crouch then сидит end
if self.move_mgr.standing_danger then сидит в состоянии danger end
Задание имен вейпоинтов:
Имя вейпоинта должно иметь следующий вид:
Первое слово является именем и игнорируется парсером. Остальные фразы, разделенные символом '|' будут обработаны.
Если задано имя поля, но не задано значение – автоматически парсер подставит true.
Т.е. не надо писать “ wp 0|r=true| d = true ”, достаточно просто написать “ wp 0|r| d ”.
Флаги пути path_walk:
n = 0 .. 9999 – номер точки синхронизации. Рекомендуется первой точке задавать значение 0, остальным – числа по возрастанию с произвольным шагом. Прийдя в точку с большим n, сталкер будет ждать отстающих напарников. Примечание: сталкер дожидается опаздывающих напарников _только_ в точках остановки (т.е. только в тех местах, где точка path_walk имеет общие флаги с одной из точек path_look).
Внимание – для поддержки зацикленных маршрутов, сталкеры на точке с минимальным n дожидаются сталкеров на точке с максимальным n. Поэтому минимальное количество точек синхронизации для корректной работы схемы должно составлять 3 точки или больше!
s = имя_звуковой_схемы – пробегая через эту точку, сталкер включит указанную звуковую схему. Звук стартует ДО начала поворота и старта анимации. Для того, чтобы звук стартовал синхронно с анимацией – задавайте его в path_look соответствующей точки, а не в path_walk. Если нужно стартовать звук одновременно с ЛЮБОЙ из анимаций в этой точке, можно воспользоваться параметром sa.
sp = с какой вероятностью будет проигран звук (по умолчанию 100)
sa = true – ждать начала анимации в точке, прежде чем стартовать проигрывание звука (по умолчанию false).
sc = true – разрешить проигрывать звуки схемы неоднократно (по умолчанию false).
sf , st – временной интервал повторения фраз из выбранной звуковой схемы в секундах (по умолчанию от 5 до 10 сек).
c = true – дальше перемещаться в присяде (по умолчанию false)
r = true – дальше перемещаться бегом (по умолчанию false)
d = true – перемещаться в состоянии danger (по умолчанию false)
ds = имена_диалогов – имена диалогов, которые разрешено стартовать начиная с этой точки (разрешение действует до следующей точки). Имена задаются в виде текстовой строки, разделенной запятыми: ds=bandits_talk,weather_talk и т.д.
w = имя_ walk _пути – переводит схему на новый path _ walk . Рекомендуется также задать новый path_look с помощью параметра ” l ”, иначе текущий path_look будет сброшен. Персонаж идет на стартовую точку с режимом перемещения, заданным в точке с параметром “w”. Настройки функции коллбека при переключении пути сохраняются.
l = имя_ look _пути - сбросит схему на новый path_look. Задавать параметр “ l ” нужно вместе с параметром ” w ”, иначе “ l ” будет проигнорирован. По умолчанию path _ look при смене path_walk будет сброшен.
Флаги пути path _ look :
p = 100 – вероятность, с которой персонаж посмотрит именно в эту точку. Значения p всех возможных точек суммируются, т.е. если у одной точки p = 100, а у другой 300, то персонаж посмотрит в первую с вероятностью 25%! (т.е. 100 из 400).
Рекомендуется задавать p так, чтобы их сумма составляла 100.
По умолчанию у всех точек p = 100.
a = анимация, которую проиграет персонаж, посмотрев в эту точку (по умолчанию idle). Для того, чтобы персонаж стоял в точке без анимации, задайте значение nil : “a= nil ”
c = true – смотреть в точку в присяде (по умолчанию используется значение одноименного поля из path_walk)
d = true – смотреть в точку в состоянии danger (по умолчанию используется значение одноименного поля из path_walk)
att = 1 или 2 – номер атаки (основная, вспомогательная). Можно использовать вместо анимации (например ” a = nil | att =1”), можно вместе с анимацией (”a=стреляем_в_потолок|att=1»).
t = время, которое персонаж будет ждать, играя анимацию или стреляя (по умолчанию 5000). Если требуется ждать бесконечно долго (например, это финальная точка пути), нужно задать t равным “-1”.
Примечание: если персонаж ждет синхронизации в точке, то он будет играть анимацию столько времени, сколько нужно для того, чтобы дождаться напарников, но только по прибытию всех напарников на точки засечет заданное в ” t ” время. Исключение составляет стрельба – персонаж не станет стрелять сразу по прибытию в точку, а сперва дождется напарников, а потом уже начнет стрелять в течение заданного времени.
s = имя - звук, который персонаж разово проиграет, посмотрев в эту точку.
sp = с какой вероятностью будет проигран звук (по умолчанию 100)
sl = имя_прожектора – если задано, то при повороте в указанную точку персонаж также повернет и прожектор в неё.
Как писать скрипты в сталкер
Начнём уроки.
1)Создадим свой my.scripts и поместим его в папку scrips . Откроем с помощью НотПада и настроим подсветку.
2) Внутри файла-скрипта должны содержаться только КОД скрипта и ваши ЗАКОМЕНТИРОВАННЫе пометки. Если будет лишний текст, т.е какие-то знаки и слова, то будет вылет на этот скрипт. Так как код игры полностью собирает весь скрипт в стек и выбирает только то, что вы задали, но если будет мусор, то игра не воспримет код.
3)Архитектур. Для создания функций нужны лишь знания синтаксиса и игровые методы и глобальные функции(которые записаны в движке) можете почитать lua_help.scrip , но я советую посетить тему на АМК . Там собраны все методы, классы и полное их описание.
4) Функция. Это то, что будет делать игра.
Любая функция начинается со слов
function my_function()
.
end
И заканчивается тегом end . Этот тег означает конец функции, сравнения, он закрывающий и обязателен. Я советую при составлении функций , чтобы не забыть чего-нибудь, писать скелет извне, т.е сначала функция, потом закрывающий тег, и по нарастающей во внутрь.
() -Обязательный элемент. Позже расскажу как передавать переменные через этот тег.Между окончание функции и этим тегом ПРОБЕЛА НЕТ.
Обращаю внимание, что все функции вызываются из других скриптов. Допустим нам из одного скрипта, нужно вызвать(запустить функцию в другом) для этого мы пишем
название скрипта . название функции в скрипте (парметр если есть)
my.my_function()
Глобальные можно объявлять вначале скрипта и она будет сохранятся в коде, в памяти процесса (если я правильно понял)
Переменная объявляется только перед функцией и логическими выражениями, где используется переменная и её использует только та функция, перед которой она объявляется (На пальцах перед строкой с вашей функцией). Т.е елси функция простая без логических решений(if, elseif, for и.т.д) То ставим перед функцией, если же есть переменная, которая находится в теле такого логического решения, то она ставится строго перед этим логическим решением!
local helth = db.actor.helth
function my_function()
.
end
Чтобы сосчитать значение переменной из другого скрипта достаточно в другом скрипте сделать так:
text="Я иду гулять по бродвею"
Теперь в нашем скрипте вызываем этот параметр
local pisanina = название скрипта . text
() - При таком обращении этот тег НЕ СТАВИТСЯ !
if . then
.
end
Перевожу Если что-то то
конец тега .
Пример
if db.actor.psy==0.5 then
db.actor:kill(db.actor)
end
Если пси-здоровье ГГ - половина, то мы его убиваем.
Полная функция:
function my_function()
if db.actor.psy==0.5 then
db.actor:kill(db.actor)
end
end
[/color]
local acter = db.actor
function my_function()
local psy_zdorovie = acter.psy
if psy_zdorovie==0.5 then
acter:kill(acter)
end
end
Что я сделал?
Обозначил кусок db.actor локальной acter .
А acter.psy (db.actor)+.psy равносильно db.actor.psy
ВНИМАНИЕ. Переменные должны объявлятся так, чтобы самое то, к чему обращаются было известно. Допустим.
local psy_zdorovie = acter .psy
Нам нужна эта acter перменная, и мы ДОЛЖНЫ ОБЪЯВИТЬ её перед переменной
local psy_zdorovie = acter .psy .
Мы ее и объявили local acter = db.actor .
Думаю смысл понятен?
if . then
1 действие.
else
2 действие.
end
Перевод: Если подходит условие то
1 действие
иначе (т.е условие не выполняется)
2 действие
конец тега
Пример:
function my_function()
if db.actor.psy==0.5 then
db.actor:kill(db.actor)
else
db.actor.give_info_portion("info")
end
end
Если пси-здоровье актора равно половине, то мы его убиваем, если же значение другое(любое) , то даем ему инфопоршень.
Допустим нам нужно проверить несколько условий:
Чтобы они все выполнялись!
if (db.actor) and (db.actor.helth==1) and (db.actor.psy ==0.5) then
действие
end
Функция сработает если есть актор и здоровье актора полное и псиздоровье половина .
Тег and - означает И . Если один из элементов не выполняется, то функция не срабатывает. Кстати - это ленивый метод, как писал Kamikaze , если не выполняется первый элемент, то другие - уже не просчитывааются. Т.е. не загнружается процесс.
Если подходит хоть один элемент.
if (db.actor) or (db.actor.helth==1) or (db.actor.psy ==0.5) then
действие
end
Тег or обозначает ИЛИ . Или один, или другой. Функция сработает при условии соответствия хоть одного элемента. Так же ленивый метод. Проверяет до получения утвердительного решения, потом проверка не идет.
Данный метод заменяет перебор через таблицу. Отличается простотой и потерей производительности.
if . then
самое основное действие
elseif . then
действие 1
elseif . then
действие 2
elseif . then
действие 3
elseif . then
действие 4
elseif . then
действие 5
end
Здесь представлен перебор elseif иначе если , т.е не подходит первый вариант, мы проверяем второй и так по цепочке , до первого подходящего(где выполняется заданное условие), если же ни одно не подойдет, то ничего не произойдет. Если бы мы просто в йункции написали кучу
function perebor()
if . then
действие
end
if . then
действие
end
if . then
действие
end
if . then
действие
end
if . then
действие
end
if . then
действие
end
end
Тон ничего хорошего не вышло бы. Так как проверялись бы все функции. А в первом варианте до первого попавшегося.
Пременная нил указывает, что объекта , условия, да чего угодно НЕТ, его не существует.
ВСЕГДА проверяйте некоторые объекты на nil
Во первых это актор .
Можно написать
Но правильнее и эстетичнее, сразу писать так.
if (db.actor) then
.
end
Проверкой советую проверять многие элементы, так как в игре они зачастую не существуют в определенные моменты.
При обращении к функция из сторонних скриптов(других скрипт-файлов) я советую проверять на наличие этих скриптов:if имя скрипта then
.
end
if my then
.
end
И делайте всегда, потому как, просто удалите этот скрипт из каталога и не надо будет мучаться с переписыванием других скриптов.
math.random (1,100)
Данная функция рандомно выберет число от 1 до 100.
Сначала ставится наименьшее, потом наибольшее.
Если ставить десятичные , допустим (0.0005, 1), то перебуеруться ВСЕ значения, т.е числа с несколькими знаками, ТАК ДЕЛАТЬ НЕ НУЖНО. вы перегрузите некоторые элементы кода.
Использование
if math.random(0,1) < 1 then
действие
end
Если выбранное число меньше 1, то срабатывает функция.
if math.random(0,1) < 1 then
действие
else
.
end
Добвавляется другое действие.
Советую брать целые числа от 1 до 10 для создания процентного срабатывания, но лучше 0 и 1.
for i =1, 5000 do
действие
end
Это цикл, который прокрутнет ваше действие 5000 раз. Переменная i любая буква, число 5000 обозначает количество циклов(сколько раз пройдет ваше действие).
Допустим мы сделали такую функцию
-- удаляем объект из игры(Взято из АМК )
function remove( remove_item )
if remove_item
=nil then
alife():release(alife():object(remove_item:id()), true)
return true
end
return false
end
remove_item - это наш параметр, в данном случае это секция объекта, которую нужно удалить.
(немного по секция , если это уникальный объект, то это то,ч то в его конфиге, если нет, то нужно искать другим методом)
Нам нужно удалить уникального НПС vasek
Если функция находится в скрипте, где мы хотим удалить объект, то пишем
remove ( vasek )
Если в другом скрипте, то
имя скрипта .remove( vasek )
Вот такой пример передачи параметра, передавать можно что угодно и как угодно. Было бы воображение.
Допустим, идет проверка и если она оканчивается удачно, то функция должна вернуть одну переменную, если нет, то другую.
function my()
if proverka () == true then
.
end
end
function proverka ()
if db.actor then
return true
else
return false
end
Т.е мы хотим проверить наличие актора(можно что угодно). Создаем функцию proverka , она работает так, если актор есть - возвращает( return ) одну переменную, в данном случае true (Может быть любая другая) , если проверка не проходит, то возвращается false , а нашей первой my() стоит условие на то, что проверка вернет true
if proverka () == true then
Вот так, если вернет, то сработает первая функция.
Функции вызваются из других скриптов, нужно лишь найти место. Если она вызывается постоянно. То нужно пихать в колбэк на апдет в bind_stalker.script
function actor_binder:update(delta)
object_binder.update(self, delta)
if string.find(command_line(), "-designer") then
return
end
if self.already_jumped==false and jump_level.need_jump==true and (device().frame > self.spawn_frame+2000) then
jump_level.try_to_jump()
self.already_jumped = true
return
end
-- Вызов апдейта переноса игрока проводником
if travel_func
-- DEBUG slowdown
--slowdown.update()
local time = time_global()
game_stats.update (delta, self.object)
-- апдейт погоды
self.weather_manager:update()
-- Обновление отключения ввода с клавиатуры.
if self.st.disable_input_time
= nil and
game.get_game_time():diffSec(self.st.disable_input_time) >= self.st.disable_input_idle
then
level.enable_input()
self.st.disable_input_time = nil
end
-- Апдейт прятание оружия игрока во время диалога
if self.object:is_talking() then
if self.weapon_hide_in_dialog == false then
self.object:hide_weapon()
printf("hiding weapon. ")
self.weapon_hide_in_dialog = true
end
else
if self.weapon_hide_in_dialog == true then
printf("restoring weapon. ")
self.object:restore_weapon()
self.weapon_hide_in_dialog = false
end
end
-- Апдейт прятание оружия игрока в зоне sr_no_weapon
if check_for_weapon_hide_by_zones() == true then
if self.weapon_hide == false then
printf("hiding weapon. ")
self.object:hide_weapon()
self.weapon_hide = true
end
else
if self.weapon_hide == true then
printf("restoring weapon. ")
self.object:restore_weapon()
self.weapon_hide = false
end
end
if self.bCheckStart then
printf("SET DEFAULT INFOS")
if not has_alife_info("global_dialogs") then
self.object:give_info_portion("global_dialogs")
end
if not has_alife_info("level_changer_icons") then
self.object:give_info_portion("level_changer_icons")
end
self.bCheckStart = false
--if self.actor_weapon_on_start == true then
--db.actor:activate_slot(3)
--self.actor_weapon_on_start = false
--end
end
--device().precache_frame== 0 and
if not self.loaded_slot_applied then
self.object:activate_slot(self.loaded_active_slot)
self.loaded_slot_applied = true
end
xr_s.on_actor_update(delta)
= true) then
self.surge_manager:initialize()
self.f_surge_manager_loaded = true
end
if(self.surge_manager.levels_respawn[level.name()]) then
self.surge_manager:respawn_artefacts_and_replace_anomaly_zone()
end
self.surge_manager:update()
end
-- Апдейт доступности для симуляции.
simulation_objects.get_sim_obj_registry():update_avaliability(alife():actor())
if not self.loaded then
get_console():execute("dump_infos")
self.loaded = true
end
treasure_manager.get_treasure_manager():update()
if not(primary_objects_filled) then
pda.fill_primary_objects()
primary_objects_filled = true
end
pda.fill_sleep_zones()
--СЮДА в САМЫЙ КОНЕЦ
end
В том же скрипте есть колбэки на взятие, потерю, использование предметов. Нужно лишь искать.
Этим вы займетесь сами, или спросите у меня.
Создание диалогов
character_desc_zombied.xml character_desc_stalker.xml character_desc_garbage.xml .
Файлы character_desc_*.xml можно сравнить со стволом дерева диалогов. В них перечисляется названия прикрепляемых веток диалогов
Например вот список веток диалога с Сидоровичем взятый из файла character_desc_escape.xml
В свою очередь каждая ветка диалога также может ветвится.
2) Ветвление диалогов прописывается уже в других файлах.
Например, ветвление диалога с Сидоровичем содержится в файле gamedata/config/gameplay/dialogs_escape.xml Возьмем оттуда, например, ветвление escape_trader_jobs.
Ветвление имеет довольно большие масштабы, поэтому приведу только часть:
Здесь <precondition>…</precondition> - это проверка выполнения условия. Ветка появится в диалоге, только если условие выполняется. Конкретно <precondition>escape_dialog.trader_has_talk_info_wr</precondition> из ветки escape_trader_talk_info - это обращение к функции trader_has_talk_info_wr, находящейся в файле скрипте gamedata/scripts/escape_dialog.script
Функция выглядит так:
То есть, судя по его структуре, <precondition>escape_dialog.trader_has_talk_info_wr</precondition> выполняется всегда, т.к. функция всегда возвращает истину и <dialog id="escape_trader_talk_info"> пропускается в списк реплик.
Но для конкретной ветки может быть несколько precondition и других условий.
Далее, <has_info>tutorial_end</has_info> - это еще одна проверка, на этот раз на наличие у игрока так называемых infoportions, выдаваемы в процессе ключевых диалогов. В данном случае это проверка на то, закончена ли определенная стадия туториала, или нет. Т.е. ветка допустится в список реплик если стадия туториала закончена.
Более детально мы это разберем в конце статьи.
А далее идут конкретные фразы, содержащие ссылки на вытекающие фразы, например:
Это основа ветки escape_trader_talk_info.
Важно! В любой основной ветке любого диалога фраза <phrase > будет основой, из которой далее будет все вытекать. Она должна обязательно присутствовать и в вашем диалоге.
<next>1</next> - это ссылка на вытекающую фразу <phrase >:
В свою очередь <next>100</next>, <next>99</next>, <next>9995</next> это ссылки на фразы веточки растущие из фразы <phrase >.
3) Текст каждой фразы содержится в третьем файле. Для диалога с Сидоровичем тексты лежат в файле gamedata/config/text/rus/stable_dialogs_escape.xml
Эти строки содержат тексты для фраз <phrase > и <phrase >
Итого диалоги разложены по трем, а то и более файлам.
Да кстати, путь по веткам может быть зацикленным, если того требует диалог. Например так:
Практика
Добавим в диалог с Сидоровичем ветку своего собственного изготовления.Например такую:
1) В файле gamedata/config/gameplay/character_desc_escape.xml в конце списка веток для trader припишем свою ветку с произвольным названием. Это будет, например, <actor_dialog>escape_trader_letat_gusi</actor_dialog>.
Т.е у нас получится так:
Записываем изменения, с этим файлом пока всё.
2) Теперь берем файл gamedata/config/gameplay/dialogs_escape.xml
Будет соответствовать такая структура:
Условия наличия ветки в диалоге можно взять из ветки <dialog >.
Т.е берем условия <precondition>escape_dialog.trader_has_talk_info_wr</precondition> и <has_info>tutorial_end</has_info>. Можно было, конечно, прописать в скрипте еще одно условие для ветки, чтобы она появилась только один раз, а потом больше не возникала. Но об этом как-нибудь позже.
В итоге у нас получилась такая структура:
Её нужно вставить в любом месте между dialog id'ами других веток в файле dialogs_escape.xml. Главное - не промахнутся и засунуть именно между, а не внутрь одного из dialog id.
После сохранения внесенных изменений с файлом dialogs_escape.xml все.
3) Теперь вбиваем сами текстовички в файле gamedata/config/text/rus/stable_dialogs_escape.xml
Т.е нам надо в файле stable_dialogs_escape.xml вставить такую конструкцию:
В любом месте между уже существующими string id. После сохранения изменений, у нас все готово. Можно загружать игру и смотреть что получилось. <. ВНИМАНИЕ. > Если вы сделали всё правильно,но при обращении к НПЦ вылетает с таким логом
Создайте свой файл и назовите его как душе угодно,пример: letat_gusi_my_test.xml Впишите туда:
Дополнительно
Внимание! После патча 1.002 данный урок перестал работать. Причина - со вторым патчем несовместимо это условие:
«Смерть» переменных
Положим, у нас с reminder_count происходят ещё какие-нибудь занимательные вещи, например мы его значение зачем-нибудь присваиваем другой переменной вот так:
Ну и вот, если у нас reminder_count в момент присвоения ещё не имеет никакого значения, то мы получим занимательную штуку — у нас это выражение сработает как
В результате чего переменная antirad_check_delay «сдохнет» и будет деловито убрана «сборщиком мусора» из памяти, что моментально приведёт к вылету, когда в дальнейшем какая-то часть кода обратится к значению antirad_check_delay.
Предисловие
Сразу оговорюсь — я программист, поэтому к отладке скриптов подхожу со своей профессиональной точки зрения, и то что я тут изложу вам — сие для программиста есть непреложная истина, и если вы хотите уменьшить количество глюков — старайтесь придерживаться нижеприведённого стиля программирования.
Для начала маленький экскурс в историю. Итак, скрипты в сталкере написаны на языке Lua — разработали его бразильцы, он очень гибок, легко встраивается в игры и поэтому очень часто используется для написания скриптов любой степени сложности. Так вот, у этого языка и его реализации в игре есть несколько исключительно важных особенностей, которые, при их несоблюдении, будут приводить к вылетам вашего скрипта. Итак:
Типы данных и проблемы с nil.
Сначала сделаю маленькое отступление для не знакомых с основами скриптеров, чтобы понятнее было. Итак, в Lua используются следующие логические операторы:
Всё. Теперь поехали дальше… Язык Lua Изначально создавался для работы с большими строчными базами данных, поэтому ВСЕ виды конструкций в языке — это типы данных, то есть по сути либо переменные, либо константы. Это касается так же и функций! Поэтому даже с функциями в Lua можно и нужно обращатся как с переменными. Далее. Касательно, собственно «привычных» переменных. Обычные переменные в Lua получают свой тип данных только в момент присвоения им значения (запомните, это важно). При этом, в Lua есть такой важный и полезный тип данных как nil. nil — Это «пустота», то есть отсутствие какого-либо значения. При этом этот тип используется компилятором скриптов для «сбора мусора», то есть для освобождения занимаемой памяти. Смотрите пример, я объясню подробнее, что это значит:
Тут у нас 2 локальные переменные, одна из которых просто создана, а вторая — создана и инициализарована. Если сейчас обратиться к переменной reminder_count, считав её значение, то мы получим в качестве значения — nil, то есть пустоту. Если же мы обратимся к переменной exposure_count — то получим, как и ожидали, 0 — так как мы это значение проинициализировали ЗАРАНЕЕ. При этом не надо путать nil и 0 — так как 0 — это всё-таки какая-то информация, а вот nil — это её полное отсутствие. Так вот, собственно, чем это чревато. Я уже сказал, что nil используется для сбора мусора. Вручную это работает так — когда вам переменная уже не нужна, вы просто пишете:
Во время игры всё это скорее всего отработает хорошо, но вот при загрузке… тут могут быть проблемы, так как скрипт при запуске может пролететь ту часть, где переменной reminder_count присваивается значение! В итоге при запуске вышеприведённой функции check_antirad_supplies() нас получится что reminder_count не существует — он равен nil, и в итоге проверка
выдаст ошибку и приведёт к вылету! Почему? Да потому что НЕЛЬЗЯ сравнивать НИЧТО с вещественным значением. Именно поэтому, когда вы создаёте новую переменную, вы ОБЯЗАНЫ задать ей значение по умолчанию, даже если вы полностью уверены, что она нигде не будет использована до инициализации. Поверьте — в программировании бывает всё, и запросто может так случиться, что ваша переменная будет использована и спровоцирует вылет.
Простой способ обойти такую кучу проверок и лишних инициализаций — использование or-оператора. пример
если переменная равна nil, то выражение вычисляется дальше и получается равным 0, а это уже число и сравнение происходит безболезненно.
Кроме вышеприведённой ситуации, возможен ещё один неприятный момент, связанный с определением переменных и значением nil. Суть его заключается в том, что при присваивании значений или операциями с переменными мы можем получить кучу проблем… покажу конкретнее каких:
Ошибки деления на 0
Теперь положим что у нас reminder_count используется в каком-нибудь расчёте вот так:
Как мы помним, у нас reminder_count не инициализирована, и как следствие равна nil. В итоге мы получим попытку поделить exposure_count на ноль (в нашем случае на nil, что суть одно и то же в нашем случае) и получим «классический» программистский вылет из-за ошибки деления на ноль.
Типичные ошибки при именовании функций, присвоении и инициализации переменных.
Типичная ошибка, приводящая к вылету:
В этом коде агрумент функции совпадает с её именем, и в итоге функция пытается передать функции alife():release вместо указателя на вещь — указатель на саму себя. А так как у функции remove_item нет никакого id, код выпадает с ошибкой:
Следует помнить, что Lua — это язык прямого (JIT) компилирования, и он, как любой такой язык не проверяет ошибки при компиляции. Поэтому нужно очень аккуратно раздавать имена переменным и функциям, чтобы избежать подобных ошибок. Данную функцию нужно переписать следующим образом:
Однажды, отлаживая скрипт, натолкнулся на вот такой код:
Казалось бы, всё верно, и значение проверяется перед использованием. Ан нет, скрипт вызывал вылет, причём именно на проверке. Методом тыка было выясено, что nil вызвавший вылет, был не в результате обработки, а был передан фукнции. Это был параметр npc_id.
Пришлось сделать вот так:
Касательно именования переменных — не забывайте давать им атрибут local если вы не собираетесь их пользовать во внешних скриптах. Иначе, если попадётся где-то аналогичное имя — будет сбой логики или вылет, помните об этом.
И ещё один совет — для самых начинающих:
Когда пишете скрипты, не забывайте о соблюдении синтаксиса! То есть внимательно и вдумчиво ставьте в концах функций и оснований ключевые слова end — помните, что как отсутствие нужных end’ов, так и наличие лишних приводят к тому, что границы фукций сбиваются, скрипт не парсится обработчиком и вызывает при попытке своего вызова вылет, примерно вот такой:
Для того, чтобы вам проще было следить за синтаксисом и не допускать лишних end’ов, выстраивайте текст скриптов каскадными лесенками:
..и пользуйтесь редакторами с подсветкой синтаксиса, такими как Notepad++ например. В результате каждый уровень вложенности находится на своём отступе. Вы всегда увидите, какую конструкцию нужно закрыть, а какую — нет. Я же например, делаю ещё проще — я, открывая новую функцию, сразу ставлю сразу в её конце, парой строк ниже, end, и потом уже пишу наполняющий её текст с отступами как в примере выше.
И ещё — касательно жизни переменных: не забывайте, что определённые внутри проверок переменные живут только до выхода из проверки. То есть, например:
В данном случае переменная test создастся, но просуществует только до выхода из проверки, и дальше обращение к ней выдаст nil. Для того чтобы этого избежать, определение переменных надо выносить «за скобки» вот так:
Как с этим бороться
Бороться с такими ситуациями очень просто на самом деле. Во-первых: ВСЕГДА ИНИЦИАЛИЗИРУЙТЕ ПЕРЕМЕННЫЕ. То есть вот такое описание переменной:
НЕДОПУСТИМО! ВЫ ОБЯЗАТЕЛЬНО ДОЛЖНЫ ЗАДАТЬ ПЕРЕМЕННОЙ ЗНАЧЕНИЕ! Если переменная числовая, сделайте это так:
Если строчная — то сделайте это так:
(получится вместо nil пустая строка)
Если логическая, то так:
В общем, делайте так, как вам удобнее, но ДЕЛАЙТЕ ОБЯЗАТЕЛЬНО!
И во-вторых: ВСЕГДА ПРОВЕРЯЙТЕ ПЕРЕМЕННУЮ ПЕРЕД ИСПОЛЬЗОВАНИЕМ!
То есть в кусках кода, чреватых вылетами (к ним относятся АБСОЛЮТНО ВСЕ части, где идёт работа с вещами или оружием) обязательно вставляйте проверки переменных на nil следующим образом:
Точно так же, если вы используете ЛЮБЫЕ математические операции например деление (но не только деление, всего остального тоже касается), тоже ОБЯЗАТЕЛЬНО поверяйте переменную. Вот так:
Таким образом потенциальные вылеты будут аккуратно изолированы, и больше не будут создавать проблем. И ещё одно небольшое дополнение к этой же теме. Итак, вы вставили обходные проверки вот таким образом:
И у вас в случае некорректного значения компилятор спокойно пропустил этот код, ничего не сделав ни с self.weapon_id (из первого примера), ни с antirad_check_delay (из второго). Однако вам всё-таки нужно, чтобы с ними что-то происходило, даже если проверяемые переменные неверны. Тогда я бы советовал вам дополнить этот обход отработкой нештатной ситуации. Делается это банально просто:
И для второго случая аналогично:
Более конкретная реализация зависит от того, что именно вы пытаетесь сделать — тут уже вам виднее…
Смертельные циклы по таблицам
Если вы строите цикл по таблице следующим образом:
То никогда, ни при каких обстоятельствах не используйте внутри этого цикла удаление/добавление строк, т.е:
применительно к таблице по которой гоняете цикл. При использовании это приводит к тому, что количество строк в таблице уменьшается, а цикл пытается получить из таблицы строки сверх существующего количества, что в итоге приводит к выходу цикла за отведённый ему диапазон памяти. Результатом будет веер самых разнообразных последствий, самое безобидное из которых это безлоговый вылет на рабочий стол, а серьёзное — сбой в работе а-лайфа с последующим боем сейвов! Тяжесть последствий зависит от того, какие действия вы выполняете внутри цикла кроме удаления строки.
Чтобы избежать подобных ситуаций, стройте циклы по таблице лучше следующим образом:
Ошибки присвоения
Предположим что мы написали скрипт, который помогает выбирать оружие при наличии вблизи врагов. И вот, у нас в скрипте есть такой кусок кода, где NPC вынимает из рюкзака оружие:
В помеченном выражении self.weapon_id = wpn:id(), и всё вроде бы отлично, так как стоит проверка if wpn then, однако иногда движок умудряется передать объект функциям некорректно, либо сам скриптер может перепутать при вызове игровой объект с объектом а-лайфа, и присвовение self.weapon_id = wpn:id() либо приравняет self.weapon_id к nil либо сразу приведёт к вылету из-за того что соответствующее свойство объекта не было обнаружено в его классе.
Читайте также: