Как вы наверно заметили, в папке с игрой содержатся несколько файлов типа gamedata.dbN , где N – это число или буква. Это игровые архивы, в которые запакованы все файлы конфигураций, видеоролики, музыка, звуки, скрипты и прочее. Можно их править HEX-editor’ом, но лучше скачать специальную утилитку STALKER_Data_Unpacker и распаковать в отдельную папку(с именем gamedata, позже поймёте почему) их содержимое. Вот после этого можно начать разгребать весь тот мусор, который вы извлекли.
Структура файлов и папок:
Рассмотрим, что-же содержится в папке gamedata: Папка ai – в ней содержатся файлы типа *.efd, через которые очевидно управляется искусственный интеллект тех же npc например. Папка anims – здесь и так всё понятно, игровая анимация, а также эффекты камеры. Папка config содержит в себе многие игровые параметры, к ней мы ещё вернёмся. Папка levels – это игровые уровни, ака локации, на которые поделена игра (как карты в CS) Папка meshes – игровые модели, в формате .ogf Папка scripts – скрипты Папка shaders – шейдеры, для двух рендеров(DX8 и 9) Папка sounds – звуки и музыка, кот. используются в игре. Формат .ogg* Папка spawns – отвечает за самую ужасную вещь в игре – респаун противников и др. людей, животных. Папка textures – в ней содержаться текстуры домов, оружия, монстров и др. Формат - .dds(DirectDraw Surface Image) Файлы: game.graph, gamemtl.xr, lanims.xr, particles.xr, resource.h, senvironment.xr, shaders.xr, shaders_xrlc.xr, stalkergame.inf. В звуках, кстати, можно найти много интересного, например: в папке sounds\car содержатся звуки машин(которых в игре нет), а sounds\music\harmonica_1.ogg – файл губной гармошки. Ах да, чуть не забыл, самый главный лол, это файл sounds\car\trabantnoise.ogg
Добавлено (18.04.2010, 15:51) --------------------------------------------- Введение в моддинг:
Именно с папкой config придётся работать для редактирования многих параметров игры.
Но начнём сначала:
Перед тем, как начать редактирование файлов игры, нужно в каталог Сталкера поместить ту самую папку gamedata, в которую были извлечены игровые файлы. *Все файлы не понадобятся, поэтому можно оставить только папки config, scripts(а конкретно файл se_respawn*****ipt), textures, sounds, а всё остальное удалить(а можно и не удалять) Стоит обратить внимание на токой факт , если распаковать все архивы .dbN в папку с игрой, то это может дать несколько дополнительных FPS.
Текстуры:
В папке textures лежат файлы формата .dds, которые можно открыть программой Nero PhotoSnap Viewer(входит в состав NERO7) например или windows texture viewer (63кб), и там же отредактировать по вкусу. Так, например, существует мод, уменьшающий размер PDA Меченого. Текстура PDA находится по адресу textures\ui\ui_pda.dds.
Скрипты:
se_respawn*****ipt – файл, отвечающий за респаун. Время респауна можно уменьшить, путём небольших изменений в этом файле. Он открывается обычным блокнотом(notepad). Ищем в нём строку idle_time:setHMSms( 0, 0, 0, math.random(self.idle_spawn_min, self.idle_spawn_max)*1000) и меняем её на idle_time:setHMSms( math.random(X, Y), 0, 0, math.random(self.idle_spawn_min, self.idle_spawn_max)*1000) где X и Y - числа, задают диапазон случайной переменной в часах отвечающей за респаун.
Звуки и музыка:
Любой файл в папке sounds можно заменить на другой, но при этом нужно сохранить все параметры звукового файла, который был использован в игре. Например, чтобы заменить музыку в приёмнике у Сидоровича, нужно файл sounds\scripts\magnitofon\magnitofon_2.ogg, заменить на любой другой.(У оригинального файла винамп мне выдал свойства: Average bitrate : 60 kbps Nominal bitrate : 80 kbps Channels : 1 Sampling rate: 44100 Hz.
Config/Weapons:
Параметры оружия вы можете найти в папке config\weapons, файлы типа w_ak74.ltx открываются тоже блокнотом. Разберём содержимое файла например w_groza.ltx Во первых стоит обратить внимание на значение пременной(буду их так называть) cost, это цена оружия, т.е. сколько гроза будет стоить у продавца. Переменная weapon_class означает, к какому классу относится оружие, в данном случае - assault_rifle. ammo_limit – лимит боеприпасов(в игре по-моему не используется) ammo_mag_size – размер магазина. Можно поставить например 900, тогда о перезарядке можно совсем не вспоминать. ammo_class – класс патронов, используемых оружием. Здесь ammo_9x39_pab9, ammo_9x39_sp5, ammo_9x39_ap. hit_power – сила оружия. Чем больше, тем лучше silencer_hit_power - сила оружия, когда на него одет глушитель. misfire_probability - вероятность осечки при максимальном износе. condition_shot_dec - увеличение износа при каждом выстреле. visual – модель оружия. zoom_enabled - поддерживается ли оптика.
Config/Сreatures:
actor.ltx – параметры главного героя. В этой же папке находятся файлы с параметрами npc, монстров и пр. Рассмотрим некоторые переменные на примере actor.ltx visual – модель персонажа. max_walk_weight - максимальный вес, при достижении которого игрок перестает двигаться. pickup_info_radius – радиус от ГГ, в котром над предметами выводятся их названия. camera_height_factor – высота ГГ. По умолчанию стоит 0.85, но можно увеличить рост ГГ до 0.95 например. disp_base – разброс. Если уменьшать, то и разлёт уменьшается соответственно. wound_incarnation_v - скорость заживления раны. cant_sprint_power_begin - порог силы меньше которого актер не может бежать в спринте. hit_probability_gd_novice hit_probability_gd_stalker hit_probability_gd_veteran hit_probability_gd_master Эти строчки отвечают за вероятность попадания по вам, при разных уровнях сложности. Хотите чтобы враги стреляли не очень метко, уменьшайте значения. Также в файле присутсвуют комментарии на русском языке, так что разобраться в остальных переменных не составит труда.
Config/System.ltx:
start_blood_size - размер раны чтоб начала капать кровь. stop_blood_size - размер раны чтоб кровь остановилась. max_weight – вес, с которым ГГ может бежать.
Config/Misc.ltx:
В дополнение к конфигам оружия:
cвойства патронов находятся в файле weapons.ltx, а бронежилетов – в outfit.ltx. Свойства артефактов находятся в файле artefacts.ltx и файлах zone_ART.ltx, предметов – items. ltx, где ART – название артефакта(например zone_gravi.ltx). Параметры артефактов: health_restore_speed - скорость восстановления здоровья (0.0001 = +100%) radiation_restore_speed - скорость накопления радиации (0.0004 = 4). Если поставить отрицательное число, то артефакт будет ускорять выведение радиации. idle_particles – если закоментить её символами «--« -, то аномалия исчезнет. Чтобы отключить звуковое оповещение, то находим строку visible_by_detector и меняем on на off. Иммунитеты: strike_immunity - удар. shock_immunity - электрошок. chemical_burn_immunity - химический ожог. telepatic_immunity - телепатия. radiation_immunity - радиация. wound_immunity - разрыв. explosion_immunity - взрыв. fire_wound_immunity - огнестрел.
items.ltx:
Параметры предметов:
В этом файле в квадратных скобках написан тот предмет, чьи параметры идут ниже, за квадр. скобками. Например: [kolbasa]:identity_immunities. Параметры на примере колбасы: class – к какому классу принадлежит предмет. visual – модель. cost – цена. eat_health – сколько восстанавливает здоровья.(если будет 0, то колбаса лечить не будет.) eat_power - сколько восстанавливает силы. eat_radiation - сколько выводит/или добавляет радиации. (Насчёт этого не уверен, надо тестить.) inv_weight – вес предмета.
Outfit.ltx:
Параметры костюмов:
actor_visual – модель костюма. inv_weight – вес костюма. cost – цена костюма. коэффициенты иммунитета самого костюма: burn_immunity см. коэффиц. ... fire_wound_immunity ; NO RESISTANCE burn_protection - коэффициент защиты от огня strike_protection - коэффициент защиты от пуль shock_protection - коэффициент защиты от молний wound_protection - коэффициент защиты от кровотечений (?) radiation_protection - коэффициент защиты от радиации telepatic_protection - коэффициент защиты от телепатии chemical_burn_protection - коэффициент защиты от хим. ожогов. explosion_protection - коэффициент защиты от взрыва fire_wound_protection - коэффициент защиты от кровотечений(от огня) nightvision_sect – тип ночного видения(плохой/хороший). Если ПНВ нету, то строку добавить, если его нужно убрать, строку закомментируйте(--).
FAQ по модостроюЭтот FAQ поможет новичкам в модострое
=========================== Как увеличить скорость бега?
Ответ: Файл <Диск>:\<Каталог установки>\STALKER\gamedata\config\creatures\actor.ltx отвечает за параметры нашего игрока. В нем есть строка
Код: sprint_k=75;150;185;180;200; ;коэффициент на "sprint" бег (умножается walk_power, walk_weight_power)
Она отвечает за то насколько быстро персонаж устает при беге. Если поставить значение не 75, ну а скажем 5, то он практически не будет уставать.
============================ Как увеличить силу оружия?
Ответ: Заходим в папку gamedata/config/weapons, находим нужное нам оружие и строку hit_pover. Меняем её значения на большие.
============================ Как сделать нового и желательно уникального NPC?
Ответ: Для этого нам понадобятся координаты нужного места, где и будет заспаунен наш NPC. Возьмём Кордон, лагерь новичков. Можете снять координаты сами, а можете взять мною уже снятые: (-218.20,-20.2,-145.63),35362,47). Далее. Создаём файл в папке gamedata/scripts файл, скажем esc_unik_npc.script. В нём создаём функцию. Код: function esc_unik_npc () alife():create("esc_unik_npc",vector():set(-218.20,-20.2,-145.63),35362,47) end
Теперь мы в файле npc_profile.xml (в папке gameplay) добавляем нашего НПС. Код: <!-- Escape --> <character id="escape_trader"> <class>Trader</class> <specific_character>escape_trader</specific_character> </character>
Копируем или создаём по образцу только нижний блок Второго торговца нам ненадо. Теперь моздадим секцию нашего персоонажа. В файле spawn_section.ltx (gamedata/config/creatures) в блоке: ;--------Escape-------------- ;--------neutrals------------ Создаём секцию. Код: [esc_unik_npc]:stalker $spawn = "respawn\esc_unik_npc" character_profile = esc_unik_npc spec_rank = regular community = dolg
В строке community можете вместо долга вписать другую группировку. Однако если мы хотим, чтоба наш персоонаж не гулял по Зоне, пока не наткнётся на первуюю аномалию, то пропишем чтобы он стоял на месте. Для этого под community вставляем: custom_data = scripts\esc_unik_npc.ltx Далее создаём в gamedata/config папку scripts в в папке создаём файл esc_unik_npc.ltx В наш созданый файл вставляем: Код: [logic] active = remark1 danger = danger_ignore
[danger_ignore] ignore_distance = 5
[remark1] no_move = true
Всё наш НПС будет стоять на месте своего спауна. Едем дальше. Нам надо активировать нашего персоонажа. для этого мы должны вставить строку: <action>esc_unik_npc.esc_unik_npc</action> Либо в диалог, либо в какой-нибудь квест. Давайте вставим строку в файл info_portions.xml (gamedata/gameplay) под Код: <game_information_portions> <info_portion id="storyline_actor_start"> <task>storyline_eliminate_gunslinger</task>
Однако мы не прописали самого НПС, как личность В файле character_desc_escape.xml (gamedata/gameplay) добавляем: Код: <!---------------------------------------esc_unik_npc-----------------------------------------------------> <specific_character id="esc_unik_npc" team_default = "1"> <name>GENERATE_NAME_bandit</name> <icon>ui_npc_u_stalker_bandit_3</icon> <bio>esc_wolf_bio</bio>
Правда тут получится у нас долговец в одежде бандита и говорящего голосом монолитовца xD Всё сохроняем. НПС готов.
Но так же можно добавить нашему НПС диалог) Как добавлять диалоги написано в нашем факе) Если хотяте сделать нашему НПС своё имя то в файле stable_bio_name.xml (gamedata/config/text/rus) добавляем: Код: <string id="esc_unik_npc"> <text>Факер-мазафакер</text> </string>
А в файле сharacter_desc_escape.xml (gamedata/gameplay) находим нашего уже прописаного НПС и вставляем в секцию <name></name> (у нас в ней написано GENERATE_NAME_bandit) esc_unik_npc Всё Поздравляю)
============================== Как добавить новый побочный (второстепенный) квест?
Ответ: Файл task_manager (config/misc) отвечает за ВСЕ второстепенные (побочные) квесты (задания). Например хотим добавить задание на часть мутанта торговцу. Прописываем: Код: В разделе: ;----------Trader-------------------------------------------------- Находим подраздел: ;---------Monster Part----------- Дописываем: tm_monster_part_3
Дальше находим (ну наверное не обязательно, но чтобы был порядок в файле): Код: [tm_monster_part_1] type = monster_part community = actor condlist = {+agroprom_military_case_have} text = tm_monster_part_1_text description = tm_monster_part_1_descr parent = trader target = mutant_krovosos_jaw reward_money = 3000 reward_reputation = +20 reward_rank = 5 time = 86400 reward_item = ammo_12x76_dart, ammo_12x76_dart, ammo_12x76_dart, ammo_12x76_dart, ammo_12x76_dart prior = 2
Дальше после этого идёт слудющий квест но первая строка начинается с tm_monster_part_2 И после этого блока прописываем к примеру:
Код: [tm_monster_part_3] type = monster_part community = actor text = tm_monster_part_3_text description = tm_monster_part_3_descr parent = trader target = mutant_burer_hand reward_money = 1500 reward_reputation = +10 reward_rank = 5 time = 86400 reward_item = mutant_flesh_eye, mutant_boar_leg, mutant_snork_leg prior = 1
Обозначения:
Код: type - тип (в данном случае найти часть монстра) community - типо команда, но желательно не трогать text - текст который отображается в диалоге при выборе задания description - текст отображающийся, когда вы выберите задание parent - тот кто выдаёт задание target - место, вещь, арт, часть мутанта, сталкер, которых надо защитить, найти, убить reward_money - сколько денег вам дадут за выполнение задания reward_reputation - на скока понизится (-)/повысится(+) репутация ГГ reward_rank - количивство прибовляемых ранговых очков time - время в сек. на выполнение задания reward_item - вознаграждение в предметах prior - на каком месте будет стоять задание в ПДА reward_relation - повышение или ухудшение отношения ГГ при выполении задания (reward_relation = ecolog, +10000 - эокологи станут друзьями)
Всё с этим разобрались теперь в файле storyline_info_taskmanager (config/gameplay): Код: Находим:
article id - внутреннее имя статьи, именно на него ссылаются в файлах игры name - имя статьи, отображаемое в игре, подгружается из строкового массива texture - картинка и её позиция в статье, в данном случае мы использовали обычный белый шум text - текст статьи, отображаемый в игре, подгружается из строкового массива Добавим в, например, config\gameplay\info_l01escape.xml (инфопорции уровня "Кордон") ссылку на получение статьи - скажем, к трупу у туннеля, при обыске которого выдается информация о аномалиях в туннеле (вы его обнаруживаете, проходя второе спецзадание от Сидоровича). Найдем эти строки и дополним их: Код: <!-- труп у аномалии --> <info_portion id="esc_tutorial_dead_novice"> <article>tutorial_moving_anomaly</article> '''<article>zone_anomalies_activation_basic</article>''' </info_portion>
Главное - никогда не путайте article id, name и text статьи. Я в данном примере это сделать легко. Лучше называйте их непохожими друг на друга названиями.
Так как в статье у нас есть такое поле:
<text>enc_zone_anomalies_activation_basic</text>
Да и название тоже не написано прямо, а ссылается на определенную строку, то добавим этот самый text в config\text\rus\string_table_enc_zone.xml, в нашем случае:
Код: <string id="enc_zone_anomalies_activation-basic"> <text>Активация - базис</text> </string> <string id="enc_zone_anomalies_activation_basic"> <text>С артефактами связана, помимо всего прочего, ''(ну, и так далее, там большой текст)''...</text> </string>
Дополнительно Чтобы статья добавлялась при получении определенного задания, в ..._task.xml (вместо ... стоит название уровня) нужно прописать конструкцию вида:
Код: <article>название_задания_descr</article>
[Наверх]
============================= Как Менять диалоги?
Ответ: gamedata/config/text/rus и найди dialogs_escape (или наоборот escape_dialogs ) открывай блокнотом и сможешь редактировать текст диалогов которые на кордоне. Так же и по остальным разобраться легко=) Приятной игры!=)
============================== Как сделать, чтобы сталкеры играли на губной гармошке?
Ответ:В файле gamedata\scripts\xr_kamp.scriptнаходим: -- играть на гармошке --if npc:object("harmonica_a") then -- self.npc[npc_id].states["play_harmonica"] = true -- self.npc[npc_id].states["wait_harmonica"] = true --self.kamp_states["pre_harmonica"] = true -- self.kamp_states["harmonica"] = true --self.kamp_states["post_harmonica"] = true --else Раскоментируем эти строки, для этого удалим --. Внимание! Удалять нужно только те --, которые здесь отмечены красным. Затем нужно добавить музыку. Например сталкерам, открываем папку gamedata\sounds\characters_voice\human_01\stalker\musicи добавляем туда свою музыку. Имя файла должно быть harmonica_* (где *, 1, 2, 3 и т.д.). Формат файла должен быть *.ogg. Таким же образом добавляем музыку остальным группировкам.
================================= Как сделать полный квест?
Ответ: Мы создадим квест (например для того чтобы нас пустили к Воронину) Приступим: <text>Найти артефакт панцирь</text> <icon height="50" width="50" x="100" y="50">ui\ui_icons_task</icon> <function_complete>dan_dialog.bar_dolg_propusk_have</function_complete> <infoportion_set_complete>bar_dolg_propusk_have</infoportion_set_complete> </objective> <objective> <text>Принести артефакт Пличко</text> <map_location_type hint="Пличко">blue_location</map_location_type> <object_story_id>bar_dolg_guard_commander</object_story_id> <infoportion_complete>bar_dolg_propusk_done</infoportion_complete> </objective> </game_task>
Все теперь нам надо добавить инфопоршни в наш диалог, находим dialogs_bar ищем в нем наши диалоги и добавляем инфопоршни; <dialog id="bar_ohran_propusk"> <dont_has_info>bar_dolg_propusk_start</dont_has_info> это значит что диалог не появиться у Пличко <dont_has_info>bar_dolg_propusk_have</dont_has_info> если сработали наши инфопоршни т.е. чтобы <dont_has_info>bar_dolg_propusk_done</dont_has_info> после выполнения задания диалог вновь не появлялся <phrase_list> <phrase id="0"> <text>bar_ohran_propusk_0</text> <next>1</next> </phrase> <phrase id="1"> <text>bar_ohran_propusk_1</text> <next>2</next> <next>3</next> </phrase> <phrase id="2"> <text>bar_ohran_propusk_2</text> <action>dan_dialog.bar_dolg_propusk_have</action> т.е. будет выполняться функция <give_info>bar_dolg_propusk_start</give_info> bar_dolg_propusk_have из сриптового файла dan_dialog. </phrase> <phrase id="3"> <text>bar_ohran_propusk_3</text> <give_info>bar_dolg_propusk_start</give_info> информации о получении первого инфопоршня. <action>dialogs.break_dialog</action> </phrase> </phrase_list> </dialog>
<dialog id="bar_friend_propusk"> <dont_has_info>bar_dolg_propusk_done</dont_has_info> не показывать после получения 3 инфопоршня <has_info>bar_dolg_propusk_have</has_info> показывать если есть второй инфопоршень <phrase_list> <phrase id="0"> <text>bar_friend_propusk_0</text> <next>1</next> </phrase> <phrase id="1"> <text>bar_friend_propusk_1</text> <next>2</next> </phrase> <phrase id="2"> <text>bar_friend_propusk_2</text> <action>dan_dialog.bar_dolg_propusk_done</action> вызывается функция bar_dolg_propusk_done <give_info>bar_dolg_propusk_done</give_info> информация о получения 3 инфопоршня задание выполенено <next>3</next> </phrase> <phrase id="3"> <text>bar_friend_propusk_3</text> <give_info>bar_dolg_base_pass</give_info> это срабатывает инфопоршни о пропуске нас на базу долга. <give_info>bar_dolg_community_start</give_info> <give_info>bar_dolg_community_leader</give_info> </phrase> </phrase_list> </dialog> Теперь нам надо создать свой скриптовой файл и вписать 2 функции. Одна нужна для проверки наличия в инвентаре панциря и соответственно включения инфопоршня bar_dolg_propusk_have. Вторая для передачи Пличко арта. Например dan_dialog и вписать наши функции.
function kill_stalker_padla_escape(task, objective) эта функция проверяет наличие арта if db.actor ~= nil then return db.actor:object("af_armor_3") ~= nil end return false end
function bar_dolg_propusk_done(first_speaker, second_speaker) эта функция отдачи арта Пличко dialogs.relocate_item_section(second_speaker, "af_armor_3", "out") second_speaker:set_relation(game_object.friend, first_speaker) эта строчка делает Пличко другом. end
Ну вот вроде все. Если сделали все правильно, то после того, как принесли арт Пличко, вас свободно пропустят на базу долга.
Добавлено (20.04.2010, 17:18) --------------------------------------------- Вот еще одна статья по написанию скриптов.
Отладив несколько скриптов на предмет вылетов решил написать это небольшое руководство, чтобы помочь скриптописателям избежать потенциальных пробем. Сразу оговорюсь - я программист, поэтому к отладке скриптов подхожу со своей профессиональной точки зрения, и то что я тут изложу вам - сие для программиста есть непреложная истина, и если вы хотите уменьшить количество глюков - старайтесь придерживаться нижеприведённого стиля программирования.
Для начала маленький экскурс в историю. Итак, скрипты в сталкере написаны на языке Lua - разработали его бразильцы, он очень гибок, легко встраивается в игры и поэтому очень часто используется для написания скриптов любой степени сложности. Так вот, у этого языка есть несколько исключительно важных особенностей, которые, при их несоблюдении, будут приводить к вылетам вашего скрипта. Итак:
Особенность первая. Типы данных и западло с nil.
Сначала сделаю маленькое отступление, чтобы понятнее было. Итак, в Lua используются следующие операторы:
= оператор присваивания
== сравнение, равно ли значение
~= сравнение, НЕ равно ли значение
< сравнение, меньше ли значение
> сравнение, больше ли значение
<= сравнение, меньше ли значение или равно
>= сравнение, больше ли значение или равно
and логический оператор И
not логический оператор НЕ
or логический оператор ИЛИ
Всё. Теперь поехали дальше... Язык Lua Изначально создавался для работы с большими строчными базами данных, поэтому ВСЕ виды конструкций в языке - это типы данных, т.е. по сути либо переменные, либо константы. Это касается так же и функций! Поэтому даже с функциями в Lua можно и нужно обращатся как с константами. Далее. Касательно, собственно "привычных" переменных. Обычные переменные в Lua получают свой тип данных только в момент присвоения им значения (запомните, это важно). При этом, в Lua есть такой важный и полезный тип данных как nil. nil - Это "пустота", т.е. отсутствие какого-либо значения. При этом этот тип используется компилятором скриптов для "сбора мусора", т.е. для освобождения занимаемой памяти. Смотрите пример, я объясню подробнее, что это значит:
local reminder_count
local exposure_count = 0
Тут у нас 2 локальные переменные, одна из которых просто создана, а вторая - создана и инициализирована. Если сейчас обратиться к переменной reminder_count, считав её значение, то мы получим в качестве значения - nil, т.е. пустоту. Если же мы обратимся к переменной exposure_count - то получим, как и ожидали, 0 - так как мы это значение проинициализировали ЗАРАНЕЕ. При этом не надо путать nil и 0 - так как 0 - это всё-таки какая-то информация, а вот nil - это её полное отсутствие. Так вот, собственно, чем это чревато. Я уже сказал, что nil используется для сбора мусора. Вручную это работает так - когда вам переменная уже не нужна, вы просто пишете:
test_variable = nil
И ваша переменная test_variable сотрётся из памяти, и любые ссылки на неё будут выдавать на выходе nil (типа нет такой переменной, "абонент вне зоны действия сети"). Ну так собственно вот, зачем я это всё рассказываю... если не определить значение переменной сразу, как это было сделано с переменной reminder_count, то любые попытки обратиться к ней внутри скрипта приведут к вылетам. Происходит это следующим образом: допустим, reminder_count - это у нас счётчик напоминаний. Т.е. мы о чём-то напоминаем игроку текстовыми сообщениями, и помечаем, сколько раз мы это сделали. Определили мы переменную именно так, как в примере выше, т.е. не задав ей никакого значения. Значение же её должно присваиваться ниже по коду, после первого оповещения юзера. При этом у нас есть в коде обращение к этой переменной, по которому решается что делать дальше.
Выглядит это примерно так:
function check_antirad_supplies()
if auto_injection_active then
if antirad_check_delay == nil or game.get_game_time():diffSec(antirad_check_delay) > 60 then
if not db.actor:object("antirad") and not (use_scientific_kit and db.actor:object("medkit_scientic")) then
--обратите внимание сюда if reminder and reminder_count == 0 or reminder_count >= mins_till_next_remind then --обратите внимание сюда if use_text then local news_text = "%c[255,160,160,160]Автоматическая система ввода медицинских препаратов\\n".."%c[default]Напоминаю: %c[255,230,0,0]Противорадиационные препараты отстутствуют! %c[default]Автоматический ввод препаратов невозможен." db.actor:give_game_news(news_text, "ui\\ui_iconsTotal", Frect():set(0,188,83,47), 0, 3000) end if use_sounds then local snd_obj if use_custom_sounds then snd_obj = xr_sound.get_safe_sound_object( [[HEV\no-anti-rad]] ) else snd_obj = xr_sound.get_safe_sound_object( [[device\pda\pda_tip]] ) end if snd_obj then snd_obj:play_no_feedback(db.actor, sound_object.s2d, 0, vector(), 1.0) end end reminder_count = 1 elseif reminder then reminder_count = reminder_count + 1 end else radiation_warning = true reminder_count = 0 end antirad_check_delay = game.get_game_time() end end end
Во время игры всё это скорее всего отработает хорошо, но вот при загрузке... тут могут быть проблемы, так как скрипт при запуске может пролететь ту часть, где переменной reminder_count присваивается значение!
В итоге при запуске вышеприведённой функции check_antirad_supplies() нас получится что reminder_count не существует - он равен nil, и в итоге проверка
if reminder and reminder_count == 0 or reminder_count >= mins_till_next_remind then
выдаст ошибку и приведёт к вылету!
Почему? Да потому что НЕЛЬЗЯ сравнивать НИЧТО с вещественным значением. Именно поэтому, когда вы создаёте новую переменную, вы ОБЯЗАНЫ задать ей значение по умолчанию, даже если вы полностью уверены, что она нигде не будет использована до инициализации.
Поверьте - в программировании бывает всё, и запросто может так случиться, что ваша переменная будет использована и спровоцирует вылет.
begin xStream upd. --- Простой способ обойти такую кучу проверок и лишних инициализаций - использование or-оператора. пример
if (reminder_count or 0) >= mins_till_next_remind then
если переменная равна nil, то выражение вычисляется дальше и получается равным 0, а это уже число и сравнение происходит безболезненно end xStream upd. ---
Кроме вышеприведённой ситуации, возможен ещё один неприятный момент, связанный с определением переменных и значением nil. Суть его заключается в том, что при присваивании значений или операциями с переменными мы можем получить кучу проблем... покажу конкретнее каких:
1) "Убийство" переменных
Положим, у нас с reminder_count происходят ещё какие-нибудь занимательные вещи, например мы его значение зачем-нибудь присваиваем другой переменной вот так:
antirad_check_delay = reminder_count
Ну и вот, если у нас reminder_count в момент присвоения ещё не имеет никакого значения, то мы получим занимательную штуку - у нас это выражение сработает как
antirad_check_delay = nil
В результате чего переменная antirad_check_delay "сдохнет" и будет деловито убрана "сборщиком мусора" из памяти, что моментально приведёт к вылету, когда в дальнейшем какая-то часть кода обратится к значению antirad_check_delay.
2) Ошибки деления на 0.
Теперь положим что у нас reminder_count используется в каком-нибудь расчёте вот так:
Как мы помним, у нас reminder_count не инициализирована, и как следствие равна nil.
В итоге мы получим попытку поделить exposure_count на ноль (в нашем случае на nil, что суть одно и то же в нашем случае) и получим "классический" программистский вылет из-за ошибки деления на ноль. 3) Ошибки присвоения.
Предположим что мы написали скрипт, который помогает выбирать оружие при наличии вблизи врагов. И вот, у нас в скрипте есть такой кусок кода, где NPC вынимает из рюкзака оружие:
function weapon_manager:set_weapon(wpn)
local enemy = self.npc:best_enemy() if wpn then --обратите внимание сюда self.weapon_id = wpn:id() --обратите внимание сюда self:return_items(self.weapon_id) -- printf("set_weapon[%s]:set %s[%s]",self.npc:character_name(),wpn:id(),wpn:section()) else printw("set_wpn:weapon not exist") end if self.modes.process_mode == "3" and enemy then for k,v in pairs(self.weapons) do for i,w in ipairs(v) do if w.id ~= self.weapon_id then local item = level.object_by_id(w.id) if item and item:parent() and item:parent():id() == self.npc_id then printw("set_weapon[%s]:process %s[%s]",self.npc:character_name(),w.id,w.sec) self:process_item(item) end end end end end if enemy then self.npc:set_item(object.idle,wpn) end self.weapons = nil
end
В помеченном выражении self.weapon_id = wpn:id(), чтобы было понятнее, self.weapon_id - это оружие, которое сейчас в руках у персонажа, а wpn:id() ссылается на лучшее оружие из инвентаря NPC. Т.е. это выражение заставляет NPC заменить ТЕКУЩЕЕ оружие на лучшее. Всё было бы отлично если бы не одна характерная вещь - когда NPC сидит например у костра отдыхает, У НЕГО НЕТ В РУКАХ СТВОЛА! И разумеется, в этом случае значение self.weapon_id не существует! Оно равно nil. В итоге при подобном выражении
self.weapon_id = wpn:id()
произойдёт попытка присвоить значение ПУСТОТЕ, т.е. nil = wpn:id(), и это МГНОВЕННО приведёт к вылету...
Как с этим бороться.
Бороться с такими ситуациями очень просто на самом деле. Во-первых: ВСЕГДА ИНИЦИАЛИЗИРУЙТЕ ПЕРЕМЕННЫЕ.
Т.е. вот такое описание переменной: local reminder_count
НЕДОПУСТИМО! ВЫ ОБЯЗАТЕЛЬНО ДОЛЖНЫ ЗАДАТЬ ПЕРЕМЕННОЙ ЗНАЧЕНИЕ! Если переменная числовая, сделайте это так:
local reminder_count = 0
Если строчная - то сделайте это так:
local reminder_count = "" (получится вместо nil пустая строка)
Если логическая, то так:
local reminder_count = false
В общем, делайте так, как вам удобнее, но ДЕЛАЙТЕ ОБЯЗАТЕЛЬНО!
И во-вторых: ВСЕГДА ПРОВЕРЯЙТЕ ПЕРЕМЕННУЮ ПЕРЕД ИСПОЛЬЗОВАНИЕМ! Т.е. в кусках кода, чреватых вылетами (к ним относятся АБСОЛЮТНО ВСЕ части, где идёт работа с вещами или оружием) обязательно вставляйте проверки переменных на nil следующим образом:
Код: Выделить всё
if self.weapon_id ~= nil and wpn:id() ~= nil then self.weapon_id = wpn:id() end
Тогда сначала выпонится проверка, и если обе переменные имеют значение, то операция отработает, а если один из параметров пуст - то функция просто пропустит этот код. Точно так же, если вы используете ЛЮБЫЕ математические операции например деление (но не только деление, всего остального тоже касается), тоже ОБЯЗАТЕЛЬНО поверяйте переменную. Вот так:
Код: Выделить всё
if reminder_count ~= nil and reminder_count ~= 0 then antirad_check_delay = exposure_count/reminder_count end
Таким образом потенциальные вылеты будут аккуратно изолированы, и больше не будут создавать проблем. Настоятельно советую всем, кто уже писал скрипты, внимательно их просмотреть, и вставить в "опасные" места подобные проверки, и в будущем делать это сразу, чтобы самим себе облегчить жизнь...
16/02/2009 KamikaZze
Дополнения:
Небольшое дополнение итак, вы вставили обходные проверки вот таким образом:
if self.weapon_id ~= nil and wpn:id() ~= nil then self.weapon_id = wpn:id() end
Или таким:
if reminder_count ~= nil and reminder_count ~= 0 then antirad_check_delay = exposure_count/reminder_count end
И у вас в случае некорректного значения компилятор спокойно пропустил этот код, ничего не сделав ни с self.weapon_id (из первого примера), ни с antirad_check_delay (из второго). Однако вам всё-таки нужно, чтобы с ними что-то происходило, даже если проверяемые переменные неверны. Тогда я бы советовал вам дополнить этот обход отработкой нештатной ситуации. Делается это банально просто:
if self.weapon_id ~= nil and wpn:id() ~= nil then self.weapon_id = wpn:id() elseif self.weapon_id == nil then --вставьте сюда код, что делать если self.weapon_id не существует elseif wpn:id() == nil then --а сюда, если wpn:id() не существует end
И для второго случая аналогично:
if reminder_count ~= nil and reminder_count ~= 0 then antirad_check_delay = exposure_count/reminder_count else --а тут что мы сделаем если reminder_count не существует --я бы сделал например вот так: antirad_check_delay = exposure_count/1 --и все дела end
Более конкретная реализация зависит от того, что именно вы пытаетесь сделать - тут уже вам виднее...
Файлы character_desc_*.xml можно сравнить со стволом дерева диалогов. В них перечисляется названия прикрепляемых веток диалогов
Например вот список веток диалога с Сидоровичем взятый из файла character_desc_escape.xml <start_dialog>escape_trader_start_dialog</start_dialog> <actor_dialog>escape_trader_talk_info</actor_dialog> <actor_dialog>escape_trader_jobs</actor_dialog> <actor_dialog>tm_trader_dialog</actor_dialog> <actor_dialog>tm_trader_reward</actor_dialog> <actor_dialog>escape_trader_done_blockpost_box</actor_dialog>
В свою очередь каждая ветка диалога также может ветвится.
2) Ветвление диалогов прописывается уже в других файлах.
Например, ветвление диалога с Сидоровичем содержится в файле gamedata/config/gameplay/dialogs_escape.xml Возьмем оттуда, например, ветвление escape_trader_jobs.
Ветвление имеет довольно большие масштабы, поэтому приведу только часть: <dialog id="escape_trader_talk_info"> <precondition>escape_dialog.trader_has_talk_info_wr</precondition> <has_info>tutorial_end</has_info> <phrase_list> <phrase id="1"> <text>escape_trader_talk_info_1</text> <next>100</next> <next>99</next> <next>9995</next> </phrase> ... <phrase id="0"> <text>escape_trader_talk_info_0</text> <next>1</next> </phrase> </phrase_list> </dialog>
Здесь <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
Функция выглядит так: function trader_has_talk_info_wr( trader, actor ) return true end
То есть, судя по его структуре, <precondition>escape_dialog.trader_has_talk_info_wr</precondition> выполняется всегда, т.к. функция всегда возвращает истину и <dialog id="escape_trader_talk_info"> пропускается в списк реплик.
Но для конкретной ветки может быть несколько precondition и других условий.
Далее, <has_info>tutorial_end</has_info> - это еще одна проверка, на этот раз на наличие у игрока так называемых infoportions, выдаваемы в процессе ключевых диалогов. В данном случае это проверка на то, закончена ли определенная стадия туториала, или нет. Т.е. ветка допустится в список реплик если стадия туториала закончена.
Более детально мы это разберем в конце статьи.
А далее идут конкретные фразы, содержащие ссылки на вытекающие фразы, например: <phrase id="0"> <text>escape_trader_talk_info_0</text> <next>1</next> </phrase>
Это основа ветки escape_trader_talk_info.
Важно! В любой основной ветке любого диалога фраза <phrase id="0"> будет основой, из которой далее будет все вытекать. Она должна обязательно присутствовать и в вашем диалоге.
<next>1</next> - это ссылка на вытекающую фразу <phrase id="1">: <phrase id="1"> <text>escape_trader_talk_info_1</text> <next>100</next> <next>99</next> <next>9995</next> </phrase>
В свою очередь <next>100</next>, <next>99</next>, <next>9995</next> это ссылки на фразы веточки растущие из фразы <phrase id="1">.
3) Текст каждой фразы содержится в третьем файле. Для диалога с Сидоровичем тексты лежат в файле gamedata/config/text/rus/stable_dialogs_escape.xml <string id="escape_trader_talk_info_0"> <text>Есть несколько вопросов.</text> </string> <string id="escape_trader_talk_info_1"> <text>Спрашивай, только я ведь всего не знаю. Сам понимаешь, сижу тут целыми днями, а жизнь - она вся там, снаружи, в Зоне. Могу рассказать о Зоне вообще, а немного могу о ближайших окрестностях, где сам ходил.</text> </string> ...
Эти строки содержат тексты для фраз <phrase id="0"> и <phrase id="1">
Итого диалоги разложены по трем, а то и более файлам.
Да кстати, путь по веткам может быть зацикленным, если того требует диалог. Например так: <phrase id="0"> <text>...</text> <next>1</next> <next>2</next> </phrase> <phrase id="1"> <text>...</text> <next>11</next> <next>12</next> </phrase> <phrase id="11"> <text>...</text> <next>1</next> - Это возврат к фразе №1 (зацикливание) <next>111</next> </phrase> Практика
Добавим в диалог с Сидоровичем ветку своего собственного изготовления.Например такую: Меченый: Сидрыч, а чего это у тебя зеленые человечки, что по столу бегают, такие худые? Сидорович: Чего?! Меченый: Ты их совсем, совсем не кормишь? Сидорович: В следующий раз, как пойдешь в зону, бери-ка вместо водяры побольше антирада. А то мало что таким перегаром дышишь, уже до зеленых человечков долечился... Шутник.
Для этого:
1) В файле gamedata/config/gameplay/character_desc_escape.xml в конце списка веток для trader припишем свою ветку с произвольным названием. Это будет, например, <actor_dialog>escape_trader_letat_gusi</actor_dialog>.
Т.е у нас получится так: <specific_character id="escape_trader" no_random = "1"> ... <start_dialog>escape_trader_start_dialog</start_dialog> <actor_dialog>escape_trader_talk_info</actor_dialog> <actor_dialog>escape_trader_jobs</actor_dialog> <actor_dialog>tm_trader_dialog</actor_dialog> <actor_dialog>tm_trader_reward</actor_dialog> <actor_dialog>escape_trader_done_blockpost_box</actor_dialog> <actor_dialog>escape_trader_letat_gusi</actor_dialog> </specific_character> …
Записываем изменения, с этим файлом пока всё.
2) Теперь берем файл gamedata/config/gameplay/dialogs_escape.xml
Диалогу: Меченый: Сидрыч а почему это у тебя зеленые человечки, что по столу бегают, такие худые? Сидорович: Чего?!! Меченый: Ты их совсем, совсем не кормишь? Сидорович: В следующий раз, как пойдешь в зону, бери-ка вместо водяры побольше антирада. А то мало что таким перегаром дышишь, уже до зеленых человечков долечился... Шутник.
Условия наличия ветки в диалоге можно взять из ветки <dialog id="escape_trader_talk_info">.
Т.е берем условия <precondition>escape_dialog.trader_has_talk_info_wr</precondition> и <has_info>tutorial_end</has_info>. Можно было, конечно, прописать в скрипте еще одно условие для ветки, чтобы она появилась только один раз, а потом больше не возникала. Но об этом как-нибудь позже.
В итоге у нас получилась такая структура: <dialog id="escape_trader_letat_gusi"> <precondition>escape_dialog.trader_has_talk_info_wr</precondition> <has_info>tutorial_end</has_info> <phrase_list> <phrase id="0"> <text>escape_trader_letat_gusi_0</text> <next>1</next> </phrase> <phrase id="1"> <text>escape_trader_letat_gusi_1</text> <next>2</next> </phrase> <phrase id="2"> <text> escape_trader_letat_gusi_2</text> <next>3</next> </phrase> <phrase id="3"> <text> escape_trader_letat_gusi_3</text> </phrase> </phrase_list> </dialog>
Её нужно вставить в любом месте между 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="escape_trader_letat_gusi_0"> <text>Сидрыч, а чего это у тебя зеленые человечки, что по столу бегают, такие худые?</text> </string> <string id="escape_trader_letat_gusi_1"> <text>Чего?!</text> </string> <string id="escape_trader_letat_gusi_2"> <text>Ты их совсем, совсем не кормишь?</text> </string> <string id="escape_trader_letat_gusi_3"> <text>В следующий раз, как пойдешь в зону, бери-ка вместо водяры побольше антирада. А то мало что таким перегаром дышишь, уже до зеленых человечков долечился... Шутник.</text> </string>
В любом месте между уже существующими string id. После сохранения изменений, у нас все готово. Можно загружать игру и смотреть что получилось. Дополнительно
Внимание! После патча 1.002 данный урок перестал работать. Причина - со вторым патчем несовместимо это условие: <precondition>escape_dialog.trader_has_talk_info_wr</precondition>
Дело в том, что во втором патче из файла escape_dialog.script была удалена функция: function trader_has_talk_info_wr( trader, actor ) return true end
Можно либо записать эту функцию обратно в escape_dialog.script, либо использовать другие более-менее подходящие условия, например: <precondition>escape_dialog.trader_alredy_give_job</precondition>