<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="http://stalkerin.gameru.net/wiki/skins/common/feed.css?303"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
		<id>http://stalkerin.gameru.net/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Kamikazze</id>
		<title>S.T.A.L.K.E.R. Inside Wiki - Вклад участника [ru]</title>
		<link rel="self" type="application/atom+xml" href="http://stalkerin.gameru.net/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Kamikazze"/>
		<link rel="alternate" type="text/html" href="http://stalkerin.gameru.net/wiki/index.php?title=%D0%A1%D0%BB%D1%83%D0%B6%D0%B5%D0%B1%D0%BD%D0%B0%D1%8F:%D0%92%D0%BA%D0%BB%D0%B0%D0%B4/Kamikazze"/>
		<updated>2026-04-30T04:39:34Z</updated>
		<subtitle>Вклад участника</subtitle>
		<generator>MediaWiki 1.22.6</generator>

	<entry>
		<id>http://stalkerin.gameru.net/wiki/index.php?title=%D0%9F%D0%BE%D0%B4%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BB%D0%BE%D0%BA%D0%B0%D1%86%D0%B8%D0%B9_%D0%BA_%D0%BC%D0%BE%D0%B4%D1%83</id>
		<title>Подключение локаций к моду</title>
		<link rel="alternate" type="text/html" href="http://stalkerin.gameru.net/wiki/index.php?title=%D0%9F%D0%BE%D0%B4%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BB%D0%BE%D0%BA%D0%B0%D1%86%D0%B8%D0%B9_%D0%BA_%D0%BC%D0%BE%D0%B4%D1%83"/>
				<updated>2012-07-16T07:54:44Z</updated>
		
		<summary type="html">&lt;p&gt;Kamikazze: Новая страница: «Category:SDK  = Предварительная настройка конфигов = Самое главное - правильно настроенные кон...»&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:SDK]]&lt;br /&gt;
&lt;br /&gt;
= Предварительная настройка конфигов =&lt;br /&gt;
Самое главное - правильно настроенные конфиги ЗАРАНЕЕ, маппером при сборке локации. Что нужно сделать с конфигами:&lt;br /&gt;
&lt;br /&gt;
''game_graphs.ltx'' - добавить в конец секции ''[location_0]'' новую локацию, аналогично тем что там уже прописаны. У меня получилось вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;[location_0]&lt;br /&gt;
000 = &amp;quot;...&amp;quot;&lt;br /&gt;
001 = &amp;quot;эскейп&amp;quot;&lt;br /&gt;
002 = &amp;quot;свалка&amp;quot;&lt;br /&gt;
003 = &amp;quot;агропром&amp;quot;&lt;br /&gt;
004 = &amp;quot;агропром-подземка&amp;quot;&lt;br /&gt;
005 = &amp;quot;темная долина&amp;quot;&lt;br /&gt;
006 = &amp;quot;лаборатория X-18&amp;quot;&lt;br /&gt;
008 = &amp;quot;Бар&amp;quot;&lt;br /&gt;
009 = &amp;quot;Росток&amp;quot;&lt;br /&gt;
010 = &amp;quot;Янтарь&amp;quot;&lt;br /&gt;
011 = &amp;quot;лаборатория X-16&amp;quot;&lt;br /&gt;
012 = &amp;quot;Милитари&amp;quot;&lt;br /&gt;
013 = &amp;quot;Мертвый город&amp;quot;&lt;br /&gt;
014 = &amp;quot;Радар&amp;quot;&lt;br /&gt;
015 = &amp;quot;Радар бункер&amp;quot;&lt;br /&gt;
016 = &amp;quot;Припять&amp;quot;&lt;br /&gt;
017 = &amp;quot;станция АЭС&amp;quot;&lt;br /&gt;
018 = &amp;quot;ЧАЭС-2&amp;quot;&lt;br /&gt;
019 = &amp;quot;бункер выжигателя&amp;quot;&lt;br /&gt;
021 = &amp;quot;даркскейп&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Главное - чтобы локация '''НЕ ВКЛИНИЛАСЬ''' в сетку, надо ей дать новый id - тут у нас он 21. Запомним его, он должен совпадать во всех конфигах.&lt;br /&gt;
&lt;br /&gt;
''game_levels.ltx'' - добавить в конец секции ''[levels]'', и создать новую подсекцию локации с тем же именем. У меня получилась level21. Создаём для неё в конце конфига пропись:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;[level21]&lt;br /&gt;
name = k01_darkscape_ogse -- имя локации - задаётся по имени папки&lt;br /&gt;
caption = &amp;quot;k01_darkscape_ogse&amp;quot; -- имя на карте, можно потом в строки добавить для подстановки&lt;br /&gt;
offset = 3000.0, 1000.0, 0.0 -- смещение (на глобальной картие видимо)&lt;br /&gt;
id = 21 -- новый id, должен быть равен указанному в game_graphs.ltx, и быть самым большим, иначе локация вклинится в середину геймграфа&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
''game_maps_single.ltx'' - дописать в конец ''[level_maps_single]'' текстовое имя локации - ''k01_darkscape_ogse'', затем создать новую подсекцию локации - ''[k01_darkscape_ogse]''. Заполнить параметры:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;[k01_darkscape_ogse]&lt;br /&gt;
texture = map\map_darkscape&lt;br /&gt;
bound_rect = -702.00,-704.090, 708.500,716.7780&lt;br /&gt;
global_rect = 701.0, 2072.0, 1020.0,2393.0 ;касается только карты в КПК&lt;br /&gt;
;1 значение - ширина текстуры (чем значение больше, тем карта уже. правый край неподвижен)&lt;br /&gt;
;2 значение - длина текстуры (чем значение больше, тем карта короче. нижний край неподвижен)&lt;br /&gt;
;3 значение - ширина текстуры (чем значение больше, тем карта шире. левый край неподвижен)&lt;br /&gt;
;4 значение - длина текстуры (чем значение больше, тем карта длиннее. верхний край неподвижен)&lt;br /&gt;
weathers = default&lt;br /&gt;
;music_tracks = l04_darkvalley_musics&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Компиляция локации =&lt;br /&gt;
Если раньше геометрию уже компилировали, можно сделать на драфте, потом обсчитать только сетку, затем забросить её файлы *.ai, *.gct в папку уже готовой карты).&lt;br /&gt;
Убедитесь что на локации '''НЕТ''' точки спавна актора (она должна быть только одна на игру), а так же что локация равномерно покрыта графпоинтами. Кроме того&lt;br /&gt;
можете сразу сделать графпоинты переходов, чтобы потом не выделять под это один из свободных графпоинтов.&lt;br /&gt;
&lt;br /&gt;
Как положено прогоняем все батники aiwrapper - строим сетку, левелграф, геймграф, аллспавн.&lt;br /&gt;
&lt;br /&gt;
= Сведение единого спавна и графа =&lt;br /&gt;
&lt;br /&gt;
Нам понадобятся файлы:&lt;br /&gt;
&lt;br /&gt;
*Старый геймграф мода&lt;br /&gt;
*Геймграф сформированный при компиляции локации&lt;br /&gt;
*Старый аллспавн мода&lt;br /&gt;
*Аллспавн сформированный при компиляции локации&lt;br /&gt;
*Файлы level.ai и level.gct ВСЕХ локаций, которые уже есть в моде - если новых нету то все оригинала&lt;br /&gt;
*Папка с полностью готовой локацией&lt;br /&gt;
*Утилиты ACDC, gg_cdc_x, game.graph_recompiler&lt;br /&gt;
&lt;br /&gt;
Сначала надо выяснить, правильно ли у нас по геймвертексам села локация. Для этого кидаем геймграф сформированный при компиляции локации в папку с ACDC и прогоняем команду&lt;br /&gt;
&lt;br /&gt;
''ggtool.pl game.graph''&lt;br /&gt;
&lt;br /&gt;
Утилита подумает немного и выдаст вам табличку всех переходов и геймвертексов локаций. Нас интересует список вертексов:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code perl&amp;gt;l01_escape =&amp;gt; 0&lt;br /&gt;
l02_garbage =&amp;gt; 252&lt;br /&gt;
l03_agroprom =&amp;gt; 416&lt;br /&gt;
l03u_agr_underground =&amp;gt; 703&lt;br /&gt;
l04_darkvalley =&amp;gt; 811&lt;br /&gt;
l04u_labx18 =&amp;gt; 1109&lt;br /&gt;
l05_bar =&amp;gt; 1168&lt;br /&gt;
l06_rostok =&amp;gt; 1308&lt;br /&gt;
l08_yantar =&amp;gt; 1438&lt;br /&gt;
l08u_brainlab =&amp;gt; 1529&lt;br /&gt;
l07_military =&amp;gt; 1545&lt;br /&gt;
l10_radar =&amp;gt; 1862&lt;br /&gt;
l11_pripyat =&amp;gt; 2117&lt;br /&gt;
l12_stancia =&amp;gt; 2273&lt;br /&gt;
l12u_sarcofag =&amp;gt; 2402&lt;br /&gt;
l12u_control_monolith =&amp;gt; 2467&lt;br /&gt;
l12_stancia_2 =&amp;gt; 2517&lt;br /&gt;
l10u_bunker =&amp;gt; 2661&lt;br /&gt;
k01_darkscape_ogse =&amp;gt; 2792&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Обратите внимание - новая локация должна быть САМОЙ последней, и за ней не должно больше идти никаких локаций. Запишите себе её стартовый вертекс - в данном случае 2792. Если локация вклинилась в середину списка значит вы ей дали не самый последний Id - это не годится, так как переколбасит весь игровой граф, спавн и вы вручную замордуетесь это сводить. Поэтому возвращаемся к шагу 1. Если же локация как положено в конце, всё отлично, можно работать дальше.&lt;br /&gt;
&lt;br /&gt;
Теперь надо поправить АСДС - открываем acdc.pl, находим по тексту &amp;quot;constant levels_inf&amp;quot; структуру с разбиением локаций по геймграфам. Убеждаемся что границы локаций совпадают с теми что у нас получились при проверке графа, удаляем оттуда лишние и вписываем в самый верх нашу локацию. Получится примерно вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code perl&amp;gt;use constant levels_info =&amp;gt; (&lt;br /&gt;
{ gvid0 =&amp;gt;3700, },&lt;br /&gt;
{ gvid0 =&amp;gt; 2792, name =&amp;gt; 'k01_darkscape_ogse' },&lt;br /&gt;
{ gvid0 =&amp;gt; 2661, name =&amp;gt; 'l10u_bunker' },&lt;br /&gt;
{ gvid0 =&amp;gt; 2517, name =&amp;gt; 'l12_stancia_2' },&lt;br /&gt;
{ gvid0 =&amp;gt; 2467, name =&amp;gt; 'l12u_control_monolith' },&lt;br /&gt;
{ gvid0 =&amp;gt; 2402, name =&amp;gt; 'l12u_sarcofag' },&lt;br /&gt;
{ gvid0 =&amp;gt; 2273, name =&amp;gt; 'l12_stancia' },&lt;br /&gt;
{ gvid0 =&amp;gt; 2117, name =&amp;gt; 'l11_pripyat' },&lt;br /&gt;
{ gvid0 =&amp;gt; 1862, name =&amp;gt; 'l10_radar' },&lt;br /&gt;
{ gvid0 =&amp;gt; 1545, name =&amp;gt; 'l07_military' },&lt;br /&gt;
{ gvid0 =&amp;gt; 1529, name =&amp;gt; 'l08u_brainlab' },&lt;br /&gt;
{ gvid0 =&amp;gt; 1438, name =&amp;gt; 'l08_yantar' },&lt;br /&gt;
{ gvid0 =&amp;gt; 1308, name =&amp;gt; 'l06_rostok' },&lt;br /&gt;
{ gvid0 =&amp;gt; 1168, name =&amp;gt; 'l05_bar' },&lt;br /&gt;
{ gvid0 =&amp;gt; 1109, name =&amp;gt; 'l04u_labx18' },&lt;br /&gt;
{ gvid0 =&amp;gt; 811, name =&amp;gt; 'l04_darkvalley' },&lt;br /&gt;
{ gvid0 =&amp;gt; 703, name =&amp;gt; 'l03u_agr_underground' },&lt;br /&gt;
{ gvid0 =&amp;gt; 416, name =&amp;gt; 'l03_agroprom' },&lt;br /&gt;
{ gvid0 =&amp;gt; 252, name =&amp;gt; 'l02_garbage' },&lt;br /&gt;
{ gvid0 =&amp;gt; 0, name =&amp;gt; 'l01_escape' },&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохраняем ''acdc.pl'', она нам чуть позже понадобится для спавна. Теперь надо свести единый граф с нашей новой локацией. Для этого берём ''game.graph_recompiler''. Копируем в его папку в папку ''levels'' целиком нашу папку с новой локацией, а так же создаём папки всех локаций что уже есть в игре, и копируем туда файлы ''level.ai'' и ''level.gct'' ВСЕХ локаций, которые уже есть. В корень копируем старый граф мода, и туда же рядом - новый граф, переименовав его в ''game2.graph''&lt;br /&gt;
Теперь правим батник для подключения локации - в нём надо указать какие локации брать из первого графа, и какие из второго, а так же типы графов. У меня получился вот такой батник:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code bash&amp;gt;ggrc.pl -g1 game.graph,soc -l1 l01_escape,l02_garbage,l03_agroprom,l03u_agr_underground,l04_darkvalley,l04u_labx18,l05_bar,l06_rostok,l08_yantar,l08u_brainlab,l07_military,l10_radar,l11_pripyat,l12_stancia,l12u_sarcofag,l12u_control_monolith,l12_stancia_2,l10u_bunker -g2 game2.graph,soc -l2 k01_darkscape_ogse&lt;br /&gt;
pause&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Запускаем, ждём пока обработается. Если всё прокатило, тогда мы получим новый файл ''game.graph.new'' - в который вписана локация. Нужно тут же его утащить и распаковать с помощью ''gg_cdc_x'' - проверить ''game.graph.info.cross_edges.ltx'' - все ли переходы остались на месте (должны быть все), а так же как вписалась локация - в ''game.graph.levels.ltx'' наша локация должна быть конечной и с самым большим ''level_id''. В ''game.graph.levels_info.ltx'' она тоже должна быть последней, там же можно увидеть её минимальный и максимальный вертексы. Минимальный вертекс должен быть тот же что был когда проверяли граф, сделанный компилятором - у нас это был вертекс 2792.&lt;br /&gt;
&lt;br /&gt;
Теперь необходимо проверить, не сдвинулись ли идентификаторы локаций в графе. Почти наверняка - сдвинулись, так как в оригинале там есть груда ссылок на тестовые локации разработчиков. Проверить это можно в ''game.graph.levels.ltx'', сравнив идентификаторы локаций там с идентификаторами в конфигах ''game_graphs.ltx'' и ''game_levels.ltx'' - если они отличаются, то обязательно поправляем идентификаторы в конфигах на те, что в геймграфе!&lt;br /&gt;
&lt;br /&gt;
По добавлению переходов - чтобы их добавить вам желательно было заранее создать кроссграфы на локации. Если вы это сделали, тогда вам нужно сделать вот что - распаковать геймграф, сформированный при компиляции локации. Найти там в конфиге графов новой локации - ''game.graph.edges.&amp;lt;id_вашей_локации&amp;gt;'' сделанные вами переходы - они будут помечены как ''&amp;quot;crosslevel edge&amp;quot;''. Ещё их легко найти по параметру ''target_level_id'' - он у них отличается от исходного для этой локации. После того как нашли - скопируйте их прописи в отдельный файл. Затем откройте конфиг новой локации в сведённом вместе графе. При сведении переходы прибились, поэтому надо теперь в новом конфиге найти те же самые графпоинты на которых висели переходы. Ищите их по параметру ''parent_level_vertex_id''. Как только найдете - перед тем как править - скопируйте в отдельный файл. Затем замените им параметры ''target_level_id'', ''target_level_vertex_id'' и ''game_vertex_id'' на те, которые были у сделанных вами переходов. Не забудьте выставить дистанцию - это будет расстояние перехода. Сохранитесь. Теперь откройте конфиги ''game.graph.edges.&amp;lt;id_вашей_локации&amp;gt;'' тех локаций на которые создали переходы - нужно же сделать ещё переходы в обратную сторону. Открыв, найдите там те графы, с которыми только что сделали соединения с новой локации. Ищите по параметру ''parent_level_vertex_id'' - у наиболее подходящего графпоинта он будет равен ''target_level_vertex_id'' перехода с новой локации. После того как нашли, откройте файл в который сохранили не изменённые ещё графпоинты новой локации, на которые были сделаны переходы. Теперь аналогично - затем замените им параметры ''target_level_id'', ''target_level_vertex_id'' и ''game_vertex_id'' на те, которые были у графпоинтов новой локации до изменения. Так мы добьемся того, чтобы входы и выходы с локации были примерно на одном месте. Не забудьте выставить дистанцию! Теперь всё сохраняем, и компилируем граф. Если всё сделано правильно граф сохранится, и при проверке его ''ggtool.pl'' вы увидите в списке сделанные вами переходы.&lt;br /&gt;
&lt;br /&gt;
Если всё нормально, то можно скопировать граф обратно. Теперь важно - нужно так же не забыть скопировать в итоговый пакет файлы, которые при слиянии были поправлены в папке нашей локации. Это:&lt;br /&gt;
&lt;br /&gt;
level.gct&lt;br /&gt;
level.graph&lt;br /&gt;
level.ai&lt;br /&gt;
&lt;br /&gt;
А так же проверить - были ли внесены изменения в файлы ''level.ai'' и ''level.gct'' уже существующих локаций. Если да, то изменённые файлы тоже надо будет скопировать в итоговый пакет. Теперь займёмся спавном. Берём наш поправленный АСДС и распаковываем спавн, полученный при компиляции локации. Забираем оттуда конфиг нашей новой локации. Затем распаковываем аллспавн мода и кидаем в его папку конфиг нашей новой локации. Прописываем его в ''all.ltx'' аллспавна мода, и увеличиваем в ''all.ltx'' индекс локаций ''level_count'' на единицу. Больше в ''all.ltx'' ничего не трогаем. Далее проверяем число объектов в нашем спавне, и перенумеровываем объекты в конфиге нашей новой локации так, чтобы они были добавлены в конец нумерации. Проверяем прописанные в конфиге нашей новой локации геймвертексы объектов - они должны лежать в пределах вертексов, которые мы получили при проверке геймграфа в файле ''game.graph.levels_info.ltx''&lt;br /&gt;
&lt;br /&gt;
Если всё получилось, то добавляем в аллспавн точки переходов для ГГ и запаковываем спавн обратно. Он готов к работе, можно его скопировать в итоговый пакет.&lt;br /&gt;
&lt;br /&gt;
= Cобираем общий пакет обновления =&lt;br /&gt;
&lt;br /&gt;
В пакет у нас попадут:&lt;br /&gt;
&lt;br /&gt;
*Конфиги&lt;br /&gt;
&lt;br /&gt;
game_graphs.ltx&lt;br /&gt;
game_levels.ltx&lt;br /&gt;
game_maps_single.ltx&lt;br /&gt;
&lt;br /&gt;
*Папка с самой локацией, при этом в ней должны быть обновлены файлы&lt;br /&gt;
&lt;br /&gt;
level.gct&lt;br /&gt;
level.graph&lt;br /&gt;
level.ai&lt;br /&gt;
&lt;br /&gt;
- теми, которые получили при слиянии графа&lt;br /&gt;
&lt;br /&gt;
*Изменённые при слиянии графа файлы level.ai и level.gct уже существующих локаций (если локация добавлена нормально, то изменённых может и не быть вообще)&lt;br /&gt;
&lt;br /&gt;
*Слитый game.graph&lt;br /&gt;
&lt;br /&gt;
*Слитый all.spawn&lt;br /&gt;
&lt;br /&gt;
*Необходимые для локации шейдеры&lt;br /&gt;
&lt;br /&gt;
*Необходимые для локации текстуры&lt;br /&gt;
&lt;br /&gt;
= Если нам нужно заменить только геометрию локации не трогая сетку =&lt;br /&gt;
&lt;br /&gt;
Тогда надо заменить в папке локации только вот эти файлы:&lt;br /&gt;
 &lt;br /&gt;
*level&lt;br /&gt;
*level.cform&lt;br /&gt;
*build_details.dds&lt;br /&gt;
*level_lods.dds&lt;br /&gt;
*level_lods_nm.dds&lt;br /&gt;
*level.details&lt;br /&gt;
*level.game&lt;br /&gt;
*level.geom&lt;br /&gt;
*level.geomx&lt;br /&gt;
*build.prj&lt;br /&gt;
*level.wallmarks&lt;br /&gt;
&lt;br /&gt;
--[[Участник:Kamikazze|KamikaZze (OGSE Team)]] 11:46, 16 июля 2012 (UTC)&lt;br /&gt;
&lt;br /&gt;
= Авторы =&lt;br /&gt;
Статья создана: [[Участник:Kamikazze|Kamikazze]]&lt;/div&gt;</summary>
		<author><name>Kamikazze</name></author>	</entry>

	<entry>
		<id>http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2_(%D1%87%D0%B0%D1%81%D1%82%D1%8C_2)</id>
		<title>Как писать скрипты, не приводящие к вылетам и бою сейвов (часть 2)</title>
		<link rel="alternate" type="text/html" href="http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2_(%D1%87%D0%B0%D1%81%D1%82%D1%8C_2)"/>
				<updated>2011-03-31T17:52:17Z</updated>
		
		<summary type="html">&lt;p&gt;Kamikazze: Причина зависаний алайфа из-за ошибок в конфиге торговли&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;----&lt;br /&gt;
&lt;br /&gt;
[[Как писать скрипты, не приводящие к вылетам и бою сейвов|Начало в первой части статьи]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Как безопасно использовать коллбэки и таймерные события =&lt;br /&gt;
&lt;br /&gt;
В скриптовом движке есть удобный способ реакции на события - использование коллбэков - процедурных вызовов, привязанных к определённым событиям в жизни игрового объекта - получению повреждений, спавну, смерти и т.д. и т.п. Это hit_callback, death_callback из xr_motivator и многие другие... Все моддеры очень широко и совершенно спокойно пользуются ими, совершенно забывая при этом о таком важном факте, что это - обработки реального времени, как и таймерные события. Что это значит? А собственно вот что... Возьмём для примера коллбэк смерти неписей, мою головную боль последнего времени:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;xr_motivator.script -&lt;br /&gt;
function motivator_binder:death_callback(victim, who)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это функция обрабатывает смерть неписей. Когда вызывается этот коллбек он начинает по очереди вызывать внутренние функции, расположенные в других модулях. Они разрегистрируют умершего непися в гулагах, спавнят в него лут, обновляют статистику, отключают его от доп. схем логики и т.д.&lt;br /&gt;
&lt;br /&gt;
Так вот, обычно, когда в функциях возникает конфликт параметров, бесконечный цикл, попытка индексации nil и т.д., функция вылетает со стандартным логом. '''Но только не в случае, когда она находится внутри коллбэка. Если функция внутри него, то в случае возникновении в ней любой нештатной ситуации, коллбэк наглухо виснет.''' Это происходит из-за того, что функции вызываются строго друг за другом, и каждая из них вызывается только тогда, когда её предшественница вернула управление коллбэку. В случае с death_callback опознать такого непися очень просто - в его трупе окажется фонарик, КПК и возможно ещё немного разных &amp;quot;мусорных&amp;quot; вещей, что говорит о том, что обработка его смерти повисла не дойдя даже до спавна лута. В подобной ситуации можно быть на 100% уверенным, что труп этот не был корректно разрегистрирован, и игра всё ещё считает его живым неписем. Кроме того, зависший коллбэк не освобождает стек (а он у Луа-подсистемы единый на все скрипты), что '''в итоге приводит к вылетам игры с переполнением памяти (вот она, реальная причина этих &amp;quot;родных&amp;quot; вылетов).''' Но было бы слишком хорошо, если бы всё ограничивалось этим... однако тут всё намного хуже... такие &amp;quot;зависшие&amp;quot; коллбэки, особенно если их произошло несколько подряд, очень серьёзно влияют на работу а-лайфа. В лучшем случае они, забивая, стек, мешают нормально работать схемам логики, в худшем вызывают зависания самого  а-лайфа (этот эффект, кстати, производят и сами трупы таких неписей, так как они, как мы помним, не разрегистрировались корректно). '''Основной итог таких событий - бой сейвов, сделанных после возникновения таких ситуаций.''' Если повис один коллбэк, то такие сейвы ещё через раз загружаются, если же несколько, и остановилась работа а-лайфа - всё, сейвы бьются наглухо и реанимации не подлежат.&lt;br /&gt;
&lt;br /&gt;
Поэтому, чтобы избежать такого развития событий, каждый раз, когда вы вносите в коллбэк новую функцию - проверьте её самым тщательным образом. Она не должна содержать никаких рекурсивных циклов, в ней обязательно должны быть проверки на валидность обрабатываемых объектов и значений, и обязательно должна быть обработка нештатных ситуаций - т.е. функция должна обязательно, в абсолютно любой ситуации вернуть управление коллбэку, так или иначе. В самом наихудшем случае - делайте как делали разработчики игры - вставляйте принудительный вылет на рабстол функцией abort - она позволяет передавать отладочное сообщение, и это всяко лучше чем незаметный бой сейвов. Если обработка оборвалась в самом начале, а ф-ция обязательно должна вернуть значение - заведите ей &amp;quot;безопасное&amp;quot; возвращаемое значение по-умолчанию, которое она будет выдавать, если всё пошло плохо. И никогда не пренебрегайте пошаговой отладкой коллбэков с выводом в лог, особенно когда пишете схемы логики - это критически важно для стабильности вашего мода.&lt;br /&gt;
&lt;br /&gt;
== Основные внутриигровые признаки зависания коллбэков типа hit_callback, death_callback ==&lt;br /&gt;
&lt;br /&gt;
1. В трупах попадаются фонарики, КПК, разный мусор и общий лут слишком богат.&lt;br /&gt;
&lt;br /&gt;
2. Частые вылеты во время интенсивных боёв с логами типа&lt;br /&gt;
    Sheduler tried to update object...&lt;br /&gt;
    smart_terrain:1145(1146)&lt;br /&gt;
    LUA: out of memory&lt;br /&gt;
    любой_модуль_логики:любая_cтрока - stack overflow&lt;br /&gt;
&lt;br /&gt;
3. Частые &amp;quot;родные&amp;quot; вылеты в момент смерти непися или попадания по нему&lt;br /&gt;
&lt;br /&gt;
4. Произвольно бьются сейвы во время сражений, выброса и других насыщенных действиями событий&lt;br /&gt;
&lt;br /&gt;
= Использование защищённого кода в LUA =&lt;br /&gt;
&lt;br /&gt;
Периодически случаются такие ситуации, когда мы можем получить вылет при проверке аргумента, и не можем его адекватно заизолировать с помощью предварительной проверки на валидность значения. Вот простой пример: когда я отлаживал '''death_callback''' неписей, я периодически сталкивался с тем, что обращение к методу '''smart_terrain_id()''' при смерти непися иногда вызывало вылет &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;smart_terrain:1143 &amp;quot;attempt to index a nil value&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
- хотя это свойство является родным методом объекта, и отсутствовать напрочь никак не может. В итоге я пришёл к выводу, что его просто иногда не успевает отработать движок, так как вылет этот проявлялся в основном в интенсивных боях и совершенно произвольно.&lt;br /&gt;
&lt;br /&gt;
Вот код, в котором происходил вылет:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj     = sim:object( obj_id )&lt;br /&gt;
		local strn_id = obj:smart_terrain_id()  --- вылет происходит тут&lt;br /&gt;
&lt;br /&gt;
		if strn_id ~= 65535 then&lt;br /&gt;
			sim:object( strn_id ).gulag:clear_dead(obj_id)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это кусок родного кода из версии 1,0005 игры. Я долго пытался разными способами отсечь этот вылет, вводя предварительные проверки, однако это совершенно ничего не давало - вылет всё равно периодически случался, так как проверки эти сами его вызывали. Тогда я зарылся в документацию по Lua и обнаружил замечательную родную базовую функцию, введённую ещё с первых версий Lua, которой почему-то не пользовались ни разработчики игры, ни моддеры (хотя сама она в Lua сталкера присутствует, и работает отлично, без каких-либо нареканий). Вот она:&lt;br /&gt;
&lt;br /&gt;
'''pcall (f, arg1, ···)'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Вызывает функцию f с указанными через запятую аргументами в защищённом режиме. Это означает, что любая ошибка, даже критическая, внутри вызванной функции, не передаётся наружу - вызывавшей подсистеме. Вместо этого '''pcall''' перехватывает ошибку и возвращает код статуса. Первая возвращаемая переменная это сам код, (true или false) и если всё прошло хорошо, он равен true. В этом случае '''pcall''' сразу после статуса возвращает все результаты от работы защищённой им функции. Если же в защищённой функции произошла ошибка, то '''pcall''' вернёт false и затем сообщение об ошибке. (Обратите внимание, обработка ошибки присходит БЕЗ вылета! Вместо вылета вы получите вполне адекватную строку с ошибкой, которую можно вывести в лог для последующей обработки)&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Я настоятельно советую пользоваться этой функцией в случаях, когда из коллбэков вызываются сложные комплексные обработки, вроде обработки из менеджера вооружений AI-пака. Это позволяет предотвратить как вылеты, так и зависания обработок, и в итоге позволяет хорошо стабилизировать игру.&lt;br /&gt;
&lt;br /&gt;
Возвращаясь к нашим смарттеррейнам... вот как в итоге я подавил вылет типа smart_terrain:1143 с помощью pcall:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;--- эта функция пытается проверить св-во smart_terrain_id объекта. Именно её мы вызовем в защищённом режиме.&lt;br /&gt;
function prot_smt_td(obj)&lt;br /&gt;
	if IsStalker(obj) or IsMonster(obj) then&lt;br /&gt;
		return obj:smart_terrain_id()&lt;br /&gt;
	else	&lt;br /&gt;
		return 65535&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj = sim:object( obj_id )&lt;br /&gt;
		if obj then&lt;br /&gt;
			local strn_id = 65535  --- предварительно проинитим переменную, на &lt;br /&gt;
						--- случай если у нас prot_smt_td выдаст ошибку&lt;br /&gt;
			local result, smt_id = pcall(prot_smt_td,obj)	--- вызываем prot_smt_td в защищённом режиме &lt;br /&gt;
									--- и сразу присваиваем его вывод переменным&lt;br /&gt;
			if result then --- если pcall выдало true&lt;br /&gt;
				strn_id = smt_id  --- тогда применяем полученное значение&lt;br /&gt;
			end&lt;br /&gt;
			--- если же обработка выдаст ошибку, то strn_id останется неизменным...&lt;br /&gt;
			if strn_id ~= 65535 then&lt;br /&gt;
				sim:object(strn_id).gulag:clear_dead(obj_id)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В других местах это делается совершенно аналогично. Подробнее об этой и многих других функция для контроля кода, незаслуженно игнорируемых большинством моддеров, можно почитать тут: http://lua-users.org/wiki/FinalizedExceptions - на английском правда, но захотите - разберётесь, там всё просто. &lt;br /&gt;
&lt;br /&gt;
Настоятельно советую вам изучить работу кода в таких условиях и научиться его правильно применять - этим вы сильно облегчите жизнь как себе, так и тем, кто после вас будет рыться в вашем коде или импортировать его в свои разработки.&lt;br /&gt;
&lt;br /&gt;
= Скрытые критические проблемы в обработке вылетов игрой =&lt;br /&gt;
&lt;br /&gt;
Ведя на днях отладку, выяснил в чём проблема с периодическим боем сейвов и многими другими заморочками как в оригинале игры, так и во многих модах... дело, как выяснилось, далеко не всегда в кривых руках. Есть такая стандартная ф-ция '''abort''' - предназначенная для выкидывания из игры, если что-то пошло не так. И как оказалось, она срабатывает далеко не всегда. Выяснилось это следующим образом:&lt;br /&gt;
&lt;br /&gt;
В одном из логов нашего бета-тестера я увидел '''стандартное сообщение о вылете внутри рабочего лога'''... да-да, то самое которое '''FATAL ERROR''' и дальше по тексту. При этом игра у него НЕ вылетала, это сообщение об ошибке мы обнаружили позже, по случайности. Я заподозрил, что что-то не в порядке, и вставил внутрь этой ф-ции контрольную метку, кидавшую в консоль сообщение, в котором содержался паттерн сообщения об ошибке и само сообщение. Так вот, оказалось, что эта самая функция '''abort''' вызывается в игре с завидным постоянством (вы удивитесь насколько часто), когда возникают исключения в схемах логики, звука и т.д., но игра от этого вылетает на рабочий стол '''максимум только 3 раза из 10 вызовов'''. Вылет НЕ происходит обычно, когда функции передан паттерн ошибки, а остальные параметры пустые, такое бывает, и частенько. И если не сделать внутри этой функции особой метки для вывода в лог, как сделал это я, её вызовы проходят совершенно незаметно, и '''игра после критических ошибок продолжается как ни в чём ни бывало.''' А приводит это вот к чему... Внутри '''xr_logic''' в процедуре записи пстора (хранилища логики и флагов) неписей есть вызовы этого самого аборта в случае если на запись в пстор передана некорректная величина. Ну а так как аборт периодически вообще не срабатывает, то часто попадается ситуация, что неписям в пстор пишется полный ахтунг: куски кода из ОЗУ, всякая муть из лтх-ов, куски аллспавна, всё что угодно. Происходит это оттого, что кодер, писавший эту функцию ('''xr_logic.pstor_store(obj, varname, val)'''), явно и думать не думал что '''abort''' может не сработать. У него запись в пстор стояла после проверки, а не внутри неё (very bad idea), и если abort не срабатывал, игра писала в пстор мусор совершенно спокойно и незаметно для игрока. Потом вся эта хрень попадала прямо в сейвы. Вот проблемный код для наглядности:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_store(obj, varname, val)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	if db.storage[npc_id].pstor == nil then&lt;br /&gt;
		db.storage[npc_id].pstor = {}&lt;br /&gt;
	end&lt;br /&gt;
	local tv = type(val)&lt;br /&gt;
	if not pstor_is_registered_type(tv) then&lt;br /&gt;
		abort(&amp;quot;xr_logic: pstor_store: not registered type '%s' encountered&amp;quot;, tv) --- вот тут мы должны если что вылететь&lt;br /&gt;
	end&lt;br /&gt;
	db.storage[npc_id].pstor[varname] = val -- а если не вылетели, всё, получим запись в пстор левой мути&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Разумеется игра этим пстором в итоге давится, и сейвы сделанные после такой милой записи практически лопаются. Результат - &amp;quot;битые&amp;quot; (на самом деле подлежат реанимации) сейвы. Происходит это потому, что игра из сейва грузит неписям псторы сплошным чтением по словам, пока они не закончатся. В случае же если в псторе обнаруживается записанный ранее мусор, то обработка либо вылетает сразу, либо наглухо виснет, пытаясь запихать эдак с миллион слов в пстор особо отличившегося непися. Как вам например непись с размером пстора в 1697451 слова? В результате попытки его обработать игра просто на стадии синхронизации выжрала всю доступную ОЗУ и повисла. &lt;br /&gt;
&lt;br /&gt;
Решение этой проблемы оказалось достатоно простым: во-первых я предположил максимальный размер полезной части пстора неписей в 20 слов r_u32() (пока ориентировочно, я ещё уточняю эту величину), и соответственно сделал остановку цикла загрузки пстора для неписей через 20 итераций. Там же, где в цикле стояла проверка на валидность записываемых данных (кстати тоже с вылетом в случае провала проверки), я сделал так, что если параметр не относится к валидному типу данных, то запись параметра в пстор не производится совсем. Это необходимо для того, чтобы если вдруг в сейве обнаружится мусор, то он был бы просто отброшен обработкой. Практика показала, что в итоге такие неписи вполне адекватны и в дальнейшем никаких проблем не вызывают, так как начало их пстора, с нормальными данными, обычно не повреждается - мусор дописывается после них, а не вместо них. &lt;br /&gt;
&lt;br /&gt;
Ну и во-вторых модифицировал запись параметров в пстор, просто убрав запись под основание if-else так, чтобы если параметр неверен, он не записывался совсем. Теперь кстати, очень интересно, сохранились ли те же заморочки с ф-цией '''abort''' в Чистом Небе, и если да, то останутся ли в Зове Припяти?&lt;br /&gt;
&lt;br /&gt;
------------&lt;br /&gt;
&lt;br /&gt;
== Необходимые для стабилизации игры правки в модулях ==&lt;br /&gt;
&lt;br /&gt;
'''Эта правка предотвращает запись в пстор если не сработал аборт:'''&lt;br /&gt;
&lt;br /&gt;
''xr_logic.script''&lt;br /&gt;
&lt;br /&gt;
'''Было:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_store(obj, varname, val)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	if db.storage[npc_id].pstor == nil then&lt;br /&gt;
		db.storage[npc_id].pstor = {}&lt;br /&gt;
	end&lt;br /&gt;
	local tv = type(val)&lt;br /&gt;
	if not pstor_is_registered_type(tv) then&lt;br /&gt;
		abort(&amp;quot;xr_logic: pstor_store: not registered type '%s' encountered&amp;quot;, tv)&lt;br /&gt;
	end&lt;br /&gt;
	db.storage[npc_id].pstor[varname] = val&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Стало:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_store(obj, varname, val)&lt;br /&gt;
	if not obj then return end&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	if db.storage[npc_id].pstor == nil then&lt;br /&gt;
		db.storage[npc_id].pstor = {}&lt;br /&gt;
	end&lt;br /&gt;
	local tv = type(val)&lt;br /&gt;
	if not pstor_is_registered_type(tv) then&lt;br /&gt;
		dgblog(&amp;quot;xr_logic: pstor_store: not registered type encountered - write in pstor_store cancelled&amp;quot;)&lt;br /&gt;
		-- abort убран, так как один хрен не работает. Пусть тогда хотя бы в лог что-то валится.&lt;br /&gt;
	else&lt;br /&gt;
		db.storage[npc_id].pstor[varname] = val&lt;br /&gt;
		-- вот так и только так. Если значение не валидно, ничего не происходит.&lt;br /&gt;
	end	&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''А эта правка выкинет из пстора весь мусор при загрузке сейва, если он как-то в него попал'''&lt;br /&gt;
&lt;br /&gt;
'''Было:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_load_all(obj, reader)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	local pstor = db.storage[npc_id].pstor&lt;br /&gt;
	if not pstor then&lt;br /&gt;
		pstor = {}&lt;br /&gt;
		db.storage[npc_id].pstor = pstor&lt;br /&gt;
	end&lt;br /&gt;
	local ctr = reader:r_u32()&lt;br /&gt;
	for i = 1, ctr do&lt;br /&gt;
		local varname = reader:r_stringZ()&lt;br /&gt;
		local tn = reader:r_u8()&lt;br /&gt;
		if tn == pstor_number then&lt;br /&gt;
			pstor[varname] = reader:r_float()&lt;br /&gt;
		elseif tn == pstor_string then&lt;br /&gt;
			pstor[varname] = reader:r_stringZ()&lt;br /&gt;
		elseif tn == pstor_boolean then&lt;br /&gt;
			pstor[varname] = reader:r_bool()&lt;br /&gt;
		else&lt;br /&gt;
			abort(&amp;quot;xr_logic: pstor_load_all: not registered type N %d encountered&amp;quot;, tn)&lt;br /&gt;
		end&lt;br /&gt;
		printf(&amp;quot;_bp: pstor_load_all: loaded [%s]='%s'&amp;quot;, varname, utils.to_str(pstor[varname]))&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Стало:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_load_all(obj, reader)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	local pstor = db.storage[npc_id].pstor&lt;br /&gt;
	if not pstor then&lt;br /&gt;
		pstor = {}&lt;br /&gt;
		db.storage[npc_id].pstor = pstor&lt;br /&gt;
	end&lt;br /&gt;
	local ctr = reader:r_u32()&lt;br /&gt;
	if tonumber(ctr) &amp;gt; 20 and tostring(obj:name()) ~= &amp;quot;single_player&amp;quot; and npc_id ~= db.actor:id() then&lt;br /&gt;
		-- максимум 20 итераций - это число ещё уточняется, возможно понадобится больше&lt;br /&gt;
                -- если у вас в пстор что-то свое пишется, ориентируйтесь на свои значения&lt;br /&gt;
		-- и обязательно убираем из проверки актора - у него очень толстый пстор, и к тому же&lt;br /&gt;
                -- если уж поврежденным будет его пстор, то тут точно уже ничего не поможет&lt;br /&gt;
		dgblog(&amp;quot;ОБНАРУЖЕН ОБЪЕКТ С ПОВРЕЖДЕННЫМ PSTOR: &amp;quot;..tostring(obj:name())..&lt;br /&gt;
&amp;quot; БУДЕТ ПРОИЗВЕДЕНА ПОПЫТКА ВОССТАНОВЛЕНИЯ&amp;quot;)&lt;br /&gt;
		ctr = 20 &lt;br /&gt;
	end&lt;br /&gt;
	for i = 1, ctr do&lt;br /&gt;
		local varname = reader:r_stringZ()&lt;br /&gt;
		local tn = reader:r_u8()&lt;br /&gt;
		if tn == pstor_number then&lt;br /&gt;
			pstor[varname] = reader:r_float()&lt;br /&gt;
		elseif tn == pstor_string then&lt;br /&gt;
			pstor[varname] = reader:r_stringZ()&lt;br /&gt;
		elseif tn == pstor_boolean then&lt;br /&gt;
			pstor[varname] = reader:r_bool()&lt;br /&gt;
		else&lt;br /&gt;
			-- не надо пытаться вылетать - просто не пишем поврежденные данные&lt;br /&gt;
			-- при этом обязательно удалять саму переменную - в результате записи&lt;br /&gt;
 			-- мусора в пстор одно только ее название может повесить загрузку&lt;br /&gt;
			pstor[varname] = nil&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Эти примеры приведены для чистой игры. Единственное в чем не уверен пока, это в том, что максимум полезного размера - 20 слов. Возможно нужно выделить больше, это надо будет проверить ещё экспериментальным путем...&lt;br /&gt;
&lt;br /&gt;
Кроме этого, советую ещё внутрь ф-ции abort вставить отладочные метки, чтобы точно знать когда она вызывалась. Настоятельно советую сделать это даже если вы матёрый моддер со стажем - гарантирую, будете неприятно удивлены.&lt;br /&gt;
&lt;br /&gt;
Я это сделал вот так:&lt;br /&gt;
&lt;br /&gt;
''_g.script''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- Крешнуть игру (после вывода сообщения об ошибке в лог)&lt;br /&gt;
function abort(fmt, msg)&lt;br /&gt;
	local message = tostring(msg)&lt;br /&gt;
	dbglog(&amp;quot;ERROR PATTERN: &amp;quot;..tostring(fmt))&lt;br /&gt;
	dbglog(&amp;quot;ERROR REASON: &amp;quot;..message)&lt;br /&gt;
	local reason = string.format(fmt, message)&lt;br /&gt;
	assert(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	printf(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	dbglog(&amp;quot;%s&amp;quot;, reason)&lt;br /&gt;
	printf(&amp;quot;%s&amp;quot;)&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
По поводу частичной неработоспособности ф-ции '''abort''' я беседовал с Колмогором, и он пришёл к выводу, что видимо вылет игры должен был бы производиться при обработке функции '''printf(&amp;quot;%s&amp;quot;)''' - ей тут передаётся заведомо отсутствующий оператор и она по уму должна бы сразу крашить игру. Однако в релизе функция printf фактически не работает(она реализована в _g.script через вырезанную функцию log). В результате игра не крашится. Но что поделать, в итоге мне пришлось модифицировать его таким образом, чтобы вылет при его срабатывании был гарантирован:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- Крешнуть игру (после вывода сообщения об ошибке в лог)&lt;br /&gt;
function abort(fmt, msg)&lt;br /&gt;
	local message = tostring(msg)&lt;br /&gt;
	dbglog(&amp;quot;ERROR PATTERN: &amp;quot;..tostring(fmt))&lt;br /&gt;
	dbglog(&amp;quot;ERROR REASON: &amp;quot;..message)&lt;br /&gt;
	local reason = string.format(fmt, message)&lt;br /&gt;
	assert(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	printf(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	dbglog(&amp;quot;%s&amp;quot;, reason)&lt;br /&gt;
	printf(&amp;quot;%s&amp;quot;)&lt;br /&gt;
	local crash&lt;br /&gt;
	local ooops = 1/crash&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Вылет происходит при попытке произвести арифметическую операцию с неинициализированной переменной crash.&lt;br /&gt;
&lt;br /&gt;
== Лечение зависаний алайфа при смерти персонажей ==&lt;br /&gt;
&lt;br /&gt;
Недавно, отлаживая проблемы с зависанием алайфа, мне удалось найти причину этого периодически во всех модах всплывающего сбоя, приводящего к порче сейвов и сильно мешающего нормально играть. Сбой этот возникает при смерти некоторых NPC, обычно квестовых. В частности в моём случае изолировать и отладить это зависание удалось на Юрике, новичке со Свалке, учавствующем в сцене с гоп-стопом. Причина оказалась в обработке посмертной отрегистрации NPC из гулагов, причём сбой там был настоящей матрёшкой, составной из нескольких частей. Правок в итоге было совсем немного, но чтобы сделать их мне пришлось несколько часов распутывать клубок из кросс-вызовов между скриптами smart_terrain и xr_gulag. Итак, начнём с самого начала. Работая над OGSE 069 и 0691 я периодически сталкивался с зависаниями и вылетами в посмертных обработках неписей. Один из таких вылетов - всем хорошо знакомый вылет:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;smart_terrain:1143 &amp;quot;attempt to index a nil value&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Происходящий в функции '''smart_terrain.on_death( obj_id )''' - я тогда его заблокировал вызовом его внутри безопасного кода функцией '''pcall''', однако, как теперь выяснилось, этого оказалось недостаточно - баг тут состоит из нескольких частей, и этот вылет указывает только на одну из них. Вот исходный код:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj     = sim:object( obj_id )&lt;br /&gt;
		local strn_id = obj:smart_terrain_id()  --- первый вылет/зависнаие алайфа происходит тут&lt;br /&gt;
&lt;br /&gt;
		if strn_id ~= 65535 then&lt;br /&gt;
			sim:object( strn_id ).gulag:clear_dead(obj_id) -- а вот в этой обработке происходит зависание алайфа. Она очень комплексная, и её сложно распутывать.&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Во-первых выяснилось, что изредка вызов '''obj:smart_terrain_id()''' вызывает зависание алайфа даже когда он производится изнутри защищённого кода. Тогда я решил избавиться от использования этой функции в данном месте совсем. После нескольких экспериментов выяснилось, что самым простым, быстрым и вылетобезопасным способом будет считать нетпакет существа в таблицу и выудить идентификатор смарттеррейна из неё. Для этого можно написать свою обработку, однако я, как весьма ленивый программист, не склонен изобретать велосипеды, поэтому я воспользовался уже проверенной у нас и активно используемой в OGSE библиотекой функций для работы с нетпакетами '''m_net_utils''' Артоса. Кроме того, я сразу сделал более безопасным вызов обработки на отрегистрацию в гулагах. Вот, собственно, что в итоге получилось:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj = sim:object( obj_id )&lt;br /&gt;
		if (obj and obj.smart_terrain_id) then&lt;br /&gt;
			local strn_id = 65535  -- значение по умолчанию&lt;br /&gt;
			&lt;br /&gt;
			local t = nil -- сюда запихнём табличку из пакета&lt;br /&gt;
			if IsStalker(obj) then t = m_net_utils.get_stalker_data(obj) elseif IsMonster(obj) then t = m_net_utils.get_monster_data(obj) end &lt;br /&gt;
			-- вызываем парсинг пакета для неписей и монстров отдельно&lt;br /&gt;
			&lt;br /&gt;
			-- print_table_inlog(t)&lt;br /&gt;
			if t.smtrid then&lt;br /&gt;
				strn_id = tonumber(t.smtrid) -- получаем идентификатор смарта, если его нету даже в пакете, хрен с ним, будет 65535&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
			if strn_id ~= 65535 then -- если сняли идентификатор, попробуем отрегать...&lt;br /&gt;
				local gulag = sim:object(strn_id)&lt;br /&gt;
				if gulag and gulag.gulag then -- ...но сначала выясним если вообще такой гулаг и инициализирован ли он&lt;br /&gt;
					sim:object(strn_id).gulag:clear_dead(obj_id)&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При этом сделаю отсупление и предупрежу об одном очень странном сбое с которым я столкнулся редактируя эту функцию. Так вот, всё нормально работает только тогда когда у ф-ции этой есть строго определённая структура. '''Стоит только добавить пару строк, убрать закомментированную и сдвинуть пару условий, просто в тексте сдвинуть, не меняя внутренней логики, как игра начинает вылетать, причём ещё до загрузки сейва, при кэшировании (!) и с очень странными логами, хотя код написан синтаксически безупречно! Например с таким:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Description: xr_gulag:1035 value not found ObjectJobPathName[obj_id]&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Вернёшь строки на место, перестроишь текст - работает снова. Совершенная чертовщина, шаманил я над этой функцией около получаса, перестраивая текст таким образом чтобы игра нормально запускалась.''' Имейте это в виду когда в неё полезете и если что не пугайтесь если игра начнёт так вылетать - в этом случае пошаманьте немного над ней, меняя её форматирование. О причинах такого поведения я не могу даже догадываться - то ли движок её вызывает по смещению внутри файла вручную, то ли это какой-то баг Lua-парсера, но факт фактом.&lt;br /&gt;
&lt;br /&gt;
Итак, теперь проблема с получением идентификатора смарта разрешилась, однако алайф всё равно зависал! Простая трассировка показала, что теперь зависание происходило внутри обработки&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;sim:object(strn_id).gulag:clear_dead(obj_id)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И мне пришлость распутывать её по частям, доискиваясь до причины. Обработка честно говоря мерзкая, размазана по smart_terrain и xr_gulag, при этом взаимные вызовы идут не менее десятка раз, в следующем стиле: ф-ция в смарте вызывает ф-цию в гулаге, которая вызывает фцию в смарте, которая обрабатывает параметр фцией в гулаге, который передётся ф-цией в логике, которая получает её из смарта. Нечто подобное. Опуская все нецензурные выражения, употребленные мной при трассировке, я лучше расскажу что собственно вышло в итоге. А в итоге я вышел вот на этот код в xr_gulag, именно в нём при отрегистрации некоторых мёртвых неписей происходит зависание алайфа:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- освободить объект от работы и переинициализировать логику.&lt;br /&gt;
-- если сталкер в онлайне и начал работу, то сбросить его схему поведения&lt;br /&gt;
-- как будто он только что загрузился&lt;br /&gt;
function gulag:free_obj_and_reinit( obj_id )&lt;br /&gt;
	self:free_obj(obj_id)&lt;br /&gt;
&lt;br /&gt;
	local t = self.Object[obj_id]&lt;br /&gt;
	if t ~= nil and t ~= true and self.Object_begin_job[obj_id] then&lt;br /&gt;
		xr_logic.initialize_obj( t, nil, false, db.actor, self:get_stype( obj_id ) ) -- вот эта обработка вешает алайф&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Я, честно говоря, ни разу не понимаю, на кой чёрт нужно переконфигурировать схемы логики и выбирать из них активную '''трупу, который лежит себе спокойно и никого не трогает.''' Может быть в это есть некий высший смысл, или это было продиктовано неким аккуратизмом, однако одно я могу сказать однозначно - подобная переинициализация у некоторых трупов неписей приводит к глухому зависанию алайфа. При этом если для трупов эту обработку заблокировать, то трупы разрегистрируются вполне нормально и спокойно лежат с нетронутой логикой, не вызывая никаких проблем. Итоговый код после правок выглядит вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- освободить объект от работы и переинициализировать логику.&lt;br /&gt;
-- если сталкер в онлайне и начал работу, то сбросить его схему поведения&lt;br /&gt;
-- как будто он только что загрузился&lt;br /&gt;
function gulag:free_obj_and_reinit( obj_id )&lt;br /&gt;
	self:free_obj(obj_id)&lt;br /&gt;
	local t = self.Object[obj_id]&lt;br /&gt;
	if t ~= nil and t ~= true and self.Object_begin_job[obj_id] then&lt;br /&gt;
		if check_game() then -- тут проверяется, запущена ли игра, если ли актор и жив ли он. Если да, делаем по новому.&lt;br /&gt;
			local s_obj = alife():object(obj_id) -- проверим есть ли у цели разрегистрации валидный серверный объект&lt;br /&gt;
			if s_obj and (IsStalker(s_obj) or IsMonster(s_obj)) and s_obj:alive() then -- если есть, он жив и сталкер или монстр&lt;br /&gt;
				xr_logic.initialize_obj( t, nil, false, db.actor, self:get_stype( obj_id ) ) -- только тогда инициализируем логику&lt;br /&gt;
			end&lt;br /&gt;
		else -- а если игра не запущена, то как раньше. Это нужно для того, чтобы обработка запуска игры нормально работала.&lt;br /&gt;
			xr_logic.initialize_obj( t, nil, false, db.actor, self:get_stype( obj_id ) )&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Проверка, запущена ли игра&lt;br /&gt;
function check_game()&lt;br /&gt;
	if level.present() and (db.actor ~= nil) and db.actor:alive() then&lt;br /&gt;
		return true&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
После этих поправок зависания алайфа при смерти неписей удалось побороть окончательно. Решение с отрезанием реинита логики для трупов несколько грубовато, но честно говоря, у меня нет ни малейшего желания трассировать и разбирать на части функцию xr_logic.initialize_obj, выясняя, чем же ей так данный конкретный труп не приглянулся. Если хотите - займитесь, найдёте причину - дополните данную статью. Я же успокоился на том, что заблокировал баг, периодически убивающий людям игру, так как многие пользователи, игнорируя предупреждения, играют на одном-двух сейвах, а то и вовсе на квиксейвах всю игру.&lt;br /&gt;
&lt;br /&gt;
= Другие частые проблемы =&lt;br /&gt;
&lt;br /&gt;
Спустя некоторое время я обнаружил причины ещё нескольких часто встречающихся вылетов, и решил записать их описание сюда - эта информация наверняка ещё много кому пригодится.&lt;br /&gt;
&lt;br /&gt;
== Вылеты при удалении объектов из игры ==&lt;br /&gt;
&lt;br /&gt;
При использовании для удаления объектов родной движковой функции '''alife():release(alife():object(id), true)''' возможен целый ворох разнообразнейших вылетов, обычно - безлоговых, что сильно затрудняет их отладку. Вот из-за чего они возникают:&lt;br /&gt;
&lt;br /&gt;
1) Вылет при удалении непися или монстра, находящегося в онлайне.&lt;br /&gt;
   Решение: с помощью alife():release '''можно удалять только мёртвые объекты'''. Поэтому если вам нужно удалить с её помощью непися или монстра, который жив и находится в онлайне, его нужно убить любым доступным методом, хотя бы нанеся ему hit() с любым запредельным уроном по вкусу.&lt;br /&gt;
&lt;br /&gt;
2) Вылет при удалении оружия или артефакта.&lt;br /&gt;
   Решение: такая проблема часто встречается в случае если объект неудачно расположен или находится в руках у непися. Для того чтобы не произошло вылета, убедитесь что объект доступен как серверный перед удалением. Вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;	local obj = alife():object(i)&lt;br /&gt;
	if obj then&lt;br /&gt;
		alife():release(obj, true)&lt;br /&gt;
	end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Эту конструкцию вообще желательно использовать всегда, когда вы так удаляете объекты.&lt;br /&gt;
&lt;br /&gt;
3) Вылет при удалении аномалии.&lt;br /&gt;
   Решение: аномалии - очень капризные при подобном с ними обращении объекты. Они влияют на своё окружение, и если рядом с ними находится непись или монстр, то удаление такой аномалии приведёт к вылету игры. Чтобы этого не произошло, аномалию надо сначала выключить функцией '''disable_anomaly''', и удалять затем ТОЛЬКО тогда, когда она не будет занята влиянием на динамический объект. Для этого нужно получить список мобов на локации, и из их нетпакетов считать идентификаторы действующих на них рестрикторов. Если ваша аномалия будет в этом списке - удалять её нельзя. Дождитесь пока она освободится.&lt;br /&gt;
&lt;br /&gt;
== Вылет при открытии закладки &amp;quot;Контакты&amp;quot; в ПДА ==&lt;br /&gt;
&lt;br /&gt;
Простой безлоговый вылет при открытии закладки &amp;quot;Контакты&amp;quot;. Встречался во всех крупных модах, и никто не знал как его излечить. А лечится он банально - '''его причина - дублирование идентификаторов секций в XML-файле, описывающем иконки неписей для закладки &amp;quot;Контакты&amp;quot;'''. Нужно всего лишь проверить этот файл на наличие дублированных идентификаторов и удалить их. Вылет пропадёт и никогда больше не будет встречаться.&lt;br /&gt;
&lt;br /&gt;
== Вылеты при вызове несуществующих функций из XML ==&lt;br /&gt;
&lt;br /&gt;
В ХML-файлах, используемых для описания инфопоршенов, для многих инфопоршенов прописаны действия, которые игра вызывает при взятии этого инфопоршена. Вот так примерно:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
	&amp;lt;info_portion id=&amp;quot;barman_document_have&amp;quot;&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;dialogs.set_actor_prebandit1&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandits2&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandits3&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandit7&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandit8&amp;lt;/action&amp;gt;&lt;br /&gt;
	&amp;lt;/info_portion&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так вот, если вы допустите опечатку в названии вызываемой функции или же случайно её удалите - игра будет стабильно вылетать без лога при взятии этого инфопоршена.&lt;br /&gt;
&lt;br /&gt;
== Повреждения сейвов на Радаре и других местах в модах, основанных на OGSM ==&lt;br /&gt;
&lt;br /&gt;
В оригинале ОГСМ и основанных на нём модах часто встречались проблемы с сохранениями на Радаре. Эту проблему долго не удавалось победить, пока наконец благодаря помощи Маландринуса не удалось выявить её первопричину. Как выяснилось, она очень проста - гражданские зомби в моде (монстры) имели в конфиге ту же пропись вида (параметр конфига specie), что и монолитовцы и зомбированные (неписи). И там и тут было проставлено &amp;quot;zombie&amp;quot;, и так оно было ещё с оригинала. Как оказалось, так делать категорически нельзя. Дело в том, что у неписей есть такой функционал, как хитовая память - в ней какое-то время хранятся ссылки на атакующие объекты. У монстров тоже есть остатки этого функционала, но он неработоспособен, и использовать его нельзя. В случае же когда монстры и неписи попадают в один вид, в ситуации когда они находятся рядом в бою, хитовая память монстров автоматически получает от неписей того же вида распространяемую внутри вида информацию об атакующих - а хранить её монстрам нельзя. Если после создания такой ситуации сохраниться - сейв будет вызывать вылет при загрузке. То есть проще говоря, если в бою с монолитовцами рядом оказывались гражданские зомби - и игрок сохранял игру - сейв этот не загружался. Чтобы предотвратить эти проблемы, вполне достаточно создать для гражданских зомби свой отдельный вид, добавив его прописи в конфиг game_relations.&lt;br /&gt;
&lt;br /&gt;
== Застывания NPC после боя / лечения ранения в модах, использующих дополнительные схемы поведения ==&lt;br /&gt;
&lt;br /&gt;
Во многих модах, использующих дополнительные схемы поведения часто можно заметить NPC, которые после боя застывают, прицелившись в одну точку. Аналогично часто это встречается с вылеченными от ранения NPC - они встают и замирают намертво, до тех пор пока их не выведет из этого состояния атаковавший враг. Как правило сейв/загрузка этой проблемы не решают. В ходе работы над OGSE 0.6.9.3 мне удалось выяснить причину таких проблем и успешно её устранить. Причина проблемы заключается в том, что NPC управляются не только скриптовыми схемами, но и движком, и для переключения управления между одним и другим используется скрипт ''state_mgr'' - менеджер состояний. Его директивы имеют наивысший приоритет. Аналогичный же приоритет себе как правило назначают доп. схемы поведения, используя пропись эвалуатора в ''xr_motivatior.addCommonPrecondition(action)''. В итоге при выходе из движковой боевки или при переключении на движковый алайф (это происходит после излечения ранения) доп. схемы поведения вступают с менеджером состояний в конфликт, блокируя смену состояния NPC. Для того чтобы этого не происходило, '''необходимо во всех схемах поведения, использующих принудительное назначение состояния через функцию ''state_mgr.set_state'' сделать блокировку перехвата управления менеджером состояний''', добавив соответствующие прописи в биндер. Вот таким образом, как в этом примере - тут я правил биндер схемы лечения/самолечения ''xrs_medic'':&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;&lt;br /&gt;
function add_to_binder(object, ini, scheme, section, storage)&lt;br /&gt;
	local operators	= {}&lt;br /&gt;
	local properties  = {}&lt;br /&gt;
&lt;br /&gt;
	local manager = object:motivation_action_manager()&lt;br /&gt;
&lt;br /&gt;
	operators[&amp;quot;medic&amp;quot;]			= actid_medic&lt;br /&gt;
	operators[&amp;quot;self_medic&amp;quot;]		= actid_self_medic&lt;br /&gt;
&lt;br /&gt;
	properties[&amp;quot;medic&amp;quot;]			= evid_medic&lt;br /&gt;
	properties[&amp;quot;self_medic&amp;quot;]	= evid_self_medic&lt;br /&gt;
	&lt;br /&gt;
	local state_mgr_to_idle_combat = xr_actions_id.state_mgr + 1 ---&amp;lt; Это переключение на движковую боевку, но оно тут не использовано&lt;br /&gt;
	local state_mgr_to_idle_alife = xr_actions_id.state_mgr + 2 ---&amp;lt; Это переключение на движковый алайф&lt;br /&gt;
	local state_mgr_to_idle_off = xr_actions_id.state_mgr + 3   ---&amp;lt; Это переключение в статичное состояние&lt;br /&gt;
&lt;br /&gt;
	local zombi=object:character_community()==&amp;quot;zombied&amp;quot; or object:character_community()==&amp;quot;trader&amp;quot; or&lt;br /&gt;
		  object:character_community()==&amp;quot;arena_enemy&amp;quot; or object:name()==&amp;quot;mil_stalker0012&amp;quot; or object:name()==&amp;quot;yantar_ecolog_general&amp;quot;&lt;br /&gt;
&lt;br /&gt;
	if zombi then&lt;br /&gt;
		manager:add_evaluator (properties[&amp;quot;medic&amp;quot;], property_evaluator_const(false))&lt;br /&gt;
		manager:add_evaluator (properties[&amp;quot;self_medic&amp;quot;], property_evaluator_const(false))&lt;br /&gt;
	else&lt;br /&gt;
		manager:add_evaluator (properties[&amp;quot;medic&amp;quot;], evaluator_medic(&amp;quot;medic&amp;quot;, storage))&lt;br /&gt;
		manager:add_evaluator (properties[&amp;quot;self_medic&amp;quot;], evaluator_self_medic(&amp;quot;self_medic&amp;quot;, storage))&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	local action = action_medic (object,&amp;quot;medic&amp;quot;, storage)&lt;br /&gt;
	action:add_precondition(world_property(stalker_ids.property_alive, true))&lt;br /&gt;
	action:add_precondition(world_property(xr_evaluators_id.sidor_wounded_base, false))&lt;br /&gt;
	action:add_precondition	(world_property(properties[&amp;quot;medic&amp;quot;], true))&lt;br /&gt;
	action:add_effect (world_property(properties[&amp;quot;medic&amp;quot;], false))&lt;br /&gt;
	manager:add_action (operators[&amp;quot;medic&amp;quot;], action)&lt;br /&gt;
	&lt;br /&gt;
	local action = action_self_medic (object,&amp;quot;self_medic&amp;quot;, storage)&lt;br /&gt;
	action:add_precondition(world_property(stalker_ids.property_alive, true))&lt;br /&gt;
	action:add_precondition(world_property(stalker_ids.property_enemy,false))	&lt;br /&gt;
	action:add_precondition(world_property(xr_evaluators_id.sidor_wounded_base, false))&lt;br /&gt;
	action:add_precondition	(world_property(properties[&amp;quot;medic&amp;quot;], false))&lt;br /&gt;
	action:add_precondition	(world_property(properties[&amp;quot;self_medic&amp;quot;], true))&lt;br /&gt;
	action:add_effect (world_property(properties[&amp;quot;self_medic&amp;quot;], false))&lt;br /&gt;
	manager:add_action (operators[&amp;quot;self_medic&amp;quot;], action)&lt;br /&gt;
		&lt;br /&gt;
	action = manager:action (stalker_ids.action_alife_planner)	&lt;br /&gt;
	action:add_precondition	(world_property(properties[&amp;quot;medic&amp;quot;], false))&lt;br /&gt;
	action:add_precondition	(world_property(properties[&amp;quot;self_medic&amp;quot;], false))&lt;br /&gt;
	&lt;br /&gt;
	action = manager:action(state_mgr_to_idle_alife)&lt;br /&gt;
	action:add_precondition	(world_property(properties[&amp;quot;self_medic&amp;quot;], false)) ---&amp;lt; Блокируем попытки переключиться на движковый алайф пока работает самолечение&lt;br /&gt;
&lt;br /&gt;
	action = manager:action(state_mgr_to_idle_off)&lt;br /&gt;
	action:add_precondition	(world_property(properties[&amp;quot;self_medic&amp;quot;], false)) ---&amp;lt; Блокируем попытки переключиться статичное состояние пока работает самолечение&lt;br /&gt;
&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Вставка этих директив в биндеры всех схем, которые меняют состояния принудительно устраняет конфликты, и NPC больше не виснут при смене схем.&lt;br /&gt;
&lt;br /&gt;
== Зависания алайфа из-за ошибок в конфигах торговли ==&lt;br /&gt;
&lt;br /&gt;
Очень частая проблема у начинающих модостроителей. Обнаруживается боем сейвов в радиусе алайфа от торговца с некорректным конфигом. Обычно главная и единственная причина этой проблемы - наличие в секции ''buy_supplies'' секций, у которых не прописан один или оба параметра, или же наличие записей вида &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
novice_outfit				;NO TRADE&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Запомните - в секции ''buy_supplies'' конфига торговли таких записей быть не должно вообще. Если вы хотите убрать вещь из общего ассортимента - удалите её строку из ''buy_supplies''.&lt;br /&gt;
&lt;br /&gt;
--[[Участник:Kamikazze|KamikaZze (OGSE Team)]] 11:04, 31 марта 2011 (UTC)&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[Как писать скрипты, не приводящие к вылетам и бою сейвов|Начало в первой части статьи]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
= Авторы =&lt;br /&gt;
Статья создана: [[Участник:Kamikazze|Kamikazze]]&lt;br /&gt;
&lt;br /&gt;
[[Категория:Скрипты]]&lt;/div&gt;</summary>
		<author><name>Kamikazze</name></author>	</entry>

	<entry>
		<id>http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2_(%D1%87%D0%B0%D1%81%D1%82%D1%8C_2)</id>
		<title>Как писать скрипты, не приводящие к вылетам и бою сейвов (часть 2)</title>
		<link rel="alternate" type="text/html" href="http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2_(%D1%87%D0%B0%D1%81%D1%82%D1%8C_2)"/>
				<updated>2011-03-31T11:57:57Z</updated>
		
		<summary type="html">&lt;p&gt;Kamikazze: поправка&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;----&lt;br /&gt;
&lt;br /&gt;
[[Как писать скрипты, не приводящие к вылетам и бою сейвов|Начало в первой части статьи]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Как безопасно использовать коллбэки и таймерные события =&lt;br /&gt;
&lt;br /&gt;
В скриптовом движке есть удобный способ реакции на события - использование коллбэков - процедурных вызовов, привязанных к определённым событиям в жизни игрового объекта - получению повреждений, спавну, смерти и т.д. и т.п. Это hit_callback, death_callback из xr_motivator и многие другие... Все моддеры очень широко и совершенно спокойно пользуются ими, совершенно забывая при этом о таком важном факте, что это - обработки реального времени, как и таймерные события. Что это значит? А собственно вот что... Возьмём для примера коллбэк смерти неписей, мою головную боль последнего времени:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;xr_motivator.script -&lt;br /&gt;
function motivator_binder:death_callback(victim, who)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это функция обрабатывает смерть неписей. Когда вызывается этот коллбек он начинает по очереди вызывать внутренние функции, расположенные в других модулях. Они разрегистрируют умершего непися в гулагах, спавнят в него лут, обновляют статистику, отключают его от доп. схем логики и т.д.&lt;br /&gt;
&lt;br /&gt;
Так вот, обычно, когда в функциях возникает конфликт параметров, бесконечный цикл, попытка индексации nil и т.д., функция вылетает со стандартным логом. '''Но только не в случае, когда она находится внутри коллбэка. Если функция внутри него, то в случае возникновении в ней любой нештатной ситуации, коллбэк наглухо виснет.''' Это происходит из-за того, что функции вызываются строго друг за другом, и каждая из них вызывается только тогда, когда её предшественница вернула управление коллбэку. В случае с death_callback опознать такого непися очень просто - в его трупе окажется фонарик, КПК и возможно ещё немного разных &amp;quot;мусорных&amp;quot; вещей, что говорит о том, что обработка его смерти повисла не дойдя даже до спавна лута. В подобной ситуации можно быть на 100% уверенным, что труп этот не был корректно разрегистрирован, и игра всё ещё считает его живым неписем. Кроме того, зависший коллбэк не освобождает стек (а он у Луа-подсистемы единый на все скрипты), что '''в итоге приводит к вылетам игры с переполнением памяти (вот она, реальная причина этих &amp;quot;родных&amp;quot; вылетов).''' Но было бы слишком хорошо, если бы всё ограничивалось этим... однако тут всё намного хуже... такие &amp;quot;зависшие&amp;quot; коллбэки, особенно если их произошло несколько подряд, очень серьёзно влияют на работу а-лайфа. В лучшем случае они, забивая, стек, мешают нормально работать схемам логики, в худшем вызывают зависания самого  а-лайфа (этот эффект, кстати, производят и сами трупы таких неписей, так как они, как мы помним, не разрегистрировались корректно). '''Основной итог таких событий - бой сейвов, сделанных после возникновения таких ситуаций.''' Если повис один коллбэк, то такие сейвы ещё через раз загружаются, если же несколько, и остановилась работа а-лайфа - всё, сейвы бьются наглухо и реанимации не подлежат.&lt;br /&gt;
&lt;br /&gt;
Поэтому, чтобы избежать такого развития событий, каждый раз, когда вы вносите в коллбэк новую функцию - проверьте её самым тщательным образом. Она не должна содержать никаких рекурсивных циклов, в ней обязательно должны быть проверки на валидность обрабатываемых объектов и значений, и обязательно должна быть обработка нештатных ситуаций - т.е. функция должна обязательно, в абсолютно любой ситуации вернуть управление коллбэку, так или иначе. В самом наихудшем случае - делайте как делали разработчики игры - вставляйте принудительный вылет на рабстол функцией abort - она позволяет передавать отладочное сообщение, и это всяко лучше чем незаметный бой сейвов. Если обработка оборвалась в самом начале, а ф-ция обязательно должна вернуть значение - заведите ей &amp;quot;безопасное&amp;quot; возвращаемое значение по-умолчанию, которое она будет выдавать, если всё пошло плохо. И никогда не пренебрегайте пошаговой отладкой коллбэков с выводом в лог, особенно когда пишете схемы логики - это критически важно для стабильности вашего мода.&lt;br /&gt;
&lt;br /&gt;
== Основные внутриигровые признаки зависания коллбэков типа hit_callback, death_callback ==&lt;br /&gt;
&lt;br /&gt;
1. В трупах попадаются фонарики, КПК, разный мусор и общий лут слишком богат.&lt;br /&gt;
&lt;br /&gt;
2. Частые вылеты во время интенсивных боёв с логами типа&lt;br /&gt;
    Sheduler tried to update object...&lt;br /&gt;
    smart_terrain:1145(1146)&lt;br /&gt;
    LUA: out of memory&lt;br /&gt;
    любой_модуль_логики:любая_cтрока - stack overflow&lt;br /&gt;
&lt;br /&gt;
3. Частые &amp;quot;родные&amp;quot; вылеты в момент смерти непися или попадания по нему&lt;br /&gt;
&lt;br /&gt;
4. Произвольно бьются сейвы во время сражений, выброса и других насыщенных действиями событий&lt;br /&gt;
&lt;br /&gt;
= Использование защищённого кода в LUA =&lt;br /&gt;
&lt;br /&gt;
Периодически случаются такие ситуации, когда мы можем получить вылет при проверке аргумента, и не можем его адекватно заизолировать с помощью предварительной проверки на валидность значения. Вот простой пример: когда я отлаживал '''death_callback''' неписей, я периодически сталкивался с тем, что обращение к методу '''smart_terrain_id()''' при смерти непися иногда вызывало вылет &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;smart_terrain:1143 &amp;quot;attempt to index a nil value&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
- хотя это свойство является родным методом объекта, и отсутствовать напрочь никак не может. В итоге я пришёл к выводу, что его просто иногда не успевает отработать движок, так как вылет этот проявлялся в основном в интенсивных боях и совершенно произвольно.&lt;br /&gt;
&lt;br /&gt;
Вот код, в котором происходил вылет:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj     = sim:object( obj_id )&lt;br /&gt;
		local strn_id = obj:smart_terrain_id()  --- вылет происходит тут&lt;br /&gt;
&lt;br /&gt;
		if strn_id ~= 65535 then&lt;br /&gt;
			sim:object( strn_id ).gulag:clear_dead(obj_id)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это кусок родного кода из версии 1,0005 игры. Я долго пытался разными способами отсечь этот вылет, вводя предварительные проверки, однако это совершенно ничего не давало - вылет всё равно периодически случался, так как проверки эти сами его вызывали. Тогда я зарылся в документацию по Lua и обнаружил замечательную родную базовую функцию, введённую ещё с первых версий Lua, которой почему-то не пользовались ни разработчики игры, ни моддеры (хотя сама она в Lua сталкера присутствует, и работает отлично, без каких-либо нареканий). Вот она:&lt;br /&gt;
&lt;br /&gt;
'''pcall (f, arg1, ···)'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Вызывает функцию f с указанными через запятую аргументами в защищённом режиме. Это означает, что любая ошибка, даже критическая, внутри вызванной функции, не передаётся наружу - вызывавшей подсистеме. Вместо этого '''pcall''' перехватывает ошибку и возвращает код статуса. Первая возвращаемая переменная это сам код, (true или false) и если всё прошло хорошо, он равен true. В этом случае '''pcall''' сразу после статуса возвращает все результаты от работы защищённой им функции. Если же в защищённой функции произошла ошибка, то '''pcall''' вернёт false и затем сообщение об ошибке. (Обратите внимание, обработка ошибки присходит БЕЗ вылета! Вместо вылета вы получите вполне адекватную строку с ошибкой, которую можно вывести в лог для последующей обработки)&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Я настоятельно советую пользоваться этой функцией в случаях, когда из коллбэков вызываются сложные комплексные обработки, вроде обработки из менеджера вооружений AI-пака. Это позволяет предотвратить как вылеты, так и зависания обработок, и в итоге позволяет хорошо стабилизировать игру.&lt;br /&gt;
&lt;br /&gt;
Возвращаясь к нашим смарттеррейнам... вот как в итоге я подавил вылет типа smart_terrain:1143 с помощью pcall:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;--- эта функция пытается проверить св-во smart_terrain_id объекта. Именно её мы вызовем в защищённом режиме.&lt;br /&gt;
function prot_smt_td(obj)&lt;br /&gt;
	if IsStalker(obj) or IsMonster(obj) then&lt;br /&gt;
		return obj:smart_terrain_id()&lt;br /&gt;
	else	&lt;br /&gt;
		return 65535&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj = sim:object( obj_id )&lt;br /&gt;
		if obj then&lt;br /&gt;
			local strn_id = 65535  --- предварительно проинитим переменную, на &lt;br /&gt;
						--- случай если у нас prot_smt_td выдаст ошибку&lt;br /&gt;
			local result, smt_id = pcall(prot_smt_td,obj)	--- вызываем prot_smt_td в защищённом режиме &lt;br /&gt;
									--- и сразу присваиваем его вывод переменным&lt;br /&gt;
			if result then --- если pcall выдало true&lt;br /&gt;
				strn_id = smt_id  --- тогда применяем полученное значение&lt;br /&gt;
			end&lt;br /&gt;
			--- если же обработка выдаст ошибку, то strn_id останется неизменным...&lt;br /&gt;
			if strn_id ~= 65535 then&lt;br /&gt;
				sim:object(strn_id).gulag:clear_dead(obj_id)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В других местах это делается совершенно аналогично. Подробнее об этой и многих других функция для контроля кода, незаслуженно игнорируемых большинством моддеров, можно почитать тут: http://lua-users.org/wiki/FinalizedExceptions - на английском правда, но захотите - разберётесь, там всё просто. &lt;br /&gt;
&lt;br /&gt;
Настоятельно советую вам изучить работу кода в таких условиях и научиться его правильно применять - этим вы сильно облегчите жизнь как себе, так и тем, кто после вас будет рыться в вашем коде или импортировать его в свои разработки.&lt;br /&gt;
&lt;br /&gt;
= Скрытые критические проблемы в обработке вылетов игрой =&lt;br /&gt;
&lt;br /&gt;
Ведя на днях отладку, выяснил в чём проблема с периодическим боем сейвов и многими другими заморочками как в оригинале игры, так и во многих модах... дело, как выяснилось, далеко не всегда в кривых руках. Есть такая стандартная ф-ция '''abort''' - предназначенная для выкидывания из игры, если что-то пошло не так. И как оказалось, она срабатывает далеко не всегда. Выяснилось это следующим образом:&lt;br /&gt;
&lt;br /&gt;
В одном из логов нашего бета-тестера я увидел '''стандартное сообщение о вылете внутри рабочего лога'''... да-да, то самое которое '''FATAL ERROR''' и дальше по тексту. При этом игра у него НЕ вылетала, это сообщение об ошибке мы обнаружили позже, по случайности. Я заподозрил, что что-то не в порядке, и вставил внутрь этой ф-ции контрольную метку, кидавшую в консоль сообщение, в котором содержался паттерн сообщения об ошибке и само сообщение. Так вот, оказалось, что эта самая функция '''abort''' вызывается в игре с завидным постоянством (вы удивитесь насколько часто), когда возникают исключения в схемах логики, звука и т.д., но игра от этого вылетает на рабочий стол '''максимум только 3 раза из 10 вызовов'''. Вылет НЕ происходит обычно, когда функции передан паттерн ошибки, а остальные параметры пустые, такое бывает, и частенько. И если не сделать внутри этой функции особой метки для вывода в лог, как сделал это я, её вызовы проходят совершенно незаметно, и '''игра после критических ошибок продолжается как ни в чём ни бывало.''' А приводит это вот к чему... Внутри '''xr_logic''' в процедуре записи пстора (хранилища логики и флагов) неписей есть вызовы этого самого аборта в случае если на запись в пстор передана некорректная величина. Ну а так как аборт периодически вообще не срабатывает, то часто попадается ситуация, что неписям в пстор пишется полный ахтунг: куски кода из ОЗУ, всякая муть из лтх-ов, куски аллспавна, всё что угодно. Происходит это оттого, что кодер, писавший эту функцию ('''xr_logic.pstor_store(obj, varname, val)'''), явно и думать не думал что '''abort''' может не сработать. У него запись в пстор стояла после проверки, а не внутри неё (very bad idea), и если abort не срабатывал, игра писала в пстор мусор совершенно спокойно и незаметно для игрока. Потом вся эта хрень попадала прямо в сейвы. Вот проблемный код для наглядности:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_store(obj, varname, val)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	if db.storage[npc_id].pstor == nil then&lt;br /&gt;
		db.storage[npc_id].pstor = {}&lt;br /&gt;
	end&lt;br /&gt;
	local tv = type(val)&lt;br /&gt;
	if not pstor_is_registered_type(tv) then&lt;br /&gt;
		abort(&amp;quot;xr_logic: pstor_store: not registered type '%s' encountered&amp;quot;, tv) --- вот тут мы должны если что вылететь&lt;br /&gt;
	end&lt;br /&gt;
	db.storage[npc_id].pstor[varname] = val -- а если не вылетели, всё, получим запись в пстор левой мути&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Разумеется игра этим пстором в итоге давится, и сейвы сделанные после такой милой записи практически лопаются. Результат - &amp;quot;битые&amp;quot; (на самом деле подлежат реанимации) сейвы. Происходит это потому, что игра из сейва грузит неписям псторы сплошным чтением по словам, пока они не закончатся. В случае же если в псторе обнаруживается записанный ранее мусор, то обработка либо вылетает сразу, либо наглухо виснет, пытаясь запихать эдак с миллион слов в пстор особо отличившегося непися. Как вам например непись с размером пстора в 1697451 слова? В результате попытки его обработать игра просто на стадии синхронизации выжрала всю доступную ОЗУ и повисла. &lt;br /&gt;
&lt;br /&gt;
Решение этой проблемы оказалось достатоно простым: во-первых я предположил максимальный размер полезной части пстора неписей в 20 слов r_u32() (пока ориентировочно, я ещё уточняю эту величину), и соответственно сделал остановку цикла загрузки пстора для неписей через 20 итераций. Там же, где в цикле стояла проверка на валидность записываемых данных (кстати тоже с вылетом в случае провала проверки), я сделал так, что если параметр не относится к валидному типу данных, то запись параметра в пстор не производится совсем. Это необходимо для того, чтобы если вдруг в сейве обнаружится мусор, то он был бы просто отброшен обработкой. Практика показала, что в итоге такие неписи вполне адекватны и в дальнейшем никаких проблем не вызывают, так как начало их пстора, с нормальными данными, обычно не повреждается - мусор дописывается после них, а не вместо них. &lt;br /&gt;
&lt;br /&gt;
Ну и во-вторых модифицировал запись параметров в пстор, просто убрав запись под основание if-else так, чтобы если параметр неверен, он не записывался совсем. Теперь кстати, очень интересно, сохранились ли те же заморочки с ф-цией '''abort''' в Чистом Небе, и если да, то останутся ли в Зове Припяти?&lt;br /&gt;
&lt;br /&gt;
------------&lt;br /&gt;
&lt;br /&gt;
== Необходимые для стабилизации игры правки в модулях ==&lt;br /&gt;
&lt;br /&gt;
'''Эта правка предотвращает запись в пстор если не сработал аборт:'''&lt;br /&gt;
&lt;br /&gt;
''xr_logic.script''&lt;br /&gt;
&lt;br /&gt;
'''Было:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_store(obj, varname, val)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	if db.storage[npc_id].pstor == nil then&lt;br /&gt;
		db.storage[npc_id].pstor = {}&lt;br /&gt;
	end&lt;br /&gt;
	local tv = type(val)&lt;br /&gt;
	if not pstor_is_registered_type(tv) then&lt;br /&gt;
		abort(&amp;quot;xr_logic: pstor_store: not registered type '%s' encountered&amp;quot;, tv)&lt;br /&gt;
	end&lt;br /&gt;
	db.storage[npc_id].pstor[varname] = val&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Стало:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_store(obj, varname, val)&lt;br /&gt;
	if not obj then return end&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	if db.storage[npc_id].pstor == nil then&lt;br /&gt;
		db.storage[npc_id].pstor = {}&lt;br /&gt;
	end&lt;br /&gt;
	local tv = type(val)&lt;br /&gt;
	if not pstor_is_registered_type(tv) then&lt;br /&gt;
		dgblog(&amp;quot;xr_logic: pstor_store: not registered type encountered - write in pstor_store cancelled&amp;quot;)&lt;br /&gt;
		-- abort убран, так как один хрен не работает. Пусть тогда хотя бы в лог что-то валится.&lt;br /&gt;
	else&lt;br /&gt;
		db.storage[npc_id].pstor[varname] = val&lt;br /&gt;
		-- вот так и только так. Если значение не валидно, ничего не происходит.&lt;br /&gt;
	end	&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''А эта правка выкинет из пстора весь мусор при загрузке сейва, если он как-то в него попал'''&lt;br /&gt;
&lt;br /&gt;
'''Было:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_load_all(obj, reader)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	local pstor = db.storage[npc_id].pstor&lt;br /&gt;
	if not pstor then&lt;br /&gt;
		pstor = {}&lt;br /&gt;
		db.storage[npc_id].pstor = pstor&lt;br /&gt;
	end&lt;br /&gt;
	local ctr = reader:r_u32()&lt;br /&gt;
	for i = 1, ctr do&lt;br /&gt;
		local varname = reader:r_stringZ()&lt;br /&gt;
		local tn = reader:r_u8()&lt;br /&gt;
		if tn == pstor_number then&lt;br /&gt;
			pstor[varname] = reader:r_float()&lt;br /&gt;
		elseif tn == pstor_string then&lt;br /&gt;
			pstor[varname] = reader:r_stringZ()&lt;br /&gt;
		elseif tn == pstor_boolean then&lt;br /&gt;
			pstor[varname] = reader:r_bool()&lt;br /&gt;
		else&lt;br /&gt;
			abort(&amp;quot;xr_logic: pstor_load_all: not registered type N %d encountered&amp;quot;, tn)&lt;br /&gt;
		end&lt;br /&gt;
		printf(&amp;quot;_bp: pstor_load_all: loaded [%s]='%s'&amp;quot;, varname, utils.to_str(pstor[varname]))&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Стало:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_load_all(obj, reader)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	local pstor = db.storage[npc_id].pstor&lt;br /&gt;
	if not pstor then&lt;br /&gt;
		pstor = {}&lt;br /&gt;
		db.storage[npc_id].pstor = pstor&lt;br /&gt;
	end&lt;br /&gt;
	local ctr = reader:r_u32()&lt;br /&gt;
	if tonumber(ctr) &amp;gt; 20 and tostring(obj:name()) ~= &amp;quot;single_player&amp;quot; and npc_id ~= db.actor:id() then&lt;br /&gt;
		-- максимум 20 итераций - это число ещё уточняется, возможно понадобится больше&lt;br /&gt;
                -- если у вас в пстор что-то свое пишется, ориентируйтесь на свои значения&lt;br /&gt;
		-- и обязательно убираем из проверки актора - у него очень толстый пстор, и к тому же&lt;br /&gt;
                -- если уж поврежденным будет его пстор, то тут точно уже ничего не поможет&lt;br /&gt;
		dgblog(&amp;quot;ОБНАРУЖЕН ОБЪЕКТ С ПОВРЕЖДЕННЫМ PSTOR: &amp;quot;..tostring(obj:name())..&lt;br /&gt;
&amp;quot; БУДЕТ ПРОИЗВЕДЕНА ПОПЫТКА ВОССТАНОВЛЕНИЯ&amp;quot;)&lt;br /&gt;
		ctr = 20 &lt;br /&gt;
	end&lt;br /&gt;
	for i = 1, ctr do&lt;br /&gt;
		local varname = reader:r_stringZ()&lt;br /&gt;
		local tn = reader:r_u8()&lt;br /&gt;
		if tn == pstor_number then&lt;br /&gt;
			pstor[varname] = reader:r_float()&lt;br /&gt;
		elseif tn == pstor_string then&lt;br /&gt;
			pstor[varname] = reader:r_stringZ()&lt;br /&gt;
		elseif tn == pstor_boolean then&lt;br /&gt;
			pstor[varname] = reader:r_bool()&lt;br /&gt;
		else&lt;br /&gt;
			-- не надо пытаться вылетать - просто не пишем поврежденные данные&lt;br /&gt;
			-- при этом обязательно удалять саму переменную - в результате записи&lt;br /&gt;
 			-- мусора в пстор одно только ее название может повесить загрузку&lt;br /&gt;
			pstor[varname] = nil&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Эти примеры приведены для чистой игры. Единственное в чем не уверен пока, это в том, что максимум полезного размера - 20 слов. Возможно нужно выделить больше, это надо будет проверить ещё экспериментальным путем...&lt;br /&gt;
&lt;br /&gt;
Кроме этого, советую ещё внутрь ф-ции abort вставить отладочные метки, чтобы точно знать когда она вызывалась. Настоятельно советую сделать это даже если вы матёрый моддер со стажем - гарантирую, будете неприятно удивлены.&lt;br /&gt;
&lt;br /&gt;
Я это сделал вот так:&lt;br /&gt;
&lt;br /&gt;
''_g.script''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- Крешнуть игру (после вывода сообщения об ошибке в лог)&lt;br /&gt;
function abort(fmt, msg)&lt;br /&gt;
	local message = tostring(msg)&lt;br /&gt;
	dbglog(&amp;quot;ERROR PATTERN: &amp;quot;..tostring(fmt))&lt;br /&gt;
	dbglog(&amp;quot;ERROR REASON: &amp;quot;..message)&lt;br /&gt;
	local reason = string.format(fmt, message)&lt;br /&gt;
	assert(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	printf(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	dbglog(&amp;quot;%s&amp;quot;, reason)&lt;br /&gt;
	printf(&amp;quot;%s&amp;quot;)&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
По поводу частичной неработоспособности ф-ции '''abort''' я беседовал с Колмогором, и он пришёл к выводу, что видимо вылет игры должен был бы производиться при обработке функции '''printf(&amp;quot;%s&amp;quot;)''' - ей тут передаётся заведомо отсутствующий оператор и она по уму должна бы сразу крашить игру. Однако в релизе функция printf фактически не работает(она реализована в _g.script через вырезанную функцию log). В результате игра не крашится. Но что поделать, в итоге мне пришлось модифицировать его таким образом, чтобы вылет при его срабатывании был гарантирован:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- Крешнуть игру (после вывода сообщения об ошибке в лог)&lt;br /&gt;
function abort(fmt, msg)&lt;br /&gt;
	local message = tostring(msg)&lt;br /&gt;
	dbglog(&amp;quot;ERROR PATTERN: &amp;quot;..tostring(fmt))&lt;br /&gt;
	dbglog(&amp;quot;ERROR REASON: &amp;quot;..message)&lt;br /&gt;
	local reason = string.format(fmt, message)&lt;br /&gt;
	assert(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	printf(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	dbglog(&amp;quot;%s&amp;quot;, reason)&lt;br /&gt;
	printf(&amp;quot;%s&amp;quot;)&lt;br /&gt;
	local crash&lt;br /&gt;
	local ooops = 1/crash&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Вылет происходит при попытке произвести арифметическую операцию с неинициализированной переменной crash.&lt;br /&gt;
&lt;br /&gt;
== Лечение зависаний алайфа при смерти персонажей ==&lt;br /&gt;
&lt;br /&gt;
Недавно, отлаживая проблемы с зависанием алайфа, мне удалось найти причину этого периодически во всех модах всплывающего сбоя, приводящего к порче сейвов и сильно мешающего нормально играть. Сбой этот возникает при смерти некоторых NPC, обычно квестовых. В частности в моём случае изолировать и отладить это зависание удалось на Юрике, новичке со Свалке, учавствующем в сцене с гоп-стопом. Причина оказалась в обработке посмертной отрегистрации NPC из гулагов, причём сбой там был настоящей матрёшкой, составной из нескольких частей. Правок в итоге было совсем немного, но чтобы сделать их мне пришлось несколько часов распутывать клубок из кросс-вызовов между скриптами smart_terrain и xr_gulag. Итак, начнём с самого начала. Работая над OGSE 069 и 0691 я периодически сталкивался с зависаниями и вылетами в посмертных обработках неписей. Один из таких вылетов - всем хорошо знакомый вылет:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;smart_terrain:1143 &amp;quot;attempt to index a nil value&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Происходящий в функции '''smart_terrain.on_death( obj_id )''' - я тогда его заблокировал вызовом его внутри безопасного кода функцией '''pcall''', однако, как теперь выяснилось, этого оказалось недостаточно - баг тут состоит из нескольких частей, и этот вылет указывает только на одну из них. Вот исходный код:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj     = sim:object( obj_id )&lt;br /&gt;
		local strn_id = obj:smart_terrain_id()  --- первый вылет/зависнаие алайфа происходит тут&lt;br /&gt;
&lt;br /&gt;
		if strn_id ~= 65535 then&lt;br /&gt;
			sim:object( strn_id ).gulag:clear_dead(obj_id) -- а вот в этой обработке происходит зависание алайфа. Она очень комплексная, и её сложно распутывать.&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Во-первых выяснилось, что изредка вызов '''obj:smart_terrain_id()''' вызывает зависание алайфа даже когда он производится изнутри защищённого кода. Тогда я решил избавиться от использования этой функции в данном месте совсем. После нескольких экспериментов выяснилось, что самым простым, быстрым и вылетобезопасным способом будет считать нетпакет существа в таблицу и выудить идентификатор смарттеррейна из неё. Для этого можно написать свою обработку, однако я, как весьма ленивый программист, не склонен изобретать велосипеды, поэтому я воспользовался уже проверенной у нас и активно используемой в OGSE библиотекой функций для работы с нетпакетами '''m_net_utils''' Артоса. Кроме того, я сразу сделал более безопасным вызов обработки на отрегистрацию в гулагах. Вот, собственно, что в итоге получилось:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj = sim:object( obj_id )&lt;br /&gt;
		if (obj and obj.smart_terrain_id) then&lt;br /&gt;
			local strn_id = 65535  -- значение по умолчанию&lt;br /&gt;
			&lt;br /&gt;
			local t = nil -- сюда запихнём табличку из пакета&lt;br /&gt;
			if IsStalker(obj) then t = m_net_utils.get_stalker_data(obj) elseif IsMonster(obj) then t = m_net_utils.get_monster_data(obj) end &lt;br /&gt;
			-- вызываем парсинг пакета для неписей и монстров отдельно&lt;br /&gt;
			&lt;br /&gt;
			-- print_table_inlog(t)&lt;br /&gt;
			if t.smtrid then&lt;br /&gt;
				strn_id = tonumber(t.smtrid) -- получаем идентификатор смарта, если его нету даже в пакете, хрен с ним, будет 65535&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
			if strn_id ~= 65535 then -- если сняли идентификатор, попробуем отрегать...&lt;br /&gt;
				local gulag = sim:object(strn_id)&lt;br /&gt;
				if gulag and gulag.gulag then -- ...но сначала выясним если вообще такой гулаг и инициализирован ли он&lt;br /&gt;
					sim:object(strn_id).gulag:clear_dead(obj_id)&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При этом сделаю отсупление и предупрежу об одном очень странном сбое с которым я столкнулся редактируя эту функцию. Так вот, всё нормально работает только тогда когда у ф-ции этой есть строго определённая структура. '''Стоит только добавить пару строк, убрать закомментированную и сдвинуть пару условий, просто в тексте сдвинуть, не меняя внутренней логики, как игра начинает вылетать, причём ещё до загрузки сейва, при кэшировании (!) и с очень странными логами, хотя код написан синтаксически безупречно! Например с таким:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Description: xr_gulag:1035 value not found ObjectJobPathName[obj_id]&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Вернёшь строки на место, перестроишь текст - работает снова. Совершенная чертовщина, шаманил я над этой функцией около получаса, перестраивая текст таким образом чтобы игра нормально запускалась.''' Имейте это в виду когда в неё полезете и если что не пугайтесь если игра начнёт так вылетать - в этом случае пошаманьте немного над ней, меняя её форматирование. О причинах такого поведения я не могу даже догадываться - то ли движок её вызывает по смещению внутри файла вручную, то ли это какой-то баг Lua-парсера, но факт фактом.&lt;br /&gt;
&lt;br /&gt;
Итак, теперь проблема с получением идентификатора смарта разрешилась, однако алайф всё равно зависал! Простая трассировка показала, что теперь зависание происходило внутри обработки&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;sim:object(strn_id).gulag:clear_dead(obj_id)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И мне пришлость распутывать её по частям, доискиваясь до причины. Обработка честно говоря мерзкая, размазана по smart_terrain и xr_gulag, при этом взаимные вызовы идут не менее десятка раз, в следующем стиле: ф-ция в смарте вызывает ф-цию в гулаге, которая вызывает фцию в смарте, которая обрабатывает параметр фцией в гулаге, который передётся ф-цией в логике, которая получает её из смарта. Нечто подобное. Опуская все нецензурные выражения, употребленные мной при трассировке, я лучше расскажу что собственно вышло в итоге. А в итоге я вышел вот на этот код в xr_gulag, именно в нём при отрегистрации некоторых мёртвых неписей происходит зависание алайфа:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- освободить объект от работы и переинициализировать логику.&lt;br /&gt;
-- если сталкер в онлайне и начал работу, то сбросить его схему поведения&lt;br /&gt;
-- как будто он только что загрузился&lt;br /&gt;
function gulag:free_obj_and_reinit( obj_id )&lt;br /&gt;
	self:free_obj(obj_id)&lt;br /&gt;
&lt;br /&gt;
	local t = self.Object[obj_id]&lt;br /&gt;
	if t ~= nil and t ~= true and self.Object_begin_job[obj_id] then&lt;br /&gt;
		xr_logic.initialize_obj( t, nil, false, db.actor, self:get_stype( obj_id ) ) -- вот эта обработка вешает алайф&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Я, честно говоря, ни разу не понимаю, на кой чёрт нужно переконфигурировать схемы логики и выбирать из них активную '''трупу, который лежит себе спокойно и никого не трогает.''' Может быть в это есть некий высший смысл, или это было продиктовано неким аккуратизмом, однако одно я могу сказать однозначно - подобная переинициализация у некоторых трупов неписей приводит к глухому зависанию алайфа. При этом если для трупов эту обработку заблокировать, то трупы разрегистрируются вполне нормально и спокойно лежат с нетронутой логикой, не вызывая никаких проблем. Итоговый код после правок выглядит вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- освободить объект от работы и переинициализировать логику.&lt;br /&gt;
-- если сталкер в онлайне и начал работу, то сбросить его схему поведения&lt;br /&gt;
-- как будто он только что загрузился&lt;br /&gt;
function gulag:free_obj_and_reinit( obj_id )&lt;br /&gt;
	self:free_obj(obj_id)&lt;br /&gt;
	local t = self.Object[obj_id]&lt;br /&gt;
	if t ~= nil and t ~= true and self.Object_begin_job[obj_id] then&lt;br /&gt;
		if check_game() then -- тут проверяется, запущена ли игра, если ли актор и жив ли он. Если да, делаем по новому.&lt;br /&gt;
			local s_obj = alife():object(obj_id) -- проверим есть ли у цели разрегистрации валидный серверный объект&lt;br /&gt;
			if s_obj and (IsStalker(s_obj) or IsMonster(s_obj)) and s_obj:alive() then -- если есть, он жив и сталкер или монстр&lt;br /&gt;
				xr_logic.initialize_obj( t, nil, false, db.actor, self:get_stype( obj_id ) ) -- только тогда инициализируем логику&lt;br /&gt;
			end&lt;br /&gt;
		else -- а если игра не запущена, то как раньше. Это нужно для того, чтобы обработка запуска игры нормально работала.&lt;br /&gt;
			xr_logic.initialize_obj( t, nil, false, db.actor, self:get_stype( obj_id ) )&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Проверка, запущена ли игра&lt;br /&gt;
function check_game()&lt;br /&gt;
	if level.present() and (db.actor ~= nil) and db.actor:alive() then&lt;br /&gt;
		return true&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
После этих поправок зависания алайфа при смерти неписей удалось побороть окончательно. Решение с отрезанием реинита логики для трупов несколько грубовато, но честно говоря, у меня нет ни малейшего желания трассировать и разбирать на части функцию xr_logic.initialize_obj, выясняя, чем же ей так данный конкретный труп не приглянулся. Если хотите - займитесь, найдёте причину - дополните данную статью. Я же успокоился на том, что заблокировал баг, периодически убивающий людям игру, так как многие пользователи, игнорируя предупреждения, играют на одном-двух сейвах, а то и вовсе на квиксейвах всю игру.&lt;br /&gt;
&lt;br /&gt;
= Другие частые проблемы =&lt;br /&gt;
&lt;br /&gt;
Спустя некоторое время я обнаружил причины ещё нескольких часто встречающихся вылетов, и решил записать их описание сюда - эта информация наверняка ещё много кому пригодится.&lt;br /&gt;
&lt;br /&gt;
== Вылеты при удалении объектов из игры ==&lt;br /&gt;
&lt;br /&gt;
При использовании для удаления объектов родной движковой функции '''alife():release(alife():object(id), true)''' возможен целый ворох разнообразнейших вылетов, обычно - безлоговых, что сильно затрудняет их отладку. Вот из-за чего они возникают:&lt;br /&gt;
&lt;br /&gt;
1) Вылет при удалении непися или монстра, находящегося в онлайне.&lt;br /&gt;
   Решение: с помощью alife():release '''можно удалять только мёртвые объекты'''. Поэтому если вам нужно удалить с её помощью непися или монстра, который жив и находится в онлайне, его нужно убить любым доступным методом, хотя бы нанеся ему hit() с любым запредельным уроном по вкусу.&lt;br /&gt;
&lt;br /&gt;
2) Вылет при удалении оружия или артефакта.&lt;br /&gt;
   Решение: такая проблема часто встречается в случае если объект неудачно расположен или находится в руках у непися. Для того чтобы не произошло вылета, убедитесь что объект доступен как серверный перед удалением. Вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;	local obj = alife():object(i)&lt;br /&gt;
	if obj then&lt;br /&gt;
		alife():release(obj, true)&lt;br /&gt;
	end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Эту конструкцию вообще желательно использовать всегда, когда вы так удаляете объекты.&lt;br /&gt;
&lt;br /&gt;
3) Вылет при удалении аномалии.&lt;br /&gt;
   Решение: аномалии - очень капризные при подобном с ними обращении объекты. Они влияют на своё окружение, и если рядом с ними находится непись или монстр, то удаление такой аномалии приведёт к вылету игры. Чтобы этого не произошло, аномалию надо сначала выключить функцией '''disable_anomaly''', и удалять затем ТОЛЬКО тогда, когда она не будет занята влиянием на динамический объект. Для этого нужно получить список мобов на локации, и из их нетпакетов считать идентификаторы действующих на них рестрикторов. Если ваша аномалия будет в этом списке - удалять её нельзя. Дождитесь пока она освободится.&lt;br /&gt;
&lt;br /&gt;
== Вылет при открытии закладки &amp;quot;Контакты&amp;quot; в ПДА ==&lt;br /&gt;
&lt;br /&gt;
Простой безлоговый вылет при открытии закладки &amp;quot;Контакты&amp;quot;. Встречался во всех крупных модах, и никто не знал как его излечить. А лечится он банально - '''его причина - дублирование идентификаторов секций в XML-файле, описывающем иконки неписей для закладки &amp;quot;Контакты&amp;quot;'''. Нужно всего лишь проверить этот файл на наличие дублированных идентификаторов и удалить их. Вылет пропадёт и никогда больше не будет встречаться.&lt;br /&gt;
&lt;br /&gt;
== Вылеты при вызове несуществующих функций из XML ==&lt;br /&gt;
&lt;br /&gt;
В ХML-файлах, используемых для описания инфопоршенов, для многих инфопоршенов прописаны действия, которые игра вызывает при взятии этого инфопоршена. Вот так примерно:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
	&amp;lt;info_portion id=&amp;quot;barman_document_have&amp;quot;&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;dialogs.set_actor_prebandit1&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandits2&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandits3&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandit7&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandit8&amp;lt;/action&amp;gt;&lt;br /&gt;
	&amp;lt;/info_portion&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так вот, если вы допустите опечатку в названии вызываемой функции или же случайно её удалите - игра будет стабильно вылетать без лога при взятии этого инфопоршена.&lt;br /&gt;
&lt;br /&gt;
== Повреждения сейвов на Радаре и других местах в модах, основанных на OGSM ==&lt;br /&gt;
&lt;br /&gt;
В оригинале ОГСМ и основанных на нём модах часто встречались проблемы с сохранениями на Радаре. Эту проблему долго не удавалось победить, пока наконец благодаря помощи Маландринуса не удалось выявить её первопричину. Как выяснилось, она очень проста - гражданские зомби в моде (монстры) имели в конфиге ту же пропись вида (параметр конфига specie), что и монолитовцы и зомбированные (неписи). И там и тут было проставлено &amp;quot;zombie&amp;quot;, и так оно было ещё с оригинала. Как оказалось, так делать категорически нельзя. Дело в том, что у неписей есть такой функционал, как хитовая память - в ней какое-то время хранятся ссылки на атакующие объекты. У монстров тоже есть остатки этого функционала, но он неработоспособен, и использовать его нельзя. В случае же когда монстры и неписи попадают в один вид, в ситуации когда они находятся рядом в бою, хитовая память монстров автоматически получает от неписей того же вида распространяемую внутри вида информацию об атакующих - а хранить её монстрам нельзя. Если после создания такой ситуации сохраниться - сейв будет вызывать вылет при загрузке. То есть проще говоря, если в бою с монолитовцами рядом оказывались гражданские зомби - и игрок сохранял игру - сейв этот не загружался. Чтобы предотвратить эти проблемы, вполне достаточно создать для гражданских зомби свой отдельный вид, добавив его прописи в конфиг game_relations.&lt;br /&gt;
&lt;br /&gt;
== Застывания NPC после боя / лечения ранения в модах, использующих дополнительные схемы поведения ==&lt;br /&gt;
&lt;br /&gt;
Во многих модах, использующих дополнительные схемы поведения часто можно заметить NPC, которые после боя застывают, прицелившись в одну точку. Аналогично часто это встречается с вылеченными от ранения NPC - они встают и замирают намертво, до тех пор пока их не выведет из этого состояния атаковавший враг. Как правило сейв/загрузка этой проблемы не решают. В ходе работы над OGSE 0.6.9.3 мне удалось выяснить причину таких проблем и успешно её устранить. Причина проблемы заключается в том, что NPC управляются не только скриптовыми схемами, но и движком, и для переключения управления между одним и другим используется скрипт ''state_mgr'' - менеджер состояний. Его директивы имеют наивысший приоритет. Аналогичный же приоритет себе как правило назначают доп. схемы поведения, используя пропись эвалуатора в ''xr_motivatior.addCommonPrecondition(action)''. В итоге при выходе из движковой боевки или при переключении на движковый алайф (это происходит после излечения ранения) доп. схемы поведения вступают с менеджером состояний в конфликт, блокируя смену состояния NPC. Для того чтобы этого не происходило, '''необходимо во всех схемах поведения, использующих принудительное назначение состояния через функцию ''state_mgr.set_state'' сделать блокировку перехвата управления менеджером состояний''', добавив соответствующие прописи в биндер. Вот таким образом, как в этом примере - тут я правил биндер схемы лечения/самолечения ''xrs_medic'':&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;&lt;br /&gt;
function add_to_binder(object, ini, scheme, section, storage)&lt;br /&gt;
	local operators	= {}&lt;br /&gt;
	local properties  = {}&lt;br /&gt;
&lt;br /&gt;
	local manager = object:motivation_action_manager()&lt;br /&gt;
&lt;br /&gt;
	operators[&amp;quot;medic&amp;quot;]			= actid_medic&lt;br /&gt;
	operators[&amp;quot;self_medic&amp;quot;]		= actid_self_medic&lt;br /&gt;
&lt;br /&gt;
	properties[&amp;quot;medic&amp;quot;]			= evid_medic&lt;br /&gt;
	properties[&amp;quot;self_medic&amp;quot;]	= evid_self_medic&lt;br /&gt;
	&lt;br /&gt;
	local state_mgr_to_idle_combat = xr_actions_id.state_mgr + 1 ---&amp;lt; Это переключение на движковую боевку, но оно тут не использовано&lt;br /&gt;
	local state_mgr_to_idle_alife = xr_actions_id.state_mgr + 2 ---&amp;lt; Это переключение на движковый алайф&lt;br /&gt;
	local state_mgr_to_idle_off = xr_actions_id.state_mgr + 3   ---&amp;lt; Это переключение в статичное состояние&lt;br /&gt;
&lt;br /&gt;
	local zombi=object:character_community()==&amp;quot;zombied&amp;quot; or object:character_community()==&amp;quot;trader&amp;quot; or&lt;br /&gt;
		  object:character_community()==&amp;quot;arena_enemy&amp;quot; or object:name()==&amp;quot;mil_stalker0012&amp;quot; or object:name()==&amp;quot;yantar_ecolog_general&amp;quot;&lt;br /&gt;
&lt;br /&gt;
	if zombi then&lt;br /&gt;
		manager:add_evaluator (properties[&amp;quot;medic&amp;quot;], property_evaluator_const(false))&lt;br /&gt;
		manager:add_evaluator (properties[&amp;quot;self_medic&amp;quot;], property_evaluator_const(false))&lt;br /&gt;
	else&lt;br /&gt;
		manager:add_evaluator (properties[&amp;quot;medic&amp;quot;], evaluator_medic(&amp;quot;medic&amp;quot;, storage))&lt;br /&gt;
		manager:add_evaluator (properties[&amp;quot;self_medic&amp;quot;], evaluator_self_medic(&amp;quot;self_medic&amp;quot;, storage))&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	local action = action_medic (object,&amp;quot;medic&amp;quot;, storage)&lt;br /&gt;
	action:add_precondition(world_property(stalker_ids.property_alive, true))&lt;br /&gt;
	action:add_precondition(world_property(xr_evaluators_id.sidor_wounded_base, false))&lt;br /&gt;
	action:add_precondition	(world_property(properties[&amp;quot;medic&amp;quot;], true))&lt;br /&gt;
	action:add_effect (world_property(properties[&amp;quot;medic&amp;quot;], false))&lt;br /&gt;
	manager:add_action (operators[&amp;quot;medic&amp;quot;], action)&lt;br /&gt;
	&lt;br /&gt;
	local action = action_self_medic (object,&amp;quot;self_medic&amp;quot;, storage)&lt;br /&gt;
	action:add_precondition(world_property(stalker_ids.property_alive, true))&lt;br /&gt;
	action:add_precondition(world_property(stalker_ids.property_enemy,false))	&lt;br /&gt;
	action:add_precondition(world_property(xr_evaluators_id.sidor_wounded_base, false))&lt;br /&gt;
	action:add_precondition	(world_property(properties[&amp;quot;medic&amp;quot;], false))&lt;br /&gt;
	action:add_precondition	(world_property(properties[&amp;quot;self_medic&amp;quot;], true))&lt;br /&gt;
	action:add_effect (world_property(properties[&amp;quot;self_medic&amp;quot;], false))&lt;br /&gt;
	manager:add_action (operators[&amp;quot;self_medic&amp;quot;], action)&lt;br /&gt;
		&lt;br /&gt;
	action = manager:action (stalker_ids.action_alife_planner)	&lt;br /&gt;
	action:add_precondition	(world_property(properties[&amp;quot;medic&amp;quot;], false))&lt;br /&gt;
	action:add_precondition	(world_property(properties[&amp;quot;self_medic&amp;quot;], false))&lt;br /&gt;
	&lt;br /&gt;
	action = manager:action(state_mgr_to_idle_alife)&lt;br /&gt;
	action:add_precondition	(world_property(properties[&amp;quot;self_medic&amp;quot;], false)) ---&amp;lt; Блокируем попытки переключиться на движковый алайф пока работает самолечение&lt;br /&gt;
&lt;br /&gt;
	action = manager:action(state_mgr_to_idle_off)&lt;br /&gt;
	action:add_precondition	(world_property(properties[&amp;quot;self_medic&amp;quot;], false)) ---&amp;lt; Блокируем попытки переключиться статичное состояние пока работает самолечение&lt;br /&gt;
&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Вставка этих директив в биндеры всех схем, которые меняют состояния принудительно устраняет конфликты, и NPC больше не виснут при смене схем.&lt;br /&gt;
&lt;br /&gt;
--[[Участник:Kamikazze|KamikaZze (OGSE Team)]] 11:04, 31 марта 2011 (UTC)&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[Как писать скрипты, не приводящие к вылетам и бою сейвов|Начало в первой части статьи]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
= Авторы =&lt;br /&gt;
Статья создана: [[Участник:Kamikazze|Kamikazze]]&lt;br /&gt;
&lt;br /&gt;
[[Категория:Скрипты]]&lt;/div&gt;</summary>
		<author><name>Kamikazze</name></author>	</entry>

	<entry>
		<id>http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2_(%D1%87%D0%B0%D1%81%D1%82%D1%8C_2)</id>
		<title>Как писать скрипты, не приводящие к вылетам и бою сейвов (часть 2)</title>
		<link rel="alternate" type="text/html" href="http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2_(%D1%87%D0%B0%D1%81%D1%82%D1%8C_2)"/>
				<updated>2011-03-31T11:56:34Z</updated>
		
		<summary type="html">&lt;p&gt;Kamikazze: Добавлено решение проблемы зависания NPC, дополнение&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;----&lt;br /&gt;
&lt;br /&gt;
[[Как писать скрипты, не приводящие к вылетам и бою сейвов|Начало в первой части статьи]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Как безопасно использовать коллбэки и таймерные события =&lt;br /&gt;
&lt;br /&gt;
В скриптовом движке есть удобный способ реакции на события - использование коллбэков - процедурных вызовов, привязанных к определённым событиям в жизни игрового объекта - получению повреждений, спавну, смерти и т.д. и т.п. Это hit_callback, death_callback из xr_motivator и многие другие... Все моддеры очень широко и совершенно спокойно пользуются ими, совершенно забывая при этом о таком важном факте, что это - обработки реального времени, как и таймерные события. Что это значит? А собственно вот что... Возьмём для примера коллбэк смерти неписей, мою головную боль последнего времени:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;xr_motivator.script -&lt;br /&gt;
function motivator_binder:death_callback(victim, who)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это функция обрабатывает смерть неписей. Когда вызывается этот коллбек он начинает по очереди вызывать внутренние функции, расположенные в других модулях. Они разрегистрируют умершего непися в гулагах, спавнят в него лут, обновляют статистику, отключают его от доп. схем логики и т.д.&lt;br /&gt;
&lt;br /&gt;
Так вот, обычно, когда в функциях возникает конфликт параметров, бесконечный цикл, попытка индексации nil и т.д., функция вылетает со стандартным логом. '''Но только не в случае, когда она находится внутри коллбэка. Если функция внутри него, то в случае возникновении в ней любой нештатной ситуации, коллбэк наглухо виснет.''' Это происходит из-за того, что функции вызываются строго друг за другом, и каждая из них вызывается только тогда, когда её предшественница вернула управление коллбэку. В случае с death_callback опознать такого непися очень просто - в его трупе окажется фонарик, КПК и возможно ещё немного разных &amp;quot;мусорных&amp;quot; вещей, что говорит о том, что обработка его смерти повисла не дойдя даже до спавна лута. В подобной ситуации можно быть на 100% уверенным, что труп этот не был корректно разрегистрирован, и игра всё ещё считает его живым неписем. Кроме того, зависший коллбэк не освобождает стек (а он у Луа-подсистемы единый на все скрипты), что '''в итоге приводит к вылетам игры с переполнением памяти (вот она, реальная причина этих &amp;quot;родных&amp;quot; вылетов).''' Но было бы слишком хорошо, если бы всё ограничивалось этим... однако тут всё намного хуже... такие &amp;quot;зависшие&amp;quot; коллбэки, особенно если их произошло несколько подряд, очень серьёзно влияют на работу а-лайфа. В лучшем случае они, забивая, стек, мешают нормально работать схемам логики, в худшем вызывают зависания самого  а-лайфа (этот эффект, кстати, производят и сами трупы таких неписей, так как они, как мы помним, не разрегистрировались корректно). '''Основной итог таких событий - бой сейвов, сделанных после возникновения таких ситуаций.''' Если повис один коллбэк, то такие сейвы ещё через раз загружаются, если же несколько, и остановилась работа а-лайфа - всё, сейвы бьются наглухо и реанимации не подлежат.&lt;br /&gt;
&lt;br /&gt;
Поэтому, чтобы избежать такого развития событий, каждый раз, когда вы вносите в коллбэк новую функцию - проверьте её самым тщательным образом. Она не должна содержать никаких рекурсивных циклов, в ней обязательно должны быть проверки на валидность обрабатываемых объектов и значений, и обязательно должна быть обработка нештатных ситуаций - т.е. функция должна обязательно, в абсолютно любой ситуации вернуть управление коллбэку, так или иначе. В самом наихудшем случае - делайте как делали разработчики игры - вставляйте принудительный вылет на рабстол функцией abort - она позволяет передавать отладочное сообщение, и это всяко лучше чем незаметный бой сейвов. Если обработка оборвалась в самом начале, а ф-ция обязательно должна вернуть значение - заведите ей &amp;quot;безопасное&amp;quot; возвращаемое значение по-умолчанию, которое она будет выдавать, если всё пошло плохо. И никогда не пренебрегайте пошаговой отладкой коллбэков с выводом в лог, особенно когда пишете схемы логики - это критически важно для стабильности вашего мода.&lt;br /&gt;
&lt;br /&gt;
== Основные внутриигровые признаки зависания коллбэков типа hit_callback, death_callback ==&lt;br /&gt;
&lt;br /&gt;
1. В трупах попадаются фонарики, КПК, разный мусор и общий лут слишком богат.&lt;br /&gt;
&lt;br /&gt;
2. Частые вылеты во время интенсивных боёв с логами типа&lt;br /&gt;
    Sheduler tried to update object...&lt;br /&gt;
    smart_terrain:1145(1146)&lt;br /&gt;
    LUA: out of memory&lt;br /&gt;
    любой_модуль_логики:любая_cтрока - stack overflow&lt;br /&gt;
&lt;br /&gt;
3. Частые &amp;quot;родные&amp;quot; вылеты в момент смерти непися или попадания по нему&lt;br /&gt;
&lt;br /&gt;
4. Произвольно бьются сейвы во время сражений, выброса и других насыщенных действиями событий&lt;br /&gt;
&lt;br /&gt;
= Использование защищённого кода в LUA =&lt;br /&gt;
&lt;br /&gt;
Периодически случаются такие ситуации, когда мы можем получить вылет при проверке аргумента, и не можем его адекватно заизолировать с помощью предварительной проверки на валидность значения. Вот простой пример: когда я отлаживал '''death_callback''' неписей, я периодически сталкивался с тем, что обращение к методу '''smart_terrain_id()''' при смерти непися иногда вызывало вылет &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;smart_terrain:1143 &amp;quot;attempt to index a nil value&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
- хотя это свойство является родным методом объекта, и отсутствовать напрочь никак не может. В итоге я пришёл к выводу, что его просто иногда не успевает отработать движок, так как вылет этот проявлялся в основном в интенсивных боях и совершенно произвольно.&lt;br /&gt;
&lt;br /&gt;
Вот код, в котором происходил вылет:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj     = sim:object( obj_id )&lt;br /&gt;
		local strn_id = obj:smart_terrain_id()  --- вылет происходит тут&lt;br /&gt;
&lt;br /&gt;
		if strn_id ~= 65535 then&lt;br /&gt;
			sim:object( strn_id ).gulag:clear_dead(obj_id)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это кусок родного кода из версии 1,0005 игры. Я долго пытался разными способами отсечь этот вылет, вводя предварительные проверки, однако это совершенно ничего не давало - вылет всё равно периодически случался, так как проверки эти сами его вызывали. Тогда я зарылся в документацию по Lua и обнаружил замечательную родную базовую функцию, введённую ещё с первых версий Lua, которой почему-то не пользовались ни разработчики игры, ни моддеры (хотя сама она в Lua сталкера присутствует, и работает отлично, без каких-либо нареканий). Вот она:&lt;br /&gt;
&lt;br /&gt;
'''pcall (f, arg1, ···)'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Вызывает функцию f с указанными через запятую аргументами в защищённом режиме. Это означает, что любая ошибка, даже критическая, внутри вызванной функции, не передаётся наружу - вызывавшей подсистеме. Вместо этого '''pcall''' перехватывает ошибку и возвращает код статуса. Первая возвращаемая переменная это сам код, (true или false) и если всё прошло хорошо, он равен true. В этом случае '''pcall''' сразу после статуса возвращает все результаты от работы защищённой им функции. Если же в защищённой функции произошла ошибка, то '''pcall''' вернёт false и затем сообщение об ошибке. (Обратите внимание, обработка ошибки присходит БЕЗ вылета! Вместо вылета вы получите вполне адекватную строку с ошибкой, которую можно вывести в лог для последующей обработки)&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Я настоятельно советую пользоваться этой функцией в случаях, когда из коллбэков вызываются сложные комплексные обработки, вроде обработки из менеджера вооружений AI-пака. Это позволяет предотвратить как вылеты, так и зависания обработок, и в итоге позволяет хорошо стабилизировать игру.&lt;br /&gt;
&lt;br /&gt;
Возвращаясь к нашим смарттеррейнам... вот как в итоге я подавил вылет типа smart_terrain:1143 с помощью pcall:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;--- эта функция пытается проверить св-во smart_terrain_id объекта. Именно её мы вызовем в защищённом режиме.&lt;br /&gt;
function prot_smt_td(obj)&lt;br /&gt;
	if IsStalker(obj) or IsMonster(obj) then&lt;br /&gt;
		return obj:smart_terrain_id()&lt;br /&gt;
	else	&lt;br /&gt;
		return 65535&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj = sim:object( obj_id )&lt;br /&gt;
		if obj then&lt;br /&gt;
			local strn_id = 65535  --- предварительно проинитим переменную, на &lt;br /&gt;
						--- случай если у нас prot_smt_td выдаст ошибку&lt;br /&gt;
			local result, smt_id = pcall(prot_smt_td,obj)	--- вызываем prot_smt_td в защищённом режиме &lt;br /&gt;
									--- и сразу присваиваем его вывод переменным&lt;br /&gt;
			if result then --- если pcall выдало true&lt;br /&gt;
				strn_id = smt_id  --- тогда применяем полученное значение&lt;br /&gt;
			end&lt;br /&gt;
			--- если же обработка выдаст ошибку, то strn_id останется неизменным...&lt;br /&gt;
			if strn_id ~= 65535 then&lt;br /&gt;
				sim:object(strn_id).gulag:clear_dead(obj_id)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В других местах это делается совершенно аналогично. Подробнее об этой и многих других функция для контроля кода, незаслуженно игнорируемых большинством моддеров, можно почитать тут: http://lua-users.org/wiki/FinalizedExceptions - на английском правда, но захотите - разберётесь, там всё просто. &lt;br /&gt;
&lt;br /&gt;
Настоятельно советую вам изучить работу кода в таких условиях и научиться его правильно применять - этим вы сильно облегчите жизнь как себе, так и тем, кто после вас будет рыться в вашем коде или импортировать его в свои разработки.&lt;br /&gt;
&lt;br /&gt;
= Скрытые критические проблемы в обработке вылетов игрой =&lt;br /&gt;
&lt;br /&gt;
Ведя на днях отладку, выяснил в чём проблема с периодическим боем сейвов и многими другими заморочками как в оригинале игры, так и во многих модах... дело, как выяснилось, далеко не всегда в кривых руках. Есть такая стандартная ф-ция '''abort''' - предназначенная для выкидывания из игры, если что-то пошло не так. И как оказалось, она срабатывает далеко не всегда. Выяснилось это следующим образом:&lt;br /&gt;
&lt;br /&gt;
В одном из логов нашего бета-тестера я увидел '''стандартное сообщение о вылете внутри рабочего лога'''... да-да, то самое которое '''FATAL ERROR''' и дальше по тексту. При этом игра у него НЕ вылетала, это сообщение об ошибке мы обнаружили позже, по случайности. Я заподозрил, что что-то не в порядке, и вставил внутрь этой ф-ции контрольную метку, кидавшую в консоль сообщение, в котором содержался паттерн сообщения об ошибке и само сообщение. Так вот, оказалось, что эта самая функция '''abort''' вызывается в игре с завидным постоянством (вы удивитесь насколько часто), когда возникают исключения в схемах логики, звука и т.д., но игра от этого вылетает на рабочий стол '''максимум только 3 раза из 10 вызовов'''. Вылет НЕ происходит обычно, когда функции передан паттерн ошибки, а остальные параметры пустые, такое бывает, и частенько. И если не сделать внутри этой функции особой метки для вывода в лог, как сделал это я, её вызовы проходят совершенно незаметно, и '''игра после критических ошибок продолжается как ни в чём ни бывало.''' А приводит это вот к чему... Внутри '''xr_logic''' в процедуре записи пстора (хранилища логики и флагов) неписей есть вызовы этого самого аборта в случае если на запись в пстор передана некорректная величина. Ну а так как аборт периодически вообще не срабатывает, то часто попадается ситуация, что неписям в пстор пишется полный ахтунг: куски кода из ОЗУ, всякая муть из лтх-ов, куски аллспавна, всё что угодно. Происходит это оттого, что кодер, писавший эту функцию ('''xr_logic.pstor_store(obj, varname, val)'''), явно и думать не думал что '''abort''' может не сработать. У него запись в пстор стояла после проверки, а не внутри неё (very bad idea), и если abort не срабатывал, игра писала в пстор мусор совершенно спокойно и незаметно для игрока. Потом вся эта хрень попадала прямо в сейвы. Вот проблемный код для наглядности:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_store(obj, varname, val)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	if db.storage[npc_id].pstor == nil then&lt;br /&gt;
		db.storage[npc_id].pstor = {}&lt;br /&gt;
	end&lt;br /&gt;
	local tv = type(val)&lt;br /&gt;
	if not pstor_is_registered_type(tv) then&lt;br /&gt;
		abort(&amp;quot;xr_logic: pstor_store: not registered type '%s' encountered&amp;quot;, tv) --- вот тут мы должны если что вылететь&lt;br /&gt;
	end&lt;br /&gt;
	db.storage[npc_id].pstor[varname] = val -- а если не вылетели, всё, получим запись в пстор левой мути&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Разумеется игра этим пстором в итоге давится, и сейвы сделанные после такой милой записи практически лопаются. Результат - &amp;quot;битые&amp;quot; (на самом деле подлежат реанимации) сейвы. Происходит это потому, что игра из сейва грузит неписям псторы сплошным чтением по словам, пока они не закончатся. В случае же если в псторе обнаруживается записанный ранее мусор, то обработка либо вылетает сразу, либо наглухо виснет, пытаясь запихать эдак с миллион слов в пстор особо отличившегося непися. Как вам например непись с размером пстора в 1697451 слова? В результате попытки его обработать игра просто на стадии синхронизации выжрала всю доступную ОЗУ и повисла. &lt;br /&gt;
&lt;br /&gt;
Решение этой проблемы оказалось достатоно простым: во-первых я предположил максимальный размер полезной части пстора неписей в 20 слов r_u32() (пока ориентировочно, я ещё уточняю эту величину), и соответственно сделал остановку цикла загрузки пстора для неписей через 20 итераций. Там же, где в цикле стояла проверка на валидность записываемых данных (кстати тоже с вылетом в случае провала проверки), я сделал так, что если параметр не относится к валидному типу данных, то запись параметра в пстор не производится совсем. Это необходимо для того, чтобы если вдруг в сейве обнаружится мусор, то он был бы просто отброшен обработкой. Практика показала, что в итоге такие неписи вполне адекватны и в дальнейшем никаких проблем не вызывают, так как начало их пстора, с нормальными данными, обычно не повреждается - мусор дописывается после них, а не вместо них. &lt;br /&gt;
&lt;br /&gt;
Ну и во-вторых модифицировал запись параметров в пстор, просто убрав запись под основание if-else так, чтобы если параметр неверен, он не записывался совсем. Теперь кстати, очень интересно, сохранились ли те же заморочки с ф-цией '''abort''' в Чистом Небе, и если да, то останутся ли в Зове Припяти?&lt;br /&gt;
&lt;br /&gt;
------------&lt;br /&gt;
&lt;br /&gt;
== Необходимые для стабилизации игры правки в модулях ==&lt;br /&gt;
&lt;br /&gt;
'''Эта правка предотвращает запись в пстор если не сработал аборт:'''&lt;br /&gt;
&lt;br /&gt;
''xr_logic.script''&lt;br /&gt;
&lt;br /&gt;
'''Было:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_store(obj, varname, val)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	if db.storage[npc_id].pstor == nil then&lt;br /&gt;
		db.storage[npc_id].pstor = {}&lt;br /&gt;
	end&lt;br /&gt;
	local tv = type(val)&lt;br /&gt;
	if not pstor_is_registered_type(tv) then&lt;br /&gt;
		abort(&amp;quot;xr_logic: pstor_store: not registered type '%s' encountered&amp;quot;, tv)&lt;br /&gt;
	end&lt;br /&gt;
	db.storage[npc_id].pstor[varname] = val&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Стало:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_store(obj, varname, val)&lt;br /&gt;
	if not obj then return end&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	if db.storage[npc_id].pstor == nil then&lt;br /&gt;
		db.storage[npc_id].pstor = {}&lt;br /&gt;
	end&lt;br /&gt;
	local tv = type(val)&lt;br /&gt;
	if not pstor_is_registered_type(tv) then&lt;br /&gt;
		dgblog(&amp;quot;xr_logic: pstor_store: not registered type encountered - write in pstor_store cancelled&amp;quot;)&lt;br /&gt;
		-- abort убран, так как один хрен не работает. Пусть тогда хотя бы в лог что-то валится.&lt;br /&gt;
	else&lt;br /&gt;
		db.storage[npc_id].pstor[varname] = val&lt;br /&gt;
		-- вот так и только так. Если значение не валидно, ничего не происходит.&lt;br /&gt;
	end	&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''А эта правка выкинет из пстора весь мусор при загрузке сейва, если он как-то в него попал'''&lt;br /&gt;
&lt;br /&gt;
'''Было:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_load_all(obj, reader)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	local pstor = db.storage[npc_id].pstor&lt;br /&gt;
	if not pstor then&lt;br /&gt;
		pstor = {}&lt;br /&gt;
		db.storage[npc_id].pstor = pstor&lt;br /&gt;
	end&lt;br /&gt;
	local ctr = reader:r_u32()&lt;br /&gt;
	for i = 1, ctr do&lt;br /&gt;
		local varname = reader:r_stringZ()&lt;br /&gt;
		local tn = reader:r_u8()&lt;br /&gt;
		if tn == pstor_number then&lt;br /&gt;
			pstor[varname] = reader:r_float()&lt;br /&gt;
		elseif tn == pstor_string then&lt;br /&gt;
			pstor[varname] = reader:r_stringZ()&lt;br /&gt;
		elseif tn == pstor_boolean then&lt;br /&gt;
			pstor[varname] = reader:r_bool()&lt;br /&gt;
		else&lt;br /&gt;
			abort(&amp;quot;xr_logic: pstor_load_all: not registered type N %d encountered&amp;quot;, tn)&lt;br /&gt;
		end&lt;br /&gt;
		printf(&amp;quot;_bp: pstor_load_all: loaded [%s]='%s'&amp;quot;, varname, utils.to_str(pstor[varname]))&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Стало:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_load_all(obj, reader)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	local pstor = db.storage[npc_id].pstor&lt;br /&gt;
	if not pstor then&lt;br /&gt;
		pstor = {}&lt;br /&gt;
		db.storage[npc_id].pstor = pstor&lt;br /&gt;
	end&lt;br /&gt;
	local ctr = reader:r_u32()&lt;br /&gt;
	if tonumber(ctr) &amp;gt; 20 and tostring(obj:name()) ~= &amp;quot;single_player&amp;quot; and npc_id ~= db.actor:id() then&lt;br /&gt;
		-- максимум 20 итераций - это число ещё уточняется, возможно понадобится больше&lt;br /&gt;
                -- если у вас в пстор что-то свое пишется, ориентируйтесь на свои значения&lt;br /&gt;
		-- и обязательно убираем из проверки актора - у него очень толстый пстор, и к тому же&lt;br /&gt;
                -- если уж поврежденным будет его пстор, то тут точно уже ничего не поможет&lt;br /&gt;
		dgblog(&amp;quot;ОБНАРУЖЕН ОБЪЕКТ С ПОВРЕЖДЕННЫМ PSTOR: &amp;quot;..tostring(obj:name())..&lt;br /&gt;
&amp;quot; БУДЕТ ПРОИЗВЕДЕНА ПОПЫТКА ВОССТАНОВЛЕНИЯ&amp;quot;)&lt;br /&gt;
		ctr = 20 &lt;br /&gt;
	end&lt;br /&gt;
	for i = 1, ctr do&lt;br /&gt;
		local varname = reader:r_stringZ()&lt;br /&gt;
		local tn = reader:r_u8()&lt;br /&gt;
		if tn == pstor_number then&lt;br /&gt;
			pstor[varname] = reader:r_float()&lt;br /&gt;
		elseif tn == pstor_string then&lt;br /&gt;
			pstor[varname] = reader:r_stringZ()&lt;br /&gt;
		elseif tn == pstor_boolean then&lt;br /&gt;
			pstor[varname] = reader:r_bool()&lt;br /&gt;
		else&lt;br /&gt;
			-- не надо пытаться вылетать - просто не пишем поврежденные данные&lt;br /&gt;
			-- при этом обязательно удалять саму переменную - в результате записи&lt;br /&gt;
 			-- мусора в пстор одно только ее название может повесить загрузку&lt;br /&gt;
			pstor[varname] = nil&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Эти примеры приведены для чистой игры. Единственное в чем не уверен пока, это в том, что максимум полезного размера - 20 слов. Возможно нужно выделить больше, это надо будет проверить ещё экспериментальным путем...&lt;br /&gt;
&lt;br /&gt;
Кроме этого, советую ещё внутрь ф-ции abort вставить отладочные метки, чтобы точно знать когда она вызывалась. Настоятельно советую сделать это даже если вы матёрый моддер со стажем - гарантирую, будете неприятно удивлены.&lt;br /&gt;
&lt;br /&gt;
Я это сделал вот так:&lt;br /&gt;
&lt;br /&gt;
''_g.script''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- Крешнуть игру (после вывода сообщения об ошибке в лог)&lt;br /&gt;
function abort(fmt, msg)&lt;br /&gt;
	local message = tostring(msg)&lt;br /&gt;
	dbglog(&amp;quot;ERROR PATTERN: &amp;quot;..tostring(fmt))&lt;br /&gt;
	dbglog(&amp;quot;ERROR REASON: &amp;quot;..message)&lt;br /&gt;
	local reason = string.format(fmt, message)&lt;br /&gt;
	assert(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	printf(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	dbglog(&amp;quot;%s&amp;quot;, reason)&lt;br /&gt;
	printf(&amp;quot;%s&amp;quot;)&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
По поводу частичной неработоспособности ф-ции '''abort''' я беседовал с Колмогором, и он пришёл к выводу, что видимо вылет игры должен был бы производиться при обработке функции '''printf(&amp;quot;%s&amp;quot;)''' - ей тут передаётся заведомо отсутствующий оператор и она по уму должна бы сразу крашить игру. Однако в релизе функция printf фактически не работает(она реализована в _g.script через вырезанную функцию log). В результате игра не крашится. Но что поделать, в итоге мне пришлось модифицировать его таким образом, чтобы вылет при его срабатывании был гарантирован:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- Крешнуть игру (после вывода сообщения об ошибке в лог)&lt;br /&gt;
function abort(fmt, msg)&lt;br /&gt;
	local message = tostring(msg)&lt;br /&gt;
	dbglog(&amp;quot;ERROR PATTERN: &amp;quot;..tostring(fmt))&lt;br /&gt;
	dbglog(&amp;quot;ERROR REASON: &amp;quot;..message)&lt;br /&gt;
	local reason = string.format(fmt, message)&lt;br /&gt;
	assert(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	printf(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	dbglog(&amp;quot;%s&amp;quot;, reason)&lt;br /&gt;
	printf(&amp;quot;%s&amp;quot;)&lt;br /&gt;
	local crash&lt;br /&gt;
	local ooops = 1/crash&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Вылет происходит при попытке произвести арифметическую операцию с неинициализированной переменной crash.&lt;br /&gt;
&lt;br /&gt;
== Лечение зависаний алайфа при смерти персонажей ==&lt;br /&gt;
&lt;br /&gt;
Недавно, отлаживая проблемы с зависанием алайфа, мне удалось найти причину этого периодически во всех модах всплывающего сбоя, приводящего к порче сейвов и сильно мешающего нормально играть. Сбой этот возникает при смерти некоторых NPC, обычно квестовых. В частности в моём случае изолировать и отладить это зависание удалось на Юрике, новичке со Свалке, учавствующем в сцене с гоп-стопом. Причина оказалась в обработке посмертной отрегистрации NPC из гулагов, причём сбой там был настоящей матрёшкой, составной из нескольких частей. Правок в итоге было совсем немного, но чтобы сделать их мне пришлось несколько часов распутывать клубок из кросс-вызовов между скриптами smart_terrain и xr_gulag. Итак, начнём с самого начала. Работая над OGSE 069 и 0691 я периодически сталкивался с зависаниями и вылетами в посмертных обработках неписей. Один из таких вылетов - всем хорошо знакомый вылет:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;smart_terrain:1143 &amp;quot;attempt to index a nil value&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Происходящий в функции '''smart_terrain.on_death( obj_id )''' - я тогда его заблокировал вызовом его внутри безопасного кода функцией '''pcall''', однако, как теперь выяснилось, этого оказалось недостаточно - баг тут состоит из нескольких частей, и этот вылет указывает только на одну из них. Вот исходный код:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj     = sim:object( obj_id )&lt;br /&gt;
		local strn_id = obj:smart_terrain_id()  --- первый вылет/зависнаие алайфа происходит тут&lt;br /&gt;
&lt;br /&gt;
		if strn_id ~= 65535 then&lt;br /&gt;
			sim:object( strn_id ).gulag:clear_dead(obj_id) -- а вот в этой обработке происходит зависание алайфа. Она очень комплексная, и её сложно распутывать.&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Во-первых выяснилось, что изредка вызов '''obj:smart_terrain_id()''' вызывает зависание алайфа даже когда он производится изнутри защищённого кода. Тогда я решил избавиться от использования этой функции в данном месте совсем. После нескольких экспериментов выяснилось, что самым простым, быстрым и вылетобезопасным способом будет считать нетпакет существа в таблицу и выудить идентификатор смарттеррейна из неё. Для этого можно написать свою обработку, однако я, как весьма ленивый программист, не склонен изобретать велосипеды, поэтому я воспользовался уже проверенной у нас и активно используемой в OGSE библиотекой функций для работы с нетпакетами '''m_net_utils''' Артоса. Кроме того, я сразу сделал более безопасным вызов обработки на отрегистрацию в гулагах. Вот, собственно, что в итоге получилось:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj = sim:object( obj_id )&lt;br /&gt;
		if (obj and obj.smart_terrain_id) then&lt;br /&gt;
			local strn_id = 65535  -- значение по умолчанию&lt;br /&gt;
			&lt;br /&gt;
			local t = nil -- сюда запихнём табличку из пакета&lt;br /&gt;
			if IsStalker(obj) then t = m_net_utils.get_stalker_data(obj) elseif IsMonster(obj) then t = m_net_utils.get_monster_data(obj) end &lt;br /&gt;
			-- вызываем парсинг пакета для неписей и монстров отдельно&lt;br /&gt;
			&lt;br /&gt;
			-- print_table_inlog(t)&lt;br /&gt;
			if t.smtrid then&lt;br /&gt;
				strn_id = tonumber(t.smtrid) -- получаем идентификатор смарта, если его нету даже в пакете, хрен с ним, будет 65535&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
			if strn_id ~= 65535 then -- если сняли идентификатор, попробуем отрегать...&lt;br /&gt;
				local gulag = sim:object(strn_id)&lt;br /&gt;
				if gulag and gulag.gulag then -- ...но сначала выясним если вообще такой гулаг и инициализирован ли он&lt;br /&gt;
					sim:object(strn_id).gulag:clear_dead(obj_id)&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При этом сделаю отсупление и предупрежу об одном очень странном сбое с которым я столкнулся редактируя эту функцию. Так вот, всё нормально работает только тогда когда у ф-ции этой есть строго определённая структура. '''Стоит только добавить пару строк, убрать закомментированную и сдвинуть пару условий, просто в тексте сдвинуть, не меняя внутренней логики, как игра начинает вылетать, причём ещё до загрузки сейва, при кэшировании (!) и с очень странными логами, хотя код написан синтаксически безупречно! Например с таким:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Description: xr_gulag:1035 value not found ObjectJobPathName[obj_id]&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Вернёшь строки на место, перестроишь текст - работает снова. Совершенная чертовщина, шаманил я над этой функцией около получаса, перестраивая текст таким образом чтобы игра нормально запускалась.''' Имейте это в виду когда в неё полезете и если что не пугайтесь если игра начнёт так вылетать - в этом случае пошаманьте немного над ней, меняя её форматирование. О причинах такого поведения я не могу даже догадываться - то ли движок её вызывает по смещению внутри файла вручную, то ли это какой-то баг Lua-парсера, но факт фактом.&lt;br /&gt;
&lt;br /&gt;
Итак, теперь проблема с получением идентификатора смарта разрешилась, однако алайф всё равно зависал! Простая трассировка показала, что теперь зависание происходило внутри обработки&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;sim:object(strn_id).gulag:clear_dead(obj_id)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И мне пришлость распутывать её по частям, доискиваясь до причины. Обработка честно говоря мерзкая, размазана по smart_terrain и xr_gulag, при этом взаимные вызовы идут не менее десятка раз, в следующем стиле: ф-ция в смарте вызывает ф-цию в гулаге, которая вызывает фцию в смарте, которая обрабатывает параметр фцией в гулаге, который передётся ф-цией в логике, которая получает её из смарта. Нечто подобное. Опуская все нецензурные выражения, употребленные мной при трассировке, я лучше расскажу что собственно вышло в итоге. А в итоге я вышел вот на этот код в xr_gulag, именно в нём при отрегистрации некоторых мёртвых неписей происходит зависание алайфа:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- освободить объект от работы и переинициализировать логику.&lt;br /&gt;
-- если сталкер в онлайне и начал работу, то сбросить его схему поведения&lt;br /&gt;
-- как будто он только что загрузился&lt;br /&gt;
function gulag:free_obj_and_reinit( obj_id )&lt;br /&gt;
	self:free_obj(obj_id)&lt;br /&gt;
&lt;br /&gt;
	local t = self.Object[obj_id]&lt;br /&gt;
	if t ~= nil and t ~= true and self.Object_begin_job[obj_id] then&lt;br /&gt;
		xr_logic.initialize_obj( t, nil, false, db.actor, self:get_stype( obj_id ) ) -- вот эта обработка вешает алайф&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Я, честно говоря, ни разу не понимаю, на кой чёрт нужно переконфигурировать схемы логики и выбирать из них активную '''трупу, который лежит себе спокойно и никого не трогает.''' Может быть в это есть некий высший смысл, или это было продиктовано неким аккуратизмом, однако одно я могу сказать однозначно - подобная переинициализация у некоторых трупов неписей приводит к глухому зависанию алайфа. При этом если для трупов эту обработку заблокировать, то трупы разрегистрируются вполне нормально и спокойно лежат с нетронутой логикой, не вызывая никаких проблем. Итоговый код после правок выглядит вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- освободить объект от работы и переинициализировать логику.&lt;br /&gt;
-- если сталкер в онлайне и начал работу, то сбросить его схему поведения&lt;br /&gt;
-- как будто он только что загрузился&lt;br /&gt;
function gulag:free_obj_and_reinit( obj_id )&lt;br /&gt;
	self:free_obj(obj_id)&lt;br /&gt;
	local t = self.Object[obj_id]&lt;br /&gt;
	if t ~= nil and t ~= true and self.Object_begin_job[obj_id] then&lt;br /&gt;
		if check_game() then -- тут проверяется, запущена ли игра, если ли актор и жив ли он. Если да, делаем по новому.&lt;br /&gt;
			local s_obj = alife():object(obj_id) -- проверим есть ли у цели разрегистрации валидный серверный объект&lt;br /&gt;
			if s_obj and (IsStalker(s_obj) or IsMonster(s_obj)) and s_obj:alive() then -- если есть, он жив и сталкер или монстр&lt;br /&gt;
				xr_logic.initialize_obj( t, nil, false, db.actor, self:get_stype( obj_id ) ) -- только тогда инициализируем логику&lt;br /&gt;
			end&lt;br /&gt;
		else -- а если игра не запущена, то как раньше. Это нужно для того, чтобы обработка запуска игры нормально работала.&lt;br /&gt;
			xr_logic.initialize_obj( t, nil, false, db.actor, self:get_stype( obj_id ) )&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Проверка, запущена ли игра&lt;br /&gt;
function check_game()&lt;br /&gt;
	if level.present() and (db.actor ~= nil) and db.actor:alive() then&lt;br /&gt;
		return true&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
После этих поправок зависания алайфа при смерти неписей удалось побороть окончательно. Решение с отрезанием реинита логики для трупов несколько грубовато, но честно говоря, у меня нет ни малейшего желания трассировать и разбирать на части функцию xr_logic.initialize_obj, выясняя, чем же ей так данный конкретный труп не приглянулся. Если хотите - займитесь, найдёте причину - дополните данную статью. Я же успокоился на том, что заблокировал баг, периодически убивающий людям игру, так как многие пользователи, игнорируя предупреждения, играют на одном-двух сейвах, а то и вовсе на квиксейвах всю игру.&lt;br /&gt;
&lt;br /&gt;
= Другие частые проблемы =&lt;br /&gt;
&lt;br /&gt;
Спустя некоторое время я обнаружил причины ещё нескольких часто встречающихся вылетов, и решил записать их описание сюда - эта информация наверняка ещё много кому пригодится.&lt;br /&gt;
&lt;br /&gt;
== Вылеты при удалении объектов из игры ==&lt;br /&gt;
&lt;br /&gt;
При использовании для удаления объектов родной движковой функции '''alife():release(alife():object(id), true)''' возможен целый ворох разнообразнейших вылетов, обычно - безлоговых, что сильно затрудняет их отладку. Вот из-за чего они возникают:&lt;br /&gt;
&lt;br /&gt;
1) Вылет при удалении непися или монстра, находящегося в онлайне.&lt;br /&gt;
   Решение: с помощью alife():release '''можно удалять только мёртвые объекты'''. Поэтому если вам нужно удалить с её помощью непися или монстра, который жив и находится в онлайне, его нужно убить любым доступным методом, хотя бы нанеся ему hit() с любым запредельным уроном по вкусу.&lt;br /&gt;
&lt;br /&gt;
2) Вылет при удалении оружия или артефакта.&lt;br /&gt;
   Решение: такая проблема часто встречается в случае если объект неудачно расположен или находится в руках у непися. Для того чтобы не произошло вылета, убедитесь что объект доступен как серверный перед удалением. Вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;	local obj = alife():object(i)&lt;br /&gt;
	if obj then&lt;br /&gt;
		alife():release(obj, true)&lt;br /&gt;
	end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Эту конструкцию вообще желательно использовать всегда, когда вы так удаляете объекты.&lt;br /&gt;
&lt;br /&gt;
3) Вылет при удалении аномалии.&lt;br /&gt;
   Решение: аномалии - очень капризные при подобном с ними обращении объекты. Они влияют на своё окружение, и если рядом с ними находится непись или монстр, то удаление такой аномалии приведёт к вылету игры. Чтобы этого не произошло, аномалию надо сначала выключить функцией '''disable_anomaly''', и удалять затем ТОЛЬКО тогда, когда она не будет занята влиянием на динамический объект. Для этого нужно получить список мобов на локации, и из их нетпакетов считать идентификаторы действующих на них рестрикторов. Если ваша аномалия будет в этом списке - удалять её нельзя. Дождитесь пока она освободится.&lt;br /&gt;
&lt;br /&gt;
== Вылет при открытии закладки &amp;quot;Контакты&amp;quot; в ПДА ==&lt;br /&gt;
&lt;br /&gt;
Простой безлоговый вылет при открытии закладки &amp;quot;Контакты&amp;quot;. Встречался во всех крупных модах, и никто не знал как его излечить. А лечится он банально - '''его причина - дублирование идентификаторов секций в XML-файле, описывающем иконки неписей для закладки &amp;quot;Контакты&amp;quot;'''. Нужно всего лишь проверить этот файл на наличие дублированных идентификаторов и удалить их. Вылет пропадёт и никогда больше не будет встречаться.&lt;br /&gt;
&lt;br /&gt;
== Вылеты при вызове несуществующих функций из XML ==&lt;br /&gt;
&lt;br /&gt;
В ХML-файлах, используемых для описания инфопоршенов, для многих инфопоршенов прописаны действия, которые игра вызывает при взятии этого инфопоршена. Вот так примерно:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
	&amp;lt;info_portion id=&amp;quot;barman_document_have&amp;quot;&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;dialogs.set_actor_prebandit1&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandits2&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandits3&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandit7&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandit8&amp;lt;/action&amp;gt;&lt;br /&gt;
	&amp;lt;/info_portion&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так вот, если вы допустите опечатку в названии вызываемой функции или же случайно её удалите - игра будет стабильно вылетать без лога при взятии этого инфопоршена.&lt;br /&gt;
&lt;br /&gt;
== Повреждения сейвов на Радаре и других местах в модах, основанных на OGSM ==&lt;br /&gt;
&lt;br /&gt;
В оригинале ОГСМ и основанных на нём модах часто встречались проблемы с сохранениями на Радаре. Эту проблему долго не удавалось победить, пока наконец благодаря помощи Маландринуса не удалось выявить её первопричину. Как выяснилось, она очень проста - гражданские зомби в моде (монстры) имели в конфиге ту же пропись вида (параметр конфига specie), что и монолитовцы и зомбированные (неписи). И там и тут было проставлено &amp;quot;zombie&amp;quot;, и так оно было ещё с оригинала. Как оказалось, так делать категорически нельзя. Дело в том, что у неписей есть такой функционал, как хитовая память - в ней какое-то время хранятся ссылки на атакующие объекты. У монстров тоже есть остатки этого функционала, но он неработоспособен, и использовать его нельзя. В случае же когда монстры и неписи попадают в один вид, в ситуации когда они находятся рядом в бою, хитовая память монстров автоматически получает от неписей того же вида распространяемую внутри вида информацию об атакующих - а хранить её монстрам нельзя. Если после создания такой ситуации сохраниться - сейв будет вызывать вылет при загрузке. То есть проще говоря, если в бою с монолитовцами рядом оказывались гражданские зомби - и игрок сохранял игру - сейв этот не загружался. Чтобы предотвратить эти проблемы, вполне достаточно создать для гражданских зомби свой отдельный вид, добавив его прописи в конфиг game_relations.&lt;br /&gt;
&lt;br /&gt;
== Застывания NPC после боя / лечения ранения в модах, использующих дополнительные схемы поведения ==&lt;br /&gt;
&lt;br /&gt;
Во многих модах, использующих дополнительные схемы поведения часто можно заметить NPC, которые после боя застывают, прицелившись в одну точку. Аналогично часто это встречается с вылеченными от ранения NPC - они встают и замирают намертво, до тех пор пока их не выведет из этого состояния атаковавший враг. Как правило сейв/загрузка этой проблемы не решают. В ходе работы над OGSE 0.6.9.3 мне удалось выяснить причину таких проблем и успешно её устранить. Причина проблемы заключается в том, что NPC управляются не только скриптовыми схемами, но и движком, и для переключения управления между одним и другим используется скрипт ''state_mgr'' - менеджер состояний. Его директивы имеют наивысший приоритет. Аналогичный же приоритет себе как правило назначают доп. схемы поведения, используя пропись эвалуатора в ''xr_motivatior.addCommonPrecondition(action)''. В итоге при выходе из движковой боевки или при переключении на движковый алайф (это происходит после излечения ранения) доп. схемы поведения вступают с менеджером состояний в конфликт, блокируя смену состояния NPC. Для того чтобы этого не происходило, '''необходимо во всех схемах поведения, использующих принудительное назначение состояния через функцию ''state_mgr.set_state'' сделать блокировку перехвата управления менеджером состояний''', добавив соответствующие прописи в биндер. Вот таким образом, как в этом примере - тут я правил биндер схемы лечения/самолечения ''xrs_medic'':&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;&lt;br /&gt;
function add_to_binder(object, ini, scheme, section, storage)&lt;br /&gt;
	local operators	= {}&lt;br /&gt;
	local properties  = {}&lt;br /&gt;
&lt;br /&gt;
	local manager = object:motivation_action_manager()&lt;br /&gt;
&lt;br /&gt;
	operators[&amp;quot;medic&amp;quot;]			= actid_medic&lt;br /&gt;
	operators[&amp;quot;self_medic&amp;quot;]		= actid_self_medic&lt;br /&gt;
&lt;br /&gt;
	properties[&amp;quot;medic&amp;quot;]			= evid_medic&lt;br /&gt;
	properties[&amp;quot;self_medic&amp;quot;]	= evid_self_medic&lt;br /&gt;
	&lt;br /&gt;
	local state_mgr_to_idle_combat = xr_actions_id.state_mgr + 2 ---&amp;lt; Это переключение на движковую боевку, но оно тут не использовано&lt;br /&gt;
	local state_mgr_to_idle_alife = xr_actions_id.state_mgr + 2 ---&amp;lt; Это переключение на движковый алайф&lt;br /&gt;
	local state_mgr_to_idle_off = xr_actions_id.state_mgr + 3   ---&amp;lt; Это переключение в статичное состояние&lt;br /&gt;
&lt;br /&gt;
	local zombi=object:character_community()==&amp;quot;zombied&amp;quot; or object:character_community()==&amp;quot;trader&amp;quot; or&lt;br /&gt;
		  object:character_community()==&amp;quot;arena_enemy&amp;quot; or object:name()==&amp;quot;mil_stalker0012&amp;quot; or object:name()==&amp;quot;yantar_ecolog_general&amp;quot;&lt;br /&gt;
&lt;br /&gt;
	if zombi then&lt;br /&gt;
		manager:add_evaluator (properties[&amp;quot;medic&amp;quot;], property_evaluator_const(false))&lt;br /&gt;
		manager:add_evaluator (properties[&amp;quot;self_medic&amp;quot;], property_evaluator_const(false))&lt;br /&gt;
	else&lt;br /&gt;
		manager:add_evaluator (properties[&amp;quot;medic&amp;quot;], evaluator_medic(&amp;quot;medic&amp;quot;, storage))&lt;br /&gt;
		manager:add_evaluator (properties[&amp;quot;self_medic&amp;quot;], evaluator_self_medic(&amp;quot;self_medic&amp;quot;, storage))&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	local action = action_medic (object,&amp;quot;medic&amp;quot;, storage)&lt;br /&gt;
	action:add_precondition(world_property(stalker_ids.property_alive, true))&lt;br /&gt;
	action:add_precondition(world_property(xr_evaluators_id.sidor_wounded_base, false))&lt;br /&gt;
	action:add_precondition	(world_property(properties[&amp;quot;medic&amp;quot;], true))&lt;br /&gt;
	action:add_effect (world_property(properties[&amp;quot;medic&amp;quot;], false))&lt;br /&gt;
	manager:add_action (operators[&amp;quot;medic&amp;quot;], action)&lt;br /&gt;
	&lt;br /&gt;
	local action = action_self_medic (object,&amp;quot;self_medic&amp;quot;, storage)&lt;br /&gt;
	action:add_precondition(world_property(stalker_ids.property_alive, true))&lt;br /&gt;
	action:add_precondition(world_property(stalker_ids.property_enemy,false))	&lt;br /&gt;
	action:add_precondition(world_property(xr_evaluators_id.sidor_wounded_base, false))&lt;br /&gt;
	action:add_precondition	(world_property(properties[&amp;quot;medic&amp;quot;], false))&lt;br /&gt;
	action:add_precondition	(world_property(properties[&amp;quot;self_medic&amp;quot;], true))&lt;br /&gt;
	action:add_effect (world_property(properties[&amp;quot;self_medic&amp;quot;], false))&lt;br /&gt;
	manager:add_action (operators[&amp;quot;self_medic&amp;quot;], action)&lt;br /&gt;
		&lt;br /&gt;
	action = manager:action (stalker_ids.action_alife_planner)	&lt;br /&gt;
	action:add_precondition	(world_property(properties[&amp;quot;medic&amp;quot;], false))&lt;br /&gt;
	action:add_precondition	(world_property(properties[&amp;quot;self_medic&amp;quot;], false))&lt;br /&gt;
	&lt;br /&gt;
	action = manager:action(state_mgr_to_idle_alife)&lt;br /&gt;
	action:add_precondition	(world_property(properties[&amp;quot;self_medic&amp;quot;], false)) ---&amp;lt; Блокируем попытки переключиться на движковый алайф пока работает самолечение&lt;br /&gt;
&lt;br /&gt;
	action = manager:action(state_mgr_to_idle_off)&lt;br /&gt;
	action:add_precondition	(world_property(properties[&amp;quot;self_medic&amp;quot;], false)) ---&amp;lt; Блокируем попытки переключиться статичное состояние пока работает самолечение&lt;br /&gt;
&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Вставка этих директив в биндеры всех схем, которые меняют состояния принудительно устраняет конфликты, и NPC больше не виснут при смене схем.&lt;br /&gt;
&lt;br /&gt;
--[[Участник:Kamikazze|KamikaZze (OGSE Team)]] 11:04, 31 марта 2011 (UTC)&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[Как писать скрипты, не приводящие к вылетам и бою сейвов|Начало в первой части статьи]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
= Авторы =&lt;br /&gt;
Статья создана: [[Участник:Kamikazze|Kamikazze]]&lt;br /&gt;
&lt;br /&gt;
[[Категория:Скрипты]]&lt;/div&gt;</summary>
		<author><name>Kamikazze</name></author>	</entry>

	<entry>
		<id>http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2_(%D1%87%D0%B0%D1%81%D1%82%D1%8C_2)</id>
		<title>Как писать скрипты, не приводящие к вылетам и бою сейвов (часть 2)</title>
		<link rel="alternate" type="text/html" href="http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2_(%D1%87%D0%B0%D1%81%D1%82%D1%8C_2)"/>
				<updated>2011-03-10T06:31:18Z</updated>
		
		<summary type="html">&lt;p&gt;Kamikazze: /* Повреждения сейвов на Радаре и других местах в модах, основанных на OGSM */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;----&lt;br /&gt;
&lt;br /&gt;
[[Как писать скрипты, не приводящие к вылетам и бою сейвов|Начало в первой части статьи]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Как безопасно использовать коллбэки и таймерные события =&lt;br /&gt;
&lt;br /&gt;
В скриптовом движке есть удобный способ реакции на события - использование коллбэков - процедурных вызовов, привязанных к определённым событиям в жизни игрового объекта - получению повреждений, спавну, смерти и т.д. и т.п. Это hit_callback, death_callback из xr_motivator и многие другие... Все моддеры очень широко и совершенно спокойно пользуются ими, совершенно забывая при этом о таком важном факте, что это - обработки реального времени, как и таймерные события. Что это значит? А собственно вот что... Возьмём для примера коллбэк смерти неписей, мою головную боль последнего времени:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;xr_motivator.script -&lt;br /&gt;
function motivator_binder:death_callback(victim, who)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это функция обрабатывает смерть неписей. Когда вызывается этот коллбек он начинает по очереди вызывать внутренние функции, расположенные в других модулях. Они разрегистрируют умершего непися в гулагах, спавнят в него лут, обновляют статистику, отключают его от доп. схем логики и т.д.&lt;br /&gt;
&lt;br /&gt;
Так вот, обычно, когда в функциях возникает конфликт параметров, бесконечный цикл, попытка индексации nil и т.д., функция вылетает со стандартным логом. '''Но только не в случае, когда она находится внутри коллбэка. Если функция внутри него, то в случае возникновении в ней любой нештатной ситуации, коллбэк наглухо виснет.''' Это происходит из-за того, что функции вызываются строго друг за другом, и каждая из них вызывается только тогда, когда её предшественница вернула управление коллбэку. В случае с death_callback опознать такого непися очень просто - в его трупе окажется фонарик, КПК и возможно ещё немного разных &amp;quot;мусорных&amp;quot; вещей, что говорит о том, что обработка его смерти повисла не дойдя даже до спавна лута. В подобной ситуации можно быть на 100% уверенным, что труп этот не был корректно разрегистрирован, и игра всё ещё считает его живым неписем. Кроме того, зависший коллбэк не освобождает стек (а он у Луа-подсистемы единый на все скрипты), что '''в итоге приводит к вылетам игры с переполнением памяти (вот она, реальная причина этих &amp;quot;родных&amp;quot; вылетов).''' Но было бы слишком хорошо, если бы всё ограничивалось этим... однако тут всё намного хуже... такие &amp;quot;зависшие&amp;quot; коллбэки, особенно если их произошло несколько подряд, очень серьёзно влияют на работу а-лайфа. В лучшем случае они, забивая, стек, мешают нормально работать схемам логики, в худшем вызывают зависания самого  а-лайфа (этот эффект, кстати, производят и сами трупы таких неписей, так как они, как мы помним, не разрегистрировались корректно). '''Основной итог таких событий - бой сейвов, сделанных после возникновения таких ситуаций.''' Если повис один коллбэк, то такие сейвы ещё через раз загружаются, если же несколько, и остановилась работа а-лайфа - всё, сейвы бьются наглухо и реанимации не подлежат.&lt;br /&gt;
&lt;br /&gt;
Поэтому, чтобы избежать такого развития событий, каждый раз, когда вы вносите в коллбэк новую функцию - проверьте её самым тщательным образом. Она не должна содержать никаких рекурсивных циклов, в ней обязательно должны быть проверки на валидность обрабатываемых объектов и значений, и обязательно должна быть обработка нештатных ситуаций - т.е. функция должна обязательно, в абсолютно любой ситуации вернуть управление коллбэку, так или иначе. В самом наихудшем случае - делайте как делали разработчики игры - вставляйте принудительный вылет на рабстол функцией abort - она позволяет передавать отладочное сообщение, и это всяко лучше чем незаметный бой сейвов. Если обработка оборвалась в самом начале, а ф-ция обязательно должна вернуть значение - заведите ей &amp;quot;безопасное&amp;quot; возвращаемое значение по-умолчанию, которое она будет выдавать, если всё пошло плохо. И никогда не пренебрегайте пошаговой отладкой коллбэков с выводом в лог, особенно когда пишете схемы логики - это критически важно для стабильности вашего мода.&lt;br /&gt;
&lt;br /&gt;
== Основные внутриигровые признаки зависания коллбэков типа hit_callback, death_callback ==&lt;br /&gt;
&lt;br /&gt;
1. В трупах попадаются фонарики, КПК, разный мусор и общий лут слишком богат.&lt;br /&gt;
&lt;br /&gt;
2. Частые вылеты во время интенсивных боёв с логами типа&lt;br /&gt;
    Sheduler tried to update object...&lt;br /&gt;
    smart_terrain:1145(1146)&lt;br /&gt;
    LUA: out of memory&lt;br /&gt;
    любой_модуль_логики:любая_cтрока - stack overflow&lt;br /&gt;
&lt;br /&gt;
3. Частые &amp;quot;родные&amp;quot; вылеты в момент смерти непися или попадания по нему&lt;br /&gt;
&lt;br /&gt;
4. Произвольно бьются сейвы во время сражений, выброса и других насыщенных действиями событий&lt;br /&gt;
&lt;br /&gt;
= Использование защищённого кода в LUA =&lt;br /&gt;
&lt;br /&gt;
Периодически случаются такие ситуации, когда мы можем получить вылет при проверке аргумента, и не можем его адекватно заизолировать с помощью предварительной проверки на валидность значения. Вот простой пример: когда я отлаживал '''death_callback''' неписей, я периодически сталкивался с тем, что обращение к методу '''smart_terrain_id()''' при смерти непися иногда вызывало вылет &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;smart_terrain:1143 &amp;quot;attempt to index a nil value&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
- хотя это свойство является родным методом объекта, и отсутствовать напрочь никак не может. В итоге я пришёл к выводу, что его просто иногда не успевает отработать движок, так как вылет этот проявлялся в основном в интенсивных боях и совершенно произвольно.&lt;br /&gt;
&lt;br /&gt;
Вот код, в котором происходил вылет:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj     = sim:object( obj_id )&lt;br /&gt;
		local strn_id = obj:smart_terrain_id()  --- вылет происходит тут&lt;br /&gt;
&lt;br /&gt;
		if strn_id ~= 65535 then&lt;br /&gt;
			sim:object( strn_id ).gulag:clear_dead(obj_id)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это кусок родного кода из версии 1,0005 игры. Я долго пытался разными способами отсечь этот вылет, вводя предварительные проверки, однако это совершенно ничего не давало - вылет всё равно периодически случался, так как проверки эти сами его вызывали. Тогда я зарылся в документацию по Lua и обнаружил замечательную родную базовую функцию, введённую ещё с первых версий Lua, которой почему-то не пользовались ни разработчики игры, ни моддеры (хотя сама она в Lua сталкера присутствует, и работает отлично, без каких-либо нареканий). Вот она:&lt;br /&gt;
&lt;br /&gt;
'''pcall (f, arg1, ···)'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Вызывает функцию f с указанными через запятую аргументами в защищённом режиме. Это означает, что любая ошибка, даже критическая, внутри вызванной функции, не передаётся наружу - вызывавшей подсистеме. Вместо этого '''pcall''' перехватывает ошибку и возвращает код статуса. Первая возвращаемая переменная это сам код, (true или false) и если всё прошло хорошо, он равен true. В этом случае '''pcall''' сразу после статуса возвращает все результаты от работы защищённой им функции. Если же в защищённой функции произошла ошибка, то '''pcall''' вернёт false и затем сообщение об ошибке. (Обратите внимание, обработка ошибки присходит БЕЗ вылета! Вместо вылета вы получите вполне адекватную строку с ошибкой, которую можно вывести в лог для последующей обработки)&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Я настоятельно советую пользоваться этой функцией в случаях, когда из коллбэков вызываются сложные комплексные обработки, вроде обработки из менеджера вооружений AI-пака. Это позволяет предотвратить как вылеты, так и зависания обработок, и в итоге позволяет хорошо стабилизировать игру.&lt;br /&gt;
&lt;br /&gt;
Возвращаясь к нашим смарттеррейнам... вот как в итоге я подавил вылет типа smart_terrain:1143 с помощью pcall:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;--- эта функция пытается проверить св-во smart_terrain_id объекта. Именно её мы вызовем в защищённом режиме.&lt;br /&gt;
function prot_smt_td(obj)&lt;br /&gt;
	if IsStalker(obj) or IsMonster(obj) then&lt;br /&gt;
		return obj:smart_terrain_id()&lt;br /&gt;
	else	&lt;br /&gt;
		return 65535&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj = sim:object( obj_id )&lt;br /&gt;
		if obj then&lt;br /&gt;
			local strn_id = 65535  --- предварительно проинитим переменную, на &lt;br /&gt;
						--- случай если у нас prot_smt_td выдаст ошибку&lt;br /&gt;
			local result, smt_id = pcall(prot_smt_td,obj)	--- вызываем prot_smt_td в защищённом режиме &lt;br /&gt;
									--- и сразу присваиваем его вывод переменным&lt;br /&gt;
			if result then --- если pcall выдало true&lt;br /&gt;
				strn_id = smt_id  --- тогда применяем полученное значение&lt;br /&gt;
			end&lt;br /&gt;
			--- если же обработка выдаст ошибку, то strn_id останется неизменным...&lt;br /&gt;
			if strn_id ~= 65535 then&lt;br /&gt;
				sim:object(strn_id).gulag:clear_dead(obj_id)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В других местах это делается совершенно аналогично. Подробнее об этой и многих других функция для контроля кода, незаслуженно игнорируемых большинством моддеров, можно почитать тут: http://lua-users.org/wiki/FinalizedExceptions - на английском правда, но захотите - разберётесь, там всё просто. &lt;br /&gt;
&lt;br /&gt;
Настоятельно советую вам изучить работу кода в таких условиях и научиться его правильно применять - этим вы сильно облегчите жизнь как себе, так и тем, кто после вас будет рыться в вашем коде или импортировать его в свои разработки.&lt;br /&gt;
&lt;br /&gt;
= Скрытые критические проблемы в обработке вылетов игрой =&lt;br /&gt;
&lt;br /&gt;
Ведя на днях отладку, выяснил в чём проблема с периодическим боем сейвов и многими другими заморочками как в оригинале игры, так и во многих модах... дело, как выяснилось, далеко не всегда в кривых руках. Есть такая стандартная ф-ция '''abort''' - предназначенная для выкидывания из игры, если что-то пошло не так. И как оказалось, она срабатывает далеко не всегда. Выяснилось это следующим образом:&lt;br /&gt;
&lt;br /&gt;
В одном из логов нашего бета-тестера я увидел '''стандартное сообщение о вылете внутри рабочего лога'''... да-да, то самое которое '''FATAL ERROR''' и дальше по тексту. При этом игра у него НЕ вылетала, это сообщение об ошибке мы обнаружили позже, по случайности. Я заподозрил, что что-то не в порядке, и вставил внутрь этой ф-ции контрольную метку, кидавшую в консоль сообщение, в котором содержался паттерн сообщения об ошибке и само сообщение. Так вот, оказалось, что эта самая функция '''abort''' вызывается в игре с завидным постоянством (вы удивитесь насколько часто), когда возникают исключения в схемах логики, звука и т.д., но игра от этого вылетает на рабочий стол '''максимум только 3 раза из 10 вызовов'''. Вылет НЕ происходит обычно, когда функции передан паттерн ошибки, а остальные параметры пустые, такое бывает, и частенько. И если не сделать внутри этой функции особой метки для вывода в лог, как сделал это я, её вызовы проходят совершенно незаметно, и '''игра после критических ошибок продолжается как ни в чём ни бывало.''' А приводит это вот к чему... Внутри '''xr_logic''' в процедуре записи пстора (хранилища логики и флагов) неписей есть вызовы этого самого аборта в случае если на запись в пстор передана некорректная величина. Ну а так как аборт периодически вообще не срабатывает, то часто попадается ситуация, что неписям в пстор пишется полный ахтунг: куски кода из ОЗУ, всякая муть из лтх-ов, куски аллспавна, всё что угодно. Происходит это оттого, что кодер, писавший эту функцию ('''xr_logic.pstor_store(obj, varname, val)'''), явно и думать не думал что '''abort''' может не сработать. У него запись в пстор стояла после проверки, а не внутри неё (very bad idea), и если abort не срабатывал, игра писала в пстор мусор совершенно спокойно и незаметно для игрока. Потом вся эта хрень попадала прямо в сейвы. Вот проблемный код для наглядности:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_store(obj, varname, val)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	if db.storage[npc_id].pstor == nil then&lt;br /&gt;
		db.storage[npc_id].pstor = {}&lt;br /&gt;
	end&lt;br /&gt;
	local tv = type(val)&lt;br /&gt;
	if not pstor_is_registered_type(tv) then&lt;br /&gt;
		abort(&amp;quot;xr_logic: pstor_store: not registered type '%s' encountered&amp;quot;, tv) --- вот тут мы должны если что вылететь&lt;br /&gt;
	end&lt;br /&gt;
	db.storage[npc_id].pstor[varname] = val -- а если не вылетели, всё, получим запись в пстор левой мути&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Разумеется игра этим пстором в итоге давится, и сейвы сделанные после такой милой записи практически лопаются. Результат - &amp;quot;битые&amp;quot; (на самом деле подлежат реанимации) сейвы. Происходит это потому, что игра из сейва грузит неписям псторы сплошным чтением по словам, пока они не закончатся. В случае же если в псторе обнаруживается записанный ранее мусор, то обработка либо вылетает сразу, либо наглухо виснет, пытаясь запихать эдак с миллион слов в пстор особо отличившегося непися. Как вам например непись с размером пстора в 1697451 слова? В результате попытки его обработать игра просто на стадии синхронизации выжрала всю доступную ОЗУ и повисла. &lt;br /&gt;
&lt;br /&gt;
Решение этой проблемы оказалось достатоно простым: во-первых я предположил максимальный размер полезной части пстора неписей в 20 слов r_u32() (пока ориентировочно, я ещё уточняю эту величину), и соответственно сделал остановку цикла загрузки пстора для неписей через 20 итераций. Там же, где в цикле стояла проверка на валидность записываемых данных (кстати тоже с вылетом в случае провала проверки), я сделал так, что если параметр не относится к валидному типу данных, то запись параметра в пстор не производится совсем. Это необходимо для того, чтобы если вдруг в сейве обнаружится мусор, то он был бы просто отброшен обработкой. Практика показала, что в итоге такие неписи вполне адекватны и в дальнейшем никаких проблем не вызывают, так как начало их пстора, с нормальными данными, обычно не повреждается - мусор дописывается после них, а не вместо них. &lt;br /&gt;
&lt;br /&gt;
Ну и во-вторых модифицировал запись параметров в пстор, просто убрав запись под основание if-else так, чтобы если параметр неверен, он не записывался совсем. Теперь кстати, очень интересно, сохранились ли те же заморочки с ф-цией '''abort''' в Чистом Небе, и если да, то останутся ли в Зове Припяти?&lt;br /&gt;
&lt;br /&gt;
------------&lt;br /&gt;
&lt;br /&gt;
== Необходимые для стабилизации игры правки в модулях ==&lt;br /&gt;
&lt;br /&gt;
'''Эта правка предотвращает запись в пстор если не сработал аборт:'''&lt;br /&gt;
&lt;br /&gt;
''xr_logic.script''&lt;br /&gt;
&lt;br /&gt;
'''Было:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_store(obj, varname, val)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	if db.storage[npc_id].pstor == nil then&lt;br /&gt;
		db.storage[npc_id].pstor = {}&lt;br /&gt;
	end&lt;br /&gt;
	local tv = type(val)&lt;br /&gt;
	if not pstor_is_registered_type(tv) then&lt;br /&gt;
		abort(&amp;quot;xr_logic: pstor_store: not registered type '%s' encountered&amp;quot;, tv)&lt;br /&gt;
	end&lt;br /&gt;
	db.storage[npc_id].pstor[varname] = val&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Стало:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_store(obj, varname, val)&lt;br /&gt;
	if not obj then return end&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	if db.storage[npc_id].pstor == nil then&lt;br /&gt;
		db.storage[npc_id].pstor = {}&lt;br /&gt;
	end&lt;br /&gt;
	local tv = type(val)&lt;br /&gt;
	if not pstor_is_registered_type(tv) then&lt;br /&gt;
		dgblog(&amp;quot;xr_logic: pstor_store: not registered type encountered - write in pstor_store cancelled&amp;quot;)&lt;br /&gt;
		-- abort убран, так как один хрен не работает. Пусть тогда хотя бы в лог что-то валится.&lt;br /&gt;
	else&lt;br /&gt;
		db.storage[npc_id].pstor[varname] = val&lt;br /&gt;
		-- вот так и только так. Если значение не валидно, ничего не происходит.&lt;br /&gt;
	end	&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''А эта правка выкинет из пстора весь мусор при загрузке сейва, если он как-то в него попал'''&lt;br /&gt;
&lt;br /&gt;
'''Было:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_load_all(obj, reader)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	local pstor = db.storage[npc_id].pstor&lt;br /&gt;
	if not pstor then&lt;br /&gt;
		pstor = {}&lt;br /&gt;
		db.storage[npc_id].pstor = pstor&lt;br /&gt;
	end&lt;br /&gt;
	local ctr = reader:r_u32()&lt;br /&gt;
	for i = 1, ctr do&lt;br /&gt;
		local varname = reader:r_stringZ()&lt;br /&gt;
		local tn = reader:r_u8()&lt;br /&gt;
		if tn == pstor_number then&lt;br /&gt;
			pstor[varname] = reader:r_float()&lt;br /&gt;
		elseif tn == pstor_string then&lt;br /&gt;
			pstor[varname] = reader:r_stringZ()&lt;br /&gt;
		elseif tn == pstor_boolean then&lt;br /&gt;
			pstor[varname] = reader:r_bool()&lt;br /&gt;
		else&lt;br /&gt;
			abort(&amp;quot;xr_logic: pstor_load_all: not registered type N %d encountered&amp;quot;, tn)&lt;br /&gt;
		end&lt;br /&gt;
		printf(&amp;quot;_bp: pstor_load_all: loaded [%s]='%s'&amp;quot;, varname, utils.to_str(pstor[varname]))&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Стало:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_load_all(obj, reader)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	local pstor = db.storage[npc_id].pstor&lt;br /&gt;
	if not pstor then&lt;br /&gt;
		pstor = {}&lt;br /&gt;
		db.storage[npc_id].pstor = pstor&lt;br /&gt;
	end&lt;br /&gt;
	local ctr = reader:r_u32()&lt;br /&gt;
	if tonumber(ctr) &amp;gt; 20 and tostring(obj:name()) ~= &amp;quot;single_player&amp;quot; and npc_id ~= db.actor:id() then&lt;br /&gt;
		-- максимум 20 итераций - это число ещё уточняется, возможно понадобится больше&lt;br /&gt;
                -- если у вас в пстор что-то свое пишется, ориентируйтесь на свои значения&lt;br /&gt;
		-- и обязательно убираем из проверки актора - у него очень толстый пстор, и к тому же&lt;br /&gt;
                -- если уж поврежденным будет его пстор, то тут точно уже ничего не поможет&lt;br /&gt;
		dgblog(&amp;quot;ОБНАРУЖЕН ОБЪЕКТ С ПОВРЕЖДЕННЫМ PSTOR: &amp;quot;..tostring(obj:name())..&lt;br /&gt;
&amp;quot; БУДЕТ ПРОИЗВЕДЕНА ПОПЫТКА ВОССТАНОВЛЕНИЯ&amp;quot;)&lt;br /&gt;
		ctr = 20 &lt;br /&gt;
	end&lt;br /&gt;
	for i = 1, ctr do&lt;br /&gt;
		local varname = reader:r_stringZ()&lt;br /&gt;
		local tn = reader:r_u8()&lt;br /&gt;
		if tn == pstor_number then&lt;br /&gt;
			pstor[varname] = reader:r_float()&lt;br /&gt;
		elseif tn == pstor_string then&lt;br /&gt;
			pstor[varname] = reader:r_stringZ()&lt;br /&gt;
		elseif tn == pstor_boolean then&lt;br /&gt;
			pstor[varname] = reader:r_bool()&lt;br /&gt;
		else&lt;br /&gt;
			-- не надо пытаться вылетать - просто не пишем поврежденные данные&lt;br /&gt;
			-- при этом обязательно удалять саму переменную - в результате записи&lt;br /&gt;
 			-- мусора в пстор одно только ее название может повесить загрузку&lt;br /&gt;
			pstor[varname] = nil&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Эти примеры приведены для чистой игры. Единственное в чем не уверен пока, это в том, что максимум полезного размера - 20 слов. Возможно нужно выделить больше, это надо будет проверить ещё экспериментальным путем...&lt;br /&gt;
&lt;br /&gt;
Кроме этого, советую ещё внутрь ф-ции abort вставить отладочные метки, чтобы точно знать когда она вызывалась. Настоятельно советую сделать это даже если вы матёрый моддер со стажем - гарантирую, будете неприятно удивлены.&lt;br /&gt;
&lt;br /&gt;
Я это сделал вот так:&lt;br /&gt;
&lt;br /&gt;
''_g.script''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- Крешнуть игру (после вывода сообщения об ошибке в лог)&lt;br /&gt;
function abort(fmt, msg)&lt;br /&gt;
	local message = tostring(msg)&lt;br /&gt;
	dbglog(&amp;quot;ERROR PATTERN: &amp;quot;..tostring(fmt))&lt;br /&gt;
	dbglog(&amp;quot;ERROR REASON: &amp;quot;..message)&lt;br /&gt;
	local reason = string.format(fmt, message)&lt;br /&gt;
	assert(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	printf(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	dbglog(&amp;quot;%s&amp;quot;, reason)&lt;br /&gt;
	printf(&amp;quot;%s&amp;quot;)&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
По поводу частичной неработоспособности ф-ции '''abort''' я беседовал с Колмогором, и он пришёл к выводу, что видимо вылет игры должен был бы производиться при обработке функции '''printf(&amp;quot;%s&amp;quot;)''' - ей тут передаётся заведомо отсутствующий оператор и она по уму должна бы сразу крашить игру. Однако в релизе функция printf фактически не работает(она реализована в _g.script через вырезанную функцию log). В результате игра не крашится. Но что поделать, в итоге мне пришлось модифицировать его таким образом, чтобы вылет при его срабатывании был гарантирован:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- Крешнуть игру (после вывода сообщения об ошибке в лог)&lt;br /&gt;
function abort(fmt, msg)&lt;br /&gt;
	local message = tostring(msg)&lt;br /&gt;
	dbglog(&amp;quot;ERROR PATTERN: &amp;quot;..tostring(fmt))&lt;br /&gt;
	dbglog(&amp;quot;ERROR REASON: &amp;quot;..message)&lt;br /&gt;
	local reason = string.format(fmt, message)&lt;br /&gt;
	assert(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	printf(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	dbglog(&amp;quot;%s&amp;quot;, reason)&lt;br /&gt;
	printf(&amp;quot;%s&amp;quot;)&lt;br /&gt;
	local crash&lt;br /&gt;
	local ooops = 1/crash&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Вылет происходит при попытке произвести арифметическую операцию с неинициализированной переменной crash.&lt;br /&gt;
&lt;br /&gt;
== Лечение зависаний алайфа при смерти персонажей ==&lt;br /&gt;
&lt;br /&gt;
Недавно, отлаживая проблемы с зависанием алайфа, мне удалось найти причину этого периодически во всех модах всплывающего сбоя, приводящего к порче сейвов и сильно мешающего нормально играть. Сбой этот возникает при смерти некоторых NPC, обычно квестовых. В частности в моём случае изолировать и отладить это зависание удалось на Юрике, новичке со Свалке, учавствующем в сцене с гоп-стопом. Причина оказалась в обработке посмертной отрегистрации NPC из гулагов, причём сбой там был настоящей матрёшкой, составной из нескольких частей. Правок в итоге было совсем немного, но чтобы сделать их мне пришлось несколько часов распутывать клубок из кросс-вызовов между скриптами smart_terrain и xr_gulag. Итак, начнём с самого начала. Работая над OGSE 069 и 0691 я периодически сталкивался с зависаниями и вылетами в посмертных обработках неписей. Один из таких вылетов - всем хорошо знакомый вылет:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;smart_terrain:1143 &amp;quot;attempt to index a nil value&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Происходящий в функции '''smart_terrain.on_death( obj_id )''' - я тогда его заблокировал вызовом его внутри безопасного кода функцией '''pcall''', однако, как теперь выяснилось, этого оказалось недостаточно - баг тут состоит из нескольких частей, и этот вылет указывает только на одну из них. Вот исходный код:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj     = sim:object( obj_id )&lt;br /&gt;
		local strn_id = obj:smart_terrain_id()  --- первый вылет/зависнаие алайфа происходит тут&lt;br /&gt;
&lt;br /&gt;
		if strn_id ~= 65535 then&lt;br /&gt;
			sim:object( strn_id ).gulag:clear_dead(obj_id) -- а вот в этой обработке происходит зависание алайфа. Она очень комплексная, и её сложно распутывать.&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Во-первых выяснилось, что изредка вызов '''obj:smart_terrain_id()''' вызывает зависание алайфа даже когда он производится изнутри защищённого кода. Тогда я решил избавиться от использования этой функции в данном месте совсем. После нескольких экспериментов выяснилось, что самым простым, быстрым и вылетобезопасным способом будет считать нетпакет существа в таблицу и выудить идентификатор смарттеррейна из неё. Для этого можно написать свою обработку, однако я, как весьма ленивый программист, не склонен изобретать велосипеды, поэтому я воспользовался уже проверенной у нас и активно используемой в OGSE библиотекой функций для работы с нетпакетами '''m_net_utils''' Артоса. Кроме того, я сразу сделал более безопасным вызов обработки на отрегистрацию в гулагах. Вот, собственно, что в итоге получилось:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj = sim:object( obj_id )&lt;br /&gt;
		if (obj and obj.smart_terrain_id) then&lt;br /&gt;
			local strn_id = 65535  -- значение по умолчанию&lt;br /&gt;
			&lt;br /&gt;
			local t = nil -- сюда запихнём табличку из пакета&lt;br /&gt;
			if IsStalker(obj) then t = m_net_utils.get_stalker_data(obj) elseif IsMonster(obj) then t = m_net_utils.get_monster_data(obj) end &lt;br /&gt;
			-- вызываем парсинг пакета для неписей и монстров отдельно&lt;br /&gt;
			&lt;br /&gt;
			-- print_table_inlog(t)&lt;br /&gt;
			if t.smtrid then&lt;br /&gt;
				strn_id = tonumber(t.smtrid) -- получаем идентификатор смарта, если его нету даже в пакете, хрен с ним, будет 65535&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
			if strn_id ~= 65535 then -- если сняли идентификатор, попробуем отрегать...&lt;br /&gt;
				local gulag = sim:object(strn_id)&lt;br /&gt;
				if gulag and gulag.gulag then -- ...но сначала выясним если вообще такой гулаг и инициализирован ли он&lt;br /&gt;
					sim:object(strn_id).gulag:clear_dead(obj_id)&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При этом сделаю отсупление и предупрежу об одном очень странном сбое с которым я столкнулся редактируя эту функцию. Так вот, всё нормально работает только тогда когда у ф-ции этой есть строго определённая структура. '''Стоит только добавить пару строк, убрать закомментированную и сдвинуть пару условий, просто в тексте сдвинуть, не меняя внутренней логики, как игра начинает вылетать, причём ещё до загрузки сейва, при кэшировании (!) и с очень странными логами, хотя код написан синтаксически безупречно! Например с таким:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Description: xr_gulag:1035 value not found ObjectJobPathName[obj_id]&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Вернёшь строки на место, перестроишь текст - работает снова. Совершенная чертовщина, шаманил я над этой функцией около получаса, перестраивая текст таким образом чтобы игра нормально запускалась.''' Имейте это в виду когда в неё полезете и если что не пугайтесь если игра начнёт так вылетать - в этом случае пошаманьте немного над ней, меняя её форматирование. О причинах такого поведения я не могу даже догадываться - то ли движок её вызывает по смещению внутри файла вручную, то ли это какой-то баг Lua-парсера, но факт фактом.&lt;br /&gt;
&lt;br /&gt;
Итак, теперь проблема с получением идентификатора смарта разрешилась, однако алайф всё равно зависал! Простая трассировка показала, что теперь зависание происходило внутри обработки&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;sim:object(strn_id).gulag:clear_dead(obj_id)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И мне пришлость распутывать её по частям, доискиваясь до причины. Обработка честно говоря мерзкая, размазана по smart_terrain и xr_gulag, при этом взаимные вызовы идут не менее десятка раз, в следующем стиле: ф-ция в смарте вызывает ф-цию в гулаге, которая вызывает фцию в смарте, которая обрабатывает параметр фцией в гулаге, который передётся ф-цией в логике, которая получает её из смарта. Нечто подобное. Опуская все нецензурные выражения, употребленные мной при трассировке, я лучше расскажу что собственно вышло в итоге. А в итоге я вышел вот на этот код в xr_gulag, именно в нём при отрегистрации некоторых мёртвых неписей происходит зависание алайфа:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- освободить объект от работы и переинициализировать логику.&lt;br /&gt;
-- если сталкер в онлайне и начал работу, то сбросить его схему поведения&lt;br /&gt;
-- как будто он только что загрузился&lt;br /&gt;
function gulag:free_obj_and_reinit( obj_id )&lt;br /&gt;
	self:free_obj(obj_id)&lt;br /&gt;
&lt;br /&gt;
	local t = self.Object[obj_id]&lt;br /&gt;
	if t ~= nil and t ~= true and self.Object_begin_job[obj_id] then&lt;br /&gt;
		xr_logic.initialize_obj( t, nil, false, db.actor, self:get_stype( obj_id ) ) -- вот эта обработка вешает алайф&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Я, честно говоря, ни разу не понимаю, на кой чёрт нужно переконфигурировать схемы логики и выбирать из них активную '''трупу, который лежит себе спокойно и никого не трогает.''' Может быть в это есть некий высший смысл, или это было продиктовано неким аккуратизмом, однако одно я могу сказать однозначно - подобная переинициализация у некоторых трупов неписей приводит к глухому зависанию алайфа. При этом если для трупов эту обработку заблокировать, то трупы разрегистрируются вполне нормально и спокойно лежат с нетронутой логикой, не вызывая никаких проблем. Итоговый код после правок выглядит вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- освободить объект от работы и переинициализировать логику.&lt;br /&gt;
-- если сталкер в онлайне и начал работу, то сбросить его схему поведения&lt;br /&gt;
-- как будто он только что загрузился&lt;br /&gt;
function gulag:free_obj_and_reinit( obj_id )&lt;br /&gt;
	self:free_obj(obj_id)&lt;br /&gt;
	local t = self.Object[obj_id]&lt;br /&gt;
	if t ~= nil and t ~= true and self.Object_begin_job[obj_id] then&lt;br /&gt;
		if check_game() then -- тут проверяется, запущена ли игра, если ли актор и жив ли он. Если да, делаем по новому.&lt;br /&gt;
			local s_obj = alife():object(obj_id) -- проверим есть ли у цели разрегистрации валидный серверный объект&lt;br /&gt;
			if s_obj and (IsStalker(s_obj) or IsMonster(s_obj)) and s_obj:alive() then -- если есть, он жив и сталкер или монстр&lt;br /&gt;
				xr_logic.initialize_obj( t, nil, false, db.actor, self:get_stype( obj_id ) ) -- только тогда инициализируем логику&lt;br /&gt;
			end&lt;br /&gt;
		else -- а если игра не запущена, то как раньше. Это нужно для того, чтобы обработка запуска игры нормально работала.&lt;br /&gt;
			xr_logic.initialize_obj( t, nil, false, db.actor, self:get_stype( obj_id ) )&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Проверка, запущена ли игра&lt;br /&gt;
function check_game()&lt;br /&gt;
	if level.present() and (db.actor ~= nil) and db.actor:alive() then&lt;br /&gt;
		return true&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
После этих поправок зависания алайфа при смерти неписей удалось побороть окончательно. Решение с отрезанием реинита логики для трупов несколько грубовато, но честно говоря, у меня нет ни малейшего желания трассировать и разбирать на части функцию xr_logic.initialize_obj, выясняя, чем же ей так данный конкретный труп не приглянулся. Если хотите - займитесь, найдёте причину - дополните данную статью. Я же успокоился на том, что заблокировал баг, периодически убивающий людям игру, так как многие пользователи, игнорируя предупреждения, играют на одном-двух сейвах, а то и вовсе на квиксейвах всю игру.&lt;br /&gt;
&lt;br /&gt;
= Другие частые проблемы =&lt;br /&gt;
&lt;br /&gt;
Спустя некоторое время я обнаружил причины ещё нескольких часто встречающихся вылетов, и решил записать их описание сюда - эта информация наверняка ещё много кому пригодится.&lt;br /&gt;
&lt;br /&gt;
== Вылеты при удалении объектов из игры ==&lt;br /&gt;
&lt;br /&gt;
При использовании для удаления объектов родной движковой функции '''alife():release(alife():object(id), true)''' возможен целый ворох разнообразнейших вылетов, обычно - безлоговых, что сильно затрудняет их отладку. Вот из-за чего они возникают:&lt;br /&gt;
&lt;br /&gt;
1) Вылет при удалении непися или монстра, находящегося в онлайне.&lt;br /&gt;
   Решение: с помощью alife():release '''можно удалять только мёртвые объекты'''. Поэтому если вам нужно удалить с её помощью непися или монстра, который жив и находится в онлайне, его нужно убить любым доступным методом, хотя бы нанеся ему hit() с любым запредельным уроном по вкусу.&lt;br /&gt;
&lt;br /&gt;
2) Вылет при удалении оружия или артефакта.&lt;br /&gt;
   Решение: такая проблема часто встречается в случае если объект неудачно расположен или находится в руках у непися. Для того чтобы не произошло вылета, убедитесь что объект доступен как серверный перед удалением. Вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;	local obj = alife():object(i)&lt;br /&gt;
	if obj then&lt;br /&gt;
		alife():release(obj, true)&lt;br /&gt;
	end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Эту конструкцию вообще желательно использовать всегда, когда вы так удаляете объекты.&lt;br /&gt;
&lt;br /&gt;
3) Вылет при удалении аномалии.&lt;br /&gt;
   Решение: аномалии - очень капризные при подобном с ними обращении объекты. Они влияют на своё окружение, и если рядом с ними находится непись или монстр, то удаление такой аномалии приведёт к вылету игры. Чтобы этого не произошло, аномалию надо сначала выключить функцией '''disable_anomaly''', и удалять затем ТОЛЬКО тогда, когда она не будет занята влиянием на динамический объект. Для этого нужно получить список мобов на локации, и из их нетпакетов считать идентификаторы действующих на них рестрикторов. Если ваша аномалия будет в этом списке - удалять её нельзя. Дождитесь пока она освободится.&lt;br /&gt;
&lt;br /&gt;
== Вылет при открытии закладки &amp;quot;Контакты&amp;quot; в ПДА ==&lt;br /&gt;
&lt;br /&gt;
Простой безлоговый вылет при открытии закладки &amp;quot;Контакты&amp;quot;. Встречался во всех крупных модах, и никто не знал как его излечить. А лечится он банально - '''его причина - дублирование идентификаторов секций в XML-файле, описывающем иконки неписей для закладки &amp;quot;Контакты&amp;quot;'''. Нужно всего лишь проверить этот файл на наличие дублированных идентификаторов и удалить их. Вылет пропадёт и никогда больше не будет встречаться.&lt;br /&gt;
&lt;br /&gt;
== Вылеты при вызове несуществующих функций из XML ==&lt;br /&gt;
&lt;br /&gt;
В ХML-файлах, используемых для описания инфопоршенов, для многих инфопоршенов прописаны действия, которые игра вызывает при взятии этого инфопоршена. Вот так примерно:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
	&amp;lt;info_portion id=&amp;quot;barman_document_have&amp;quot;&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;dialogs.set_actor_prebandit1&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandits2&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandits3&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandit7&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandit8&amp;lt;/action&amp;gt;&lt;br /&gt;
	&amp;lt;/info_portion&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так вот, если вы допустите опечатку в названии вызываемой функции или же случайно её удалите - игра будет стабильно вылетать без лога при взятии этого инфопоршена.&lt;br /&gt;
&lt;br /&gt;
== Повреждения сейвов на Радаре и других местах в модах, основанных на OGSM ==&lt;br /&gt;
&lt;br /&gt;
В оригинале ОГСМ и основанных на нём модах часто встречались проблемы с сохранениями на Радаре. Эту проблему долго не удавалось победить, пока наконец благодаря помощи Маландринуса не удалось выявить её первопричину. Как выяснилось, она очень проста - гражданские зомби в моде (монстры) имели в конфиге ту же пропись вида (параметр конфига specie), что и монолитовцы и зомбированные (неписи). И там и тут было проставлено &amp;quot;zombie&amp;quot;, и так оно было ещё с оригинала. Как оказалось, так делать категорически нельзя. Дело в том, что у неписей есть такой функционал, как хитовая память - в ней какое-то время хранятся ссылки на атакующие объекты. У монстров тоже есть остатки этого функционала, но он неработоспособен, и использовать его нельзя. В случае же когда монстры и неписи попадают в один вид, в ситуации когда они находятся рядом в бою, хитовая память монстров автоматически получает от неписей того же вида распространяемую внутри вида информацию об атакующих - а хранить её монстрам нельзя. Если после создания такой ситуации сохраниться - сейв будет вызывать вылет при загрузке. То есть проще говоря, если в бою с монолитовцами рядом оказывались гражданские зомби - и игрок сохранял игру - сейв этот не загружался. Чтобы предотвратить эти проблемы, вполне достаточно создать для гражданских зомби свой отдельный вид, добавив его прописи в конфиг game_relations.&lt;br /&gt;
&lt;br /&gt;
--[[Участник:Kamikazze|KamikaZze (OGSE Team)]] 11:04, 3 сентября 2009 (UTC)&lt;br /&gt;
&lt;br /&gt;
= Авторы =&lt;br /&gt;
Статья создана: [[Участник:Kamikazze|Kamikazze]]&lt;br /&gt;
&lt;br /&gt;
[[Категория:Скрипты]]&lt;/div&gt;</summary>
		<author><name>Kamikazze</name></author>	</entry>

	<entry>
		<id>http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2_(%D1%87%D0%B0%D1%81%D1%82%D1%8C_2)</id>
		<title>Как писать скрипты, не приводящие к вылетам и бою сейвов (часть 2)</title>
		<link rel="alternate" type="text/html" href="http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2_(%D1%87%D0%B0%D1%81%D1%82%D1%8C_2)"/>
				<updated>2011-03-10T06:30:47Z</updated>
		
		<summary type="html">&lt;p&gt;Kamikazze: Добавлена информация о предотвращении боя сейвов на Радаре&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;----&lt;br /&gt;
&lt;br /&gt;
[[Как писать скрипты, не приводящие к вылетам и бою сейвов|Начало в первой части статьи]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Как безопасно использовать коллбэки и таймерные события =&lt;br /&gt;
&lt;br /&gt;
В скриптовом движке есть удобный способ реакции на события - использование коллбэков - процедурных вызовов, привязанных к определённым событиям в жизни игрового объекта - получению повреждений, спавну, смерти и т.д. и т.п. Это hit_callback, death_callback из xr_motivator и многие другие... Все моддеры очень широко и совершенно спокойно пользуются ими, совершенно забывая при этом о таком важном факте, что это - обработки реального времени, как и таймерные события. Что это значит? А собственно вот что... Возьмём для примера коллбэк смерти неписей, мою головную боль последнего времени:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;xr_motivator.script -&lt;br /&gt;
function motivator_binder:death_callback(victim, who)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это функция обрабатывает смерть неписей. Когда вызывается этот коллбек он начинает по очереди вызывать внутренние функции, расположенные в других модулях. Они разрегистрируют умершего непися в гулагах, спавнят в него лут, обновляют статистику, отключают его от доп. схем логики и т.д.&lt;br /&gt;
&lt;br /&gt;
Так вот, обычно, когда в функциях возникает конфликт параметров, бесконечный цикл, попытка индексации nil и т.д., функция вылетает со стандартным логом. '''Но только не в случае, когда она находится внутри коллбэка. Если функция внутри него, то в случае возникновении в ней любой нештатной ситуации, коллбэк наглухо виснет.''' Это происходит из-за того, что функции вызываются строго друг за другом, и каждая из них вызывается только тогда, когда её предшественница вернула управление коллбэку. В случае с death_callback опознать такого непися очень просто - в его трупе окажется фонарик, КПК и возможно ещё немного разных &amp;quot;мусорных&amp;quot; вещей, что говорит о том, что обработка его смерти повисла не дойдя даже до спавна лута. В подобной ситуации можно быть на 100% уверенным, что труп этот не был корректно разрегистрирован, и игра всё ещё считает его живым неписем. Кроме того, зависший коллбэк не освобождает стек (а он у Луа-подсистемы единый на все скрипты), что '''в итоге приводит к вылетам игры с переполнением памяти (вот она, реальная причина этих &amp;quot;родных&amp;quot; вылетов).''' Но было бы слишком хорошо, если бы всё ограничивалось этим... однако тут всё намного хуже... такие &amp;quot;зависшие&amp;quot; коллбэки, особенно если их произошло несколько подряд, очень серьёзно влияют на работу а-лайфа. В лучшем случае они, забивая, стек, мешают нормально работать схемам логики, в худшем вызывают зависания самого  а-лайфа (этот эффект, кстати, производят и сами трупы таких неписей, так как они, как мы помним, не разрегистрировались корректно). '''Основной итог таких событий - бой сейвов, сделанных после возникновения таких ситуаций.''' Если повис один коллбэк, то такие сейвы ещё через раз загружаются, если же несколько, и остановилась работа а-лайфа - всё, сейвы бьются наглухо и реанимации не подлежат.&lt;br /&gt;
&lt;br /&gt;
Поэтому, чтобы избежать такого развития событий, каждый раз, когда вы вносите в коллбэк новую функцию - проверьте её самым тщательным образом. Она не должна содержать никаких рекурсивных циклов, в ней обязательно должны быть проверки на валидность обрабатываемых объектов и значений, и обязательно должна быть обработка нештатных ситуаций - т.е. функция должна обязательно, в абсолютно любой ситуации вернуть управление коллбэку, так или иначе. В самом наихудшем случае - делайте как делали разработчики игры - вставляйте принудительный вылет на рабстол функцией abort - она позволяет передавать отладочное сообщение, и это всяко лучше чем незаметный бой сейвов. Если обработка оборвалась в самом начале, а ф-ция обязательно должна вернуть значение - заведите ей &amp;quot;безопасное&amp;quot; возвращаемое значение по-умолчанию, которое она будет выдавать, если всё пошло плохо. И никогда не пренебрегайте пошаговой отладкой коллбэков с выводом в лог, особенно когда пишете схемы логики - это критически важно для стабильности вашего мода.&lt;br /&gt;
&lt;br /&gt;
== Основные внутриигровые признаки зависания коллбэков типа hit_callback, death_callback ==&lt;br /&gt;
&lt;br /&gt;
1. В трупах попадаются фонарики, КПК, разный мусор и общий лут слишком богат.&lt;br /&gt;
&lt;br /&gt;
2. Частые вылеты во время интенсивных боёв с логами типа&lt;br /&gt;
    Sheduler tried to update object...&lt;br /&gt;
    smart_terrain:1145(1146)&lt;br /&gt;
    LUA: out of memory&lt;br /&gt;
    любой_модуль_логики:любая_cтрока - stack overflow&lt;br /&gt;
&lt;br /&gt;
3. Частые &amp;quot;родные&amp;quot; вылеты в момент смерти непися или попадания по нему&lt;br /&gt;
&lt;br /&gt;
4. Произвольно бьются сейвы во время сражений, выброса и других насыщенных действиями событий&lt;br /&gt;
&lt;br /&gt;
= Использование защищённого кода в LUA =&lt;br /&gt;
&lt;br /&gt;
Периодически случаются такие ситуации, когда мы можем получить вылет при проверке аргумента, и не можем его адекватно заизолировать с помощью предварительной проверки на валидность значения. Вот простой пример: когда я отлаживал '''death_callback''' неписей, я периодически сталкивался с тем, что обращение к методу '''smart_terrain_id()''' при смерти непися иногда вызывало вылет &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;smart_terrain:1143 &amp;quot;attempt to index a nil value&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
- хотя это свойство является родным методом объекта, и отсутствовать напрочь никак не может. В итоге я пришёл к выводу, что его просто иногда не успевает отработать движок, так как вылет этот проявлялся в основном в интенсивных боях и совершенно произвольно.&lt;br /&gt;
&lt;br /&gt;
Вот код, в котором происходил вылет:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj     = sim:object( obj_id )&lt;br /&gt;
		local strn_id = obj:smart_terrain_id()  --- вылет происходит тут&lt;br /&gt;
&lt;br /&gt;
		if strn_id ~= 65535 then&lt;br /&gt;
			sim:object( strn_id ).gulag:clear_dead(obj_id)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это кусок родного кода из версии 1,0005 игры. Я долго пытался разными способами отсечь этот вылет, вводя предварительные проверки, однако это совершенно ничего не давало - вылет всё равно периодически случался, так как проверки эти сами его вызывали. Тогда я зарылся в документацию по Lua и обнаружил замечательную родную базовую функцию, введённую ещё с первых версий Lua, которой почему-то не пользовались ни разработчики игры, ни моддеры (хотя сама она в Lua сталкера присутствует, и работает отлично, без каких-либо нареканий). Вот она:&lt;br /&gt;
&lt;br /&gt;
'''pcall (f, arg1, ···)'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Вызывает функцию f с указанными через запятую аргументами в защищённом режиме. Это означает, что любая ошибка, даже критическая, внутри вызванной функции, не передаётся наружу - вызывавшей подсистеме. Вместо этого '''pcall''' перехватывает ошибку и возвращает код статуса. Первая возвращаемая переменная это сам код, (true или false) и если всё прошло хорошо, он равен true. В этом случае '''pcall''' сразу после статуса возвращает все результаты от работы защищённой им функции. Если же в защищённой функции произошла ошибка, то '''pcall''' вернёт false и затем сообщение об ошибке. (Обратите внимание, обработка ошибки присходит БЕЗ вылета! Вместо вылета вы получите вполне адекватную строку с ошибкой, которую можно вывести в лог для последующей обработки)&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Я настоятельно советую пользоваться этой функцией в случаях, когда из коллбэков вызываются сложные комплексные обработки, вроде обработки из менеджера вооружений AI-пака. Это позволяет предотвратить как вылеты, так и зависания обработок, и в итоге позволяет хорошо стабилизировать игру.&lt;br /&gt;
&lt;br /&gt;
Возвращаясь к нашим смарттеррейнам... вот как в итоге я подавил вылет типа smart_terrain:1143 с помощью pcall:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;--- эта функция пытается проверить св-во smart_terrain_id объекта. Именно её мы вызовем в защищённом режиме.&lt;br /&gt;
function prot_smt_td(obj)&lt;br /&gt;
	if IsStalker(obj) or IsMonster(obj) then&lt;br /&gt;
		return obj:smart_terrain_id()&lt;br /&gt;
	else	&lt;br /&gt;
		return 65535&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj = sim:object( obj_id )&lt;br /&gt;
		if obj then&lt;br /&gt;
			local strn_id = 65535  --- предварительно проинитим переменную, на &lt;br /&gt;
						--- случай если у нас prot_smt_td выдаст ошибку&lt;br /&gt;
			local result, smt_id = pcall(prot_smt_td,obj)	--- вызываем prot_smt_td в защищённом режиме &lt;br /&gt;
									--- и сразу присваиваем его вывод переменным&lt;br /&gt;
			if result then --- если pcall выдало true&lt;br /&gt;
				strn_id = smt_id  --- тогда применяем полученное значение&lt;br /&gt;
			end&lt;br /&gt;
			--- если же обработка выдаст ошибку, то strn_id останется неизменным...&lt;br /&gt;
			if strn_id ~= 65535 then&lt;br /&gt;
				sim:object(strn_id).gulag:clear_dead(obj_id)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В других местах это делается совершенно аналогично. Подробнее об этой и многих других функция для контроля кода, незаслуженно игнорируемых большинством моддеров, можно почитать тут: http://lua-users.org/wiki/FinalizedExceptions - на английском правда, но захотите - разберётесь, там всё просто. &lt;br /&gt;
&lt;br /&gt;
Настоятельно советую вам изучить работу кода в таких условиях и научиться его правильно применять - этим вы сильно облегчите жизнь как себе, так и тем, кто после вас будет рыться в вашем коде или импортировать его в свои разработки.&lt;br /&gt;
&lt;br /&gt;
= Скрытые критические проблемы в обработке вылетов игрой =&lt;br /&gt;
&lt;br /&gt;
Ведя на днях отладку, выяснил в чём проблема с периодическим боем сейвов и многими другими заморочками как в оригинале игры, так и во многих модах... дело, как выяснилось, далеко не всегда в кривых руках. Есть такая стандартная ф-ция '''abort''' - предназначенная для выкидывания из игры, если что-то пошло не так. И как оказалось, она срабатывает далеко не всегда. Выяснилось это следующим образом:&lt;br /&gt;
&lt;br /&gt;
В одном из логов нашего бета-тестера я увидел '''стандартное сообщение о вылете внутри рабочего лога'''... да-да, то самое которое '''FATAL ERROR''' и дальше по тексту. При этом игра у него НЕ вылетала, это сообщение об ошибке мы обнаружили позже, по случайности. Я заподозрил, что что-то не в порядке, и вставил внутрь этой ф-ции контрольную метку, кидавшую в консоль сообщение, в котором содержался паттерн сообщения об ошибке и само сообщение. Так вот, оказалось, что эта самая функция '''abort''' вызывается в игре с завидным постоянством (вы удивитесь насколько часто), когда возникают исключения в схемах логики, звука и т.д., но игра от этого вылетает на рабочий стол '''максимум только 3 раза из 10 вызовов'''. Вылет НЕ происходит обычно, когда функции передан паттерн ошибки, а остальные параметры пустые, такое бывает, и частенько. И если не сделать внутри этой функции особой метки для вывода в лог, как сделал это я, её вызовы проходят совершенно незаметно, и '''игра после критических ошибок продолжается как ни в чём ни бывало.''' А приводит это вот к чему... Внутри '''xr_logic''' в процедуре записи пстора (хранилища логики и флагов) неписей есть вызовы этого самого аборта в случае если на запись в пстор передана некорректная величина. Ну а так как аборт периодически вообще не срабатывает, то часто попадается ситуация, что неписям в пстор пишется полный ахтунг: куски кода из ОЗУ, всякая муть из лтх-ов, куски аллспавна, всё что угодно. Происходит это оттого, что кодер, писавший эту функцию ('''xr_logic.pstor_store(obj, varname, val)'''), явно и думать не думал что '''abort''' может не сработать. У него запись в пстор стояла после проверки, а не внутри неё (very bad idea), и если abort не срабатывал, игра писала в пстор мусор совершенно спокойно и незаметно для игрока. Потом вся эта хрень попадала прямо в сейвы. Вот проблемный код для наглядности:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_store(obj, varname, val)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	if db.storage[npc_id].pstor == nil then&lt;br /&gt;
		db.storage[npc_id].pstor = {}&lt;br /&gt;
	end&lt;br /&gt;
	local tv = type(val)&lt;br /&gt;
	if not pstor_is_registered_type(tv) then&lt;br /&gt;
		abort(&amp;quot;xr_logic: pstor_store: not registered type '%s' encountered&amp;quot;, tv) --- вот тут мы должны если что вылететь&lt;br /&gt;
	end&lt;br /&gt;
	db.storage[npc_id].pstor[varname] = val -- а если не вылетели, всё, получим запись в пстор левой мути&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Разумеется игра этим пстором в итоге давится, и сейвы сделанные после такой милой записи практически лопаются. Результат - &amp;quot;битые&amp;quot; (на самом деле подлежат реанимации) сейвы. Происходит это потому, что игра из сейва грузит неписям псторы сплошным чтением по словам, пока они не закончатся. В случае же если в псторе обнаруживается записанный ранее мусор, то обработка либо вылетает сразу, либо наглухо виснет, пытаясь запихать эдак с миллион слов в пстор особо отличившегося непися. Как вам например непись с размером пстора в 1697451 слова? В результате попытки его обработать игра просто на стадии синхронизации выжрала всю доступную ОЗУ и повисла. &lt;br /&gt;
&lt;br /&gt;
Решение этой проблемы оказалось достатоно простым: во-первых я предположил максимальный размер полезной части пстора неписей в 20 слов r_u32() (пока ориентировочно, я ещё уточняю эту величину), и соответственно сделал остановку цикла загрузки пстора для неписей через 20 итераций. Там же, где в цикле стояла проверка на валидность записываемых данных (кстати тоже с вылетом в случае провала проверки), я сделал так, что если параметр не относится к валидному типу данных, то запись параметра в пстор не производится совсем. Это необходимо для того, чтобы если вдруг в сейве обнаружится мусор, то он был бы просто отброшен обработкой. Практика показала, что в итоге такие неписи вполне адекватны и в дальнейшем никаких проблем не вызывают, так как начало их пстора, с нормальными данными, обычно не повреждается - мусор дописывается после них, а не вместо них. &lt;br /&gt;
&lt;br /&gt;
Ну и во-вторых модифицировал запись параметров в пстор, просто убрав запись под основание if-else так, чтобы если параметр неверен, он не записывался совсем. Теперь кстати, очень интересно, сохранились ли те же заморочки с ф-цией '''abort''' в Чистом Небе, и если да, то останутся ли в Зове Припяти?&lt;br /&gt;
&lt;br /&gt;
------------&lt;br /&gt;
&lt;br /&gt;
== Необходимые для стабилизации игры правки в модулях ==&lt;br /&gt;
&lt;br /&gt;
'''Эта правка предотвращает запись в пстор если не сработал аборт:'''&lt;br /&gt;
&lt;br /&gt;
''xr_logic.script''&lt;br /&gt;
&lt;br /&gt;
'''Было:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_store(obj, varname, val)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	if db.storage[npc_id].pstor == nil then&lt;br /&gt;
		db.storage[npc_id].pstor = {}&lt;br /&gt;
	end&lt;br /&gt;
	local tv = type(val)&lt;br /&gt;
	if not pstor_is_registered_type(tv) then&lt;br /&gt;
		abort(&amp;quot;xr_logic: pstor_store: not registered type '%s' encountered&amp;quot;, tv)&lt;br /&gt;
	end&lt;br /&gt;
	db.storage[npc_id].pstor[varname] = val&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Стало:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_store(obj, varname, val)&lt;br /&gt;
	if not obj then return end&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	if db.storage[npc_id].pstor == nil then&lt;br /&gt;
		db.storage[npc_id].pstor = {}&lt;br /&gt;
	end&lt;br /&gt;
	local tv = type(val)&lt;br /&gt;
	if not pstor_is_registered_type(tv) then&lt;br /&gt;
		dgblog(&amp;quot;xr_logic: pstor_store: not registered type encountered - write in pstor_store cancelled&amp;quot;)&lt;br /&gt;
		-- abort убран, так как один хрен не работает. Пусть тогда хотя бы в лог что-то валится.&lt;br /&gt;
	else&lt;br /&gt;
		db.storage[npc_id].pstor[varname] = val&lt;br /&gt;
		-- вот так и только так. Если значение не валидно, ничего не происходит.&lt;br /&gt;
	end	&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''А эта правка выкинет из пстора весь мусор при загрузке сейва, если он как-то в него попал'''&lt;br /&gt;
&lt;br /&gt;
'''Было:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_load_all(obj, reader)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	local pstor = db.storage[npc_id].pstor&lt;br /&gt;
	if not pstor then&lt;br /&gt;
		pstor = {}&lt;br /&gt;
		db.storage[npc_id].pstor = pstor&lt;br /&gt;
	end&lt;br /&gt;
	local ctr = reader:r_u32()&lt;br /&gt;
	for i = 1, ctr do&lt;br /&gt;
		local varname = reader:r_stringZ()&lt;br /&gt;
		local tn = reader:r_u8()&lt;br /&gt;
		if tn == pstor_number then&lt;br /&gt;
			pstor[varname] = reader:r_float()&lt;br /&gt;
		elseif tn == pstor_string then&lt;br /&gt;
			pstor[varname] = reader:r_stringZ()&lt;br /&gt;
		elseif tn == pstor_boolean then&lt;br /&gt;
			pstor[varname] = reader:r_bool()&lt;br /&gt;
		else&lt;br /&gt;
			abort(&amp;quot;xr_logic: pstor_load_all: not registered type N %d encountered&amp;quot;, tn)&lt;br /&gt;
		end&lt;br /&gt;
		printf(&amp;quot;_bp: pstor_load_all: loaded [%s]='%s'&amp;quot;, varname, utils.to_str(pstor[varname]))&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Стало:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_load_all(obj, reader)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	local pstor = db.storage[npc_id].pstor&lt;br /&gt;
	if not pstor then&lt;br /&gt;
		pstor = {}&lt;br /&gt;
		db.storage[npc_id].pstor = pstor&lt;br /&gt;
	end&lt;br /&gt;
	local ctr = reader:r_u32()&lt;br /&gt;
	if tonumber(ctr) &amp;gt; 20 and tostring(obj:name()) ~= &amp;quot;single_player&amp;quot; and npc_id ~= db.actor:id() then&lt;br /&gt;
		-- максимум 20 итераций - это число ещё уточняется, возможно понадобится больше&lt;br /&gt;
                -- если у вас в пстор что-то свое пишется, ориентируйтесь на свои значения&lt;br /&gt;
		-- и обязательно убираем из проверки актора - у него очень толстый пстор, и к тому же&lt;br /&gt;
                -- если уж поврежденным будет его пстор, то тут точно уже ничего не поможет&lt;br /&gt;
		dgblog(&amp;quot;ОБНАРУЖЕН ОБЪЕКТ С ПОВРЕЖДЕННЫМ PSTOR: &amp;quot;..tostring(obj:name())..&lt;br /&gt;
&amp;quot; БУДЕТ ПРОИЗВЕДЕНА ПОПЫТКА ВОССТАНОВЛЕНИЯ&amp;quot;)&lt;br /&gt;
		ctr = 20 &lt;br /&gt;
	end&lt;br /&gt;
	for i = 1, ctr do&lt;br /&gt;
		local varname = reader:r_stringZ()&lt;br /&gt;
		local tn = reader:r_u8()&lt;br /&gt;
		if tn == pstor_number then&lt;br /&gt;
			pstor[varname] = reader:r_float()&lt;br /&gt;
		elseif tn == pstor_string then&lt;br /&gt;
			pstor[varname] = reader:r_stringZ()&lt;br /&gt;
		elseif tn == pstor_boolean then&lt;br /&gt;
			pstor[varname] = reader:r_bool()&lt;br /&gt;
		else&lt;br /&gt;
			-- не надо пытаться вылетать - просто не пишем поврежденные данные&lt;br /&gt;
			-- при этом обязательно удалять саму переменную - в результате записи&lt;br /&gt;
 			-- мусора в пстор одно только ее название может повесить загрузку&lt;br /&gt;
			pstor[varname] = nil&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Эти примеры приведены для чистой игры. Единственное в чем не уверен пока, это в том, что максимум полезного размера - 20 слов. Возможно нужно выделить больше, это надо будет проверить ещё экспериментальным путем...&lt;br /&gt;
&lt;br /&gt;
Кроме этого, советую ещё внутрь ф-ции abort вставить отладочные метки, чтобы точно знать когда она вызывалась. Настоятельно советую сделать это даже если вы матёрый моддер со стажем - гарантирую, будете неприятно удивлены.&lt;br /&gt;
&lt;br /&gt;
Я это сделал вот так:&lt;br /&gt;
&lt;br /&gt;
''_g.script''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- Крешнуть игру (после вывода сообщения об ошибке в лог)&lt;br /&gt;
function abort(fmt, msg)&lt;br /&gt;
	local message = tostring(msg)&lt;br /&gt;
	dbglog(&amp;quot;ERROR PATTERN: &amp;quot;..tostring(fmt))&lt;br /&gt;
	dbglog(&amp;quot;ERROR REASON: &amp;quot;..message)&lt;br /&gt;
	local reason = string.format(fmt, message)&lt;br /&gt;
	assert(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	printf(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	dbglog(&amp;quot;%s&amp;quot;, reason)&lt;br /&gt;
	printf(&amp;quot;%s&amp;quot;)&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
По поводу частичной неработоспособности ф-ции '''abort''' я беседовал с Колмогором, и он пришёл к выводу, что видимо вылет игры должен был бы производиться при обработке функции '''printf(&amp;quot;%s&amp;quot;)''' - ей тут передаётся заведомо отсутствующий оператор и она по уму должна бы сразу крашить игру. Однако в релизе функция printf фактически не работает(она реализована в _g.script через вырезанную функцию log). В результате игра не крашится. Но что поделать, в итоге мне пришлось модифицировать его таким образом, чтобы вылет при его срабатывании был гарантирован:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- Крешнуть игру (после вывода сообщения об ошибке в лог)&lt;br /&gt;
function abort(fmt, msg)&lt;br /&gt;
	local message = tostring(msg)&lt;br /&gt;
	dbglog(&amp;quot;ERROR PATTERN: &amp;quot;..tostring(fmt))&lt;br /&gt;
	dbglog(&amp;quot;ERROR REASON: &amp;quot;..message)&lt;br /&gt;
	local reason = string.format(fmt, message)&lt;br /&gt;
	assert(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	printf(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	dbglog(&amp;quot;%s&amp;quot;, reason)&lt;br /&gt;
	printf(&amp;quot;%s&amp;quot;)&lt;br /&gt;
	local crash&lt;br /&gt;
	local ooops = 1/crash&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Вылет происходит при попытке произвести арифметическую операцию с неинициализированной переменной crash.&lt;br /&gt;
&lt;br /&gt;
== Лечение зависаний алайфа при смерти персонажей ==&lt;br /&gt;
&lt;br /&gt;
Недавно, отлаживая проблемы с зависанием алайфа, мне удалось найти причину этого периодически во всех модах всплывающего сбоя, приводящего к порче сейвов и сильно мешающего нормально играть. Сбой этот возникает при смерти некоторых NPC, обычно квестовых. В частности в моём случае изолировать и отладить это зависание удалось на Юрике, новичке со Свалке, учавствующем в сцене с гоп-стопом. Причина оказалась в обработке посмертной отрегистрации NPC из гулагов, причём сбой там был настоящей матрёшкой, составной из нескольких частей. Правок в итоге было совсем немного, но чтобы сделать их мне пришлось несколько часов распутывать клубок из кросс-вызовов между скриптами smart_terrain и xr_gulag. Итак, начнём с самого начала. Работая над OGSE 069 и 0691 я периодически сталкивался с зависаниями и вылетами в посмертных обработках неписей. Один из таких вылетов - всем хорошо знакомый вылет:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;smart_terrain:1143 &amp;quot;attempt to index a nil value&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Происходящий в функции '''smart_terrain.on_death( obj_id )''' - я тогда его заблокировал вызовом его внутри безопасного кода функцией '''pcall''', однако, как теперь выяснилось, этого оказалось недостаточно - баг тут состоит из нескольких частей, и этот вылет указывает только на одну из них. Вот исходный код:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj     = sim:object( obj_id )&lt;br /&gt;
		local strn_id = obj:smart_terrain_id()  --- первый вылет/зависнаие алайфа происходит тут&lt;br /&gt;
&lt;br /&gt;
		if strn_id ~= 65535 then&lt;br /&gt;
			sim:object( strn_id ).gulag:clear_dead(obj_id) -- а вот в этой обработке происходит зависание алайфа. Она очень комплексная, и её сложно распутывать.&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Во-первых выяснилось, что изредка вызов '''obj:smart_terrain_id()''' вызывает зависание алайфа даже когда он производится изнутри защищённого кода. Тогда я решил избавиться от использования этой функции в данном месте совсем. После нескольких экспериментов выяснилось, что самым простым, быстрым и вылетобезопасным способом будет считать нетпакет существа в таблицу и выудить идентификатор смарттеррейна из неё. Для этого можно написать свою обработку, однако я, как весьма ленивый программист, не склонен изобретать велосипеды, поэтому я воспользовался уже проверенной у нас и активно используемой в OGSE библиотекой функций для работы с нетпакетами '''m_net_utils''' Артоса. Кроме того, я сразу сделал более безопасным вызов обработки на отрегистрацию в гулагах. Вот, собственно, что в итоге получилось:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj = sim:object( obj_id )&lt;br /&gt;
		if (obj and obj.smart_terrain_id) then&lt;br /&gt;
			local strn_id = 65535  -- значение по умолчанию&lt;br /&gt;
			&lt;br /&gt;
			local t = nil -- сюда запихнём табличку из пакета&lt;br /&gt;
			if IsStalker(obj) then t = m_net_utils.get_stalker_data(obj) elseif IsMonster(obj) then t = m_net_utils.get_monster_data(obj) end &lt;br /&gt;
			-- вызываем парсинг пакета для неписей и монстров отдельно&lt;br /&gt;
			&lt;br /&gt;
			-- print_table_inlog(t)&lt;br /&gt;
			if t.smtrid then&lt;br /&gt;
				strn_id = tonumber(t.smtrid) -- получаем идентификатор смарта, если его нету даже в пакете, хрен с ним, будет 65535&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
			if strn_id ~= 65535 then -- если сняли идентификатор, попробуем отрегать...&lt;br /&gt;
				local gulag = sim:object(strn_id)&lt;br /&gt;
				if gulag and gulag.gulag then -- ...но сначала выясним если вообще такой гулаг и инициализирован ли он&lt;br /&gt;
					sim:object(strn_id).gulag:clear_dead(obj_id)&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При этом сделаю отсупление и предупрежу об одном очень странном сбое с которым я столкнулся редактируя эту функцию. Так вот, всё нормально работает только тогда когда у ф-ции этой есть строго определённая структура. '''Стоит только добавить пару строк, убрать закомментированную и сдвинуть пару условий, просто в тексте сдвинуть, не меняя внутренней логики, как игра начинает вылетать, причём ещё до загрузки сейва, при кэшировании (!) и с очень странными логами, хотя код написан синтаксически безупречно! Например с таким:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Description: xr_gulag:1035 value not found ObjectJobPathName[obj_id]&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Вернёшь строки на место, перестроишь текст - работает снова. Совершенная чертовщина, шаманил я над этой функцией около получаса, перестраивая текст таким образом чтобы игра нормально запускалась.''' Имейте это в виду когда в неё полезете и если что не пугайтесь если игра начнёт так вылетать - в этом случае пошаманьте немного над ней, меняя её форматирование. О причинах такого поведения я не могу даже догадываться - то ли движок её вызывает по смещению внутри файла вручную, то ли это какой-то баг Lua-парсера, но факт фактом.&lt;br /&gt;
&lt;br /&gt;
Итак, теперь проблема с получением идентификатора смарта разрешилась, однако алайф всё равно зависал! Простая трассировка показала, что теперь зависание происходило внутри обработки&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;sim:object(strn_id).gulag:clear_dead(obj_id)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И мне пришлость распутывать её по частям, доискиваясь до причины. Обработка честно говоря мерзкая, размазана по smart_terrain и xr_gulag, при этом взаимные вызовы идут не менее десятка раз, в следующем стиле: ф-ция в смарте вызывает ф-цию в гулаге, которая вызывает фцию в смарте, которая обрабатывает параметр фцией в гулаге, который передётся ф-цией в логике, которая получает её из смарта. Нечто подобное. Опуская все нецензурные выражения, употребленные мной при трассировке, я лучше расскажу что собственно вышло в итоге. А в итоге я вышел вот на этот код в xr_gulag, именно в нём при отрегистрации некоторых мёртвых неписей происходит зависание алайфа:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- освободить объект от работы и переинициализировать логику.&lt;br /&gt;
-- если сталкер в онлайне и начал работу, то сбросить его схему поведения&lt;br /&gt;
-- как будто он только что загрузился&lt;br /&gt;
function gulag:free_obj_and_reinit( obj_id )&lt;br /&gt;
	self:free_obj(obj_id)&lt;br /&gt;
&lt;br /&gt;
	local t = self.Object[obj_id]&lt;br /&gt;
	if t ~= nil and t ~= true and self.Object_begin_job[obj_id] then&lt;br /&gt;
		xr_logic.initialize_obj( t, nil, false, db.actor, self:get_stype( obj_id ) ) -- вот эта обработка вешает алайф&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Я, честно говоря, ни разу не понимаю, на кой чёрт нужно переконфигурировать схемы логики и выбирать из них активную '''трупу, который лежит себе спокойно и никого не трогает.''' Может быть в это есть некий высший смысл, или это было продиктовано неким аккуратизмом, однако одно я могу сказать однозначно - подобная переинициализация у некоторых трупов неписей приводит к глухому зависанию алайфа. При этом если для трупов эту обработку заблокировать, то трупы разрегистрируются вполне нормально и спокойно лежат с нетронутой логикой, не вызывая никаких проблем. Итоговый код после правок выглядит вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- освободить объект от работы и переинициализировать логику.&lt;br /&gt;
-- если сталкер в онлайне и начал работу, то сбросить его схему поведения&lt;br /&gt;
-- как будто он только что загрузился&lt;br /&gt;
function gulag:free_obj_and_reinit( obj_id )&lt;br /&gt;
	self:free_obj(obj_id)&lt;br /&gt;
	local t = self.Object[obj_id]&lt;br /&gt;
	if t ~= nil and t ~= true and self.Object_begin_job[obj_id] then&lt;br /&gt;
		if check_game() then -- тут проверяется, запущена ли игра, если ли актор и жив ли он. Если да, делаем по новому.&lt;br /&gt;
			local s_obj = alife():object(obj_id) -- проверим есть ли у цели разрегистрации валидный серверный объект&lt;br /&gt;
			if s_obj and (IsStalker(s_obj) or IsMonster(s_obj)) and s_obj:alive() then -- если есть, он жив и сталкер или монстр&lt;br /&gt;
				xr_logic.initialize_obj( t, nil, false, db.actor, self:get_stype( obj_id ) ) -- только тогда инициализируем логику&lt;br /&gt;
			end&lt;br /&gt;
		else -- а если игра не запущена, то как раньше. Это нужно для того, чтобы обработка запуска игры нормально работала.&lt;br /&gt;
			xr_logic.initialize_obj( t, nil, false, db.actor, self:get_stype( obj_id ) )&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Проверка, запущена ли игра&lt;br /&gt;
function check_game()&lt;br /&gt;
	if level.present() and (db.actor ~= nil) and db.actor:alive() then&lt;br /&gt;
		return true&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
После этих поправок зависания алайфа при смерти неписей удалось побороть окончательно. Решение с отрезанием реинита логики для трупов несколько грубовато, но честно говоря, у меня нет ни малейшего желания трассировать и разбирать на части функцию xr_logic.initialize_obj, выясняя, чем же ей так данный конкретный труп не приглянулся. Если хотите - займитесь, найдёте причину - дополните данную статью. Я же успокоился на том, что заблокировал баг, периодически убивающий людям игру, так как многие пользователи, игнорируя предупреждения, играют на одном-двух сейвах, а то и вовсе на квиксейвах всю игру.&lt;br /&gt;
&lt;br /&gt;
= Другие частые проблемы =&lt;br /&gt;
&lt;br /&gt;
Спустя некоторое время я обнаружил причины ещё нескольких часто встречающихся вылетов, и решил записать их описание сюда - эта информация наверняка ещё много кому пригодится.&lt;br /&gt;
&lt;br /&gt;
== Вылеты при удалении объектов из игры ==&lt;br /&gt;
&lt;br /&gt;
При использовании для удаления объектов родной движковой функции '''alife():release(alife():object(id), true)''' возможен целый ворох разнообразнейших вылетов, обычно - безлоговых, что сильно затрудняет их отладку. Вот из-за чего они возникают:&lt;br /&gt;
&lt;br /&gt;
1) Вылет при удалении непися или монстра, находящегося в онлайне.&lt;br /&gt;
   Решение: с помощью alife():release '''можно удалять только мёртвые объекты'''. Поэтому если вам нужно удалить с её помощью непися или монстра, который жив и находится в онлайне, его нужно убить любым доступным методом, хотя бы нанеся ему hit() с любым запредельным уроном по вкусу.&lt;br /&gt;
&lt;br /&gt;
2) Вылет при удалении оружия или артефакта.&lt;br /&gt;
   Решение: такая проблема часто встречается в случае если объект неудачно расположен или находится в руках у непися. Для того чтобы не произошло вылета, убедитесь что объект доступен как серверный перед удалением. Вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;	local obj = alife():object(i)&lt;br /&gt;
	if obj then&lt;br /&gt;
		alife():release(obj, true)&lt;br /&gt;
	end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Эту конструкцию вообще желательно использовать всегда, когда вы так удаляете объекты.&lt;br /&gt;
&lt;br /&gt;
3) Вылет при удалении аномалии.&lt;br /&gt;
   Решение: аномалии - очень капризные при подобном с ними обращении объекты. Они влияют на своё окружение, и если рядом с ними находится непись или монстр, то удаление такой аномалии приведёт к вылету игры. Чтобы этого не произошло, аномалию надо сначала выключить функцией '''disable_anomaly''', и удалять затем ТОЛЬКО тогда, когда она не будет занята влиянием на динамический объект. Для этого нужно получить список мобов на локации, и из их нетпакетов считать идентификаторы действующих на них рестрикторов. Если ваша аномалия будет в этом списке - удалять её нельзя. Дождитесь пока она освободится.&lt;br /&gt;
&lt;br /&gt;
== Вылет при открытии закладки &amp;quot;Контакты&amp;quot; в ПДА ==&lt;br /&gt;
&lt;br /&gt;
Простой безлоговый вылет при открытии закладки &amp;quot;Контакты&amp;quot;. Встречался во всех крупных модах, и никто не знал как его излечить. А лечится он банально - '''его причина - дублирование идентификаторов секций в XML-файле, описывающем иконки неписей для закладки &amp;quot;Контакты&amp;quot;'''. Нужно всего лишь проверить этот файл на наличие дублированных идентификаторов и удалить их. Вылет пропадёт и никогда больше не будет встречаться.&lt;br /&gt;
&lt;br /&gt;
== Вылеты при вызове несуществующих функций из XML ==&lt;br /&gt;
&lt;br /&gt;
В ХML-файлах, используемых для описания инфопоршенов, для многих инфопоршенов прописаны действия, которые игра вызывает при взятии этого инфопоршена. Вот так примерно:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
	&amp;lt;info_portion id=&amp;quot;barman_document_have&amp;quot;&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;dialogs.set_actor_prebandit1&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandits2&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandits3&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandit7&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandit8&amp;lt;/action&amp;gt;&lt;br /&gt;
	&amp;lt;/info_portion&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так вот, если вы допустите опечатку в названии вызываемой функции или же случайно её удалите - игра будет стабильно вылетать без лога при взятии этого инфопоршена.&lt;br /&gt;
&lt;br /&gt;
== Повреждения сейвов на Радаре и других местах в модах, основанных на OGSM ==&lt;br /&gt;
&lt;br /&gt;
В оригинале ОГСМ и основанных на нём модах часто встречались проблемы с сохранениями на Радаре. Эту проблему долго не удавалось победить, пока наконец благодаря помощи Маландринуса не удалось выявить её первопричину. Как выяснилось, она очень проста - гражданские зомби в моде (монстры) имели в конфиге ту же пропись вида (параметр конфига specie), что и монолитовцы и зомбированные (неписи). И там и тут было проставлено &amp;quot;zombie&amp;quot;, и так оно было ещё с оригинала. Как оказалось, так делать категорически нельзя. Дело в том, что неписей есть такой функционал, как хитовая память - в ней какое-то время хранятся ссылки на атакующие объекты. У монстров тоже есть остатки этого функционала, но он неработоспособен, и использовать его нельзя. В случае же когда монстры и неписи попадают в один вид, в ситуации когда они находятся рядом в бою, хитовая память монстров автоматически получает от неписей того же вида распространяемую внутри вида информацию об атакующих - а хранить её монстрам нельзя. Если после создания такой ситуации сохраниться - сейв будет вызывать вылет при загрузке. То есть проще говоря, если в бою с монолитовцами рядом оказывались гражданские зомби - и игрок сохранял игру - сейв этот не загружался. Чтобы предотвратить эти проблемы, вполне достаточно создать для гражданских зомби свой отдельный вид, добавив его прописи в конфиг game_relations.&lt;br /&gt;
&lt;br /&gt;
--[[Участник:Kamikazze|KamikaZze (OGSE Team)]] 11:04, 3 сентября 2009 (UTC)&lt;br /&gt;
&lt;br /&gt;
= Авторы =&lt;br /&gt;
Статья создана: [[Участник:Kamikazze|Kamikazze]]&lt;br /&gt;
&lt;br /&gt;
[[Категория:Скрипты]]&lt;/div&gt;</summary>
		<author><name>Kamikazze</name></author>	</entry>

	<entry>
		<id>http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2_(%D1%87%D0%B0%D1%81%D1%82%D1%8C_2)</id>
		<title>Как писать скрипты, не приводящие к вылетам и бою сейвов (часть 2)</title>
		<link rel="alternate" type="text/html" href="http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2_(%D1%87%D0%B0%D1%81%D1%82%D1%8C_2)"/>
				<updated>2010-02-19T18:36:29Z</updated>
		
		<summary type="html">&lt;p&gt;Kamikazze: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Категория:Статьи участников]]&lt;br /&gt;
[[Категория:Скрипты]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[Как писать скрипты, не приводящие к вылетам и бою сейвов|Начало в первой части статьи]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Как безопасно использовать коллбэки и таймерные события =&lt;br /&gt;
&lt;br /&gt;
В скриптовом движке есть удобный способ реакции на события - использование коллбэков - процедурных вызовов, привязанных к определённым событиям в жизни игрового объекта - получению повреждений, спавну, смерти и т.д. и т.п. Это hit_callback, death_callback из xr_motivator и многие другие... Все моддеры очень широко и совершенно спокойно пользуются ими, совершенно забывая при этом о таком важном факте, что это - обработки реального времени, как и таймерные события. Что это значит? А собственно вот что... Возьмём для примера коллбэк смерти неписей, мою головную боль последнего времени:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;xr_motivator.script -&lt;br /&gt;
function motivator_binder:death_callback(victim, who)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это функция обрабатывает смерть неписей. Когда вызывается этот коллбек он начинает по очереди вызывать внутренние функции, расположенные в других модулях. Они разрегистрируют умершего непися в гулагах, спавнят в него лут, обновляют статистику, отключают его от доп. схем логики и т.д.&lt;br /&gt;
&lt;br /&gt;
Так вот, обычно, когда в функциях возникает конфликт параметров, бесконечный цикл, попытка индексации nil и т.д., функция вылетает со стандартным логом. '''Но только не в случае, когда она находится внутри коллбэка. Если функция внутри него, то в случае возникновении в ней любой нештатной ситуации, коллбэк наглухо виснет.''' Это происходит из-за того, что функции вызываются строго друг за другом, и каждая из них вызывается только тогда, когда её предшественница вернула управление коллбэку. В случае с death_callback опознать такого непися очень просто - в его трупе окажется фонарик, КПК и возможно ещё немного разных &amp;quot;мусорных&amp;quot; вещей, что говорит о том, что обработка его смерти повисла не дойдя даже до спавна лута. В подобной ситуации можно быть на 100% уверенным, что труп этот не был корректно разрегистрирован, и игра всё ещё считает его живым неписем. Кроме того, зависший коллбэк не освобождает стек (а он у Луа-подсистемы единый на все скрипты), что '''в итоге приводит к вылетам игры с переполнением памяти (вот она, реальная причина этих &amp;quot;родных&amp;quot; вылетов).''' Но было бы слишком хорошо, если бы всё ограничивалось этим... однако тут всё намного хуже... такие &amp;quot;зависшие&amp;quot; коллбэки, особенно если их произошло несколько подряд, очень серьёзно влияют на работу а-лайфа. В лучшем случае они, забивая, стек, мешают нормально работать схемам логики, в худшем вызывают зависания самого  а-лайфа (этот эффект, кстати, производят и сами трупы таких неписей, так как они, как мы помним, не разрегистрировались корректно). '''Основной итог таких событий - бой сейвов, сделанных после возникновения таких ситуаций.''' Если повис один коллбэк, то такие сейвы ещё через раз загружаются, если же несколько, и остановилась работа а-лайфа - всё, сейвы бьются наглухо и реанимации не подлежат.&lt;br /&gt;
&lt;br /&gt;
Поэтому, чтобы избежать такого развития событий, каждый раз, когда вы вносите в коллбэк новую функцию - проверьте её самым тщательным образом. Она не должна содержать никаких рекурсивных циклов, в ней обязательно должны быть проверки на валидность обрабатываемых объектов и значений, и обязательно должна быть обработка нештатных ситуаций - т.е. функция должна обязательно, в абсолютно любой ситуации вернуть управление коллбэку, так или иначе. В самом наихудшем случае - делайте как делали разработчики игры - вставляйте принудительный вылет на рабстол функцией abort - она позволяет передавать отладочное сообщение, и это всяко лучше чем незаметный бой сейвов. Если обработка оборвалась в самом начале, а ф-ция обязательно должна вернуть значение - заведите ей &amp;quot;безопасное&amp;quot; возвращаемое значение по-умолчанию, которое она будет выдавать, если всё пошло плохо. И никогда не пренебрегайте пошаговой отладкой коллбэков с выводом в лог, особенно когда пишете схемы логики - это критически важно для стабильности вашего мода.&lt;br /&gt;
&lt;br /&gt;
== Основные внутриигровые признаки зависания коллбэков типа hit_callback, death_callback ==&lt;br /&gt;
&lt;br /&gt;
1. В трупах попадаются фонарики, КПК, разный мусор и общий лут слишком богат.&lt;br /&gt;
&lt;br /&gt;
2. Частые вылеты во время интенсивных боёв с логами типа&lt;br /&gt;
    Sheduler tried to update object...&lt;br /&gt;
    smart_terrain:1145(1146)&lt;br /&gt;
    LUA: out of memory&lt;br /&gt;
    любой_модуль_логики:любая_cтрока - stack overflow&lt;br /&gt;
&lt;br /&gt;
3. Частые &amp;quot;родные&amp;quot; вылеты в момент смерти непися или попадания по нему&lt;br /&gt;
&lt;br /&gt;
4. Произвольно бьются сейвы во время сражений, выброса и других насыщенных действиями событий&lt;br /&gt;
&lt;br /&gt;
= Использование защищённого кода в LUA =&lt;br /&gt;
&lt;br /&gt;
Периодически случаются такие ситуации, когда мы можем получить вылет при проверке аргумента, и не можем его адекватно заизолировать с помощью предварительной проверки на валидность значения. Вот простой пример: когда я отлаживал '''death_callback''' неписей, я периодически сталкивался с тем, что обращение к методу '''smart_terrain_id()''' при смерти непися иногда вызывало вылет &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;smart_terrain:1143 &amp;quot;attempt to index a nil value&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
- хотя это свойство является родным методом объекта, и отсутствовать напрочь никак не может. В итоге я пришёл к выводу, что его просто иногда не успевает отработать движок, так как вылет этот проявлялся в основном в интенсивных боях и совершенно произвольно.&lt;br /&gt;
&lt;br /&gt;
Вот код, в котором происходил вылет:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj     = sim:object( obj_id )&lt;br /&gt;
		local strn_id = obj:smart_terrain_id()  --- вылет происходит тут&lt;br /&gt;
&lt;br /&gt;
		if strn_id ~= 65535 then&lt;br /&gt;
			sim:object( strn_id ).gulag:clear_dead(obj_id)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это кусок родного кода из версии 1,0005 игры. Я долго пытался разными способами отсечь этот вылет, вводя предварительные проверки, однако это совершенно ничего не давало - вылет всё равно периодически случался, так как проверки эти сами его вызывали. Тогда я зарылся в документацию по Lua и обнаружил замечательную родную базовую функцию, введённую ещё с первых версий Lua, которой почему-то не пользовались ни разработчики игры, ни моддеры (хотя сама она в Lua сталкера присутствует, и работает отлично, без каких-либо нареканий). Вот она:&lt;br /&gt;
&lt;br /&gt;
'''pcall (f, arg1, ···)'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Вызывает функцию f с указанными через запятую аргументами в защищённом режиме. Это означает, что любая ошибка, даже критическая, внутри вызыванной функции, не передаётся наружу - вызавшей подсистеме. Вместо этого '''pcall''' перехватывает ошибку и возвращает код статуса. Первая возвращаемая переменная это сам код, (true или false) и если всё прошло хорошо, он равен true. В этом случае '''pcall''' сразу после статуса возвращает все результаты от работы защищённой им функции. Если же в защищённой функции произошла ошибка, то '''pcall''' вернёт false и затем сообщение об ошибке. (Обратите внимание, обработка ошибки присходит БЕЗ вылета! Вместо вылета вы получите вполне адекватную строку с ошибкой, которую можно вывести в лог для последующей обработки)&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Я настоятельно советую пользоваться этой функцией в случаях, когда из коллбэков вызываются сложные комплексные обработки, вроде обработки из менеджера вооружений AI-пака. Это позволяет предотвратить как вылеты, так и зависания обработок, и в итоге позволяет хорошо стабилизировать игру.&lt;br /&gt;
&lt;br /&gt;
Возвращаясь к нашим смарттеррейнам... вот как в итоге я подавил вылет типа smart_terrain:1143 с помощью pcall:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;--- эта функция пытается проверить св-во smart_terrain_id объекта. Именно её мы вызовем в защищённом режиме.&lt;br /&gt;
function prot_smt_td(obj)&lt;br /&gt;
	if IsStalker(obj) or IsMonster(obj) then&lt;br /&gt;
		return obj:smart_terrain_id()&lt;br /&gt;
	else	&lt;br /&gt;
		return 65535&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj = sim:object( obj_id )&lt;br /&gt;
		if obj then&lt;br /&gt;
			local strn_id = 65535  --- предварительно проинитим переменную, на &lt;br /&gt;
						--- случай если у нас prot_smt_td выдаст ошибку&lt;br /&gt;
			local result, smt_id = pcall(prot_smt_td,obj)	--- вызываем prot_smt_td в защищённом режиме &lt;br /&gt;
									--- и сразу присваиваем его вывод переменным&lt;br /&gt;
			if result then --- если pcall выдало true&lt;br /&gt;
				strn_id = smt_id  --- тогда применям полученное значение&lt;br /&gt;
			end&lt;br /&gt;
			--- если же обработка выдаст ошибку, то strn_id останется неизменным...&lt;br /&gt;
			if strn_id ~= 65535 then&lt;br /&gt;
				sim:object(strn_id).gulag:clear_dead(obj_id)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В других местах это делается совершенно аналогично. Подробнее об этой и многих других функция для контроля кода, незаслуженно ингорируемых большинством моддеров, можно почитать тут: http://lua-users.org/wiki/FinalizedExceptions - на английском правда, но захотите - разберётесь, там всё просто. &lt;br /&gt;
&lt;br /&gt;
Настоятельно советую вам изучить работу кода в таких условиях и научиться его правильно применять - этим вы сильно облегчите жизнь как себе, так и тем, кто после вас будет рыться в вашем коде или импортировать его в свои разработки.&lt;br /&gt;
&lt;br /&gt;
= Скрытые критические проблемы в обработке вылетов игрой =&lt;br /&gt;
&lt;br /&gt;
Ведя на днях отладку, выяснил в чём проблема с периодическим боем сейвов и многими другими заморочками как в оригинае игры, так и во многих модах... дело, как выяснилось, далеко не всегда в кривых руках. Существует есть такая стандартная ф-ция '''abort''' - предназначенная для выкидывания из игры, если что-то пошло не так. И как оказалось, она срабатывает далеко не всегда. Выяснилось это следующим образом:&lt;br /&gt;
&lt;br /&gt;
В одном из логов нашего бета-тестера я увидел '''стандартное сообщение о вылете внутри рабочего лога'''... да-да, то самое которое '''FATAL ERROR''' и дальше по тектсу. При этом игра у него НЕ вылетала, это сообщение об ошибке мы обнаружили позже, по случайности. Я заподозрил, что что-то не в порядке, и вставил внутрь этой ф-ции контрольную метку, кидавшую в консоль сообщение, в котором содержался паттерн сообщения об ошибке и само сообщение. Так вот, оказалось, что эта самая ф-ция '''abort''' вызывается в игре с завидным постоянством (вы удивитесь насколько часто), когда возникают исключения в схемах логики, звука и т.д., но игра от этого вылетает на рабочий стол '''максимум только 3 раза из 10 вызовов'''. Вылет НЕ происходит обычно, когда ф-ции передан паттерн ошибки, а остальные параметры пустые, такое бывает, и частенько. И если не сделать внутри этой ф-ции особой метки для вывода в лог, как сделал это я, её вызовы проходят совершенно незаметно, и '''игра после критических ошибок продолжается как ни в чём ни бывало.''' А приводит это вот к чему... Внутри '''xr_logic''' в процедуре записи пстора (хранилища логики и флагов) неписей есть вызовы этого самого аборта в случае если на запись в пстор передана некорректная величина. Ну а так как аборт периодически вообще не срабатывает, то часто попадается ситуация, что неписям в пстор пишется полный ахтунг: куски кода из ОЗУ, всякая муть из лтх-ов, куски аллспавна, всё что угодно. Происходит это оттого, что кодер, писавший эту ф-цию ('''xr_logic.pstor_store(obj, varname, val)'''), явно и думать не думал что '''abort''' может не сработать. У него запись в пстор стояла после провеки, а не внутри неё (very bad idea), и если abort не срабатывал, игра писала в пстор мусор совершенно спокойно и незаметно для игрока. Потом вся эта хрень попадала прямо в сейвы. Вот проблемный код для наглядности:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_store(obj, varname, val)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	if db.storage[npc_id].pstor == nil then&lt;br /&gt;
		db.storage[npc_id].pstor = {}&lt;br /&gt;
	end&lt;br /&gt;
	local tv = type(val)&lt;br /&gt;
	if not pstor_is_registered_type(tv) then&lt;br /&gt;
		abort(&amp;quot;xr_logic: pstor_store: not registered type '%s' encountered&amp;quot;, tv) --- вот тут мы должны если что вылететь&lt;br /&gt;
	end&lt;br /&gt;
	db.storage[npc_id].pstor[varname] = val -- а если не вылетели, всё, получим запись в пстор левой мути&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Разумеется игра этим пстором в итоге давится, и сейвы сделанные после такой милой записи практически лопаются. Результат - &amp;quot;битые&amp;quot; (на самом деле подлежат реанимации) сейвы. Происходит это потому, что игра из сейва грузит неписям псторы сплошным чтением по словам, пока они не закончатся. В случае же если в псторе обнаруживается записанный ранее мусор, то обработка либо вылетает сразу, либо наглухо виснет, пытаясь запихать эдак с миллион слов в пстор особо отличившегося непися. Как вам например непись с размером пстора в 1697451 слова? В результате попытки его обработать игра просто на стадии синхронизации выжрала всю доступную ОЗУ и повисла. &lt;br /&gt;
&lt;br /&gt;
Решение этой проблемы оказалось достатоно простым: во-первых я предположил максимальный размер полезной части пстора неписей в 20 слов r_u32() (пока ориентировочно, я ещё уточняю эту величину), и соответственно сделал остановку цикла загрузки пстора для неписей через 20 итераций. Там же, где в цикле стояла проверка на валидность записываемых данных (кстати тоже с вылетом в случае провала проверки), я сделал так, что если параметр не относится к валидному типу данных, то запись параметра в пстор не производится совсем. Это необходимо для того, чтобы если вдруг в сейве обнаружится мусор, то он был бы просто отброшен обработкой. Практика показала, что в итоге такие неписи вполне адекватны и в дальнейшем никаких проблем не вызывают, так как начало их пстора, с нормальными данными, обычно не повреждается - мусор дописывается после них, а не вместо них. &lt;br /&gt;
&lt;br /&gt;
Ну и во-вторых модифицировал запись параметров в пстор, просто убрав запись под основание if-else так, чтобы если параметр неверен, он не записывался совсем. Теперь кстати, очень интересно, сохранились ли те же заморочки с ф-цией '''abort''' в Чистом Небе, и если да, то останутся ли в Зове Припяти?&lt;br /&gt;
&lt;br /&gt;
------------&lt;br /&gt;
&lt;br /&gt;
== Необходимые для стабилизации игры правки в модулях ==&lt;br /&gt;
&lt;br /&gt;
'''Эта правка предотвращает запись в пстор если не сработал аборт:'''&lt;br /&gt;
&lt;br /&gt;
''xr_logic.script''&lt;br /&gt;
&lt;br /&gt;
'''Было:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_store(obj, varname, val)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	if db.storage[npc_id].pstor == nil then&lt;br /&gt;
		db.storage[npc_id].pstor = {}&lt;br /&gt;
	end&lt;br /&gt;
	local tv = type(val)&lt;br /&gt;
	if not pstor_is_registered_type(tv) then&lt;br /&gt;
		abort(&amp;quot;xr_logic: pstor_store: not registered type '%s' encountered&amp;quot;, tv)&lt;br /&gt;
	end&lt;br /&gt;
	db.storage[npc_id].pstor[varname] = val&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Стало:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_store(obj, varname, val)&lt;br /&gt;
	if not obj then return end&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	if db.storage[npc_id].pstor == nil then&lt;br /&gt;
		db.storage[npc_id].pstor = {}&lt;br /&gt;
	end&lt;br /&gt;
	local tv = type(val)&lt;br /&gt;
	if not pstor_is_registered_type(tv) then&lt;br /&gt;
		dgblog(&amp;quot;xr_logic: pstor_store: not registered type encountered - write in pstor_store cancelled&amp;quot;)&lt;br /&gt;
		-- abort убран, так как один хрен не работает. Пусть тогда хотя бы в лог что-то валится.&lt;br /&gt;
	else&lt;br /&gt;
		db.storage[npc_id].pstor[varname] = val&lt;br /&gt;
		-- вот так и только так. Если значение не валидно, ничего не происходит.&lt;br /&gt;
	end	&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''А эта правка выкинет из пстора весь мусор при загрузке сейва, если он как-то в него попал'''&lt;br /&gt;
&lt;br /&gt;
'''Было:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_load_all(obj, reader)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	local pstor = db.storage[npc_id].pstor&lt;br /&gt;
	if not pstor then&lt;br /&gt;
		pstor = {}&lt;br /&gt;
		db.storage[npc_id].pstor = pstor&lt;br /&gt;
	end&lt;br /&gt;
	local ctr = reader:r_u32()&lt;br /&gt;
	for i = 1, ctr do&lt;br /&gt;
		local varname = reader:r_stringZ()&lt;br /&gt;
		local tn = reader:r_u8()&lt;br /&gt;
		if tn == pstor_number then&lt;br /&gt;
			pstor[varname] = reader:r_float()&lt;br /&gt;
		elseif tn == pstor_string then&lt;br /&gt;
			pstor[varname] = reader:r_stringZ()&lt;br /&gt;
		elseif tn == pstor_boolean then&lt;br /&gt;
			pstor[varname] = reader:r_bool()&lt;br /&gt;
		else&lt;br /&gt;
			abort(&amp;quot;xr_logic: pstor_load_all: not registered type N %d encountered&amp;quot;, tn)&lt;br /&gt;
		end&lt;br /&gt;
		printf(&amp;quot;_bp: pstor_load_all: loaded [%s]='%s'&amp;quot;, varname, utils.to_str(pstor[varname]))&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Стало:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_load_all(obj, reader)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	local pstor = db.storage[npc_id].pstor&lt;br /&gt;
	if not pstor then&lt;br /&gt;
		pstor = {}&lt;br /&gt;
		db.storage[npc_id].pstor = pstor&lt;br /&gt;
	end&lt;br /&gt;
	local ctr = reader:r_u32()&lt;br /&gt;
	if tonumber(ctr) &amp;gt; 20 and tostring(obj:name()) ~= &amp;quot;single_player&amp;quot; and npc_id ~= db.actor:id() then&lt;br /&gt;
		-- максимум 20 итераций - это число ещё уточняется, возможно понадобится больше&lt;br /&gt;
                -- если у вас в пстор что-то свое пишется, ориентируйтесь на свои значения&lt;br /&gt;
		-- и обязательно убираем из проверки актора - у него очень толстый пстор, и к тому же&lt;br /&gt;
                -- если уж поврежденным будет его пстор, то тут точно уже ничего не поможет&lt;br /&gt;
		dgblog(&amp;quot;ОБНАРУЖЕН ОБЪЕКТ С ПОВРЕЖДЕННЫМ PSTOR: &amp;quot;..tostring(obj:name())..&lt;br /&gt;
&amp;quot; БУДЕТ ПРОИЗВЕДЕНА ПОПЫТКА ВОССТАНОВЛЕНИЯ&amp;quot;)&lt;br /&gt;
		ctr = 20 &lt;br /&gt;
	end&lt;br /&gt;
	for i = 1, ctr do&lt;br /&gt;
		local varname = reader:r_stringZ()&lt;br /&gt;
		local tn = reader:r_u8()&lt;br /&gt;
		if tn == pstor_number then&lt;br /&gt;
			pstor[varname] = reader:r_float()&lt;br /&gt;
		elseif tn == pstor_string then&lt;br /&gt;
			pstor[varname] = reader:r_stringZ()&lt;br /&gt;
		elseif tn == pstor_boolean then&lt;br /&gt;
			pstor[varname] = reader:r_bool()&lt;br /&gt;
		else&lt;br /&gt;
			-- не надо пытаться вылетать - просто не пишем поврежденные данные&lt;br /&gt;
			-- при этом обязательно удалять саму переменную - в результате записи&lt;br /&gt;
 			-- мусора в пстор одно только ее название может повесить загрузку&lt;br /&gt;
			pstor[varname] = nil&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Эти примеры приведены для чистой игры. Единственное в чем не уверен пока, это в том, что максимум полезного размера - 20 слов. Возможно нужно выделить больше, это надо будет проверить ещё экспериментальным путем...&lt;br /&gt;
&lt;br /&gt;
Кроме этого, советую ещё внутрь ф-ции abort вставить отладочные метки, чтобы точно знать когда она вызывалась. Настоятельно советую сделать это даже если вы матёрый моддер со стажем - гарантирую, будете неприятно удивлены.&lt;br /&gt;
&lt;br /&gt;
Я это сделал вот так:&lt;br /&gt;
&lt;br /&gt;
''_g.script''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- Крешнуть игру (после вывода сообщения об ошибке в лог)&lt;br /&gt;
function abort(fmt, msg)&lt;br /&gt;
	local message = tostring(msg)&lt;br /&gt;
	dbglog(&amp;quot;ERROR PATTERN: &amp;quot;..tostring(fmt))&lt;br /&gt;
	dbglog(&amp;quot;ERROR REASON: &amp;quot;..message)&lt;br /&gt;
	local reason = string.format(fmt, message)&lt;br /&gt;
	assert(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	printf(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	dbglog(&amp;quot;%s&amp;quot;, reason)&lt;br /&gt;
	printf(&amp;quot;%s&amp;quot;)&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Другие частые проблемы =&lt;br /&gt;
&lt;br /&gt;
Спустя некоторое время я обнаружил причины ещё нескольких часто встречающихся вылетов, и решил записать их описание сюда - эта информация наверняка ещё много кому пригодится.&lt;br /&gt;
&lt;br /&gt;
== Вылеты при удалении объектов из игры ==&lt;br /&gt;
&lt;br /&gt;
При использовании для удаления объектов родной движковой функции '''alife():release(obj:id(), true)''' возможен целый ворох разнообразнейших вылетов, обычно - безлоговых, что сильно затрудняет их отладку. Вот из-за чего они возникают:&lt;br /&gt;
&lt;br /&gt;
1) Вылет при удалении непися или монстра.&lt;br /&gt;
   Решение: с помощью alife():release '''можно удалять только мёртвые объекты'''. Поэтому если вам нужно удалить с её помощью непися или монстра, его нужно убить любым доступным методом, хотя бы нанеся ему hit() с любым запредельным уроном по вкусу.&lt;br /&gt;
&lt;br /&gt;
2) Вылет при удалении оружия или артефакта.&lt;br /&gt;
   Решение: такая проблема часто встречается в случае если объект неудачно расположен или находится в руках у непися. Для того чтобы не произошло вылета, убедитесь что объект доступен как серверный перед удалением. Вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;	local obj = alife():object(i)&lt;br /&gt;
	if obj then&lt;br /&gt;
		alife():release(obj, true)&lt;br /&gt;
	end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Эту конструкцию вообще желательно использовать всегда, когда вы так удаляете объекты.&lt;br /&gt;
&lt;br /&gt;
3) Вылет при удалении аномалии.&lt;br /&gt;
   Решение: аномалии - очень капризные при подобном с ними обращении объекты. Они влияют на своё окружение, и если рядом с ними находится непись или монстр, то удаление такой аномалии приведёт к вылету игры. Чтобы этого не произошло, аномалию надо сначала выключить функцией '''disable_anomaly''', и удалять затем ТОЛЬКО тогда, когда она не будет занята влиянием на динамический объект. Для этого нужно получить список мобов на локации, и из их нетпакетов считать идентификаторы действующих на них рестрикторов. Если ваша аномалия будет в этом списке - удалять её нельзя. Дождитесь пока она освободится.&lt;br /&gt;
&lt;br /&gt;
== Вылет при открытии закладки &amp;quot;Контакты&amp;quot; в ПДА ==&lt;br /&gt;
&lt;br /&gt;
Простой безлоговый вылет при открытии закладки &amp;quot;Контакты&amp;quot;. Встречался во всех крупных модах, и никто не знал как его излечить. А лечится он банально - '''его причина - дублирование идентификаторов секций в XML-файле, описывающем иконки неписей для закладки &amp;quot;Контакты&amp;quot;'''. Нужно всего лишь проверить этот файл на наличие дублированных идентификаторов и удалить их. Вылет пропадёт и никогда больше не будет встречаться.&lt;br /&gt;
&lt;br /&gt;
== Вылеты при вызове несуществующих функций из XML ==&lt;br /&gt;
&lt;br /&gt;
В ХML-файлах, используемых для описания инфопоршенов, для многих инфопоршенов прописаны действия, которые игра вызывает при взятии этого инфопоршена. Вот так примерно:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
	&amp;lt;info_portion id=&amp;quot;barman_document_have&amp;quot;&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;dialogs.set_actor_prebandit1&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandits2&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandits3&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandit7&amp;lt;/action&amp;gt;&lt;br /&gt;
		&amp;lt;action&amp;gt;bar_spawn.bandit8&amp;lt;/action&amp;gt;&lt;br /&gt;
	&amp;lt;/info_portion&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так вот, если вы допустите опечатку в названии вызываемой функции или же случайно её удалите - игра будет стабильно вылетать без лога при взятии этого инфопоршена.&lt;br /&gt;
&lt;br /&gt;
--[[Участник:Kamikazze|KamikaZze (OGSE Team)]] 11:04, 3 сентября 2009 (UTC)&lt;br /&gt;
&lt;br /&gt;
= Авторы =&lt;br /&gt;
Статья создана: [[Участник:Kamikazze|Kamikazze]]&lt;/div&gt;</summary>
		<author><name>Kamikazze</name></author>	</entry>

	<entry>
		<id>http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2_(%D1%87%D0%B0%D1%81%D1%82%D1%8C_2)</id>
		<title>Как писать скрипты, не приводящие к вылетам и бою сейвов (часть 2)</title>
		<link rel="alternate" type="text/html" href="http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2_(%D1%87%D0%B0%D1%81%D1%82%D1%8C_2)"/>
				<updated>2009-09-03T16:06:52Z</updated>
		
		<summary type="html">&lt;p&gt;Kamikazze: Категория, Ссылка на 1-ю часть&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Категория:Статьи участников]]&lt;br /&gt;
[[Категория:Скрипты]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[Как писать скрипты, не приводящие к вылетам и бою сейвов|Начало в первой части статьи]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Как безопасно использовать коллбэки и таймерные события =&lt;br /&gt;
&lt;br /&gt;
В скриптовом движке есть удобный способ реакции на события - использование коллбэков - процедурных вызовов, привязанных к определённым событиям в жизни игрового объекта - получению повреждений, спавну, смерти и т.д. и т.п. Это hit_callback, death_callback из xr_motivator и многие другие... Все моддеры очень широко и совершенно спокойно пользуются ими, совершенно забывая при этом о таком важном факте, что это - обработки реального времени, как и таймерные события. Что это значит? А собственно вот что... Возьмём для примера коллбэк смерти неписей, мою головную боль последнего времени:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;xr_motivator.script -&lt;br /&gt;
function motivator_binder:death_callback(victim, who)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это функция обрабатывает смерть неписей. Когда вызывается этот коллбек он начинает по очереди вызывать внутренние функции, расположенные в других модулях. Они разрегистрируют умершего непися в гулагах, спавнят в него лут, обновляют статистику, отключают его от доп. схем логики и т.д.&lt;br /&gt;
&lt;br /&gt;
Так вот, обычно, когда в функциях возникает конфликт параметров, бесконечный цикл, попытка индексации nil и т.д., функция вылетает со стандартным логом. '''Но только не в случае, когда она находится внутри коллбэка. Если функция внутри него, то в случае возникновении в ней любой нештатной ситуации, коллбэк наглухо виснет.''' Это происходит из-за того, что функции вызываются строго друг за другом, и каждая из них вызывается только тогда, когда её предшественница вернула управление коллбэку. В случае с death_callback опознать такого непися очень просто - в его трупе окажется фонарик, КПК и возможно ещё немного разных &amp;quot;мусорных&amp;quot; вещей, что говорит о том, что обработка его смерти повисла не дойдя даже до спавна лута. В подобной ситуации можно быть на 100% уверенным, что труп этот не был корректно разрегистрирован, и игра всё ещё считает его живым неписем. Кроме того, зависший коллбэк не освобождает стек (а он у Луа-подсистемы единый на все скрипты), что '''в итоге приводит к вылетам игры с переполнением памяти (вот она, реальная причина этих &amp;quot;родных&amp;quot; вылетов).''' Но было бы слишком хорошо, если бы всё ограничивалось этим... однако тут всё намного хуже... такие &amp;quot;зависшие&amp;quot; коллбэки, особенно если их произошло несколько подряд, очень серьёзно влияют на работу а-лайфа. В лучшем случае они, забивая, стек, мешают нормально работать схемам логики, в худшем вызывают зависания самого  а-лайфа (этот эффект, кстати, производят и сами трупы таких неписей, так как они, как мы помним, не разрегистрировались корректно). '''Основной итог таких событий - бой сейвов, сделанных после возникновения таких ситуаций.''' Если повис один коллбэк, то такие сейвы ещё через раз загружаются, если же несколько, и остановилась работа а-лайфа - всё, сейвы бьются наглухо и реанимации не подлежат.&lt;br /&gt;
&lt;br /&gt;
Поэтому, чтобы избежать такого развития событий, каждый раз, когда вы вносите в коллбэк новую функцию - проверьте её самым тщательным образом. Она не должна содержать никаких рекурсивных циклов, в ней обязательно должны быть проверки на валидность обрабатываемых объектов и значений, и обязательно должна быть обработка нештатных ситуаций - т.е. функция должна обязательно, в абсолютно любой ситуации вернуть управление коллбэку, так или иначе. В самом наихудшем случае - делайте как делали разработчики игры - вставляйте принудительный вылет на рабстол функцией abort - она позволяет передавать отладочное сообщение, и это всяко лучше чем незаметный бой сейвов. Если обработка оборвалась в самом начале, а ф-ция обязательно должна вернуть значение - заведите ей &amp;quot;безопасное&amp;quot; возвращаемое значение по-умолчанию, которое она будет выдавать, если всё пошло плохо. И никогда не пренебрегайте пошаговой отладкой коллбэков с выводом в лог, особенно когда пишете схемы логики - это критически важно для стабильности вашего мода.&lt;br /&gt;
&lt;br /&gt;
== Основные внутриигровые признаки зависания коллбэков типа hit_callback, death_callback ==&lt;br /&gt;
&lt;br /&gt;
1. В трупах попадаются фонарики, КПК, разный мусор и общий лут слишком богат.&lt;br /&gt;
&lt;br /&gt;
2. Частые вылеты во время интенсивных боёв с логами типа&lt;br /&gt;
    Sheduler tried to update object...&lt;br /&gt;
    smart_terrain:1145(1146)&lt;br /&gt;
    LUA: out of memory&lt;br /&gt;
    любой_модуль_логики:любая_cтрока - stack overflow&lt;br /&gt;
&lt;br /&gt;
3. Частые &amp;quot;родные&amp;quot; вылеты в момент смерти непися или попадания по нему&lt;br /&gt;
&lt;br /&gt;
4. Произвольно бьются сейвы во время сражений, выброса и других насыщенных действиями событий&lt;br /&gt;
&lt;br /&gt;
= Использование защищённого кода в LUA =&lt;br /&gt;
&lt;br /&gt;
Периодически случаются такие ситуации, когда мы можем получить вылет при проверке аргумента, и не можем его адекватно заизолировать с помощью предварительной проверки на валидность значения. Вот простой пример: когда я отлаживал '''death_callback''' неписей, я периодически сталкивался с тем, что обращение к методу '''smart_terrain_id()''' при смерти непися иногда вызывало вылет &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;smart_terrain:1143 &amp;quot;attempt to index a nil value&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
- хотя это свойство является родным методом объекта, и отсутствовать напрочь никак не может. В итоге я пришёл к выводу, что его просто иногда не успевает отработать движок, так как вылет этот проявлялся в основном в интенсивных боях и совершенно произвольно.&lt;br /&gt;
&lt;br /&gt;
Вот код, в котором происходил вылет:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj     = sim:object( obj_id )&lt;br /&gt;
		local strn_id = obj:smart_terrain_id()  --- вылет происходит тут&lt;br /&gt;
&lt;br /&gt;
		if strn_id ~= 65535 then&lt;br /&gt;
			sim:object( strn_id ).gulag:clear_dead(obj_id)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это кусок родного кода из версии 1,0005 игры. Я долго пытался разными способами отсечь этот вылет, вводя предварительные проверки, однако это совершенно ничего не давало - вылет всё равно периодически случался, так как проверки эти сами его вызывали. Тогда я зарылся в документацию по Lua и обнаружил замечательную родную базовую функцию, введённую ещё с первых версий Lua, которой почему-то не пользовались ни разработчики игры, ни моддеры (хотя сама она в Lua сталкера присутствует, и работает отлично, без каких-либо нареканий). Вот она:&lt;br /&gt;
&lt;br /&gt;
'''pcall (f, arg1, ···)'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Вызывает функцию f с указанными через запятую аргументами в защищённом режиме. Это означает, что любая ошибка, даже критическая, внутри вызыванной функции, не передаётся наружу - вызавшей подсистеме. Вместо этого '''pcall''' перехватывает ошибку и возвращает код статуса. Первая возвращаемая переменная это сам код, (true или false) и если всё прошло хорошо, он равен true. В этом случае '''pcall''' сразу после статуса возвращает все результаты от работы защищённой им функции. Если же в защищённой функции произошла ошибка, то '''pcall''' вернёт false и затем сообщение об ошибке. (Обратите внимание, обработка ошибки присходит БЕЗ вылета! Вместо вылета вы получите вполне адекватную строку с ошибкой, которую можно вывести в лог для последующей обработки)&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Я настоятельно советую пользоваться этой функцией в случаях, когда из коллбэков вызываются сложные комплексные обработки, вроде обработки из менеджера вооружений AI-пака. Это позволяет предотвратить как вылеты, так и зависания обработок, и в итоге позволяет хорошо стабилизировать игру.&lt;br /&gt;
&lt;br /&gt;
Возвращаясь к нашим смарттеррейнам... вот как в итоге я подавил вылет типа smart_terrain:1143 с помощью pcall:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;--- эта функция пытается проверить св-во smart_terrain_id объекта. Именно её мы вызовем в защищённом режиме.&lt;br /&gt;
function prot_smt_td(obj)&lt;br /&gt;
	if IsStalker(obj) or IsMonster(obj) then&lt;br /&gt;
		return obj:smart_terrain_id()&lt;br /&gt;
	else	&lt;br /&gt;
		return 65535&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj = sim:object( obj_id )&lt;br /&gt;
		if obj then&lt;br /&gt;
			local strn_id = 65535  --- предварительно проинитим переменную, на &lt;br /&gt;
						--- случай если у нас prot_smt_td выдаст ошибку&lt;br /&gt;
			local result, smt_id = pcall(prot_smt_td,obj)	--- вызываем prot_smt_td в защищённом режиме &lt;br /&gt;
									--- и сразу присваиваем его вывод переменным&lt;br /&gt;
			if result then --- если pcall выдало true&lt;br /&gt;
				strn_id = smt_id  --- тогда применям полученное значение&lt;br /&gt;
			end&lt;br /&gt;
			--- если же обработка выдаст ошибку, то strn_id останется неизменным...&lt;br /&gt;
			if strn_id ~= 65535 then&lt;br /&gt;
				sim:object(strn_id).gulag:clear_dead(obj_id)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В других местах это делается совершенно аналогично. Подробнее об этой и многих других функция для контроля кода, незаслуженно ингорируемых большинством моддеров, можно почитать тут: http://lua-users.org/wiki/FinalizedExceptions - на английском правда, но захотите - разберётесь, там всё просто. &lt;br /&gt;
&lt;br /&gt;
Настоятельно советую вам изучить работу кода в таких условиях и научиться его правильно применять - этим вы сильно облегчите жизнь как себе, так и тем, кто после вас будет рыться в вашем коде или импортировать его в свои разработки.&lt;br /&gt;
&lt;br /&gt;
= Скрытые критические проблемы в обработке вылетов игрой =&lt;br /&gt;
&lt;br /&gt;
Ведя на днях отладку, выяснил в чём проблема с периодическим боем сейвов и многими другими заморочками как в оригинае игры, так и во многих модах... дело, как выяснилось, далеко не всегда в кривых руках. Существует есть такая стандартная ф-ция '''abort''' - предназначенная для выкидывания из игры, если что-то пошло не так. И как оказалось, она срабатывает далеко не всегда. Выяснилось это следующим образом:&lt;br /&gt;
&lt;br /&gt;
В одном из логов нашего бета-тестера я увидел '''стандартное сообщение о вылете внутри рабочего лога'''... да-да, то самое которое '''FATAL ERROR''' и дальше по тектсу. При этом игра у него НЕ вылетала, это сообщение об ошибке мы обнаружили позже, по случайности. Я заподозрил, что что-то не в порядке, и вставил внутрь этой ф-ции контрольную метку, кидавшую в консоль сообщение, в котором содержался паттерн сообщения об ошибке и само сообщение. Так вот, оказалось, что эта самая ф-ция '''abort''' вызывается в игре с завидным постоянством (вы удивитесь насколько часто), когда возникают исключения в схемах логики, звука и т.д., но игра от этого вылетает на рабочий стол '''максимум только 3 раза из 10 вызовов'''. Вылет НЕ происходит обычно, когда ф-ции передан паттерн ошибки, а остальные параметры пустые, такое бывает, и частенько. И если не сделать внутри этой ф-ции особой метки для вывода в лог, как сделал это я, её вызовы проходят совершенно незаметно, и '''игра после критических ошибок продолжается как ни в чём ни бывало.''' А приводит это вот к чему... Внутри '''xr_logic''' в процедуре записи пстора (хранилища логики и флагов) неписей есть вызовы этого самого аборта в случае если на запись в пстор передана некорректная величина. Ну а так как аборт периодически вообще не срабатывает, то часто попадается ситуация, что неписям в пстор пишется полный ахтунг: куски кода из ОЗУ, всякая муть из лтх-ов, куски аллспавна, всё что угодно. Происходит это оттого, что кодер, писавший эту ф-цию ('''xr_logic.pstor_store(obj, varname, val)'''), явно и думать не думал что '''abort''' может не сработать. У него запись в пстор стояла после провеки, а не внутри неё (very bad idea), и если abort не срабатывал, игра писала в пстор мусор совершенно спокойно и незаметно для игрока. Потом вся эта хрень попадала прямо в сейвы. Вот проблемный код для наглядности:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_store(obj, varname, val)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	if db.storage[npc_id].pstor == nil then&lt;br /&gt;
		db.storage[npc_id].pstor = {}&lt;br /&gt;
	end&lt;br /&gt;
	local tv = type(val)&lt;br /&gt;
	if not pstor_is_registered_type(tv) then&lt;br /&gt;
		abort(&amp;quot;xr_logic: pstor_store: not registered type '%s' encountered&amp;quot;, tv) --- вот тут мы должны если что вылететь&lt;br /&gt;
	end&lt;br /&gt;
	db.storage[npc_id].pstor[varname] = val -- а если не вылетели, всё, получим запись в пстор левой мути&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Разумеется игра этим пстором в итоге давится, и сейвы сделанные после такой милой записи практически лопаются. Результат - &amp;quot;битые&amp;quot; (на самом деле подлежат реанимации) сейвы. Происходит это потому, что игра из сейва грузит неписям псторы сплошным чтением по словам, пока они не закончатся. В случае же если в псторе обнаруживается записанный ранее мусор, то обработка либо вылетает сразу, либо наглухо виснет, пытаясь запихать эдак с миллион слов в пстор особо отличившегося непися. Как вам например непись с размером пстора в 1697451 слова? В результате попытки его обработать игра просто на стадии синхронизации выжрала всю доступную ОЗУ и повисла. &lt;br /&gt;
&lt;br /&gt;
Решение этой проблемы оказалось достатоно простым: во-первых я предположил максимальный размер полезной части пстора неписей в 20 слов r_u32() (пока ориентировочно, я ещё уточняю эту величину), и соответственно сделал остановку цикла загрузки пстора для неписей через 20 итераций. Там же, где в цикле стояла проверка на валидность записываемых данных (кстати тоже с вылетом в случае провала проверки), я сделал так, что если параметр не относится к валидному типу данных, то запись параметра в пстор не производится совсем. Это необходимо для того, чтобы если вдруг в сейве обнаружится мусор, то он был бы просто отброшен обработкой. Практика показала, что в итоге такие неписи вполне адекватны и в дальнейшем никаких проблем не вызывают, так как начало их пстора, с нормальными данными, обычно не повреждается - мусор дописывается после них, а не вместо них. &lt;br /&gt;
&lt;br /&gt;
Ну и во-вторых модифицировал запись параметров в пстор, просто убрав запись под основание if-else так, чтобы если параметр неверен, он не записывался совсем. Теперь кстати, очень интересно, сохранились ли те же заморочки с ф-цией '''abort''' в Чистом Небе, и если да, то останутся ли в Зове Припяти?&lt;br /&gt;
&lt;br /&gt;
------------&lt;br /&gt;
&lt;br /&gt;
== Необходимые для стабилизации игры правки в модулях ==&lt;br /&gt;
&lt;br /&gt;
'''Эта правка предотвращает запись в пстор если не сработал аборт:'''&lt;br /&gt;
&lt;br /&gt;
''xr_logic.script''&lt;br /&gt;
&lt;br /&gt;
'''Было:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_store(obj, varname, val)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	if db.storage[npc_id].pstor == nil then&lt;br /&gt;
		db.storage[npc_id].pstor = {}&lt;br /&gt;
	end&lt;br /&gt;
	local tv = type(val)&lt;br /&gt;
	if not pstor_is_registered_type(tv) then&lt;br /&gt;
		abort(&amp;quot;xr_logic: pstor_store: not registered type '%s' encountered&amp;quot;, tv)&lt;br /&gt;
	end&lt;br /&gt;
	db.storage[npc_id].pstor[varname] = val&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Стало:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_store(obj, varname, val)&lt;br /&gt;
	if not obj then return end&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	if db.storage[npc_id].pstor == nil then&lt;br /&gt;
		db.storage[npc_id].pstor = {}&lt;br /&gt;
	end&lt;br /&gt;
	local tv = type(val)&lt;br /&gt;
	if not pstor_is_registered_type(tv) then&lt;br /&gt;
		dgblog(&amp;quot;xr_logic: pstor_store: not registered type encountered - write in pstor_store cancelled&amp;quot;)&lt;br /&gt;
		-- abort убран, так как один хрен не работает. Пусть тогда хотя бы в лог что-то валится.&lt;br /&gt;
	else&lt;br /&gt;
		db.storage[npc_id].pstor[varname] = val&lt;br /&gt;
		-- вот так и только так. Если значение не валидно, ничего не происходит.&lt;br /&gt;
	end	&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''А эта правка выкинет из пстора весь мусор при загрузке сейва, если он как-то в него попал'''&lt;br /&gt;
&lt;br /&gt;
'''Было:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_load_all(obj, reader)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	local pstor = db.storage[npc_id].pstor&lt;br /&gt;
	if not pstor then&lt;br /&gt;
		pstor = {}&lt;br /&gt;
		db.storage[npc_id].pstor = pstor&lt;br /&gt;
	end&lt;br /&gt;
	local ctr = reader:r_u32()&lt;br /&gt;
	for i = 1, ctr do&lt;br /&gt;
		local varname = reader:r_stringZ()&lt;br /&gt;
		local tn = reader:r_u8()&lt;br /&gt;
		if tn == pstor_number then&lt;br /&gt;
			pstor[varname] = reader:r_float()&lt;br /&gt;
		elseif tn == pstor_string then&lt;br /&gt;
			pstor[varname] = reader:r_stringZ()&lt;br /&gt;
		elseif tn == pstor_boolean then&lt;br /&gt;
			pstor[varname] = reader:r_bool()&lt;br /&gt;
		else&lt;br /&gt;
			abort(&amp;quot;xr_logic: pstor_load_all: not registered type N %d encountered&amp;quot;, tn)&lt;br /&gt;
		end&lt;br /&gt;
		printf(&amp;quot;_bp: pstor_load_all: loaded [%s]='%s'&amp;quot;, varname, utils.to_str(pstor[varname]))&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Стало:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function pstor_load_all(obj, reader)&lt;br /&gt;
	local npc_id = obj:id()&lt;br /&gt;
	local pstor = db.storage[npc_id].pstor&lt;br /&gt;
	if not pstor then&lt;br /&gt;
		pstor = {}&lt;br /&gt;
		db.storage[npc_id].pstor = pstor&lt;br /&gt;
	end&lt;br /&gt;
	local ctr = reader:r_u32()&lt;br /&gt;
	if tonumber(ctr) &amp;gt; 20 and tostring(obj:name()) ~= &amp;quot;single_player&amp;quot; and npc_id ~= db.actor:id() then&lt;br /&gt;
		-- максимум 20 итераций - это число ещё уточняется, возможно понадобится больше&lt;br /&gt;
                -- если у вас в пстор что-то свое пишется, ориентируйтесь на свои значения&lt;br /&gt;
		-- и обязательно убираем из проверки актора - у него очень толстый пстор, и к тому же&lt;br /&gt;
                -- если уж поврежденным будет его пстор, то тут точно уже ничего не поможет&lt;br /&gt;
		dgblog(&amp;quot;ОБНАРУЖЕН ОБЪЕКТ С ПОВРЕЖДЕННЫМ PSTOR: &amp;quot;..tostring(obj:name())..&lt;br /&gt;
&amp;quot; БУДЕТ ПРОИЗВЕДЕНА ПОПЫТКА ВОССТАНОВЛЕНИЯ&amp;quot;)&lt;br /&gt;
		ctr = 20 &lt;br /&gt;
	end&lt;br /&gt;
	for i = 1, ctr do&lt;br /&gt;
		local varname = reader:r_stringZ()&lt;br /&gt;
		local tn = reader:r_u8()&lt;br /&gt;
		if tn == pstor_number then&lt;br /&gt;
			pstor[varname] = reader:r_float()&lt;br /&gt;
		elseif tn == pstor_string then&lt;br /&gt;
			pstor[varname] = reader:r_stringZ()&lt;br /&gt;
		elseif tn == pstor_boolean then&lt;br /&gt;
			pstor[varname] = reader:r_bool()&lt;br /&gt;
		else&lt;br /&gt;
			-- не надо пытаться вылетать - просто не пишем поврежденные данные&lt;br /&gt;
			-- при этом обязательно удалять саму переменную - в результате записи&lt;br /&gt;
 			-- мусора в пстор одно только ее название может повесить загрузку&lt;br /&gt;
			pstor[varname] = nil&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Эти примеры приведены для чистой игры. Единственное в чем не уверен пока, это в том, что максимум полезного размера - 20 слов. Возможно нужно выделить больше, это надо будет проверить ещё экспериментальным путем...&lt;br /&gt;
&lt;br /&gt;
Кроме этого, советую ещё внутрь ф-ции abort вставить отладочные метки, чтобы точно знать когда она вызывалась. Настоятельно советую сделать это даже если вы матёрый моддер со стажем - гарантирую, будете неприятно удивлены.&lt;br /&gt;
&lt;br /&gt;
Я это сделал вот так:&lt;br /&gt;
&lt;br /&gt;
''_g.script''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;-- Крешнуть игру (после вывода сообщения об ошибке в лог)&lt;br /&gt;
function abort(fmt, msg)&lt;br /&gt;
	local message = tostring(msg)&lt;br /&gt;
	dbglog(&amp;quot;ERROR PATTERN: &amp;quot;..tostring(fmt))&lt;br /&gt;
	dbglog(&amp;quot;ERROR REASON: &amp;quot;..message)&lt;br /&gt;
	local reason = string.format(fmt, message)&lt;br /&gt;
	assert(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	printf(&amp;quot;ERROR: &amp;quot; .. reason)&lt;br /&gt;
	dbglog(&amp;quot;%s&amp;quot;, reason)&lt;br /&gt;
	printf(&amp;quot;%s&amp;quot;)&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
--[[Участник:Kamikazze|KamikaZze (OGSE Team)]] 11:04, 3 сентября 2009 (UTC)&lt;br /&gt;
&lt;br /&gt;
= Авторы =&lt;br /&gt;
Статья создана: [[Участник:Kamikazze|Kamikazze]]&lt;/div&gt;</summary>
		<author><name>Kamikazze</name></author>	</entry>

	<entry>
		<id>http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2</id>
		<title>Как писать скрипты, не приводящие к вылетам и бою сейвов</title>
		<link rel="alternate" type="text/html" href="http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2"/>
				<updated>2009-09-03T16:05:36Z</updated>
		
		<summary type="html">&lt;p&gt;Kamikazze: Категория, ссылка на 2-ю часть&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Категория:Статьи участников]]&lt;br /&gt;
[[Категория:Скрипты]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[Как писать скрипты, не приводящие к вылетам и бою сейвов (часть 2)|Продолжение во второй части]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Предисловие =&lt;br /&gt;
&lt;br /&gt;
'''Отладив несколько скриптов на предмет вылетов решил написать это небольшое руководство, чтобы помочь начинающим (да и опытным тоже) скриптописателям избежать потенциальных проблем.'''&lt;br /&gt;
&lt;br /&gt;
Сразу оговорюсь — я программист, поэтому к отладке скриптов подхожу со своей профессиональной точки зрения, и то что я тут изложу вам — сие для программиста есть непреложная истина, и если вы хотите уменьшить количество глюков — старайтесь придерживаться нижеприведённого стиля программирования.&lt;br /&gt;
&lt;br /&gt;
Для начала маленький экскурс в историю. Итак, скрипты в сталкере написаны на языке '''Lua''' — разработали его бразильцы, он очень гибок, легко встраивается в игры и поэтому очень часто используется для написания скриптов любой степени сложности. Так вот, у этого языка и его реализации в игре есть несколько исключительно важных особенностей, которые, при их несоблюдении, будут приводить к вылетам вашего скрипта. Итак:&lt;br /&gt;
&lt;br /&gt;
= Типы данных и проблемы с nil. =&lt;br /&gt;
&lt;br /&gt;
Сначала сделаю маленькое отступление для не знакомых с основами скриптеров, чтобы понятнее было. Итак, в Lua используются следующие логические операторы:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
=	оператор присваивания&lt;br /&gt;
==	сравнение, равно ли значение&lt;br /&gt;
≈	сравнение, НЕ равно ли значение&lt;br /&gt;
&amp;lt;	сравнение, меньше ли значение&lt;br /&gt;
&amp;gt;	сравнение, больше ли значение&lt;br /&gt;
&amp;lt;=	сравнение, меньше ли значение или равно&lt;br /&gt;
&amp;gt;=	сравнение, больше ли значение или равно&lt;br /&gt;
and	логический оператор И&lt;br /&gt;
not	логический оператор НЕ&lt;br /&gt;
or	логический оператор ИЛИ&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Всё. Теперь поехали дальше… Язык Lua Изначально создавался для работы с большими строчными базами данных, поэтому ВСЕ виды конструкций в языке — это типы данных, то есть по сути либо переменные, либо константы. Это касается так же и функций! '''Поэтому даже с функциями в Lua можно и нужно обращатся как с переменными.''' Далее. Касательно, собственно «привычных» переменных. '''Обычные переменные в Lua получают свой тип данных только в момент присвоения им значения (запомните, это важно).''' При этом, в Lua есть такой важный и полезный тип данных как nil. nil — Это «пустота», то есть отсутствие какого-либо значения. При этом этот тип используется компилятором скриптов для «сбора мусора», то есть для освобождения занимаемой памяти. Смотрите пример, я объясню подробнее, что это значит:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;local reminder_count&lt;br /&gt;
local exposure_count = 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тут у нас 2 локальные переменные, одна из которых просто создана, а вторая — создана и инициализарована. Если сейчас обратиться к переменной reminder_count, считав её значение, то мы получим в качестве значения — nil, то есть пустоту. Если же мы обратимся к переменной exposure_count — то получим, как и ожидали, 0 — так как мы это значение проинициализировали ЗАРАНЕЕ. '''При этом не надо путать nil и 0 — так как 0 — это всё-таки какая-то информация, а вот nil — это её полное отсутствие.''' Так вот, собственно, чем это чревато. Я уже сказал, что nil используется для сбора мусора. Вручную это работает так — когда вам переменная уже не нужна, вы просто пишете:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;test_variable = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И ваша переменная test_variable сотрётся из памяти, и любые ссылки на неё будут выдавать на выходе nil (типа нет такой переменной, «абонент вне зоны действия сети»). Ну так собственно вот, зачем я это всё рассказываю… '''если не определить значение переменной сразу, как это было сделано с переменной reminder_count, то любые попытки обратиться к ней внутри скрипта приведут к вылетам.''' Происходит это следующим образом: допустим, reminder_count — это у нас счётчик напоминаний. То есть мы о чём-то напоминаем игроку текстовыми сообщениями, и помечаем, сколько раз мы это сделали. Определили мы переменную именно так, как в примере выше, то есть не задав ей никакого значения. Значение же её должно присваиваться ниже по коду, после первого оповещения юзера. При этом у нас есть в коде обращение к этой переменной, по которому решается что делать дальше. Выглядит это примерно так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function check_antirad_supplies()&lt;br /&gt;
&lt;br /&gt;
	if auto_injection_active then&lt;br /&gt;
&lt;br /&gt;
		if antirad_check_delay == nil or game.get_game_time():diffSec(antirad_check_delay) &amp;gt; 60 then&lt;br /&gt;
&lt;br /&gt;
			if not db.actor:object(«antirad») and not (use_scientific_kit and db.actor:object(«medkit_scientic»)) then&lt;br /&gt;
&lt;br /&gt;
			--обратите внимание сюда&lt;br /&gt;
				if reminder_count == 0 or reminder_count &amp;gt;= mins_till_next_remind then&lt;br /&gt;
			--обратите внимание сюда&lt;br /&gt;
					if use_text then&lt;br /&gt;
						local news_text = «%c[255,160,160,160]Автоматическая система ввода медицинских&lt;br /&gt;
 препаратов\\n&amp;quot;..&amp;quot;%c[default]Напоминаю: %c[255,230,0,0]&lt;br /&gt;
Противорадиационные препараты отстутствуют! %c[default]&lt;br /&gt;
Автоматический ввод препаратов невозможен.»&lt;br /&gt;
  						db.actor:give_game_news(news_text, &amp;quot;ui\\ui_iconsTotal&amp;quot;, &lt;br /&gt;
Frect():set(0,188,83,47), 0, 3000)&lt;br /&gt;
  					end	&lt;br /&gt;
	  				if use_sounds then&lt;br /&gt;
				 		local snd_obj&lt;br /&gt;
						if use_custom_sounds then&lt;br /&gt;
							snd_obj = xr_sound.get_safe_sound_object([[HEV\no-anti-rad]])&lt;br /&gt;
						else&lt;br /&gt;
	  						snd_obj = xr_sound.get_safe_sound_object([[device\pda\pda_tip]])&lt;br /&gt;
						end&lt;br /&gt;
						if snd_obj then&lt;br /&gt;
							snd_obj:play_no_feedback(db.actor, sound_object.s2d, 0, vector(), 1.0)&lt;br /&gt;
						end&lt;br /&gt;
					end&lt;br /&gt;
					reminder_count = 1&lt;br /&gt;
				elseif reminder then&lt;br /&gt;
					reminder_count = reminder_count + 1&lt;br /&gt;
				end&lt;br /&gt;
&lt;br /&gt;
			else&lt;br /&gt;
				radiation_warning = true&lt;br /&gt;
				reminder_count = 0&lt;br /&gt;
			end&lt;br /&gt;
			antirad_check_delay = game.get_game_time()&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Во время игры всё это скорее всего отработает хорошо, но вот при загрузке… тут могут быть проблемы, так как скрипт при запуске может пролететь ту часть, где переменной reminder_count присваивается значение! В итоге при запуске вышеприведённой функции check_antirad_supplies() нас получится что reminder_count не существует — он равен nil, и в итоге проверка&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count == 0 or reminder_count &amp;gt;= mins_till_next_remind then&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
выдаст ошибку и приведёт к вылету! Почему? '''Да потому что НЕЛЬЗЯ сравнивать НИЧТО с вещественным значением.''' Именно поэтому, когда вы создаёте новую переменную, вы ОБЯЗАНЫ задать ей значение по умолчанию, даже если вы полностью уверены, что она нигде не будет использована до инициализации. Поверьте — в программировании бывает всё, и запросто может так случиться, что ваша переменная будет использована и спровоцирует вылет.&lt;br /&gt;
&lt;br /&gt;
Простой способ обойти такую кучу проверок и лишних инициализаций — использование or-оператора. пример&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if (reminder_count or 0) &amp;gt;= mins_till_next_remind then&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
если переменная равна nil, то выражение вычисляется дальше и получается равным 0, а это уже число и сравнение происходит безболезненно.&lt;br /&gt;
&lt;br /&gt;
Кроме вышеприведённой ситуации, возможен ещё один неприятный момент, связанный с определением переменных и значением nil. Суть его заключается в том, что при присваивании значений или операциями с переменными мы можем получить кучу проблем… покажу конкретнее каких:&lt;br /&gt;
&lt;br /&gt;
== «Смерть» переменных ==&lt;br /&gt;
&lt;br /&gt;
Положим, у нас с reminder_count происходят ещё какие-нибудь занимательные вещи, например мы его значение зачем-нибудь присваиваем другой переменной вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;antirad_check_delay = reminder_count&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ну и вот, если у нас reminder_count в момент присвоения ещё не имеет никакого значения, то мы получим занимательную штуку — у нас это выражение сработает как&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;antirad_check_delay = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате чего переменная antirad_check_delay «сдохнет» и будет деловито убрана «сборщиком мусора» из памяти, что моментально приведёт к вылету, когда в дальнейшем какая-то часть кода обратится к значению antirad_check_delay.&lt;br /&gt;
&lt;br /&gt;
== Ошибки деления на 0 ==&lt;br /&gt;
&lt;br /&gt;
Теперь положим что у нас reminder_count используется в каком-нибудь расчёте вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;antirad_check_delay = exposure_count/reminder_count&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Как мы помним, у нас reminder_count не инициализирована, и как следствие равна nil. В итоге мы получим попытку поделить exposure_count на ноль (в нашем случае на nil, что суть одно и то же в нашем случае) и получим «классический» программистский вылет из-за ошибки деления на ноль.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Ошибки присвоения ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Предположим что мы написали скрипт, который помогает выбирать оружие при наличии вблизи врагов. И вот, у нас в скрипте есть такой кусок кода, где NPC вынимает из рюкзака оружие:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function weapon_manager:set_weapon(wpn)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	local enemy = self.npc:best_enemy()&lt;br /&gt;
	if wpn then&lt;br /&gt;
		--обратите внимание сюда&lt;br /&gt;
		self.weapon_id = wpn:id()&lt;br /&gt;
		--обратите внимание сюда&lt;br /&gt;
		self:return_items(self.weapon_id)&lt;br /&gt;
	else&lt;br /&gt;
		printw(«set_wpn:weapon not exist»)&lt;br /&gt;
	end&lt;br /&gt;
	if self.modes.process_mode == &amp;quot;3&amp;quot; and enemy then&lt;br /&gt;
		for k, v in pairs(self.weapons) do&lt;br /&gt;
			for i, w in ipairs(v) do&lt;br /&gt;
				if w.id ≈ self.weapon_id then&lt;br /&gt;
					local item = level.object_by_id(w.id)&lt;br /&gt;
					if item and item:parent() and item:parent():id() == self.npc_id then&lt;br /&gt;
						printw(«set_weapon[%s]:process %s[%s]», self.npc:character_name(), w.id, w.sec)&lt;br /&gt;
						self:process_item(item)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if enemy then&lt;br /&gt;
		self.npc:set_item(object.idle, wpn)&lt;br /&gt;
	end&lt;br /&gt;
	self.weapons = nil&lt;br /&gt;
&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В помеченном выражении self.weapon_id = wpn:id(), и всё вроде бы отлично, так как стоит проверка if wpn then, однако иногда движок умудряется передать объект функциям некорректно, либо сам скриптер может перепутать при вызове игровой объект с объектом а-лайфа, и присвовение self.weapon_id = wpn:id() либо приравняет self.weapon_id к nil либо сразу приведёт к вылету из-за того что соответствующее свойство объекта не было обнаружено в его классе.&lt;br /&gt;
&lt;br /&gt;
== Как с этим бороться ==&lt;br /&gt;
&lt;br /&gt;
Бороться с такими ситуациями очень просто на самом деле.&lt;br /&gt;
Во-первых: ВСЕГДА ИНИЦИАЛИЗИРУЙТЕ ПЕРЕМЕННЫЕ. То есть вот такое описание переменной:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
НЕДОПУСТИМО! ВЫ ОБЯЗАТЕЛЬНО ДОЛЖНЫ ЗАДАТЬ ПЕРЕМЕННОЙ ЗНАЧЕНИЕ!&lt;br /&gt;
Если переменная числовая, сделайте это так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count = 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если строчная — то сделайте это так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count = &amp;quot;&amp;quot;&amp;lt;/code&amp;gt; (получится вместо nil пустая строка)&lt;br /&gt;
&lt;br /&gt;
Если логическая, то так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count = false&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В общем, делайте так, как вам удобнее, но ДЕЛАЙТЕ ОБЯЗАТЕЛЬНО!&lt;br /&gt;
&lt;br /&gt;
И во-вторых: ВСЕГДА ПРОВЕРЯЙТЕ ПЕРЕМЕННУЮ ПЕРЕД ИСПОЛЬЗОВАНИЕМ!&lt;br /&gt;
&lt;br /&gt;
То есть в кусках кода, чреватых вылетами (к ним относятся АБСОЛЮТНО ВСЕ части, где идёт работа с вещами или оружием) обязательно вставляйте проверки переменных на nil следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if wpn and wpn:id() then&lt;br /&gt;
	self.weapon_id = wpn:id()&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тогда сначала выполнится проверка, и если обе переменные имеют значение, то операция отработает, а если один из параметров пуст — то функция просто пропустит этот код. Тут не помешает ещё и проверку вставить на существование self.weapon_id, хотя это наверное уже моя параноя :)&lt;br /&gt;
&lt;br /&gt;
Кстати, что самое дурацкое — ошибки, приведённые выше, часто в лог попадают совершенно непохожим на ошибки скриптов образом. Например, ошибка присвоения из примера 3 приводила к вылету с сообщением «Assertion failed», и поймать её я смог только изучив внимательно предыдущие строки лога, где зафиксировались операции с вещами…&lt;br /&gt;
&lt;br /&gt;
Точно так же, если вы используете ЛЮБЫЕ математические операции например деление (но не только деление, всего остального тоже касается), тоже ОБЯЗАТЕЛЬНО поверяйте переменную. Вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count and reminder_count ≈ 0 then&lt;br /&gt;
	antirad_check_delay = exposure_count/reminder_count&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Таким образом потенциальные вылеты будут аккуратно изолированы, и больше не будут создавать проблем.&lt;br /&gt;
И ещё одно небольшое дополнение к этой же теме. Итак, вы вставили обходные проверки вот таким образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if wpn and wpn:id() then&lt;br /&gt;
   self.weapon_id = wpn:id() &lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Или таким:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count and reminder_count ≈ 0 then&lt;br /&gt;
   antirad_check_delay = exposure_count/reminder_count &lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И у вас в случае некорректного значения компилятор спокойно пропустил этот код, ничего не сделав ни с self.weapon_id (из первого примера), ни с antirad_check_delay (из второго). Однако вам всё-таки нужно, чтобы с ними что-то происходило, даже если проверяемые переменные неверны. Тогда я бы советовал вам дополнить этот обход отработкой нештатной ситуации. Делается это банально просто:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if wpn and wpn:id()then&lt;br /&gt;
   self.weapon_id = wpn:id() &lt;br /&gt;
elseif wpn == nil then&lt;br /&gt;
   --вставьте сюда код, что делать если wpn не существует&lt;br /&gt;
elseif wpn:id() == nil then&lt;br /&gt;
   --а сюда, если wpn:id() не существует&lt;br /&gt;
   --тут скорее всего перепутан объект, и можно попробовать&lt;br /&gt;
   --обратиться к wpn.id&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И для второго случая аналогично:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count and reminder_count ≈ 0 then&lt;br /&gt;
   antirad_check_delay = exposure_count/reminder_count &lt;br /&gt;
else&lt;br /&gt;
   --а тут что мы сделаем если reminder_count не существует&lt;br /&gt;
   --я бы сделал например вот так:&lt;br /&gt;
   antirad_check_delay = exposure_count/1&lt;br /&gt;
   --и все дела&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Более конкретная реализация зависит от того, что именно вы пытаетесь сделать — тут уже вам виднее…&lt;br /&gt;
&lt;br /&gt;
= Типичные ошибки при именовании функций, присвоении и инициализации переменных. =&lt;br /&gt;
&lt;br /&gt;
Типичная ошибка, приводящая к вылету:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function remove_item(remove_item)&lt;br /&gt;
     if remove_item~=nil then&lt;br /&gt;
          alife():release(alife():object(remove_item:id()), true)&lt;br /&gt;
          return true&lt;br /&gt;
     end&lt;br /&gt;
     return false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В этом коде агрумент функции совпадает с её именем, и в итоге функция пытается передать функции alife():release вместо указателя на вещь — указатель на саму себя. А так как у функции remove_item нет никакого id, код выпадает с ошибкой:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Arguments     : LUA error: ...\s.t.a.l.k.e.r\gamedata\scripts\test.script:486: attempt to call method 'id' (a nil value)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Следует помнить, что Lua — это язык прямого (JIT) компилирования, и он, как любой такой язык не проверяет ошибки при компиляции. Поэтому нужно очень аккуратно раздавать имена переменным и функциям, чтобы избежать подобных ошибок. Данную функцию нужно переписать следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function remove_item(item_to_remove)&lt;br /&gt;
     if item_to_remove then&lt;br /&gt;
          alife():release(alife():object(item_to_remove:id()), true)&lt;br /&gt;
          return true&lt;br /&gt;
     end&lt;br /&gt;
     return false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Однажды, отлаживая скрипт, натолкнулся на вот такой код:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function hit_callback(npc_id)&lt;br /&gt;
	if db.storage[npc_id].wounded ≈ nil then&lt;br /&gt;
		db.storage[npc_id].wounded.wound_manager:hit_callback()&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Казалось бы, всё верно, и значение проверяется перед использованием. Ан нет, скрипт вызывал вылет, причём именно на проверке. Методом тыка было выясено, что nil вызвавший вылет, был не в результате обработки, а был передан фукнции. Это был параметр npc_id.&lt;br /&gt;
&lt;br /&gt;
Пришлось сделать вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function hit_callback(npc_id)&lt;br /&gt;
	if npc_id and db.storage[npc_id].wounded ≈ nil then&lt;br /&gt;
		db.storage[npc_id].wounded.wound_manager:hit_callback()&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Введя предварительно проверку аргумента npc_id. Вывод — если ставите проверку, старайтесь проверять не только саму функцию, но и вначале — аргумент, который ей передаётся. Мало ли в каком виде он до вас доедет… :)&lt;br /&gt;
&lt;br /&gt;
Касательно именования переменных — не забывайте давать им атрибут local если вы не собираетесь их пользовать во внешних скриптах. Иначе, если попадётся где-то аналогичное имя — будет сбой логики или вылет, помните об этом.&lt;br /&gt;
&lt;br /&gt;
И ещё один совет — для самых начинающих:&lt;br /&gt;
&lt;br /&gt;
Когда пишете скрипты, не забывайте о соблюдении синтаксиса! То есть внимательно и вдумчиво ставьте в концах функций и оснований ключевые слова end — помните, что как отсутствие нужных end’ов, так и наличие лишних приводят к тому, что границы фукций сбиваются, скрипт не парсится обработчиком и вызывает при попытке своего вызова вылет, примерно вот такой:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Arguments : LUA error: ...g\s.t.a.l.k.e.r\gamedata\scripts\bind_stalker.script:75: attempt to index global 'test_main_new' (a nil value)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы вам проще было следить за синтаксисом и не допускать лишних end’ов, выстраивайте текст скриптов каскадными лесенками:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code &amp;gt;Начало_Работы&lt;br /&gt;
          Начало_Внуренней_Функции&lt;br /&gt;
                  Процедуры_Внутреней_Функции              &lt;br /&gt;
          Конец_Внутренней_Функции&lt;br /&gt;
Конец_Работы&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
..и пользуйтесь редакторами с подсветкой синтаксиса, такими как Notepad++ например. В результате каждый уровень вложенности находится на своём отступе. Вы всегда увидите, какую конструкцию нужно закрыть, а какую — нет. Я же например, делаю ещё проще — я, открывая новую функцию, сразу ставлю сразу в её конце, парой строк ниже, end, и потом уже пишу наполняющий её текст с отступами как в примере выше.&lt;br /&gt;
&lt;br /&gt;
И ещё — касательно жизни переменных: не забывайте, что определённые внутри проверок переменные живут только до выхода из проверки. То есть, например:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if check == 1 then&lt;br /&gt;
	local test = true&lt;br /&gt;
else&lt;br /&gt;
	local test = false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В данном случае переменная test создастся, но просуществует только до выхода из проверки, и дальше обращение к ней выдаст nil. Для того чтобы этого избежать, определение переменных надо выносить «за скобки» вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;local test = false; — инитим переменную и задаём сразу ей значение по умолчанию&lt;br /&gt;
&lt;br /&gt;
if check == 1 then&lt;br /&gt;
	test = true&lt;br /&gt;
else&lt;br /&gt;
	test = false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Смертельные циклы по таблицам =&lt;br /&gt;
&lt;br /&gt;
Если вы строите цикл по таблице следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;for i=1, table.getn(table_name) do&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''То никогда, ни при каких обстоятельствах не используйте внутри этого цикла удаление/добавление строк, т.е:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;table_name[i] = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
или&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;table.remove(table_name, index)&lt;br /&gt;
table.insert(table_name, index)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
применительно к таблице по которой гоняете цикл!!! При использовании это приводит к тому, что количество строк в таблице уменьшается, а цикл пытается получить из таблицы строки сверх существующего количества, что в итоге приводит к выходу цикла за отведённый ему диапазон памяти. Результатом будет веер самых разнообразных последствий, самое безобидное из которых это безлоговый вылет на рабочий стол, а серьёзное — сбой в работе а-лайфа с последующим боем сейвов! Тяжесть последствий зависит от того, какие действия вы выполняете внутри цикла кроме удаления строки.&lt;br /&gt;
&lt;br /&gt;
Чтобы избежать подобных ситуаций, стройте циклы по таблице лучше следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;for k, v in pairs(table_name)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
А удаление строк внутри них делайте вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;table_name[k] = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Циклы же for i=1, table.getn(table_name) do следует использовать '''только для операций, не изменяющих структуру изменяемой таблицы!'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
[[Как писать скрипты, не приводящие к вылетам и бою сейвов (часть 2)|Продолжение во второй части]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
--[[Участник:Kamikazze|KamikaZze (OGSE Team)]] 08:16, 1 сентября 2009 (UTC)&lt;br /&gt;
&lt;br /&gt;
= Авторы =&lt;br /&gt;
Статья создана: [[Участник:Kamikazze|Kamikazze]]&lt;/div&gt;</summary>
		<author><name>Kamikazze</name></author>	</entry>

	<entry>
		<id>http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2_(%D1%87%D0%B0%D1%81%D1%82%D1%8C_2)</id>
		<title>Как писать скрипты, не приводящие к вылетам и бою сейвов (часть 2)</title>
		<link rel="alternate" type="text/html" href="http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2_(%D1%87%D0%B0%D1%81%D1%82%D1%8C_2)"/>
				<updated>2009-09-03T11:06:27Z</updated>
		
		<summary type="html">&lt;p&gt;Kamikazze: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Категория:Статьи участников]]&lt;br /&gt;
&lt;br /&gt;
[[Как писать скрипты, не приводящие к вылетам и бою сейвов|Начало в первой части статьи]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Как безопасно использовать коллбэки и таймерные события =&lt;br /&gt;
&lt;br /&gt;
В скриптовом движке есть удобный способ реакции на события - использование коллбэков - процедурных вызовов, привязанных к определённым событиям в жизни игрового объекта - получению повреждений, спавну, смерти и т.д. и т.п. Это hit_callback, death_callback из xr_motivator и многие другие... Все моддеры очень широко и совершенно спокойно пользуются ими, совершенно забывая при этом о таком важном факте, что это - обработки реального времени, как и таймерные события. Что это значит? А собственно вот что... Возьмём для примера коллбэк смерти неписей, мою головную боль последнего времени:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;xr_motivator.script -&lt;br /&gt;
function motivator_binder:death_callback(victim, who)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это функция обрабатывает смерть неписей. Когда вызывается этот коллбек он начинает по очереди вызывать внутренние функции, расположенные в других модулях. Они разрегистрируют умершего непися в гулагах, спавнят в него лут, обновляют статистику, отключают его от доп. схем логики и т.д.&lt;br /&gt;
&lt;br /&gt;
Так вот, обычно, когда в функциях возникает конфликт параметров, бесконечный цикл, попытка индексации nil и т.д., функция вылетает со стандартным логом. '''Но только не в случае, когда она находится внутри коллбэка. Если функция внутри него, то в случае возникновении в ней любой нештатной ситуации, коллбэк наглухо виснет.''' Это происходит из-за того, что функции вызываются строго друг за другом, и каждая из них вызывается только тогда, когда её предшественница вернула управление коллбэку. В случае с death_callback опознать такого непися очень просто - в его трупе окажется фонарик, КПК и возможно ещё немного разных &amp;quot;мусорных&amp;quot; вещей, что говорит о том, что обработка его смерти повисла не дойдя даже до спавна лута. В подобной ситуации можно быть на 100% уверенным, что труп этот не был корректно разрегистрирован, и игра всё ещё считает его живым неписем. Кроме того, зависший коллбэк не освобождает стек (а он у Луа-подсистемы единый на все скрипты), что '''в итоге приводит к вылетам игры с переполнением памяти (вот она, реальная причина этих &amp;quot;родных&amp;quot; вылетов).''' Но было бы слишком хорошо, если бы всё ограничивалось этим... однако тут всё намного хуже... такие &amp;quot;зависшие&amp;quot; коллбэки, особенно если их произошло несколько подряд, очень серьёзно влияют на работу а-лайфа. В лучшем случае они, забивая, стек, мешают нормально работать схемам логики, в худшем вызывают зависания самого  а-лайфа (этот эффект, кстати, производят и сами трупы таких неписей, так как они, как мы помним, не разрегистрировались корректно). '''Основной итог таких событий - бой сейвов, сделанных после возникновения таких ситуаций.''' Если повис один коллбэк, то такие сейвы ещё через раз загружаются, если же несколько, и остановилась работа а-лайфа - всё, сейвы бьются наглухо и реанимации не подлежат.&lt;br /&gt;
&lt;br /&gt;
Поэтому, чтобы избежать такого развития событий, каждый раз, когда вы вносите в коллбэк новую функцию - проверьте её самым тщательным образом. Она не должна содержать никаких рекурсивных циклов, в ней обязательно должны быть проверки на валидность обрабатываемых объектов и значений, и обязательно должна быть обработка нештатных ситуаций - т.е. функция должна обязательно, в абсолютно любой ситуации вернуть управление коллбэку, так или иначе. В самом наихудшем случае - делайте как делали разработчики игры - вставляйте принудительный вылет на рабстол функцией abort - она позволяет передавать отладочное сообщение, и это всяко лучше чем незаметный бой сейвов. Если обработка оборвалась в самом начале, а ф-ция обязательно должна вернуть значение - заведите ей &amp;quot;безопасное&amp;quot; возвращаемое значение по-умолчанию, которое она будет выдавать, если всё пошло плохо. И никогда не пренебрегайте пошаговой отладкой коллбэков с выводом в лог, особенно когда пишете схемы логики - это критически важно для стабильности вашего мода.&lt;br /&gt;
&lt;br /&gt;
== Основные внутриигровые признаки зависания коллбэков типа hit_callback, death_callback ==&lt;br /&gt;
&lt;br /&gt;
1. В трупах попадаются фонарики, КПК, разный мусор и общий лут слишком богат.&lt;br /&gt;
&lt;br /&gt;
2. Частые вылеты во время интенсивных боёв с логами типа&lt;br /&gt;
    Sheduler tried to update object...&lt;br /&gt;
    smart_terrain:1145(1146)&lt;br /&gt;
    LUA: out of memory&lt;br /&gt;
    любой_модуль_логики:любая_cтрока - stack overflow&lt;br /&gt;
&lt;br /&gt;
3. Частые &amp;quot;родные&amp;quot; вылеты в момент смерти непися или попадания по нему&lt;br /&gt;
&lt;br /&gt;
4. Произвольно бьются сейвы во время сражений, выброса и других насыщенных действиями событий&lt;br /&gt;
&lt;br /&gt;
= Использование защищённого кода в LUA =&lt;br /&gt;
&lt;br /&gt;
Периодически случаются такие ситуации, когда мы можем получить вылет при проверке аргумента, и не можем его адекватно заизолировать с помощью предварительной проверки на валидность значения. Вот простой пример: когда я отлаживал '''death_callback''' неписей, я периодически сталкивался с тем, что обращение к методу '''smart_terrain_id()''' при смерти непися иногда вызывало вылет &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;smart_terrain:1143 &amp;quot;attempt to index a nil value&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
- хотя это свойство является родным методом объекта, и отсутствовать напрочь никак не может. В итоге я пришёл к выводу, что его просто иногда не успевает отработать движок, так как вылет этот проявлялся в основном в интенсивных боях и совершенно произвольно.&lt;br /&gt;
&lt;br /&gt;
Вот код, в котором происходил вылет:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj     = sim:object( obj_id )&lt;br /&gt;
		local strn_id = obj:smart_terrain_id()  --- вылет происходит тут&lt;br /&gt;
&lt;br /&gt;
		if strn_id ~= 65535 then&lt;br /&gt;
			sim:object( strn_id ).gulag:clear_dead(obj_id)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это кусок родного кода из версии 1,0005 игры. Я долго пытался разными способами отсечь этот вылет, вводя предварительные проверки, однако это совершенно ничего не давало - вылет всё равно периодически случался, так как проверки эти сами его вызывали. Тогда я зарылся в документацию по Lua и обнаружил замечательную родную базовую функцию, введённую ещё с первых версий Lua, которой почему-то не пользовались ни разработчики игры, ни моддеры (хотя сама она в Lua сталкера присутствует, и работает отлично, без каких-либо нареканий). Вот она:&lt;br /&gt;
&lt;br /&gt;
'''pcall (f, arg1, ···)'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Вызывает функцию f с указанными через запятую аргументами в защищённом режиме. Это означает, что любая ошибка, даже критическая, внутри вызыванной функции, не передаётся наружу - вызавшей подсистеме. Вместо этого '''pcall''' перехватывает ошибку и возвращает код статуса. Первая возвращаемая переменная это сам код, (true или false) и если всё прошло хорошо, он равен true. В этом случае '''pcall''' сразу после статуса возвращает все результаты от работы защищённой им функции. Если же в защищённой функции произошла ошибка, то '''pcall''' вернёт false и затем сообщение об ошибке. (Обратите внимание, обработка ошибки присходит БЕЗ вылета! Вместо вылета вы получите вполне адекватную строку с ошибкой, которую можно вывести в лог для последующей обработки)&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Я настоятельно советую пользоваться этой функцией в случаях, когда из коллбэков вызываются сложные комплексные обработки, вроде обработки из менеджера вооружений AI-пака. Это позволяет предотвратить как вылеты, так и зависания обработок, и в итоге позволяет хорошо стабилизировать игру.&lt;br /&gt;
&lt;br /&gt;
Возвращаясь к нашим смарттеррейнам... вот как в итоге я подавил вылет типа smart_terrain:1143 с помощью pcall:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;--- эта функция пытается проверить св-во smart_terrain_id объекта. Именно её мы вызовем в защищённом режиме.&lt;br /&gt;
function prot_smt_td(obj)&lt;br /&gt;
	if IsStalker(obj) or IsMonster(obj) then&lt;br /&gt;
		return obj:smart_terrain_id()&lt;br /&gt;
	else	&lt;br /&gt;
		return 65535&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj = sim:object( obj_id )&lt;br /&gt;
		if obj then&lt;br /&gt;
			local strn_id = 65535  --- предварительно проинитим переменную, на &lt;br /&gt;
						--- случай если у нас prot_smt_td выдаст ошибку&lt;br /&gt;
			local result, smt_id = pcall(prot_smt_td,obj)	--- вызываем prot_smt_td в защищённом режиме &lt;br /&gt;
									--- и сразу присваиваем его вывод переменным&lt;br /&gt;
			if result then --- если pcall выдало true&lt;br /&gt;
				strn_id = smt_id  --- тогда применям полученное значение&lt;br /&gt;
			end&lt;br /&gt;
			--- если же обработка выдаст ошибку, то strn_id останется неизменным...&lt;br /&gt;
			if strn_id ~= 65535 then&lt;br /&gt;
				sim:object(strn_id).gulag:clear_dead(obj_id)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В других местах это делается совершенно аналогично. Подробнее об этой и многих других функция для контроля кода, незаслуженно ингорируемых большинством моддеров, можно почитать тут: http://lua-users.org/wiki/FinalizedExceptions - на английском правда, но захотите - разберётесь, там всё просто. &lt;br /&gt;
&lt;br /&gt;
Настоятельно советую вам изучить работу кода в таких условиях и научиться его правильно применять - этим вы сильно облегчите жизнь как себе, так и тем, кто после вас будет рыться в вашем коде или импортировать его в свои разработки.&lt;br /&gt;
&lt;br /&gt;
--[[Участник:Kamikazze|KamikaZze (OGSE Team)]] 11:04, 3 сентября 2009 (UTC)&lt;br /&gt;
&lt;br /&gt;
= Авторы =&lt;br /&gt;
Статья создана: [[Участник:Kamikazze|Kamikazze]]&lt;/div&gt;</summary>
		<author><name>Kamikazze</name></author>	</entry>

	<entry>
		<id>http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2</id>
		<title>Как писать скрипты, не приводящие к вылетам и бою сейвов</title>
		<link rel="alternate" type="text/html" href="http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2"/>
				<updated>2009-09-03T11:05:18Z</updated>
		
		<summary type="html">&lt;p&gt;Kamikazze: /* Смертельные циклы по таблицам */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Категория:Статьи участников]]&lt;br /&gt;
= Предисловие =&lt;br /&gt;
&lt;br /&gt;
'''Отладив несколько скриптов на предмет вылетов решил написать это небольшое руководство, чтобы помочь начинающим (да и опытным тоже) скриптописателям избежать потенциальных проблем.'''&lt;br /&gt;
&lt;br /&gt;
Сразу оговорюсь — я программист, поэтому к отладке скриптов подхожу со своей профессиональной точки зрения, и то что я тут изложу вам — сие для программиста есть непреложная истина, и если вы хотите уменьшить количество глюков — старайтесь придерживаться нижеприведённого стиля программирования.&lt;br /&gt;
&lt;br /&gt;
Для начала маленький экскурс в историю. Итак, скрипты в сталкере написаны на языке '''Lua''' — разработали его бразильцы, он очень гибок, легко встраивается в игры и поэтому очень часто используется для написания скриптов любой степени сложности. Так вот, у этого языка и его реализации в игре есть несколько исключительно важных особенностей, которые, при их несоблюдении, будут приводить к вылетам вашего скрипта. Итак:&lt;br /&gt;
&lt;br /&gt;
= Типы данных и проблемы с nil. =&lt;br /&gt;
&lt;br /&gt;
Сначала сделаю маленькое отступление для не знакомых с основами скриптеров, чтобы понятнее было. Итак, в Lua используются следующие логические операторы:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
=	оператор присваивания&lt;br /&gt;
==	сравнение, равно ли значение&lt;br /&gt;
≈	сравнение, НЕ равно ли значение&lt;br /&gt;
&amp;lt;	сравнение, меньше ли значение&lt;br /&gt;
&amp;gt;	сравнение, больше ли значение&lt;br /&gt;
&amp;lt;=	сравнение, меньше ли значение или равно&lt;br /&gt;
&amp;gt;=	сравнение, больше ли значение или равно&lt;br /&gt;
and	логический оператор И&lt;br /&gt;
not	логический оператор НЕ&lt;br /&gt;
or	логический оператор ИЛИ&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Всё. Теперь поехали дальше… Язык Lua Изначально создавался для работы с большими строчными базами данных, поэтому ВСЕ виды конструкций в языке — это типы данных, то есть по сути либо переменные, либо константы. Это касается так же и функций! '''Поэтому даже с функциями в Lua можно и нужно обращатся как с переменными.''' Далее. Касательно, собственно «привычных» переменных. '''Обычные переменные в Lua получают свой тип данных только в момент присвоения им значения (запомните, это важно).''' При этом, в Lua есть такой важный и полезный тип данных как nil. nil — Это «пустота», то есть отсутствие какого-либо значения. При этом этот тип используется компилятором скриптов для «сбора мусора», то есть для освобождения занимаемой памяти. Смотрите пример, я объясню подробнее, что это значит:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;local reminder_count&lt;br /&gt;
local exposure_count = 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тут у нас 2 локальные переменные, одна из которых просто создана, а вторая — создана и инициализарована. Если сейчас обратиться к переменной reminder_count, считав её значение, то мы получим в качестве значения — nil, то есть пустоту. Если же мы обратимся к переменной exposure_count — то получим, как и ожидали, 0 — так как мы это значение проинициализировали ЗАРАНЕЕ. '''При этом не надо путать nil и 0 — так как 0 — это всё-таки какая-то информация, а вот nil — это её полное отсутствие.''' Так вот, собственно, чем это чревато. Я уже сказал, что nil используется для сбора мусора. Вручную это работает так — когда вам переменная уже не нужна, вы просто пишете:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;test_variable = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И ваша переменная test_variable сотрётся из памяти, и любые ссылки на неё будут выдавать на выходе nil (типа нет такой переменной, «абонент вне зоны действия сети»). Ну так собственно вот, зачем я это всё рассказываю… '''если не определить значение переменной сразу, как это было сделано с переменной reminder_count, то любые попытки обратиться к ней внутри скрипта приведут к вылетам.''' Происходит это следующим образом: допустим, reminder_count — это у нас счётчик напоминаний. То есть мы о чём-то напоминаем игроку текстовыми сообщениями, и помечаем, сколько раз мы это сделали. Определили мы переменную именно так, как в примере выше, то есть не задав ей никакого значения. Значение же её должно присваиваться ниже по коду, после первого оповещения юзера. При этом у нас есть в коде обращение к этой переменной, по которому решается что делать дальше. Выглядит это примерно так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function check_antirad_supplies()&lt;br /&gt;
&lt;br /&gt;
	if auto_injection_active then&lt;br /&gt;
&lt;br /&gt;
		if antirad_check_delay == nil or game.get_game_time():diffSec(antirad_check_delay) &amp;gt; 60 then&lt;br /&gt;
&lt;br /&gt;
			if not db.actor:object(«antirad») and not (use_scientific_kit and db.actor:object(«medkit_scientic»)) then&lt;br /&gt;
&lt;br /&gt;
			--обратите внимание сюда&lt;br /&gt;
				if reminder_count == 0 or reminder_count &amp;gt;= mins_till_next_remind then&lt;br /&gt;
			--обратите внимание сюда&lt;br /&gt;
					if use_text then&lt;br /&gt;
						local news_text = «%c[255,160,160,160]Автоматическая система ввода медицинских&lt;br /&gt;
 препаратов\\n&amp;quot;..&amp;quot;%c[default]Напоминаю: %c[255,230,0,0]&lt;br /&gt;
Противорадиационные препараты отстутствуют! %c[default]&lt;br /&gt;
Автоматический ввод препаратов невозможен.»&lt;br /&gt;
  						db.actor:give_game_news(news_text, &amp;quot;ui\\ui_iconsTotal&amp;quot;, &lt;br /&gt;
Frect():set(0,188,83,47), 0, 3000)&lt;br /&gt;
  					end	&lt;br /&gt;
	  				if use_sounds then&lt;br /&gt;
				 		local snd_obj&lt;br /&gt;
						if use_custom_sounds then&lt;br /&gt;
							snd_obj = xr_sound.get_safe_sound_object([[HEV\no-anti-rad]])&lt;br /&gt;
						else&lt;br /&gt;
	  						snd_obj = xr_sound.get_safe_sound_object([[device\pda\pda_tip]])&lt;br /&gt;
						end&lt;br /&gt;
						if snd_obj then&lt;br /&gt;
							snd_obj:play_no_feedback(db.actor, sound_object.s2d, 0, vector(), 1.0)&lt;br /&gt;
						end&lt;br /&gt;
					end&lt;br /&gt;
					reminder_count = 1&lt;br /&gt;
				elseif reminder then&lt;br /&gt;
					reminder_count = reminder_count + 1&lt;br /&gt;
				end&lt;br /&gt;
&lt;br /&gt;
			else&lt;br /&gt;
				radiation_warning = true&lt;br /&gt;
				reminder_count = 0&lt;br /&gt;
			end&lt;br /&gt;
			antirad_check_delay = game.get_game_time()&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Во время игры всё это скорее всего отработает хорошо, но вот при загрузке… тут могут быть проблемы, так как скрипт при запуске может пролететь ту часть, где переменной reminder_count присваивается значение! В итоге при запуске вышеприведённой функции check_antirad_supplies() нас получится что reminder_count не существует — он равен nil, и в итоге проверка&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count == 0 or reminder_count &amp;gt;= mins_till_next_remind then&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
выдаст ошибку и приведёт к вылету! Почему? '''Да потому что НЕЛЬЗЯ сравнивать НИЧТО с вещественным значением.''' Именно поэтому, когда вы создаёте новую переменную, вы ОБЯЗАНЫ задать ей значение по умолчанию, даже если вы полностью уверены, что она нигде не будет использована до инициализации. Поверьте — в программировании бывает всё, и запросто может так случиться, что ваша переменная будет использована и спровоцирует вылет.&lt;br /&gt;
&lt;br /&gt;
Простой способ обойти такую кучу проверок и лишних инициализаций — использование or-оператора. пример&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if (reminder_count or 0) &amp;gt;= mins_till_next_remind then&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
если переменная равна nil, то выражение вычисляется дальше и получается равным 0, а это уже число и сравнение происходит безболезненно.&lt;br /&gt;
&lt;br /&gt;
Кроме вышеприведённой ситуации, возможен ещё один неприятный момент, связанный с определением переменных и значением nil. Суть его заключается в том, что при присваивании значений или операциями с переменными мы можем получить кучу проблем… покажу конкретнее каких:&lt;br /&gt;
&lt;br /&gt;
== «Смерть» переменных ==&lt;br /&gt;
&lt;br /&gt;
Положим, у нас с reminder_count происходят ещё какие-нибудь занимательные вещи, например мы его значение зачем-нибудь присваиваем другой переменной вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;antirad_check_delay = reminder_count&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ну и вот, если у нас reminder_count в момент присвоения ещё не имеет никакого значения, то мы получим занимательную штуку — у нас это выражение сработает как&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;antirad_check_delay = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате чего переменная antirad_check_delay «сдохнет» и будет деловито убрана «сборщиком мусора» из памяти, что моментально приведёт к вылету, когда в дальнейшем какая-то часть кода обратится к значению antirad_check_delay.&lt;br /&gt;
&lt;br /&gt;
== Ошибки деления на 0 ==&lt;br /&gt;
&lt;br /&gt;
Теперь положим что у нас reminder_count используется в каком-нибудь расчёте вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;antirad_check_delay = exposure_count/reminder_count&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Как мы помним, у нас reminder_count не инициализирована, и как следствие равна nil. В итоге мы получим попытку поделить exposure_count на ноль (в нашем случае на nil, что суть одно и то же в нашем случае) и получим «классический» программистский вылет из-за ошибки деления на ноль.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Ошибки присвоения ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Предположим что мы написали скрипт, который помогает выбирать оружие при наличии вблизи врагов. И вот, у нас в скрипте есть такой кусок кода, где NPC вынимает из рюкзака оружие:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function weapon_manager:set_weapon(wpn)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	local enemy = self.npc:best_enemy()&lt;br /&gt;
	if wpn then&lt;br /&gt;
		--обратите внимание сюда&lt;br /&gt;
		self.weapon_id = wpn:id()&lt;br /&gt;
		--обратите внимание сюда&lt;br /&gt;
		self:return_items(self.weapon_id)&lt;br /&gt;
	else&lt;br /&gt;
		printw(«set_wpn:weapon not exist»)&lt;br /&gt;
	end&lt;br /&gt;
	if self.modes.process_mode == &amp;quot;3&amp;quot; and enemy then&lt;br /&gt;
		for k, v in pairs(self.weapons) do&lt;br /&gt;
			for i, w in ipairs(v) do&lt;br /&gt;
				if w.id ≈ self.weapon_id then&lt;br /&gt;
					local item = level.object_by_id(w.id)&lt;br /&gt;
					if item and item:parent() and item:parent():id() == self.npc_id then&lt;br /&gt;
						printw(«set_weapon[%s]:process %s[%s]», self.npc:character_name(), w.id, w.sec)&lt;br /&gt;
						self:process_item(item)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if enemy then&lt;br /&gt;
		self.npc:set_item(object.idle, wpn)&lt;br /&gt;
	end&lt;br /&gt;
	self.weapons = nil&lt;br /&gt;
&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В помеченном выражении self.weapon_id = wpn:id(), и всё вроде бы отлично, так как стоит проверка if wpn then, однако иногда движок умудряется передать объект функциям некорректно, либо сам скриптер может перепутать при вызове игровой объект с объектом а-лайфа, и присвовение self.weapon_id = wpn:id() либо приравняет self.weapon_id к nil либо сразу приведёт к вылету из-за того что соответствующее свойство объекта не было обнаружено в его классе.&lt;br /&gt;
&lt;br /&gt;
== Как с этим бороться ==&lt;br /&gt;
&lt;br /&gt;
Бороться с такими ситуациями очень просто на самом деле.&lt;br /&gt;
Во-первых: ВСЕГДА ИНИЦИАЛИЗИРУЙТЕ ПЕРЕМЕННЫЕ. То есть вот такое описание переменной:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
НЕДОПУСТИМО! ВЫ ОБЯЗАТЕЛЬНО ДОЛЖНЫ ЗАДАТЬ ПЕРЕМЕННОЙ ЗНАЧЕНИЕ!&lt;br /&gt;
Если переменная числовая, сделайте это так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count = 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если строчная — то сделайте это так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count = &amp;quot;&amp;quot;&amp;lt;/code&amp;gt; (получится вместо nil пустая строка)&lt;br /&gt;
&lt;br /&gt;
Если логическая, то так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count = false&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В общем, делайте так, как вам удобнее, но ДЕЛАЙТЕ ОБЯЗАТЕЛЬНО!&lt;br /&gt;
&lt;br /&gt;
И во-вторых: ВСЕГДА ПРОВЕРЯЙТЕ ПЕРЕМЕННУЮ ПЕРЕД ИСПОЛЬЗОВАНИЕМ!&lt;br /&gt;
&lt;br /&gt;
То есть в кусках кода, чреватых вылетами (к ним относятся АБСОЛЮТНО ВСЕ части, где идёт работа с вещами или оружием) обязательно вставляйте проверки переменных на nil следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if wpn and wpn:id() then&lt;br /&gt;
	self.weapon_id = wpn:id()&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тогда сначала выполнится проверка, и если обе переменные имеют значение, то операция отработает, а если один из параметров пуст — то функция просто пропустит этот код. Тут не помешает ещё и проверку вставить на существование self.weapon_id, хотя это наверное уже моя параноя :)&lt;br /&gt;
&lt;br /&gt;
Кстати, что самое дурацкое — ошибки, приведённые выше, часто в лог попадают совершенно непохожим на ошибки скриптов образом. Например, ошибка присвоения из примера 3 приводила к вылету с сообщением «Assertion failed», и поймать её я смог только изучив внимательно предыдущие строки лога, где зафиксировались операции с вещами…&lt;br /&gt;
&lt;br /&gt;
Точно так же, если вы используете ЛЮБЫЕ математические операции например деление (но не только деление, всего остального тоже касается), тоже ОБЯЗАТЕЛЬНО поверяйте переменную. Вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count and reminder_count ≈ 0 then&lt;br /&gt;
	antirad_check_delay = exposure_count/reminder_count&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Таким образом потенциальные вылеты будут аккуратно изолированы, и больше не будут создавать проблем.&lt;br /&gt;
И ещё одно небольшое дополнение к этой же теме. Итак, вы вставили обходные проверки вот таким образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if wpn and wpn:id() then&lt;br /&gt;
   self.weapon_id = wpn:id() &lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Или таким:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count and reminder_count ≈ 0 then&lt;br /&gt;
   antirad_check_delay = exposure_count/reminder_count &lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И у вас в случае некорректного значения компилятор спокойно пропустил этот код, ничего не сделав ни с self.weapon_id (из первого примера), ни с antirad_check_delay (из второго). Однако вам всё-таки нужно, чтобы с ними что-то происходило, даже если проверяемые переменные неверны. Тогда я бы советовал вам дополнить этот обход отработкой нештатной ситуации. Делается это банально просто:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if wpn and wpn:id()then&lt;br /&gt;
   self.weapon_id = wpn:id() &lt;br /&gt;
elseif wpn == nil then&lt;br /&gt;
   --вставьте сюда код, что делать если wpn не существует&lt;br /&gt;
elseif wpn:id() == nil then&lt;br /&gt;
   --а сюда, если wpn:id() не существует&lt;br /&gt;
   --тут скорее всего перепутан объект, и можно попробовать&lt;br /&gt;
   --обратиться к wpn.id&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И для второго случая аналогично:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count and reminder_count ≈ 0 then&lt;br /&gt;
   antirad_check_delay = exposure_count/reminder_count &lt;br /&gt;
else&lt;br /&gt;
   --а тут что мы сделаем если reminder_count не существует&lt;br /&gt;
   --я бы сделал например вот так:&lt;br /&gt;
   antirad_check_delay = exposure_count/1&lt;br /&gt;
   --и все дела&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Более конкретная реализация зависит от того, что именно вы пытаетесь сделать — тут уже вам виднее…&lt;br /&gt;
&lt;br /&gt;
= Типичные ошибки при именовании функций, присвоении и инициализации переменных. =&lt;br /&gt;
&lt;br /&gt;
Типичная ошибка, приводящая к вылету:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function remove_item(remove_item)&lt;br /&gt;
     if remove_item~=nil then&lt;br /&gt;
          alife():release(alife():object(remove_item:id()), true)&lt;br /&gt;
          return true&lt;br /&gt;
     end&lt;br /&gt;
     return false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В этом коде агрумент функции совпадает с её именем, и в итоге функция пытается передать функции alife():release вместо указателя на вещь — указатель на саму себя. А так как у функции remove_item нет никакого id, код выпадает с ошибкой:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Arguments     : LUA error: ...\s.t.a.l.k.e.r\gamedata\scripts\test.script:486: attempt to call method 'id' (a nil value)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Следует помнить, что Lua — это язык прямого (JIT) компилирования, и он, как любой такой язык не проверяет ошибки при компиляции. Поэтому нужно очень аккуратно раздавать имена переменным и функциям, чтобы избежать подобных ошибок. Данную функцию нужно переписать следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function remove_item(item_to_remove)&lt;br /&gt;
     if item_to_remove then&lt;br /&gt;
          alife():release(alife():object(item_to_remove:id()), true)&lt;br /&gt;
          return true&lt;br /&gt;
     end&lt;br /&gt;
     return false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Однажды, отлаживая скрипт, натолкнулся на вот такой код:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function hit_callback(npc_id)&lt;br /&gt;
	if db.storage[npc_id].wounded ≈ nil then&lt;br /&gt;
		db.storage[npc_id].wounded.wound_manager:hit_callback()&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Казалось бы, всё верно, и значение проверяется перед использованием. Ан нет, скрипт вызывал вылет, причём именно на проверке. Методом тыка было выясено, что nil вызвавший вылет, был не в результате обработки, а был передан фукнции. Это был параметр npc_id.&lt;br /&gt;
&lt;br /&gt;
Пришлось сделать вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function hit_callback(npc_id)&lt;br /&gt;
	if npc_id and db.storage[npc_id].wounded ≈ nil then&lt;br /&gt;
		db.storage[npc_id].wounded.wound_manager:hit_callback()&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Введя предварительно проверку аргумента npc_id. Вывод — если ставите проверку, старайтесь проверять не только саму функцию, но и вначале — аргумент, который ей передаётся. Мало ли в каком виде он до вас доедет… :)&lt;br /&gt;
&lt;br /&gt;
Касательно именования переменных — не забывайте давать им атрибут local если вы не собираетесь их пользовать во внешних скриптах. Иначе, если попадётся где-то аналогичное имя — будет сбой логики или вылет, помните об этом.&lt;br /&gt;
&lt;br /&gt;
И ещё один совет — для самых начинающих:&lt;br /&gt;
&lt;br /&gt;
Когда пишете скрипты, не забывайте о соблюдении синтаксиса! То есть внимательно и вдумчиво ставьте в концах функций и оснований ключевые слова end — помните, что как отсутствие нужных end’ов, так и наличие лишних приводят к тому, что границы фукций сбиваются, скрипт не парсится обработчиком и вызывает при попытке своего вызова вылет, примерно вот такой:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Arguments : LUA error: ...g\s.t.a.l.k.e.r\gamedata\scripts\bind_stalker.script:75: attempt to index global 'test_main_new' (a nil value)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы вам проще было следить за синтаксисом и не допускать лишних end’ов, выстраивайте текст скриптов каскадными лесенками:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code &amp;gt;Начало_Работы&lt;br /&gt;
          Начало_Внуренней_Функции&lt;br /&gt;
                  Процедуры_Внутреней_Функции              &lt;br /&gt;
          Конец_Внутренней_Функции&lt;br /&gt;
Конец_Работы&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
..и пользуйтесь редакторами с подсветкой синтаксиса, такими как Notepad++ например. В результате каждый уровень вложенности находится на своём отступе. Вы всегда увидите, какую конструкцию нужно закрыть, а какую — нет. Я же например, делаю ещё проще — я, открывая новую функцию, сразу ставлю сразу в её конце, парой строк ниже, end, и потом уже пишу наполняющий её текст с отступами как в примере выше.&lt;br /&gt;
&lt;br /&gt;
И ещё — касательно жизни переменных: не забывайте, что определённые внутри проверок переменные живут только до выхода из проверки. То есть, например:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if check == 1 then&lt;br /&gt;
	local test = true&lt;br /&gt;
else&lt;br /&gt;
	local test = false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В данном случае переменная test создастся, но просуществует только до выхода из проверки, и дальше обращение к ней выдаст nil. Для того чтобы этого избежать, определение переменных надо выносить «за скобки» вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;local test = false; — инитим переменную и задаём сразу ей значение по умолчанию&lt;br /&gt;
&lt;br /&gt;
if check == 1 then&lt;br /&gt;
	test = true&lt;br /&gt;
else&lt;br /&gt;
	test = false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Смертельные циклы по таблицам =&lt;br /&gt;
&lt;br /&gt;
Если вы строите цикл по таблице следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;for i=1, table.getn(table_name) do&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''То никогда, ни при каких обстоятельствах не используйте внутри этого цикла удаление/добавление строк, т.е:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;table_name[i] = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
или&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;table.remove(table_name, index)&lt;br /&gt;
table.insert(table_name, index)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
применительно к таблице по которой гоняете цикл!!! При использовании это приводит к тому, что количество строк в таблице уменьшается, а цикл пытается получить из таблицы строки сверх существующего количества, что в итоге приводит к выходу цикла за отведённый ему диапазон памяти. Результатом будет веер самых разнообразных последствий, самое безобидное из которых это безлоговый вылет на рабочий стол, а серьёзное — сбой в работе а-лайфа с последующим боем сейвов! Тяжесть последствий зависит от того, какие действия вы выполняете внутри цикла кроме удаления строки.&lt;br /&gt;
&lt;br /&gt;
Чтобы избежать подобных ситуаций, стройте циклы по таблице лучше следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;for k, v in pairs(table_name)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
А удаление строк внутри них делайте вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;table_name[k] = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Циклы же for i=1, table.getn(table_name) do следует использовать '''только для операций, не изменяющих структуру изменяемой таблицы!'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Как писать скрипты, не приводящие к вылетам и бою сейвов (часть 2)|Продолжение во второй части]]&lt;br /&gt;
&lt;br /&gt;
--[[Участник:Kamikazze|KamikaZze (OGSE Team)]] 08:16, 1 сентября 2009 (UTC)&lt;br /&gt;
&lt;br /&gt;
= Авторы =&lt;br /&gt;
Статья создана: [[Участник:Kamikazze|Kamikazze]]&lt;/div&gt;</summary>
		<author><name>Kamikazze</name></author>	</entry>

	<entry>
		<id>http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2_(%D1%87%D0%B0%D1%81%D1%82%D1%8C_2)</id>
		<title>Как писать скрипты, не приводящие к вылетам и бою сейвов (часть 2)</title>
		<link rel="alternate" type="text/html" href="http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2_(%D1%87%D0%B0%D1%81%D1%82%D1%8C_2)"/>
				<updated>2009-09-03T11:04:26Z</updated>
		
		<summary type="html">&lt;p&gt;Kamikazze: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Как писать скрипты, не приводящие к вылетам и бою сейвов|Начало в первой части статьи]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Как безопасно использовать коллбэки и таймерные события =&lt;br /&gt;
&lt;br /&gt;
В скриптовом движке есть удобный способ реакции на события - использование коллбэков - процедурных вызовов, привязанных к определённым событиям в жизни игрового объекта - получению повреждений, спавну, смерти и т.д. и т.п. Это hit_callback, death_callback из xr_motivator и многие другие... Все моддеры очень широко и совершенно спокойно пользуются ими, совершенно забывая при этом о таком важном факте, что это - обработки реального времени, как и таймерные события. Что это значит? А собственно вот что... Возьмём для примера коллбэк смерти неписей, мою головную боль последнего времени:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;xr_motivator.script -&lt;br /&gt;
function motivator_binder:death_callback(victim, who)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это функция обрабатывает смерть неписей. Когда вызывается этот коллбек он начинает по очереди вызывать внутренние функции, расположенные в других модулях. Они разрегистрируют умершего непися в гулагах, спавнят в него лут, обновляют статистику, отключают его от доп. схем логики и т.д.&lt;br /&gt;
&lt;br /&gt;
Так вот, обычно, когда в функциях возникает конфликт параметров, бесконечный цикл, попытка индексации nil и т.д., функция вылетает со стандартным логом. '''Но только не в случае, когда она находится внутри коллбэка. Если функция внутри него, то в случае возникновении в ней любой нештатной ситуации, коллбэк наглухо виснет.''' Это происходит из-за того, что функции вызываются строго друг за другом, и каждая из них вызывается только тогда, когда её предшественница вернула управление коллбэку. В случае с death_callback опознать такого непися очень просто - в его трупе окажется фонарик, КПК и возможно ещё немного разных &amp;quot;мусорных&amp;quot; вещей, что говорит о том, что обработка его смерти повисла не дойдя даже до спавна лута. В подобной ситуации можно быть на 100% уверенным, что труп этот не был корректно разрегистрирован, и игра всё ещё считает его живым неписем. Кроме того, зависший коллбэк не освобождает стек (а он у Луа-подсистемы единый на все скрипты), что '''в итоге приводит к вылетам игры с переполнением памяти (вот она, реальная причина этих &amp;quot;родных&amp;quot; вылетов).''' Но было бы слишком хорошо, если бы всё ограничивалось этим... однако тут всё намного хуже... такие &amp;quot;зависшие&amp;quot; коллбэки, особенно если их произошло несколько подряд, очень серьёзно влияют на работу а-лайфа. В лучшем случае они, забивая, стек, мешают нормально работать схемам логики, в худшем вызывают зависания самого  а-лайфа (этот эффект, кстати, производят и сами трупы таких неписей, так как они, как мы помним, не разрегистрировались корректно). '''Основной итог таких событий - бой сейвов, сделанных после возникновения таких ситуаций.''' Если повис один коллбэк, то такие сейвы ещё через раз загружаются, если же несколько, и остановилась работа а-лайфа - всё, сейвы бьются наглухо и реанимации не подлежат.&lt;br /&gt;
&lt;br /&gt;
Поэтому, чтобы избежать такого развития событий, каждый раз, когда вы вносите в коллбэк новую функцию - проверьте её самым тщательным образом. Она не должна содержать никаких рекурсивных циклов, в ней обязательно должны быть проверки на валидность обрабатываемых объектов и значений, и обязательно должна быть обработка нештатных ситуаций - т.е. функция должна обязательно, в абсолютно любой ситуации вернуть управление коллбэку, так или иначе. В самом наихудшем случае - делайте как делали разработчики игры - вставляйте принудительный вылет на рабстол функцией abort - она позволяет передавать отладочное сообщение, и это всяко лучше чем незаметный бой сейвов. Если обработка оборвалась в самом начале, а ф-ция обязательно должна вернуть значение - заведите ей &amp;quot;безопасное&amp;quot; возвращаемое значение по-умолчанию, которое она будет выдавать, если всё пошло плохо. И никогда не пренебрегайте пошаговой отладкой коллбэков с выводом в лог, особенно когда пишете схемы логики - это критически важно для стабильности вашего мода.&lt;br /&gt;
&lt;br /&gt;
== Основные внутриигровые признаки зависания коллбэков типа hit_callback, death_callback ==&lt;br /&gt;
&lt;br /&gt;
1. В трупах попадаются фонарики, КПК, разный мусор и общий лут слишком богат.&lt;br /&gt;
&lt;br /&gt;
2. Частые вылеты во время интенсивных боёв с логами типа&lt;br /&gt;
    Sheduler tried to update object...&lt;br /&gt;
    smart_terrain:1145(1146)&lt;br /&gt;
    LUA: out of memory&lt;br /&gt;
    любой_модуль_логики:любая_cтрока - stack overflow&lt;br /&gt;
&lt;br /&gt;
3. Частые &amp;quot;родные&amp;quot; вылеты в момент смерти непися или попадания по нему&lt;br /&gt;
&lt;br /&gt;
4. Произвольно бьются сейвы во время сражений, выброса и других насыщенных действиями событий&lt;br /&gt;
&lt;br /&gt;
= Использование защищённого кода в LUA =&lt;br /&gt;
&lt;br /&gt;
Периодически случаются такие ситуации, когда мы можем получить вылет при проверке аргумента, и не можем его адекватно заизолировать с помощью предварительной проверки на валидность значения. Вот простой пример: когда я отлаживал '''death_callback''' неписей, я периодически сталкивался с тем, что обращение к методу '''smart_terrain_id()''' при смерти непися иногда вызывало вылет &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;smart_terrain:1143 &amp;quot;attempt to index a nil value&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
- хотя это свойство является родным методом объекта, и отсутствовать напрочь никак не может. В итоге я пришёл к выводу, что его просто иногда не успевает отработать движок, так как вылет этот проявлялся в основном в интенсивных боях и совершенно произвольно.&lt;br /&gt;
&lt;br /&gt;
Вот код, в котором происходил вылет:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj     = sim:object( obj_id )&lt;br /&gt;
		local strn_id = obj:smart_terrain_id()  --- вылет происходит тут&lt;br /&gt;
&lt;br /&gt;
		if strn_id ~= 65535 then&lt;br /&gt;
			sim:object( strn_id ).gulag:clear_dead(obj_id)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это кусок родного кода из версии 1,0005 игры. Я долго пытался разными способами отсечь этот вылет, вводя предварительные проверки, однако это совершенно ничего не давало - вылет всё равно периодически случался, так как проверки эти сами его вызывали. Тогда я зарылся в документацию по Lua и обнаружил замечательную родную базовую функцию, введённую ещё с первых версий Lua, которой почему-то не пользовались ни разработчики игры, ни моддеры (хотя сама она в Lua сталкера присутствует, и работает отлично, без каких-либо нареканий). Вот она:&lt;br /&gt;
&lt;br /&gt;
'''pcall (f, arg1, ···)'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Вызывает функцию f с указанными через запятую аргументами в защищённом режиме. Это означает, что любая ошибка, даже критическая, внутри вызыванной функции, не передаётся наружу - вызавшей подсистеме. Вместо этого '''pcall''' перехватывает ошибку и возвращает код статуса. Первая возвращаемая переменная это сам код, (true или false) и если всё прошло хорошо, он равен true. В этом случае '''pcall''' сразу после статуса возвращает все результаты от работы защищённой им функции. Если же в защищённой функции произошла ошибка, то '''pcall''' вернёт false и затем сообщение об ошибке. (Обратите внимание, обработка ошибки присходит БЕЗ вылета! Вместо вылета вы получите вполне адекватную строку с ошибкой, которую можно вывести в лог для последующей обработки)&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Я настоятельно советую пользоваться этой функцией в случаях, когда из коллбэков вызываются сложные комплексные обработки, вроде обработки из менеджера вооружений AI-пака. Это позволяет предотвратить как вылеты, так и зависания обработок, и в итоге позволяет хорошо стабилизировать игру.&lt;br /&gt;
&lt;br /&gt;
Возвращаясь к нашим смарттеррейнам... вот как в итоге я подавил вылет типа smart_terrain:1143 с помощью pcall:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;--- эта функция пытается проверить св-во smart_terrain_id объекта. Именно её мы вызовем в защищённом режиме.&lt;br /&gt;
function prot_smt_td(obj)&lt;br /&gt;
	if IsStalker(obj) or IsMonster(obj) then&lt;br /&gt;
		return obj:smart_terrain_id()&lt;br /&gt;
	else	&lt;br /&gt;
		return 65535&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj = sim:object( obj_id )&lt;br /&gt;
		if obj then&lt;br /&gt;
			local strn_id = 65535  --- предварительно проинитим переменную, на &lt;br /&gt;
						--- случай если у нас prot_smt_td выдаст ошибку&lt;br /&gt;
			local result, smt_id = pcall(prot_smt_td,obj)	--- вызываем prot_smt_td в защищённом режиме &lt;br /&gt;
									--- и сразу присваиваем его вывод переменным&lt;br /&gt;
			if result then --- если pcall выдало true&lt;br /&gt;
				strn_id = smt_id  --- тогда применям полученное значение&lt;br /&gt;
			end&lt;br /&gt;
			--- если же обработка выдаст ошибку, то strn_id останется неизменным...&lt;br /&gt;
			if strn_id ~= 65535 then&lt;br /&gt;
				sim:object(strn_id).gulag:clear_dead(obj_id)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В других местах это делается совершенно аналогично. Подробнее об этой и многих других функция для контроля кода, незаслуженно ингорируемых большинством моддеров, можно почитать тут: http://lua-users.org/wiki/FinalizedExceptions - на английском правда, но захотите - разберётесь, там всё просто. &lt;br /&gt;
&lt;br /&gt;
Настоятельно советую вам изучить работу кода в таких условиях и научиться его правильно применять - этим вы сильно облегчите жизнь как себе, так и тем, кто после вас будет рыться в вашем коде или импортировать его в свои разработки.&lt;br /&gt;
&lt;br /&gt;
Циклы же for i=1, table.getn(table_name) do следует использовать '''только для операций, не изменяющих структуру изменяемой таблицы!'''&lt;br /&gt;
&lt;br /&gt;
--[[Участник:Kamikazze|KamikaZze (OGSE Team)]] 11:04, 3 сентября 2009 (UTC)&lt;br /&gt;
&lt;br /&gt;
= Авторы =&lt;br /&gt;
Статья создана: [[Участник:Kamikazze|Kamikazze]]&lt;/div&gt;</summary>
		<author><name>Kamikazze</name></author>	</entry>

	<entry>
		<id>http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2</id>
		<title>Как писать скрипты, не приводящие к вылетам и бою сейвов</title>
		<link rel="alternate" type="text/html" href="http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2"/>
				<updated>2009-09-03T11:03:55Z</updated>
		
		<summary type="html">&lt;p&gt;Kamikazze: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Категория:Статьи участников]]&lt;br /&gt;
= Предисловие =&lt;br /&gt;
&lt;br /&gt;
'''Отладив несколько скриптов на предмет вылетов решил написать это небольшое руководство, чтобы помочь начинающим (да и опытным тоже) скриптописателям избежать потенциальных проблем.'''&lt;br /&gt;
&lt;br /&gt;
Сразу оговорюсь — я программист, поэтому к отладке скриптов подхожу со своей профессиональной точки зрения, и то что я тут изложу вам — сие для программиста есть непреложная истина, и если вы хотите уменьшить количество глюков — старайтесь придерживаться нижеприведённого стиля программирования.&lt;br /&gt;
&lt;br /&gt;
Для начала маленький экскурс в историю. Итак, скрипты в сталкере написаны на языке '''Lua''' — разработали его бразильцы, он очень гибок, легко встраивается в игры и поэтому очень часто используется для написания скриптов любой степени сложности. Так вот, у этого языка и его реализации в игре есть несколько исключительно важных особенностей, которые, при их несоблюдении, будут приводить к вылетам вашего скрипта. Итак:&lt;br /&gt;
&lt;br /&gt;
= Типы данных и проблемы с nil. =&lt;br /&gt;
&lt;br /&gt;
Сначала сделаю маленькое отступление для не знакомых с основами скриптеров, чтобы понятнее было. Итак, в Lua используются следующие логические операторы:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
=	оператор присваивания&lt;br /&gt;
==	сравнение, равно ли значение&lt;br /&gt;
≈	сравнение, НЕ равно ли значение&lt;br /&gt;
&amp;lt;	сравнение, меньше ли значение&lt;br /&gt;
&amp;gt;	сравнение, больше ли значение&lt;br /&gt;
&amp;lt;=	сравнение, меньше ли значение или равно&lt;br /&gt;
&amp;gt;=	сравнение, больше ли значение или равно&lt;br /&gt;
and	логический оператор И&lt;br /&gt;
not	логический оператор НЕ&lt;br /&gt;
or	логический оператор ИЛИ&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Всё. Теперь поехали дальше… Язык Lua Изначально создавался для работы с большими строчными базами данных, поэтому ВСЕ виды конструкций в языке — это типы данных, то есть по сути либо переменные, либо константы. Это касается так же и функций! '''Поэтому даже с функциями в Lua можно и нужно обращатся как с переменными.''' Далее. Касательно, собственно «привычных» переменных. '''Обычные переменные в Lua получают свой тип данных только в момент присвоения им значения (запомните, это важно).''' При этом, в Lua есть такой важный и полезный тип данных как nil. nil — Это «пустота», то есть отсутствие какого-либо значения. При этом этот тип используется компилятором скриптов для «сбора мусора», то есть для освобождения занимаемой памяти. Смотрите пример, я объясню подробнее, что это значит:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;local reminder_count&lt;br /&gt;
local exposure_count = 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тут у нас 2 локальные переменные, одна из которых просто создана, а вторая — создана и инициализарована. Если сейчас обратиться к переменной reminder_count, считав её значение, то мы получим в качестве значения — nil, то есть пустоту. Если же мы обратимся к переменной exposure_count — то получим, как и ожидали, 0 — так как мы это значение проинициализировали ЗАРАНЕЕ. '''При этом не надо путать nil и 0 — так как 0 — это всё-таки какая-то информация, а вот nil — это её полное отсутствие.''' Так вот, собственно, чем это чревато. Я уже сказал, что nil используется для сбора мусора. Вручную это работает так — когда вам переменная уже не нужна, вы просто пишете:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;test_variable = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И ваша переменная test_variable сотрётся из памяти, и любые ссылки на неё будут выдавать на выходе nil (типа нет такой переменной, «абонент вне зоны действия сети»). Ну так собственно вот, зачем я это всё рассказываю… '''если не определить значение переменной сразу, как это было сделано с переменной reminder_count, то любые попытки обратиться к ней внутри скрипта приведут к вылетам.''' Происходит это следующим образом: допустим, reminder_count — это у нас счётчик напоминаний. То есть мы о чём-то напоминаем игроку текстовыми сообщениями, и помечаем, сколько раз мы это сделали. Определили мы переменную именно так, как в примере выше, то есть не задав ей никакого значения. Значение же её должно присваиваться ниже по коду, после первого оповещения юзера. При этом у нас есть в коде обращение к этой переменной, по которому решается что делать дальше. Выглядит это примерно так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function check_antirad_supplies()&lt;br /&gt;
&lt;br /&gt;
	if auto_injection_active then&lt;br /&gt;
&lt;br /&gt;
		if antirad_check_delay == nil or game.get_game_time():diffSec(antirad_check_delay) &amp;gt; 60 then&lt;br /&gt;
&lt;br /&gt;
			if not db.actor:object(«antirad») and not (use_scientific_kit and db.actor:object(«medkit_scientic»)) then&lt;br /&gt;
&lt;br /&gt;
			--обратите внимание сюда&lt;br /&gt;
				if reminder_count == 0 or reminder_count &amp;gt;= mins_till_next_remind then&lt;br /&gt;
			--обратите внимание сюда&lt;br /&gt;
					if use_text then&lt;br /&gt;
						local news_text = «%c[255,160,160,160]Автоматическая система ввода медицинских&lt;br /&gt;
 препаратов\\n&amp;quot;..&amp;quot;%c[default]Напоминаю: %c[255,230,0,0]&lt;br /&gt;
Противорадиационные препараты отстутствуют! %c[default]&lt;br /&gt;
Автоматический ввод препаратов невозможен.»&lt;br /&gt;
  						db.actor:give_game_news(news_text, &amp;quot;ui\\ui_iconsTotal&amp;quot;, &lt;br /&gt;
Frect():set(0,188,83,47), 0, 3000)&lt;br /&gt;
  					end	&lt;br /&gt;
	  				if use_sounds then&lt;br /&gt;
				 		local snd_obj&lt;br /&gt;
						if use_custom_sounds then&lt;br /&gt;
							snd_obj = xr_sound.get_safe_sound_object([[HEV\no-anti-rad]])&lt;br /&gt;
						else&lt;br /&gt;
	  						snd_obj = xr_sound.get_safe_sound_object([[device\pda\pda_tip]])&lt;br /&gt;
						end&lt;br /&gt;
						if snd_obj then&lt;br /&gt;
							snd_obj:play_no_feedback(db.actor, sound_object.s2d, 0, vector(), 1.0)&lt;br /&gt;
						end&lt;br /&gt;
					end&lt;br /&gt;
					reminder_count = 1&lt;br /&gt;
				elseif reminder then&lt;br /&gt;
					reminder_count = reminder_count + 1&lt;br /&gt;
				end&lt;br /&gt;
&lt;br /&gt;
			else&lt;br /&gt;
				radiation_warning = true&lt;br /&gt;
				reminder_count = 0&lt;br /&gt;
			end&lt;br /&gt;
			antirad_check_delay = game.get_game_time()&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Во время игры всё это скорее всего отработает хорошо, но вот при загрузке… тут могут быть проблемы, так как скрипт при запуске может пролететь ту часть, где переменной reminder_count присваивается значение! В итоге при запуске вышеприведённой функции check_antirad_supplies() нас получится что reminder_count не существует — он равен nil, и в итоге проверка&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count == 0 or reminder_count &amp;gt;= mins_till_next_remind then&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
выдаст ошибку и приведёт к вылету! Почему? '''Да потому что НЕЛЬЗЯ сравнивать НИЧТО с вещественным значением.''' Именно поэтому, когда вы создаёте новую переменную, вы ОБЯЗАНЫ задать ей значение по умолчанию, даже если вы полностью уверены, что она нигде не будет использована до инициализации. Поверьте — в программировании бывает всё, и запросто может так случиться, что ваша переменная будет использована и спровоцирует вылет.&lt;br /&gt;
&lt;br /&gt;
Простой способ обойти такую кучу проверок и лишних инициализаций — использование or-оператора. пример&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if (reminder_count or 0) &amp;gt;= mins_till_next_remind then&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
если переменная равна nil, то выражение вычисляется дальше и получается равным 0, а это уже число и сравнение происходит безболезненно.&lt;br /&gt;
&lt;br /&gt;
Кроме вышеприведённой ситуации, возможен ещё один неприятный момент, связанный с определением переменных и значением nil. Суть его заключается в том, что при присваивании значений или операциями с переменными мы можем получить кучу проблем… покажу конкретнее каких:&lt;br /&gt;
&lt;br /&gt;
== «Смерть» переменных ==&lt;br /&gt;
&lt;br /&gt;
Положим, у нас с reminder_count происходят ещё какие-нибудь занимательные вещи, например мы его значение зачем-нибудь присваиваем другой переменной вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;antirad_check_delay = reminder_count&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ну и вот, если у нас reminder_count в момент присвоения ещё не имеет никакого значения, то мы получим занимательную штуку — у нас это выражение сработает как&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;antirad_check_delay = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате чего переменная antirad_check_delay «сдохнет» и будет деловито убрана «сборщиком мусора» из памяти, что моментально приведёт к вылету, когда в дальнейшем какая-то часть кода обратится к значению antirad_check_delay.&lt;br /&gt;
&lt;br /&gt;
== Ошибки деления на 0 ==&lt;br /&gt;
&lt;br /&gt;
Теперь положим что у нас reminder_count используется в каком-нибудь расчёте вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;antirad_check_delay = exposure_count/reminder_count&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Как мы помним, у нас reminder_count не инициализирована, и как следствие равна nil. В итоге мы получим попытку поделить exposure_count на ноль (в нашем случае на nil, что суть одно и то же в нашем случае) и получим «классический» программистский вылет из-за ошибки деления на ноль.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Ошибки присвоения ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Предположим что мы написали скрипт, который помогает выбирать оружие при наличии вблизи врагов. И вот, у нас в скрипте есть такой кусок кода, где NPC вынимает из рюкзака оружие:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function weapon_manager:set_weapon(wpn)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	local enemy = self.npc:best_enemy()&lt;br /&gt;
	if wpn then&lt;br /&gt;
		--обратите внимание сюда&lt;br /&gt;
		self.weapon_id = wpn:id()&lt;br /&gt;
		--обратите внимание сюда&lt;br /&gt;
		self:return_items(self.weapon_id)&lt;br /&gt;
	else&lt;br /&gt;
		printw(«set_wpn:weapon not exist»)&lt;br /&gt;
	end&lt;br /&gt;
	if self.modes.process_mode == &amp;quot;3&amp;quot; and enemy then&lt;br /&gt;
		for k, v in pairs(self.weapons) do&lt;br /&gt;
			for i, w in ipairs(v) do&lt;br /&gt;
				if w.id ≈ self.weapon_id then&lt;br /&gt;
					local item = level.object_by_id(w.id)&lt;br /&gt;
					if item and item:parent() and item:parent():id() == self.npc_id then&lt;br /&gt;
						printw(«set_weapon[%s]:process %s[%s]», self.npc:character_name(), w.id, w.sec)&lt;br /&gt;
						self:process_item(item)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if enemy then&lt;br /&gt;
		self.npc:set_item(object.idle, wpn)&lt;br /&gt;
	end&lt;br /&gt;
	self.weapons = nil&lt;br /&gt;
&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В помеченном выражении self.weapon_id = wpn:id(), и всё вроде бы отлично, так как стоит проверка if wpn then, однако иногда движок умудряется передать объект функциям некорректно, либо сам скриптер может перепутать при вызове игровой объект с объектом а-лайфа, и присвовение self.weapon_id = wpn:id() либо приравняет self.weapon_id к nil либо сразу приведёт к вылету из-за того что соответствующее свойство объекта не было обнаружено в его классе.&lt;br /&gt;
&lt;br /&gt;
== Как с этим бороться ==&lt;br /&gt;
&lt;br /&gt;
Бороться с такими ситуациями очень просто на самом деле.&lt;br /&gt;
Во-первых: ВСЕГДА ИНИЦИАЛИЗИРУЙТЕ ПЕРЕМЕННЫЕ. То есть вот такое описание переменной:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
НЕДОПУСТИМО! ВЫ ОБЯЗАТЕЛЬНО ДОЛЖНЫ ЗАДАТЬ ПЕРЕМЕННОЙ ЗНАЧЕНИЕ!&lt;br /&gt;
Если переменная числовая, сделайте это так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count = 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если строчная — то сделайте это так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count = &amp;quot;&amp;quot;&amp;lt;/code&amp;gt; (получится вместо nil пустая строка)&lt;br /&gt;
&lt;br /&gt;
Если логическая, то так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count = false&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В общем, делайте так, как вам удобнее, но ДЕЛАЙТЕ ОБЯЗАТЕЛЬНО!&lt;br /&gt;
&lt;br /&gt;
И во-вторых: ВСЕГДА ПРОВЕРЯЙТЕ ПЕРЕМЕННУЮ ПЕРЕД ИСПОЛЬЗОВАНИЕМ!&lt;br /&gt;
&lt;br /&gt;
То есть в кусках кода, чреватых вылетами (к ним относятся АБСОЛЮТНО ВСЕ части, где идёт работа с вещами или оружием) обязательно вставляйте проверки переменных на nil следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if wpn and wpn:id() then&lt;br /&gt;
	self.weapon_id = wpn:id()&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тогда сначала выполнится проверка, и если обе переменные имеют значение, то операция отработает, а если один из параметров пуст — то функция просто пропустит этот код. Тут не помешает ещё и проверку вставить на существование self.weapon_id, хотя это наверное уже моя параноя :)&lt;br /&gt;
&lt;br /&gt;
Кстати, что самое дурацкое — ошибки, приведённые выше, часто в лог попадают совершенно непохожим на ошибки скриптов образом. Например, ошибка присвоения из примера 3 приводила к вылету с сообщением «Assertion failed», и поймать её я смог только изучив внимательно предыдущие строки лога, где зафиксировались операции с вещами…&lt;br /&gt;
&lt;br /&gt;
Точно так же, если вы используете ЛЮБЫЕ математические операции например деление (но не только деление, всего остального тоже касается), тоже ОБЯЗАТЕЛЬНО поверяйте переменную. Вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count and reminder_count ≈ 0 then&lt;br /&gt;
	antirad_check_delay = exposure_count/reminder_count&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Таким образом потенциальные вылеты будут аккуратно изолированы, и больше не будут создавать проблем.&lt;br /&gt;
И ещё одно небольшое дополнение к этой же теме. Итак, вы вставили обходные проверки вот таким образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if wpn and wpn:id() then&lt;br /&gt;
   self.weapon_id = wpn:id() &lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Или таким:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count and reminder_count ≈ 0 then&lt;br /&gt;
   antirad_check_delay = exposure_count/reminder_count &lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И у вас в случае некорректного значения компилятор спокойно пропустил этот код, ничего не сделав ни с self.weapon_id (из первого примера), ни с antirad_check_delay (из второго). Однако вам всё-таки нужно, чтобы с ними что-то происходило, даже если проверяемые переменные неверны. Тогда я бы советовал вам дополнить этот обход отработкой нештатной ситуации. Делается это банально просто:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if wpn and wpn:id()then&lt;br /&gt;
   self.weapon_id = wpn:id() &lt;br /&gt;
elseif wpn == nil then&lt;br /&gt;
   --вставьте сюда код, что делать если wpn не существует&lt;br /&gt;
elseif wpn:id() == nil then&lt;br /&gt;
   --а сюда, если wpn:id() не существует&lt;br /&gt;
   --тут скорее всего перепутан объект, и можно попробовать&lt;br /&gt;
   --обратиться к wpn.id&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И для второго случая аналогично:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count and reminder_count ≈ 0 then&lt;br /&gt;
   antirad_check_delay = exposure_count/reminder_count &lt;br /&gt;
else&lt;br /&gt;
   --а тут что мы сделаем если reminder_count не существует&lt;br /&gt;
   --я бы сделал например вот так:&lt;br /&gt;
   antirad_check_delay = exposure_count/1&lt;br /&gt;
   --и все дела&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Более конкретная реализация зависит от того, что именно вы пытаетесь сделать — тут уже вам виднее…&lt;br /&gt;
&lt;br /&gt;
= Типичные ошибки при именовании функций, присвоении и инициализации переменных. =&lt;br /&gt;
&lt;br /&gt;
Типичная ошибка, приводящая к вылету:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function remove_item(remove_item)&lt;br /&gt;
     if remove_item~=nil then&lt;br /&gt;
          alife():release(alife():object(remove_item:id()), true)&lt;br /&gt;
          return true&lt;br /&gt;
     end&lt;br /&gt;
     return false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В этом коде агрумент функции совпадает с её именем, и в итоге функция пытается передать функции alife():release вместо указателя на вещь — указатель на саму себя. А так как у функции remove_item нет никакого id, код выпадает с ошибкой:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Arguments     : LUA error: ...\s.t.a.l.k.e.r\gamedata\scripts\test.script:486: attempt to call method 'id' (a nil value)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Следует помнить, что Lua — это язык прямого (JIT) компилирования, и он, как любой такой язык не проверяет ошибки при компиляции. Поэтому нужно очень аккуратно раздавать имена переменным и функциям, чтобы избежать подобных ошибок. Данную функцию нужно переписать следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function remove_item(item_to_remove)&lt;br /&gt;
     if item_to_remove then&lt;br /&gt;
          alife():release(alife():object(item_to_remove:id()), true)&lt;br /&gt;
          return true&lt;br /&gt;
     end&lt;br /&gt;
     return false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Однажды, отлаживая скрипт, натолкнулся на вот такой код:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function hit_callback(npc_id)&lt;br /&gt;
	if db.storage[npc_id].wounded ≈ nil then&lt;br /&gt;
		db.storage[npc_id].wounded.wound_manager:hit_callback()&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Казалось бы, всё верно, и значение проверяется перед использованием. Ан нет, скрипт вызывал вылет, причём именно на проверке. Методом тыка было выясено, что nil вызвавший вылет, был не в результате обработки, а был передан фукнции. Это был параметр npc_id.&lt;br /&gt;
&lt;br /&gt;
Пришлось сделать вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function hit_callback(npc_id)&lt;br /&gt;
	if npc_id and db.storage[npc_id].wounded ≈ nil then&lt;br /&gt;
		db.storage[npc_id].wounded.wound_manager:hit_callback()&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Введя предварительно проверку аргумента npc_id. Вывод — если ставите проверку, старайтесь проверять не только саму функцию, но и вначале — аргумент, который ей передаётся. Мало ли в каком виде он до вас доедет… :)&lt;br /&gt;
&lt;br /&gt;
Касательно именования переменных — не забывайте давать им атрибут local если вы не собираетесь их пользовать во внешних скриптах. Иначе, если попадётся где-то аналогичное имя — будет сбой логики или вылет, помните об этом.&lt;br /&gt;
&lt;br /&gt;
И ещё один совет — для самых начинающих:&lt;br /&gt;
&lt;br /&gt;
Когда пишете скрипты, не забывайте о соблюдении синтаксиса! То есть внимательно и вдумчиво ставьте в концах функций и оснований ключевые слова end — помните, что как отсутствие нужных end’ов, так и наличие лишних приводят к тому, что границы фукций сбиваются, скрипт не парсится обработчиком и вызывает при попытке своего вызова вылет, примерно вот такой:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Arguments : LUA error: ...g\s.t.a.l.k.e.r\gamedata\scripts\bind_stalker.script:75: attempt to index global 'test_main_new' (a nil value)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы вам проще было следить за синтаксисом и не допускать лишних end’ов, выстраивайте текст скриптов каскадными лесенками:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code &amp;gt;Начало_Работы&lt;br /&gt;
          Начало_Внуренней_Функции&lt;br /&gt;
                  Процедуры_Внутреней_Функции              &lt;br /&gt;
          Конец_Внутренней_Функции&lt;br /&gt;
Конец_Работы&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
..и пользуйтесь редакторами с подсветкой синтаксиса, такими как Notepad++ например. В результате каждый уровень вложенности находится на своём отступе. Вы всегда увидите, какую конструкцию нужно закрыть, а какую — нет. Я же например, делаю ещё проще — я, открывая новую функцию, сразу ставлю сразу в её конце, парой строк ниже, end, и потом уже пишу наполняющий её текст с отступами как в примере выше.&lt;br /&gt;
&lt;br /&gt;
И ещё — касательно жизни переменных: не забывайте, что определённые внутри проверок переменные живут только до выхода из проверки. То есть, например:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if check == 1 then&lt;br /&gt;
	local test = true&lt;br /&gt;
else&lt;br /&gt;
	local test = false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В данном случае переменная test создастся, но просуществует только до выхода из проверки, и дальше обращение к ней выдаст nil. Для того чтобы этого избежать, определение переменных надо выносить «за скобки» вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;local test = false; — инитим переменную и задаём сразу ей значение по умолчанию&lt;br /&gt;
&lt;br /&gt;
if check == 1 then&lt;br /&gt;
	test = true&lt;br /&gt;
else&lt;br /&gt;
	test = false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Смертельные циклы по таблицам =&lt;br /&gt;
&lt;br /&gt;
Если вы строите цикл по таблице следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;for i=1, table.getn(table_name) do&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''То никогда, ни при каких обстоятельствах не используйте внутри этого цикла удаление/добавление строк, т.е:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;table_name[i] = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
или&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;table.remove(table_name, index)&lt;br /&gt;
table.insert(table_name, index)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
применительно к таблице по которой гоняете цикл!!! При использовании это приводит к тому, что количество строк в таблице уменьшается, а цикл пытается получить из таблицы строки сверх существующего количества, что в итоге приводит к выходу цикла за отведённый ему диапазон памяти. Результатом будет веер самых разнообразных последствий, самое безобидное из которых это безлоговый вылет на рабочий стол, а серьёзное — сбой в работе а-лайфа с последующим боем сейвов! Тяжесть последствий зависит от того, какие действия вы выполняете внутри цикла кроме удаления строки.&lt;br /&gt;
&lt;br /&gt;
Чтобы избежать подобных ситуаций, стройте циклы по таблице лучше следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;for k, v in pairs(table_name)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
А удаление строк внутри них делайте вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;table_name[k] = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Циклы же for i=1, table.getn(table_name) do следует использовать '''только для операций, не изменяющих структуру изменяемой таблицы!'''&lt;br /&gt;
&lt;br /&gt;
[[Как писать скрипты, не приводящие к вылетам и бою сейвов (часть 2)|Продолжение во второй части]]&lt;br /&gt;
&lt;br /&gt;
--[[Участник:Kamikazze|KamikaZze (OGSE Team)]] 08:16, 1 сентября 2009 (UTC)&lt;br /&gt;
&lt;br /&gt;
= Авторы =&lt;br /&gt;
Статья создана: [[Участник:Kamikazze|Kamikazze]]&lt;/div&gt;</summary>
		<author><name>Kamikazze</name></author>	</entry>

	<entry>
		<id>http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2</id>
		<title>Как писать скрипты, не приводящие к вылетам и бою сейвов</title>
		<link rel="alternate" type="text/html" href="http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2"/>
				<updated>2009-09-01T08:24:59Z</updated>
		
		<summary type="html">&lt;p&gt;Kamikazze: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Категория:Статьи участников]]&lt;br /&gt;
= Предисловие =&lt;br /&gt;
&lt;br /&gt;
'''Отладив несколько скриптов на предмет вылетов решил написать это небольшое руководство, чтобы помочь начинающим (да и опытным тоже) скриптописателям избежать потенциальных проблем.'''&lt;br /&gt;
&lt;br /&gt;
Сразу оговорюсь — я программист, поэтому к отладке скриптов подхожу со своей профессиональной точки зрения, и то что я тут изложу вам — сие для программиста есть непреложная истина, и если вы хотите уменьшить количество глюков — старайтесь придерживаться нижеприведённого стиля программирования.&lt;br /&gt;
&lt;br /&gt;
Для начала маленький экскурс в историю. Итак, скрипты в сталкере написаны на языке '''Lua''' — разработали его бразильцы, он очень гибок, легко встраивается в игры и поэтому очень часто используется для написания скриптов любой степени сложности. Так вот, у этого языка и его реализации в игре есть несколько исключительно важных особенностей, которые, при их несоблюдении, будут приводить к вылетам вашего скрипта. Итак:&lt;br /&gt;
&lt;br /&gt;
= Типы данных и проблемы с nil. =&lt;br /&gt;
&lt;br /&gt;
Сначала сделаю маленькое отступление для не знакомых с основами скриптеров, чтобы понятнее было. Итак, в Lua используются следующие логические операторы:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
=	оператор присваивания&lt;br /&gt;
==	сравнение, равно ли значение&lt;br /&gt;
≈	сравнение, НЕ равно ли значение&lt;br /&gt;
&amp;lt;	сравнение, меньше ли значение&lt;br /&gt;
&amp;gt;	сравнение, больше ли значение&lt;br /&gt;
&amp;lt;=	сравнение, меньше ли значение или равно&lt;br /&gt;
&amp;gt;=	сравнение, больше ли значение или равно&lt;br /&gt;
and	логический оператор И&lt;br /&gt;
not	логический оператор НЕ&lt;br /&gt;
or	логический оператор ИЛИ&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Всё. Теперь поехали дальше… Язык Lua Изначально создавался для работы с большими строчными базами данных, поэтому ВСЕ виды конструкций в языке — это типы данных, то есть по сути либо переменные, либо константы. Это касается так же и функций! '''Поэтому даже с функциями в Lua можно и нужно обращатся как с переменными.''' Далее. Касательно, собственно «привычных» переменных. '''Обычные переменные в Lua получают свой тип данных только в момент присвоения им значения (запомните, это важно).''' При этом, в Lua есть такой важный и полезный тип данных как nil. nil — Это «пустота», то есть отсутствие какого-либо значения. При этом этот тип используется компилятором скриптов для «сбора мусора», то есть для освобождения занимаемой памяти. Смотрите пример, я объясню подробнее, что это значит:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;local reminder_count&lt;br /&gt;
local exposure_count = 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тут у нас 2 локальные переменные, одна из которых просто создана, а вторая — создана и инициализарована. Если сейчас обратиться к переменной reminder_count, считав её значение, то мы получим в качестве значения — nil, то есть пустоту. Если же мы обратимся к переменной exposure_count — то получим, как и ожидали, 0 — так как мы это значение проинициализировали ЗАРАНЕЕ. '''При этом не надо путать nil и 0 — так как 0 — это всё-таки какая-то информация, а вот nil — это её полное отсутствие.''' Так вот, собственно, чем это чревато. Я уже сказал, что nil используется для сбора мусора. Вручную это работает так — когда вам переменная уже не нужна, вы просто пишете:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;test_variable = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И ваша переменная test_variable сотрётся из памяти, и любые ссылки на неё будут выдавать на выходе nil (типа нет такой переменной, «абонент вне зоны действия сети»). Ну так собственно вот, зачем я это всё рассказываю… '''если не определить значение переменной сразу, как это было сделано с переменной reminder_count, то любые попытки обратиться к ней внутри скрипта приведут к вылетам.''' Происходит это следующим образом: допустим, reminder_count — это у нас счётчик напоминаний. То есть мы о чём-то напоминаем игроку текстовыми сообщениями, и помечаем, сколько раз мы это сделали. Определили мы переменную именно так, как в примере выше, то есть не задав ей никакого значения. Значение же её должно присваиваться ниже по коду, после первого оповещения юзера. При этом у нас есть в коде обращение к этой переменной, по которому решается что делать дальше. Выглядит это примерно так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function check_antirad_supplies()&lt;br /&gt;
&lt;br /&gt;
	if auto_injection_active then&lt;br /&gt;
&lt;br /&gt;
		if antirad_check_delay == nil or game.get_game_time():diffSec(antirad_check_delay) &amp;gt; 60 then&lt;br /&gt;
&lt;br /&gt;
			if not db.actor:object(«antirad») and not (use_scientific_kit and db.actor:object(«medkit_scientic»)) then&lt;br /&gt;
&lt;br /&gt;
			--обратите внимание сюда&lt;br /&gt;
				if reminder_count == 0 or reminder_count &amp;gt;= mins_till_next_remind then&lt;br /&gt;
			--обратите внимание сюда&lt;br /&gt;
					if use_text then&lt;br /&gt;
						local news_text = «%c[255,160,160,160]Автоматическая система ввода медицинских&lt;br /&gt;
 препаратов\\n&amp;quot;..&amp;quot;%c[default]Напоминаю: %c[255,230,0,0]&lt;br /&gt;
Противорадиационные препараты отстутствуют! %c[default]&lt;br /&gt;
Автоматический ввод препаратов невозможен.»&lt;br /&gt;
  						db.actor:give_game_news(news_text, &amp;quot;ui\\ui_iconsTotal&amp;quot;, &lt;br /&gt;
Frect():set(0,188,83,47), 0, 3000)&lt;br /&gt;
  					end	&lt;br /&gt;
	  				if use_sounds then&lt;br /&gt;
				 		local snd_obj&lt;br /&gt;
						if use_custom_sounds then&lt;br /&gt;
							snd_obj = xr_sound.get_safe_sound_object([[HEV\no-anti-rad]])&lt;br /&gt;
						else&lt;br /&gt;
	  						snd_obj = xr_sound.get_safe_sound_object([[device\pda\pda_tip]])&lt;br /&gt;
						end&lt;br /&gt;
						if snd_obj then&lt;br /&gt;
							snd_obj:play_no_feedback(db.actor, sound_object.s2d, 0, vector(), 1.0)&lt;br /&gt;
						end&lt;br /&gt;
					end&lt;br /&gt;
					reminder_count = 1&lt;br /&gt;
				elseif reminder then&lt;br /&gt;
					reminder_count = reminder_count + 1&lt;br /&gt;
				end&lt;br /&gt;
&lt;br /&gt;
			else&lt;br /&gt;
				radiation_warning = true&lt;br /&gt;
				reminder_count = 0&lt;br /&gt;
			end&lt;br /&gt;
			antirad_check_delay = game.get_game_time()&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Во время игры всё это скорее всего отработает хорошо, но вот при загрузке… тут могут быть проблемы, так как скрипт при запуске может пролететь ту часть, где переменной reminder_count присваивается значение! В итоге при запуске вышеприведённой функции check_antirad_supplies() нас получится что reminder_count не существует — он равен nil, и в итоге проверка&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count == 0 or reminder_count &amp;gt;= mins_till_next_remind then&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
выдаст ошибку и приведёт к вылету! Почему? '''Да потому что НЕЛЬЗЯ сравнивать НИЧТО с вещественным значением.''' Именно поэтому, когда вы создаёте новую переменную, вы ОБЯЗАНЫ задать ей значение по умолчанию, даже если вы полностью уверены, что она нигде не будет использована до инициализации. Поверьте — в программировании бывает всё, и запросто может так случиться, что ваша переменная будет использована и спровоцирует вылет.&lt;br /&gt;
&lt;br /&gt;
Простой способ обойти такую кучу проверок и лишних инициализаций — использование or-оператора. пример&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if (reminder_count or 0) &amp;gt;= mins_till_next_remind then&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
если переменная равна nil, то выражение вычисляется дальше и получается равным 0, а это уже число и сравнение происходит безболезненно.&lt;br /&gt;
&lt;br /&gt;
Кроме вышеприведённой ситуации, возможен ещё один неприятный момент, связанный с определением переменных и значением nil. Суть его заключается в том, что при присваивании значений или операциями с переменными мы можем получить кучу проблем… покажу конкретнее каких:&lt;br /&gt;
&lt;br /&gt;
== «Смерть» переменных ==&lt;br /&gt;
&lt;br /&gt;
Положим, у нас с reminder_count происходят ещё какие-нибудь занимательные вещи, например мы его значение зачем-нибудь присваиваем другой переменной вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;antirad_check_delay = reminder_count&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ну и вот, если у нас reminder_count в момент присвоения ещё не имеет никакого значения, то мы получим занимательную штуку — у нас это выражение сработает как&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;antirad_check_delay = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате чего переменная antirad_check_delay «сдохнет» и будет деловито убрана «сборщиком мусора» из памяти, что моментально приведёт к вылету, когда в дальнейшем какая-то часть кода обратится к значению antirad_check_delay.&lt;br /&gt;
&lt;br /&gt;
== Ошибки деления на 0 ==&lt;br /&gt;
&lt;br /&gt;
Теперь положим что у нас reminder_count используется в каком-нибудь расчёте вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;antirad_check_delay = exposure_count/reminder_count&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Как мы помним, у нас reminder_count не инициализирована, и как следствие равна nil. В итоге мы получим попытку поделить exposure_count на ноль (в нашем случае на nil, что суть одно и то же в нашем случае) и получим «классический» программистский вылет из-за ошибки деления на ноль.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Ошибки присвоения ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Предположим что мы написали скрипт, который помогает выбирать оружие при наличии вблизи врагов. И вот, у нас в скрипте есть такой кусок кода, где NPC вынимает из рюкзака оружие:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function weapon_manager:set_weapon(wpn)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	local enemy = self.npc:best_enemy()&lt;br /&gt;
	if wpn then&lt;br /&gt;
		--обратите внимание сюда&lt;br /&gt;
		self.weapon_id = wpn:id()&lt;br /&gt;
		--обратите внимание сюда&lt;br /&gt;
		self:return_items(self.weapon_id)&lt;br /&gt;
	else&lt;br /&gt;
		printw(«set_wpn:weapon not exist»)&lt;br /&gt;
	end&lt;br /&gt;
	if self.modes.process_mode == &amp;quot;3&amp;quot; and enemy then&lt;br /&gt;
		for k, v in pairs(self.weapons) do&lt;br /&gt;
			for i, w in ipairs(v) do&lt;br /&gt;
				if w.id ≈ self.weapon_id then&lt;br /&gt;
					local item = level.object_by_id(w.id)&lt;br /&gt;
					if item and item:parent() and item:parent():id() == self.npc_id then&lt;br /&gt;
						printw(«set_weapon[%s]:process %s[%s]», self.npc:character_name(), w.id, w.sec)&lt;br /&gt;
						self:process_item(item)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if enemy then&lt;br /&gt;
		self.npc:set_item(object.idle, wpn)&lt;br /&gt;
	end&lt;br /&gt;
	self.weapons = nil&lt;br /&gt;
&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В помеченном выражении self.weapon_id = wpn:id(), и всё вроде бы отлично, так как стоит проверка if wpn then, однако иногда движок умудряется передать объект функциям некорректно, либо сам скриптер может перепутать при вызове игровой объект с объектом а-лайфа, и присвовение self.weapon_id = wpn:id() либо приравняет self.weapon_id к nil либо сразу приведёт к вылету из-за того что соответствующее свойство объекта не было обнаружено в его классе.&lt;br /&gt;
&lt;br /&gt;
== Как с этим бороться ==&lt;br /&gt;
&lt;br /&gt;
Бороться с такими ситуациями очень просто на самом деле.&lt;br /&gt;
Во-первых: ВСЕГДА ИНИЦИАЛИЗИРУЙТЕ ПЕРЕМЕННЫЕ. То есть вот такое описание переменной:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
НЕДОПУСТИМО! ВЫ ОБЯЗАТЕЛЬНО ДОЛЖНЫ ЗАДАТЬ ПЕРЕМЕННОЙ ЗНАЧЕНИЕ!&lt;br /&gt;
Если переменная числовая, сделайте это так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count = 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если строчная — то сделайте это так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count = &amp;quot;&amp;quot;&amp;lt;/code&amp;gt; (получится вместо nil пустая строка)&lt;br /&gt;
&lt;br /&gt;
Если логическая, то так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count = false&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В общем, делайте так, как вам удобнее, но ДЕЛАЙТЕ ОБЯЗАТЕЛЬНО!&lt;br /&gt;
&lt;br /&gt;
И во-вторых: ВСЕГДА ПРОВЕРЯЙТЕ ПЕРЕМЕННУЮ ПЕРЕД ИСПОЛЬЗОВАНИЕМ!&lt;br /&gt;
&lt;br /&gt;
То есть в кусках кода, чреватых вылетами (к ним относятся АБСОЛЮТНО ВСЕ части, где идёт работа с вещами или оружием) обязательно вставляйте проверки переменных на nil следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if wpn and wpn:id() then&lt;br /&gt;
	self.weapon_id = wpn:id()&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тогда сначала выполнится проверка, и если обе переменные имеют значение, то операция отработает, а если один из параметров пуст — то функция просто пропустит этот код. Тут не помешает ещё и проверку вставить на существование self.weapon_id, хотя это наверное уже моя параноя :)&lt;br /&gt;
&lt;br /&gt;
Кстати, что самое дурацкое — ошибки, приведённые выше, часто в лог попадают совершенно непохожим на ошибки скриптов образом. Например, ошибка присвоения из примера 3 приводила к вылету с сообщением «Assertion failed», и поймать её я смог только изучив внимательно предыдущие строки лога, где зафиксировались операции с вещами…&lt;br /&gt;
&lt;br /&gt;
Точно так же, если вы используете ЛЮБЫЕ математические операции например деление (но не только деление, всего остального тоже касается), тоже ОБЯЗАТЕЛЬНО поверяйте переменную. Вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count and reminder_count ≈ 0 then&lt;br /&gt;
	antirad_check_delay = exposure_count/reminder_count&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Таким образом потенциальные вылеты будут аккуратно изолированы, и больше не будут создавать проблем.&lt;br /&gt;
И ещё одно небольшое дополнение к этой же теме. Итак, вы вставили обходные проверки вот таким образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if wpn and wpn:id() then&lt;br /&gt;
   self.weapon_id = wpn:id() &lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Или таким:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count and reminder_count ≈ 0 then&lt;br /&gt;
   antirad_check_delay = exposure_count/reminder_count &lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И у вас в случае некорректного значения компилятор спокойно пропустил этот код, ничего не сделав ни с self.weapon_id (из первого примера), ни с antirad_check_delay (из второго). Однако вам всё-таки нужно, чтобы с ними что-то происходило, даже если проверяемые переменные неверны. Тогда я бы советовал вам дополнить этот обход отработкой нештатной ситуации. Делается это банально просто:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if wpn and wpn:id()then&lt;br /&gt;
   self.weapon_id = wpn:id() &lt;br /&gt;
elseif wpn == nil then&lt;br /&gt;
   --вставьте сюда код, что делать если wpn не существует&lt;br /&gt;
elseif wpn:id() == nil then&lt;br /&gt;
   --а сюда, если wpn:id() не существует&lt;br /&gt;
   --тут скорее всего перепутан объект, и можно попробовать&lt;br /&gt;
   --обратиться к wpn.id&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И для второго случая аналогично:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count and reminder_count ≈ 0 then&lt;br /&gt;
   antirad_check_delay = exposure_count/reminder_count &lt;br /&gt;
else&lt;br /&gt;
   --а тут что мы сделаем если reminder_count не существует&lt;br /&gt;
   --я бы сделал например вот так:&lt;br /&gt;
   antirad_check_delay = exposure_count/1&lt;br /&gt;
   --и все дела&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Более конкретная реализация зависит от того, что именно вы пытаетесь сделать — тут уже вам виднее…&lt;br /&gt;
&lt;br /&gt;
= Типичные ошибки при именовании функций, присвоении и инициализации переменных. =&lt;br /&gt;
&lt;br /&gt;
Типичная ошибка, приводящая к вылету:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function remove_item(remove_item)&lt;br /&gt;
     if remove_item~=nil then&lt;br /&gt;
          alife():release(alife():object(remove_item:id()), true)&lt;br /&gt;
          return true&lt;br /&gt;
     end&lt;br /&gt;
     return false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В этом коде агрумент функции совпадает с её именем, и в итоге функция пытается передать функции alife():release вместо указателя на вещь — указатель на саму себя. А так как у функции remove_item нет никакого id, код выпадает с ошибкой:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Arguments     : LUA error: ...\s.t.a.l.k.e.r\gamedata\scripts\test.script:486: attempt to call method 'id' (a nil value)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Следует помнить, что Lua — это язык прямого (JIT) компилирования, и он, как любой такой язык не проверяет ошибки при компиляции. Поэтому нужно очень аккуратно раздавать имена переменным и функциям, чтобы избежать подобных ошибок. Данную функцию нужно переписать следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function remove_item(item_to_remove)&lt;br /&gt;
     if item_to_remove then&lt;br /&gt;
          alife():release(alife():object(item_to_remove:id()), true)&lt;br /&gt;
          return true&lt;br /&gt;
     end&lt;br /&gt;
     return false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Однажды, отлаживая скрипт, натолкнулся на вот такой код:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function hit_callback(npc_id)&lt;br /&gt;
	if db.storage[npc_id].wounded ≈ nil then&lt;br /&gt;
		db.storage[npc_id].wounded.wound_manager:hit_callback()&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Казалось бы, всё верно, и значение проверяется перед использованием. Ан нет, скрипт вызывал вылет, причём именно на проверке. Методом тыка было выясено, что nil вызвавший вылет, был не в результате обработки, а был передан фукнции. Это был параметр npc_id.&lt;br /&gt;
&lt;br /&gt;
Пришлось сделать вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function hit_callback(npc_id)&lt;br /&gt;
	if npc_id and db.storage[npc_id].wounded ≈ nil then&lt;br /&gt;
		db.storage[npc_id].wounded.wound_manager:hit_callback()&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Введя предварительно проверку аргумента npc_id. Вывод — если ставите проверку, старайтесь проверять не только саму функцию, но и вначале — аргумент, который ей передаётся. Мало ли в каком виде он до вас доедет… :)&lt;br /&gt;
&lt;br /&gt;
Касательно именования переменных — не забывайте давать им атрибут local если вы не собираетесь их пользовать во внешних скриптах. Иначе, если попадётся где-то аналогичное имя — будет сбой логики или вылет, помните об этом.&lt;br /&gt;
&lt;br /&gt;
И ещё один совет — для самых начинающих:&lt;br /&gt;
&lt;br /&gt;
Когда пишете скрипты, не забывайте о соблюдении синтаксиса! То есть внимательно и вдумчиво ставьте в концах функций и оснований ключевые слова end — помните, что как отсутствие нужных end’ов, так и наличие лишних приводят к тому, что границы фукций сбиваются, скрипт не парсится обработчиком и вызывает при попытке своего вызова вылет, примерно вот такой:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Arguments : LUA error: ...g\s.t.a.l.k.e.r\gamedata\scripts\bind_stalker.script:75: attempt to index global 'test_main_new' (a nil value)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы вам проще было следить за синтаксисом и не допускать лишних end’ов, выстраивайте текст скриптов каскадными лесенками:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code &amp;gt;Начало_Работы&lt;br /&gt;
          Начало_Внуренней_Функции&lt;br /&gt;
                  Процедуры_Внутреней_Функции              &lt;br /&gt;
          Конец_Внутренней_Функции&lt;br /&gt;
Конец_Работы&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
..и пользуйтесь редакторами с подсветкой синтаксиса, такими как Notepad++ например. В результате каждый уровень вложенности находится на своём отступе. Вы всегда увидите, какую конструкцию нужно закрыть, а какую — нет. Я же например, делаю ещё проще — я, открывая новую функцию, сразу ставлю сразу в её конце, парой строк ниже, end, и потом уже пишу наполняющий её текст с отступами как в примере выше.&lt;br /&gt;
&lt;br /&gt;
И ещё — касательно жизни переменных: не забывайте, что определённые внутри проверок переменные живут только до выхода из проверки. То есть, например:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if check == 1 then&lt;br /&gt;
	local test = true&lt;br /&gt;
else&lt;br /&gt;
	local test = false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В данном случае переменная test создастся, но просуществует только до выхода из проверки, и дальше обращение к ней выдаст nil. Для того чтобы этого избежать, определение переменных надо выносить «за скобки» вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;local test = false; — инитим переменную и задаём сразу ей значение по умолчанию&lt;br /&gt;
&lt;br /&gt;
if check == 1 then&lt;br /&gt;
	test = true&lt;br /&gt;
else&lt;br /&gt;
	test = false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Смертельные циклы по таблицам =&lt;br /&gt;
&lt;br /&gt;
Если вы строите цикл по таблице следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;for i=1, table.getn(table_name) do&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''То никогда, ни при каких обстоятельствах не используйте внутри этого цикла удаление/добавление строк, т.е:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;table_name[i] = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
или&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;table.remove(table_name, index)&lt;br /&gt;
table.insert(table_name, index)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
применительно к таблице по которой гоняете цикл!!! При использовании это приводит к тому, что количество строк в таблице уменьшается, а цикл пытается получить из таблицы строки сверх существующего количества, что в итоге приводит к выходу цикла за отведённый ему диапазон памяти. Результатом будет веер самых разнообразных последствий, самое безобидное из которых это безлоговый вылет на рабочий стол, а серьёзное — сбой в работе а-лайфа с последующим боем сейвов! Тяжесть последствий зависит от того, какие действия вы выполняете внутри цикла кроме удаления строки.&lt;br /&gt;
&lt;br /&gt;
Чтобы избежать подобных ситуаций, стройте циклы по таблице лучше следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;for k, v in pairs(table_name)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
А удаление строк внутри них делайте вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;table_name[k] = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Циклы же for i=1, table.getn(table_name) do следует использовать '''только для операций, не изменяющих структуру изменяемой таблицы!'''&lt;br /&gt;
&lt;br /&gt;
= Как безопасно использовать коллбэки и таймерные события =&lt;br /&gt;
&lt;br /&gt;
В скриптовом движке есть удобный способ реакции на события - использование коллбэков - процедурных вызовов, привязанных к определённым событиям в жизни игрового объекта - получению повреждений, спавну, смерти и т.д. и т.п. Это hit_callback, death_callback из xr_motivator и многие другие... Все моддеры очень широко и совершенно спокойно пользуются ими, совершенно забывая при этом о таком важном факте, что это - обработки реального времени, как и таймерные события. Что это значит? А собственно вот что... Возьмём для примера коллбэк смерти неписей, мою головную боль последнего времени:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;xr_motivator.script -&lt;br /&gt;
function motivator_binder:death_callback(victim, who)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это функция обрабатывает смерть неписей. Когда вызывается этот коллбек он начинает по очереди вызывать внутренние функции, расположенные в других модулях. Они разрегистрируют умершего непися в гулагах, спавнят в него лут, обновляют статистику, отключают его от доп. схем логики и т.д.&lt;br /&gt;
&lt;br /&gt;
Так вот, обычно, когда в функциях возникает конфликт параметров, бесконечный цикл, попытка индексации nil и т.д., функция вылетает со стандартным логом. '''Но только не в случае, когда она находится внутри коллбэка. Если функция внутри него, то в случае возникновении в ней любой нештатной ситуации, коллбэк наглухо виснет.''' Это происходит из-за того, что функции вызываются строго друг за другом, и каждая из них вызывается только тогда, когда её предшественница вернула управление коллбэку. В случае с death_callback опознать такого непися очень просто - в его трупе окажется фонарик, КПК и возможно ещё немного разных &amp;quot;мусорных&amp;quot; вещей, что говорит о том, что обработка его смерти повисла не дойдя даже до спавна лута. В подобной ситуации можно быть на 100% уверенным, что труп этот не был корректно разрегистрирован, и игра всё ещё считает его живым неписем. Кроме того, зависший коллбэк не освобождает стек (а он у Луа-подсистемы единый на все скрипты), что '''в итоге приводит к вылетам игры с переполнением памяти (вот она, реальная причина этих &amp;quot;родных&amp;quot; вылетов).''' Но было бы слишком хорошо, если бы всё ограничивалось этим... однако тут всё намного хуже... такие &amp;quot;зависшие&amp;quot; коллбэки, особенно если их произошло несколько подряд, очень серьёзно влияют на работу а-лайфа. В лучшем случае они, забивая, стек, мешают нормально работать схемам логики, в худшем вызывают зависания самого  а-лайфа (этот эффект, кстати, производят и сами трупы таких неписей, так как они, как мы помним, не разрегистрировались корректно). '''Основной итог таких событий - бой сейвов, сделанных после возникновения таких ситуаций.''' Если повис один коллбэк, то такие сейвы ещё через раз загружаются, если же несколько, и остановилась работа а-лайфа - всё, сейвы бьются наглухо и реанимации не подлежат.&lt;br /&gt;
&lt;br /&gt;
Поэтому, чтобы избежать такого развития событий, каждый раз, когда вы вносите в коллбэк новую функцию - проверьте её самым тщательным образом. Она не должна содержать никаких рекурсивных циклов, в ней обязательно должны быть проверки на валидность обрабатываемых объектов и значений, и обязательно должна быть обработка нештатных ситуаций - т.е. функция должна обязательно, в абсолютно любой ситуации вернуть управление коллбэку, так или иначе. В самом наихудшем случае - делайте как делали разработчики игры - вставляйте принудительный вылет на рабстол функцией abort - она позволяет передавать отладочное сообщение, и это всяко лучше чем незаметный бой сейвов. Если обработка оборвалась в самом начале, а ф-ция обязательно должна вернуть значение - заведите ей &amp;quot;безопасное&amp;quot; возвращаемое значение по-умолчанию, которое она будет выдавать, если всё пошло плохо. И никогда не пренебрегайте пошаговой отладкой коллбэков с выводом в лог, особенно когда пишете схемы логики - это критически важно для стабильности вашего мода.&lt;br /&gt;
&lt;br /&gt;
== Основные внутриигровые признаки зависания коллбэков типа hit_callback, death_callback ==&lt;br /&gt;
&lt;br /&gt;
1. В трупах попадаются фонарики, КПК, разный мусор и общий лут слишком богат.&lt;br /&gt;
&lt;br /&gt;
2. Частые вылеты во время интенсивных боёв с логами типа&lt;br /&gt;
    Sheduler tried to update object...&lt;br /&gt;
    smart_terrain:1145(1146)&lt;br /&gt;
    LUA: out of memory&lt;br /&gt;
    любой_модуль_логики:любая_cтрока - stack overflow&lt;br /&gt;
&lt;br /&gt;
3. Частые &amp;quot;родные&amp;quot; вылеты в момент смерти непися или попадания по нему&lt;br /&gt;
&lt;br /&gt;
4. Произвольно бьются сейвы во время сражений, выброса и других насыщенных действиями событий&lt;br /&gt;
&lt;br /&gt;
= Использование защищённого кода в LUA =&lt;br /&gt;
&lt;br /&gt;
Периодически случаются такие ситуации, когда мы можем получить вылет при проверке аргумента, и не можем его адекватно заизолировать с помощью предварительной проверки на валидность значения. Вот простой пример: когда я отлаживал '''death_callback''' неписей, я периодически сталкивался с тем, что обращение к методу '''smart_terrain_id()''' при смерти непися иногда вызывало вылет &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;smart_terrain:1143 &amp;quot;attempt to index a nil value&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
- хотя это свойство является родным методом объекта, и отсутствовать напрочь никак не может. В итоге я пришёл к выводу, что его просто иногда не успевает отработать движок, так как вылет этот проявлялся в основном в интенсивных боях и совершенно произвольно.&lt;br /&gt;
&lt;br /&gt;
Вот код, в котором происходил вылет:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj     = sim:object( obj_id )&lt;br /&gt;
		local strn_id = obj:smart_terrain_id()  --- вылет происходит тут&lt;br /&gt;
&lt;br /&gt;
		if strn_id ~= 65535 then&lt;br /&gt;
			sim:object( strn_id ).gulag:clear_dead(obj_id)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это кусок родного кода из версии 1,0005 игры. Я долго пытался разными способами отсечь этот вылет, вводя предварительные проверки, однако это совершенно ничего не давало - вылет всё равно периодически случался, так как проверки эти сами его вызывали. Тогда я зарылся в документацию по Lua и обнаружил замечательную родную базовую функцию, введённую ещё с первых версий Lua, которой почему-то не пользовались ни разработчики игры, ни моддеры (хотя сама она в Lua сталкера присутствует, и работает отлично, без каких-либо нареканий). Вот она:&lt;br /&gt;
&lt;br /&gt;
'''pcall (f, arg1, ···)'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Вызывает функцию f с указанными через запятую аргументами в защищённом режиме. Это означает, что любая ошибка, даже критическая, внутри вызыванной функции, не передаётся наружу - вызавшей подсистеме. Вместо этого '''pcall''' перехватывает ошибку и возвращает код статуса. Первая возвращаемая переменная это сам код, (true или false) и если всё прошло хорошо, он равен true. В этом случае '''pcall''' сразу после статуса возвращает все результаты от работы защищённой им функции. Если же в защищённой функции произошла ошибка, то '''pcall''' вернёт false и затем сообщение об ошибке. (Обратите внимание, обработка ошибки присходит БЕЗ вылета! Вместо вылета вы получите вполне адекватную строку с ошибкой, которую можно вывести в лог для последующей обработки)&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Я настоятельно советую пользоваться этой функцией в случаях, когда из коллбэков вызываются сложные комплексные обработки, вроде обработки из менеджера вооружений AI-пака. Это позволяет предотвратить как вылеты, так и зависания обработок, и в итоге позволяет хорошо стабилизировать игру.&lt;br /&gt;
&lt;br /&gt;
Возвращаясь к нашим смарттеррейнам... вот как в итоге я подавил вылет типа smart_terrain:1143 с помощью pcall:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;--- эта функция пытается проверить св-во smart_terrain_id объекта. Именно её мы вызовем в защищённом режиме.&lt;br /&gt;
function prot_smt_td(obj)&lt;br /&gt;
	if IsStalker(obj) or IsMonster(obj) then&lt;br /&gt;
		return obj:smart_terrain_id()&lt;br /&gt;
	else	&lt;br /&gt;
		return 65535&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj = sim:object( obj_id )&lt;br /&gt;
		if obj then&lt;br /&gt;
			local strn_id = 65535  --- предварительно проинитим переменную, на &lt;br /&gt;
						--- случай если у нас prot_smt_td выдаст ошибку&lt;br /&gt;
			local result, smt_id = pcall(prot_smt_td,obj)	--- вызываем prot_smt_td в защищённом режиме &lt;br /&gt;
									--- и сразу присваиваем его вывод переменным&lt;br /&gt;
			if result then --- если pcall выдало true&lt;br /&gt;
				strn_id = smt_id  --- тогда применям полученное значение&lt;br /&gt;
			end&lt;br /&gt;
			--- если же обработка выдаст ошибку, то strn_id останется неизменным...&lt;br /&gt;
			if strn_id ~= 65535 then&lt;br /&gt;
				sim:object(strn_id).gulag:clear_dead(obj_id)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В других местах это делается совершенно аналогично. Подробнее об этой и многих других функция для контроля кода, незаслуженно ингорируемых большинством моддеров, можно почитать тут: http://lua-users.org/wiki/FinalizedExceptions - на английском правда, но захотите - разберётесь, там всё просто. &lt;br /&gt;
&lt;br /&gt;
Настоятельно советую вам изучить работу кода в таких условиях и научиться его правильно применять - этим вы сильно облегчите жизнь как себе, так и тем, кто после вас будет рыться в вашем коде или импортировать его в свои разработки.&lt;br /&gt;
&lt;br /&gt;
--[[Участник:Kamikazze|KamikaZze (OGSE Team)]] 08:16, 1 сентября 2009 (UTC)&lt;br /&gt;
&lt;br /&gt;
= Авторы =&lt;br /&gt;
Статья создана: [[Участник:Kamikazze|Kamikazze]]&lt;/div&gt;</summary>
		<author><name>Kamikazze</name></author>	</entry>

	<entry>
		<id>http://stalkerin.gameru.net/wiki/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Kamikazze</id>
		<title>Участник:Kamikazze</title>
		<link rel="alternate" type="text/html" href="http://stalkerin.gameru.net/wiki/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Kamikazze"/>
				<updated>2009-09-01T08:22:27Z</updated>
		
		<summary type="html">&lt;p&gt;Kamikazze: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Программист, скриптер, отладчик. &lt;br /&gt;
Работаю над модом OGS Evolution, ознакомится с которым можно на его родном сайте: http://ogse.ru&lt;br /&gt;
---------&lt;br /&gt;
Связаться со мной можно через личку на том же сайте или по номеру ICQ: 339-623-395&lt;/div&gt;</summary>
		<author><name>Kamikazze</name></author>	</entry>

	<entry>
		<id>http://stalkerin.gameru.net/wiki/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Kamikazze</id>
		<title>Участник:Kamikazze</title>
		<link rel="alternate" type="text/html" href="http://stalkerin.gameru.net/wiki/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Kamikazze"/>
				<updated>2009-09-01T08:21:53Z</updated>
		
		<summary type="html">&lt;p&gt;Kamikazze: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Программист, скриптер, отладчик. &lt;br /&gt;
Работаю над модом OGS Evolution, ознакомится с которым можно на его родном сайте: http://ogse.ru&lt;br /&gt;
---------&lt;br /&gt;
Связаться со мной можно через личку на том же сайте или по номеру ICQ: 339-623-395&lt;br /&gt;
--[[Участник:Kamikazze|KamikaZze (OGSE Team)]] 08:21, 1 сентября 2009 (UTC)&lt;/div&gt;</summary>
		<author><name>Kamikazze</name></author>	</entry>

	<entry>
		<id>http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2</id>
		<title>Как писать скрипты, не приводящие к вылетам и бою сейвов</title>
		<link rel="alternate" type="text/html" href="http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2"/>
				<updated>2009-09-01T08:16:40Z</updated>
		
		<summary type="html">&lt;p&gt;Kamikazze: дополнение&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Категория:Статьи участников]]&lt;br /&gt;
= Предисловие =&lt;br /&gt;
&lt;br /&gt;
'''Отладив несколько скриптов на предмет вылетов решил написать это небольшое руководство, чтобы помочь начинающим (да и опытным тоже) скриптописателям избежать потенциальных проблем.'''&lt;br /&gt;
&lt;br /&gt;
Сразу оговорюсь — я программист, поэтому к отладке скриптов подхожу со своей профессиональной точки зрения, и то что я тут изложу вам — сие для программиста есть непреложная истина, и если вы хотите уменьшить количество глюков — старайтесь придерживаться нижеприведённого стиля программирования.&lt;br /&gt;
&lt;br /&gt;
Для начала маленький экскурс в историю. Итак, скрипты в сталкере написаны на языке '''Lua''' — разработали его бразильцы, он очень гибок, легко встраивается в игры и поэтому очень часто используется для написания скриптов любой степени сложности. Так вот, у этого языка и его реализации в игре есть несколько исключительно важных особенностей, которые, при их несоблюдении, будут приводить к вылетам вашего скрипта. Итак:&lt;br /&gt;
&lt;br /&gt;
= Особенность первая. Типы данных и проблемы с nil. =&lt;br /&gt;
&lt;br /&gt;
Сначала сделаю маленькое отступление для не знакомых с основами скриптеров, чтобы понятнее было. Итак, в Lua используются следующие логические операторы:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
=	оператор присваивания&lt;br /&gt;
==	сравнение, равно ли значение&lt;br /&gt;
≈	сравнение, НЕ равно ли значение&lt;br /&gt;
&amp;lt;	сравнение, меньше ли значение&lt;br /&gt;
&amp;gt;	сравнение, больше ли значение&lt;br /&gt;
&amp;lt;=	сравнение, меньше ли значение или равно&lt;br /&gt;
&amp;gt;=	сравнение, больше ли значение или равно&lt;br /&gt;
and	логический оператор И&lt;br /&gt;
not	логический оператор НЕ&lt;br /&gt;
or	логический оператор ИЛИ&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Всё. Теперь поехали дальше… Язык Lua Изначально создавался для работы с большими строчными базами данных, поэтому ВСЕ виды конструкций в языке — это типы данных, то есть по сути либо переменные, либо константы. Это касается так же и функций! '''Поэтому даже с функциями в Lua можно и нужно обращатся как с переменными.''' Далее. Касательно, собственно «привычных» переменных. '''Обычные переменные в Lua получают свой тип данных только в момент присвоения им значения (запомните, это важно).''' При этом, в Lua есть такой важный и полезный тип данных как nil. nil — Это «пустота», то есть отсутствие какого-либо значения. При этом этот тип используется компилятором скриптов для «сбора мусора», то есть для освобождения занимаемой памяти. Смотрите пример, я объясню подробнее, что это значит:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;local reminder_count&lt;br /&gt;
local exposure_count = 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тут у нас 2 локальные переменные, одна из которых просто создана, а вторая — создана и инициализарована. Если сейчас обратиться к переменной reminder_count, считав её значение, то мы получим в качестве значения — nil, то есть пустоту. Если же мы обратимся к переменной exposure_count — то получим, как и ожидали, 0 — так как мы это значение проинициализировали ЗАРАНЕЕ. '''При этом не надо путать nil и 0 — так как 0 — это всё-таки какая-то информация, а вот nil — это её полное отсутствие.''' Так вот, собственно, чем это чревато. Я уже сказал, что nil используется для сбора мусора. Вручную это работает так — когда вам переменная уже не нужна, вы просто пишете:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;test_variable = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И ваша переменная test_variable сотрётся из памяти, и любые ссылки на неё будут выдавать на выходе nil (типа нет такой переменной, «абонент вне зоны действия сети»). Ну так собственно вот, зачем я это всё рассказываю… '''если не определить значение переменной сразу, как это было сделано с переменной reminder_count, то любые попытки обратиться к ней внутри скрипта приведут к вылетам.''' Происходит это следующим образом: допустим, reminder_count — это у нас счётчик напоминаний. То есть мы о чём-то напоминаем игроку текстовыми сообщениями, и помечаем, сколько раз мы это сделали. Определили мы переменную именно так, как в примере выше, то есть не задав ей никакого значения. Значение же её должно присваиваться ниже по коду, после первого оповещения юзера. При этом у нас есть в коде обращение к этой переменной, по которому решается что делать дальше. Выглядит это примерно так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function check_antirad_supplies()&lt;br /&gt;
&lt;br /&gt;
	if auto_injection_active then&lt;br /&gt;
&lt;br /&gt;
		if antirad_check_delay == nil or game.get_game_time():diffSec(antirad_check_delay) &amp;gt; 60 then&lt;br /&gt;
&lt;br /&gt;
			if not db.actor:object(«antirad») and not (use_scientific_kit and db.actor:object(«medkit_scientic»)) then&lt;br /&gt;
&lt;br /&gt;
			--обратите внимание сюда&lt;br /&gt;
				if reminder_count == 0 or reminder_count &amp;gt;= mins_till_next_remind then&lt;br /&gt;
			--обратите внимание сюда&lt;br /&gt;
					if use_text then&lt;br /&gt;
						local news_text = «%c[255,160,160,160]Автоматическая система ввода медицинских&lt;br /&gt;
 препаратов\\n&amp;quot;..&amp;quot;%c[default]Напоминаю: %c[255,230,0,0]&lt;br /&gt;
Противорадиационные препараты отстутствуют! %c[default]&lt;br /&gt;
Автоматический ввод препаратов невозможен.»&lt;br /&gt;
  						db.actor:give_game_news(news_text, &amp;quot;ui\\ui_iconsTotal&amp;quot;, &lt;br /&gt;
Frect():set(0,188,83,47), 0, 3000)&lt;br /&gt;
  					end	&lt;br /&gt;
	  				if use_sounds then&lt;br /&gt;
				 		local snd_obj&lt;br /&gt;
						if use_custom_sounds then&lt;br /&gt;
							snd_obj = xr_sound.get_safe_sound_object([[HEV\no-anti-rad]])&lt;br /&gt;
						else&lt;br /&gt;
	  						snd_obj = xr_sound.get_safe_sound_object([[device\pda\pda_tip]])&lt;br /&gt;
						end&lt;br /&gt;
						if snd_obj then&lt;br /&gt;
							snd_obj:play_no_feedback(db.actor, sound_object.s2d, 0, vector(), 1.0)&lt;br /&gt;
						end&lt;br /&gt;
					end&lt;br /&gt;
					reminder_count = 1&lt;br /&gt;
				elseif reminder then&lt;br /&gt;
					reminder_count = reminder_count + 1&lt;br /&gt;
				end&lt;br /&gt;
&lt;br /&gt;
			else&lt;br /&gt;
				radiation_warning = true&lt;br /&gt;
				reminder_count = 0&lt;br /&gt;
			end&lt;br /&gt;
			antirad_check_delay = game.get_game_time()&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Во время игры всё это скорее всего отработает хорошо, но вот при загрузке… тут могут быть проблемы, так как скрипт при запуске может пролететь ту часть, где переменной reminder_count присваивается значение! В итоге при запуске вышеприведённой функции check_antirad_supplies() нас получится что reminder_count не существует — он равен nil, и в итоге проверка&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count == 0 or reminder_count &amp;gt;= mins_till_next_remind then&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
выдаст ошибку и приведёт к вылету! Почему? '''Да потому что НЕЛЬЗЯ сравнивать НИЧТО с вещественным значением.''' Именно поэтому, когда вы создаёте новую переменную, вы ОБЯЗАНЫ задать ей значение по умолчанию, даже если вы полностью уверены, что она нигде не будет использована до инициализации. Поверьте — в программировании бывает всё, и запросто может так случиться, что ваша переменная будет использована и спровоцирует вылет.&lt;br /&gt;
&lt;br /&gt;
Простой способ обойти такую кучу проверок и лишних инициализаций — использование or-оператора. пример&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if (reminder_count or 0) &amp;gt;= mins_till_next_remind then&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
если переменная равна nil, то выражение вычисляется дальше и получается равным 0, а это уже число и сравнение происходит безболезненно.&lt;br /&gt;
&lt;br /&gt;
Кроме вышеприведённой ситуации, возможен ещё один неприятный момент, связанный с определением переменных и значением nil. Суть его заключается в том, что при присваивании значений или операциями с переменными мы можем получить кучу проблем… покажу конкретнее каких:&lt;br /&gt;
&lt;br /&gt;
== «Смерть» переменных ==&lt;br /&gt;
&lt;br /&gt;
Положим, у нас с reminder_count происходят ещё какие-нибудь занимательные вещи, например мы его значение зачем-нибудь присваиваем другой переменной вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;antirad_check_delay = reminder_count&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ну и вот, если у нас reminder_count в момент присвоения ещё не имеет никакого значения, то мы получим занимательную штуку — у нас это выражение сработает как&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;antirad_check_delay = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате чего переменная antirad_check_delay «сдохнет» и будет деловито убрана «сборщиком мусора» из памяти, что моментально приведёт к вылету, когда в дальнейшем какая-то часть кода обратится к значению antirad_check_delay.&lt;br /&gt;
&lt;br /&gt;
== Ошибки деления на 0 ==&lt;br /&gt;
&lt;br /&gt;
Теперь положим что у нас reminder_count используется в каком-нибудь расчёте вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;antirad_check_delay = exposure_count/reminder_count&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Как мы помним, у нас reminder_count не инициализирована, и как следствие равна nil. В итоге мы получим попытку поделить exposure_count на ноль (в нашем случае на nil, что суть одно и то же в нашем случае) и получим «классический» программистский вылет из-за ошибки деления на ноль.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Ошибки присвоения ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Предположим что мы написали скрипт, который помогает выбирать оружие при наличии вблизи врагов. И вот, у нас в скрипте есть такой кусок кода, где NPC вынимает из рюкзака оружие:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function weapon_manager:set_weapon(wpn)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	local enemy = self.npc:best_enemy()&lt;br /&gt;
	if wpn then&lt;br /&gt;
		--обратите внимание сюда&lt;br /&gt;
		self.weapon_id = wpn:id()&lt;br /&gt;
		--обратите внимание сюда&lt;br /&gt;
		self:return_items(self.weapon_id)&lt;br /&gt;
	else&lt;br /&gt;
		printw(«set_wpn:weapon not exist»)&lt;br /&gt;
	end&lt;br /&gt;
	if self.modes.process_mode == &amp;quot;3&amp;quot; and enemy then&lt;br /&gt;
		for k, v in pairs(self.weapons) do&lt;br /&gt;
			for i, w in ipairs(v) do&lt;br /&gt;
				if w.id ≈ self.weapon_id then&lt;br /&gt;
					local item = level.object_by_id(w.id)&lt;br /&gt;
					if item and item:parent() and item:parent():id() == self.npc_id then&lt;br /&gt;
						printw(«set_weapon[%s]:process %s[%s]», self.npc:character_name(), w.id, w.sec)&lt;br /&gt;
						self:process_item(item)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if enemy then&lt;br /&gt;
		self.npc:set_item(object.idle, wpn)&lt;br /&gt;
	end&lt;br /&gt;
	self.weapons = nil&lt;br /&gt;
&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В помеченном выражении self.weapon_id = wpn:id(), и всё вроде бы отлично, так как стоит проверка if wpn then, однако иногда движок умудряется передать объект функциям некорректно, либо сам скриптер может перепутать при вызове игровой объект с объектом а-лайфа, и присвовение self.weapon_id = wpn:id() либо приравняет self.weapon_id к nil либо сразу приведёт к вылету из-за того что соответствующее свойство объекта не было обнаружено в его классе.&lt;br /&gt;
&lt;br /&gt;
== Как с этим бороться ==&lt;br /&gt;
&lt;br /&gt;
Бороться с такими ситуациями очень просто на самом деле.&lt;br /&gt;
Во-первых: ВСЕГДА ИНИЦИАЛИЗИРУЙТЕ ПЕРЕМЕННЫЕ. То есть вот такое описание переменной:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
НЕДОПУСТИМО! ВЫ ОБЯЗАТЕЛЬНО ДОЛЖНЫ ЗАДАТЬ ПЕРЕМЕННОЙ ЗНАЧЕНИЕ!&lt;br /&gt;
Если переменная числовая, сделайте это так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count = 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если строчная — то сделайте это так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count = &amp;quot;&amp;quot;&amp;lt;/code&amp;gt; (получится вместо nil пустая строка)&lt;br /&gt;
&lt;br /&gt;
Если логическая, то так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count = false&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В общем, делайте так, как вам удобнее, но ДЕЛАЙТЕ ОБЯЗАТЕЛЬНО!&lt;br /&gt;
&lt;br /&gt;
И во-вторых: ВСЕГДА ПРОВЕРЯЙТЕ ПЕРЕМЕННУЮ ПЕРЕД ИСПОЛЬЗОВАНИЕМ!&lt;br /&gt;
&lt;br /&gt;
То есть в кусках кода, чреватых вылетами (к ним относятся АБСОЛЮТНО ВСЕ части, где идёт работа с вещами или оружием) обязательно вставляйте проверки переменных на nil следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if wpn and wpn:id() then&lt;br /&gt;
	self.weapon_id = wpn:id()&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тогда сначала выполнится проверка, и если обе переменные имеют значение, то операция отработает, а если один из параметров пуст — то функция просто пропустит этот код. Тут не помешает ещё и проверку вставить на существование self.weapon_id, хотя это наверное уже моя параноя :)&lt;br /&gt;
&lt;br /&gt;
Кстати, что самое дурацкое — ошибки, приведённые выше, часто в лог попадают совершенно непохожим на ошибки скриптов образом. Например, ошибка присвоения из примера 3 приводила к вылету с сообщением «Assertion failed», и поймать её я смог только изучив внимательно предыдущие строки лога, где зафиксировались операции с вещами…&lt;br /&gt;
&lt;br /&gt;
Точно так же, если вы используете ЛЮБЫЕ математические операции например деление (но не только деление, всего остального тоже касается), тоже ОБЯЗАТЕЛЬНО поверяйте переменную. Вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count and reminder_count ≈ 0 then&lt;br /&gt;
	antirad_check_delay = exposure_count/reminder_count&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Таким образом потенциальные вылеты будут аккуратно изолированы, и больше не будут создавать проблем.&lt;br /&gt;
И ещё одно небольшое дополнение к этой же теме. Итак, вы вставили обходные проверки вот таким образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if wpn and wpn:id() then&lt;br /&gt;
   self.weapon_id = wpn:id() &lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Или таким:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count and reminder_count ≈ 0 then&lt;br /&gt;
   antirad_check_delay = exposure_count/reminder_count &lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И у вас в случае некорректного значения компилятор спокойно пропустил этот код, ничего не сделав ни с self.weapon_id (из первого примера), ни с antirad_check_delay (из второго). Однако вам всё-таки нужно, чтобы с ними что-то происходило, даже если проверяемые переменные неверны. Тогда я бы советовал вам дополнить этот обход отработкой нештатной ситуации. Делается это банально просто:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if wpn and wpn:id()then&lt;br /&gt;
   self.weapon_id = wpn:id() &lt;br /&gt;
elseif wpn == nil then&lt;br /&gt;
   --вставьте сюда код, что делать если wpn не существует&lt;br /&gt;
elseif wpn:id() == nil then&lt;br /&gt;
   --а сюда, если wpn:id() не существует&lt;br /&gt;
   --тут скорее всего перепутан объект, и можно попробовать&lt;br /&gt;
   --обратиться к wpn.id&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И для второго случая аналогично:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count and reminder_count ≈ 0 then&lt;br /&gt;
   antirad_check_delay = exposure_count/reminder_count &lt;br /&gt;
else&lt;br /&gt;
   --а тут что мы сделаем если reminder_count не существует&lt;br /&gt;
   --я бы сделал например вот так:&lt;br /&gt;
   antirad_check_delay = exposure_count/1&lt;br /&gt;
   --и все дела&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Более конкретная реализация зависит от того, что именно вы пытаетесь сделать — тут уже вам виднее…&lt;br /&gt;
&lt;br /&gt;
= Особенность вторая. Типичные ошибки при именовании функций, присвоении и инициализации переменных. =&lt;br /&gt;
&lt;br /&gt;
Типичная ошибка, приводящая к вылету:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function remove_item(remove_item)&lt;br /&gt;
     if remove_item~=nil then&lt;br /&gt;
          alife():release(alife():object(remove_item:id()), true)&lt;br /&gt;
          return true&lt;br /&gt;
     end&lt;br /&gt;
     return false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В этом коде агрумент функции совпадает с её именем, и в итоге функция пытается передать функции alife():release вместо указателя на вещь — указатель на саму себя. А так как у функции remove_item нет никакого id, код выпадает с ошибкой:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Arguments     : LUA error: ...\s.t.a.l.k.e.r\gamedata\scripts\test.script:486: attempt to call method 'id' (a nil value)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Следует помнить, что Lua — это язык прямого (JIT) компилирования, и он, как любой такой язык не проверяет ошибки при компиляции. Поэтому нужно очень аккуратно раздавать имена переменным и функциям, чтобы избежать подобных ошибок. Данную функцию нужно переписать следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function remove_item(item_to_remove)&lt;br /&gt;
     if item_to_remove then&lt;br /&gt;
          alife():release(alife():object(item_to_remove:id()), true)&lt;br /&gt;
          return true&lt;br /&gt;
     end&lt;br /&gt;
     return false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Однажды, отлаживая скрипт, натолкнулся на вот такой код:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function hit_callback(npc_id)&lt;br /&gt;
	if db.storage[npc_id].wounded ≈ nil then&lt;br /&gt;
		db.storage[npc_id].wounded.wound_manager:hit_callback()&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Казалось бы, всё верно, и значение проверяется перед использованием. Ан нет, скрипт вызывал вылет, причём именно на проверке. Методом тыка было выясено, что nil вызвавший вылет, был не в результате обработки, а был передан фукнции. Это был параметр npc_id.&lt;br /&gt;
&lt;br /&gt;
Пришлось сделать вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function hit_callback(npc_id)&lt;br /&gt;
	if npc_id and db.storage[npc_id].wounded ≈ nil then&lt;br /&gt;
		db.storage[npc_id].wounded.wound_manager:hit_callback()&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Введя предварительно проверку аргумента npc_id. Вывод — если ставите проверку, старайтесь проверять не только саму функцию, но и вначале — аргумент, который ей передаётся. Мало ли в каком виде он до вас доедет… :)&lt;br /&gt;
&lt;br /&gt;
Касательно именования переменных — не забывайте давать им атрибут local если вы не собираетесь их пользовать во внешних скриптах. Иначе, если попадётся где-то аналогичное имя — будет сбой логики или вылет, помните об этом.&lt;br /&gt;
&lt;br /&gt;
И ещё один совет — для самых начинающих:&lt;br /&gt;
&lt;br /&gt;
Когда пишете скрипты, не забывайте о соблюдении синтаксиса! То есть внимательно и вдумчиво ставьте в концах функций и оснований ключевые слова end — помните, что как отсутствие нужных end’ов, так и наличие лишних приводят к тому, что границы фукций сбиваются, скрипт не парсится обработчиком и вызывает при попытке своего вызова вылет, примерно вот такой:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Arguments : LUA error: ...g\s.t.a.l.k.e.r\gamedata\scripts\bind_stalker.script:75: attempt to index global 'test_main_new' (a nil value)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы вам проще было следить за синтаксисом и не допускать лишних end’ов, выстраивайте текст скриптов каскадными лесенками:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code &amp;gt;Начало_Работы&lt;br /&gt;
          Начало_Внуренней_Функции&lt;br /&gt;
                  Процедуры_Внутреней_Функции              &lt;br /&gt;
          Конец_Внутренней_Функции&lt;br /&gt;
Конец_Работы&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
..и пользуйтесь редакторами с подсветкой синтаксиса, такими как Notepad++ например. В результате каждый уровень вложенности находится на своём отступе. Вы всегда увидите, какую конструкцию нужно закрыть, а какую — нет. Я же например, делаю ещё проще — я, открывая новую функцию, сразу ставлю сразу в её конце, парой строк ниже, end, и потом уже пишу наполняющий её текст с отступами как в примере выше.&lt;br /&gt;
&lt;br /&gt;
И ещё — касательно жизни переменных: не забывайте, что определённые внутри проверок переменные живут только до выхода из проверки. То есть, например:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if check == 1 then&lt;br /&gt;
	local test = true&lt;br /&gt;
else&lt;br /&gt;
	local test = false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В данном случае переменная test создастся, но просуществует только до выхода из проверки, и дальше обращение к ней выдаст nil. Для того чтобы этого избежать, определение переменных надо выносить «за скобки» вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;local test = false; — инитим переменную и задаём сразу ей значение по умолчанию&lt;br /&gt;
&lt;br /&gt;
if check == 1 then&lt;br /&gt;
	test = true&lt;br /&gt;
else&lt;br /&gt;
	test = false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Особенность третья. Смертельные циклы по таблицам. =&lt;br /&gt;
&lt;br /&gt;
Если вы строите цикл по таблице следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;for i=1, table.getn(table_name) do&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''То никогда, ни при каких обстоятельствах не используйте внутри этого цикла удаление/добавление строк, т.е:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;table_name[i] = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
или&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;table.remove(table_name, index)&lt;br /&gt;
table.insert(table_name, index)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
применительно к таблице по которой гоняете цикл!!! При использовании это приводит к тому, что количество строк в таблице уменьшается, а цикл пытается получить из таблицы строки сверх существующего количества, что в итоге приводит к выходу цикла за отведённый ему диапазон памяти. Результатом будет веер самых разнообразных последствий, самое безобидное из которых это безлоговый вылет на рабочий стол, а серьёзное — сбой в работе а-лайфа с последующим боем сейвов! Тяжесть последствий зависит от того, какие действия вы выполняете внутри цикла кроме удаления строки.&lt;br /&gt;
&lt;br /&gt;
Чтобы избежать подобных ситуаций, стройте циклы по таблице лучше следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;for k, v in pairs(table_name)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
А удаление строк внутри них делайте вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;table_name[k] = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Циклы же for i=1, table.getn(table_name) do следует использовать '''только для операций, не изменяющих структуру изменяемой таблицы!'''&lt;br /&gt;
&lt;br /&gt;
= Особенность четвёртая. Как безопасно использовать коллбэки и таймерные события. =&lt;br /&gt;
&lt;br /&gt;
В скриптовом движке есть удобный способ реакции на события - использование коллбэков - процедурных вызовов, привязанных к определённым событиям в жизни игрового объекта - получению повреждений, спавну, смерти и т.д. и т.п. Это hit_callback, death_callback из xr_motivator и многие другие... Все моддеры очень широко и совершенно спокойно пользуются ими, совершенно забывая при этом о таком важном факте, что это - обработки реального времени, как и таймерные события. Что это значит? А собственно вот что... Возьмём для примера коллбэк смерти неписей, мою головную боль последнего времени:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;xr_motivator.script -&lt;br /&gt;
function motivator_binder:death_callback(victim, who)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это функция обрабатывает смерть неписей. Когда вызывается этот коллбек он начинает по очереди вызывать внутренние функции, расположенные в других модулях. Они разрегистрируют умершего непися в гулагах, спавнят в него лут, обновляют статистику, отключают его от доп. схем логики и т.д.&lt;br /&gt;
&lt;br /&gt;
Так вот, обычно, когда в функциях возникает конфликт параметров, бесконечный цикл, попытка индексации nil и т.д., функция вылетает со стандартным логом. '''Но только не в случае, когда она находится внутри коллбэка. Если функция внутри него, то в случае возникновении в ней любой нештатной ситуации, коллбэк наглухо виснет.''' Это происходит из-за того, что функции вызываются строго друг за другом, и каждая из них вызывается только тогда, когда её предшественница вернула управление коллбэку. В случае с death_callback опознать такого непися очень просто - в его трупе окажется фонарик, КПК и возможно ещё немного разных &amp;quot;мусорных&amp;quot; вещей, что говорит о том, что обработка его смерти повисла не дойдя даже до спавна лута. В подобной ситуации можно быть на 100% уверенным, что труп этот не был корректно разрегистрирован, и игра всё ещё считает его живым неписем. Кроме того, зависший коллбэк не освобождает стек (а он у Луа-подсистемы единый на все скрипты), что '''в итоге приводит к вылетам игры с переполнением памяти (вот она, реальная причина этих &amp;quot;родных&amp;quot; вылетов).''' Но было бы слишком хорошо, если бы всё ограничивалось этим... однако тут всё намного хуже... такие &amp;quot;зависшие&amp;quot; коллбэки, особенно если их произошло несколько подряд, очень серьёзно влияют на работу а-лайфа. В лучшем случае они, забивая, стек, мешают нормально работать схемам логики, в худшем вызывают зависания самого  а-лайфа (этот эффект, кстати, производят и сами трупы таких неписей, так как они, как мы помним, не разрегистрировались корректно). '''Основной итог таких событий - бой сейвов, сделанных после возникновения таких ситуаций.''' Если повис один коллбэк, то такие сейвы ещё через раз загружаются, если же несколько, и остановилась работа а-лайфа - всё, сейвы бьются наглухо и реанимации не подлежат.&lt;br /&gt;
&lt;br /&gt;
Поэтому, чтобы избежать такого развития событий, каждый раз, когда вы вносите в коллбэк новую функцию - проверьте её самым тщательным образом. Она не должна содержать никаких рекурсивных циклов, в ней обязательно должны быть проверки на валидность обрабатываемых объектов и значений, и обязательно должна быть обработка нештатных ситуаций - т.е. функция должна обязательно, в абсолютно любой ситуации вернуть управление коллбэку, так или иначе. В самом наихудшем случае - делайте как делали разработчики игры - вставляйте принудительный вылет на рабстол функцией abort - она позволяет передавать отладочное сообщение, и это всяко лучше чем незаметный бой сейвов. Если обработка оборвалась в самом начале, а ф-ция обязательно должна вернуть значение - заведите ей &amp;quot;безопасное&amp;quot; возвращаемое значение по-умолчанию, которое она будет выдавать, если всё пошло плохо. И никогда не пренебрегайте пошаговой отладкой коллбэков с выводом в лог, особенно когда пишете схемы логики - это критически важно для стабильности вашего мода.&lt;br /&gt;
&lt;br /&gt;
== Основные внутриигровые признаки зависания коллбэков типа hit_callback, death_callback ==&lt;br /&gt;
&lt;br /&gt;
1. В трупах попадаются фонарики, КПК, разный мусор и общий лут слишком богат.&lt;br /&gt;
&lt;br /&gt;
2. Частые вылеты во время интенсивных боёв с логами типа&lt;br /&gt;
    Sheduler tried to update object...&lt;br /&gt;
    smart_terrain:1145(1146)&lt;br /&gt;
    LUA: out of memory&lt;br /&gt;
    любой_модуль_логики:любая_cтрока - stack overflow&lt;br /&gt;
&lt;br /&gt;
3. Частые &amp;quot;родные&amp;quot; вылеты в момент смерти непися или попадания по нему&lt;br /&gt;
&lt;br /&gt;
4. Произвольно бьются сейвы во время сражений, выброса и других насыщенных действиями событий&lt;br /&gt;
&lt;br /&gt;
= Использование защищённого кода в LUA =&lt;br /&gt;
&lt;br /&gt;
Периодически случаются такие ситуации, когда мы можем получить вылет при проверке аргумента, и не можем его адекватно заизолировать с помощью предварительной проверки на валидность значения. Вот простой пример: когда я отлаживал '''death_callback''' неписей, я периодически сталкивался с тем, что обращение к методу '''smart_terrain_id()''' при смерти непися иногда вызывало вылет &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;smart_terrain:1143 &amp;quot;attempt to index a nil value&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
- хотя это свойство является родным методом объекта, и отсутствовать напрочь никак не может. В итоге я пришёл к выводу, что его просто иногда не успевает отработать движок, так как вылет этот проявлялся в основном в интенсивных боях и совершенно произвольно.&lt;br /&gt;
&lt;br /&gt;
Вот код, в котором происходил вылет:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj     = sim:object( obj_id )&lt;br /&gt;
		local strn_id = obj:smart_terrain_id()  --- вылет происходит тут&lt;br /&gt;
&lt;br /&gt;
		if strn_id ~= 65535 then&lt;br /&gt;
			sim:object( strn_id ).gulag:clear_dead(obj_id)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это кусок родного кода из версии 1,0005 игры. Я долго пытался разными способами отсечь этот вылет, вводя предварительные проверки, однако это совершенно ничего не давало - вылет всё равно периодически случался, так как проверки эти сами его вызывали. Тогда я зарылся в документацию по Lua и обнаружил замечательную родную базовую функцию, введённую ещё с первых версий Lua, которой почему-то не пользовались ни разработчики игры, ни моддеры (хотя сама она в Lua сталкера присутствует, и работает отлично, без каких-либо нареканий). Вот она:&lt;br /&gt;
&lt;br /&gt;
'''pcall (f, arg1, ···)'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Вызывает функцию f с указанными через запятую аргументами в защищённом режиме. Это означает, что любая ошибка, даже критическая, внутри вызыванной функции, не передаётся наружу - вызавшей подсистеме. Вместо этого '''pcall''' перехватывает ошибку и возвращает код статуса. Первая возвращаемая переменная это сам код, (true или false) и если всё прошло хорошо, он равен true. В этом случае '''pcall''' сразу после статуса возвращает все результаты от работы защищённой им функции. Если же в защищённой функции произошла ошибка, то '''pcall''' вернёт false и затем сообщение об ошибке. (Обратите внимание, обработка ошибки присходит БЕЗ вылета! Вместо вылета вы получите вполне адекватную строку с ошибкой, которую можно вывести в лог для последующей обработки)&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Я настоятельно советую пользоваться этой функцией в случаях, когда из коллбэков вызываются сложные комплексные обработки, вроде обработки из менеджера вооружений AI-пака. Это позволяет предотвратить как вылеты, так и зависания обработок, и в итоге позволяет хорошо стабилизировать игру.&lt;br /&gt;
&lt;br /&gt;
Возвращаясь к нашим смарттеррейнам... вот как в итоге я подавил вылет типа smart_terrain:1143 с помощью pcall:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;--- эта функция пытается проверить св-во smart_terrain_id объекта. Именно её мы вызовем в защищённом режиме.&lt;br /&gt;
function prot_smt_td(obj)&lt;br /&gt;
	if IsStalker(obj) or IsMonster(obj) then&lt;br /&gt;
		return obj:smart_terrain_id()&lt;br /&gt;
	else	&lt;br /&gt;
		return 65535&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function on_death( obj_id )&lt;br /&gt;
--	printf( &amp;quot;on_death obj_id=%d&amp;quot;, obj_id )&lt;br /&gt;
	local sim = alife()&lt;br /&gt;
	if sim then&lt;br /&gt;
		local obj = sim:object( obj_id )&lt;br /&gt;
		if obj then&lt;br /&gt;
			local strn_id = 65535  --- предварительно проинитим переменную, на &lt;br /&gt;
						--- случай если у нас prot_smt_td выдаст ошибку&lt;br /&gt;
			local result, smt_id = pcall(prot_smt_td,obj)	--- вызываем prot_smt_td в защищённом режиме &lt;br /&gt;
									--- и сразу присваиваем его вывод переменным&lt;br /&gt;
			if result then --- если pcall выдало true&lt;br /&gt;
				strn_id = smt_id  --- тогда применям полученное значение&lt;br /&gt;
			end&lt;br /&gt;
			--- если же обработка выдаст ошибку, то strn_id останется неизменным...&lt;br /&gt;
			if strn_id ~= 65535 then&lt;br /&gt;
				sim:object(strn_id).gulag:clear_dead(obj_id)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В других местах это делается совершенно аналогично. Подробнее об этой и многих других функция для контроля кода, незаслуженно ингорируемых большинством моддеров, можно почитать тут: http://lua-users.org/wiki/FinalizedExceptions - на английском правда, но захотите - разберётесь, там всё просто. &lt;br /&gt;
&lt;br /&gt;
Настоятельно советую вам изучить работу кода в таких условиях и научиться его правильно применять - этим вы сильно облегчите жизнь как себе, так и тем, кто после вас будет рыться в вашем коде или импортировать его в свои разработки.&lt;br /&gt;
&lt;br /&gt;
--[[Участник:Kamikazze|KamikaZze (OGSE Team)]] 08:16, 1 сентября 2009 (UTC)&lt;br /&gt;
&lt;br /&gt;
= Авторы =&lt;br /&gt;
Статья создана: [[Участник:Kamikazze|Kamikazze]]&lt;/div&gt;</summary>
		<author><name>Kamikazze</name></author>	</entry>

	<entry>
		<id>http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2</id>
		<title>Как писать скрипты, не приводящие к вылетам и бою сейвов</title>
		<link rel="alternate" type="text/html" href="http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2"/>
				<updated>2009-08-25T10:05:33Z</updated>
		
		<summary type="html">&lt;p&gt;Kamikazze: /* Ошибки присвоения */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Категория:Статьи участников]]&lt;br /&gt;
= Предисловие =&lt;br /&gt;
&lt;br /&gt;
'''Отладив несколько скриптов на предмет вылетов решил написать это небольшое руководство, чтобы помочь начинающим (да и опытным тоже) скриптописателям избежать потенциальных проблем.'''&lt;br /&gt;
&lt;br /&gt;
Сразу оговорюсь — я программист, поэтому к отладке скриптов подхожу со своей профессиональной точки зрения, и то что я тут изложу вам — сие для программиста есть непреложная истина, и если вы хотите уменьшить количество глюков — старайтесь придерживаться нижеприведённого стиля программирования.&lt;br /&gt;
&lt;br /&gt;
Для начала маленький экскурс в историю. Итак, скрипты в сталкере написаны на языке '''Lua''' — разработали его бразильцы, он очень гибок, легко встраивается в игры и поэтому очень часто используется для написания скриптов любой степени сложности. Так вот, у этого языка и его реализации в игре есть несколько исключительно важных особенностей, которые, при их несоблюдении, будут приводить к вылетам вашего скрипта. Итак:&lt;br /&gt;
&lt;br /&gt;
= Особенность первая. Типы данных и проблемы с nil. =&lt;br /&gt;
&lt;br /&gt;
Сначала сделаю маленькое отступление для не знакомых с основами скриптеров, чтобы понятнее было. Итак, в Lua используются следующие логические операторы:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
=	оператор присваивания&lt;br /&gt;
==	сравнение, равно ли значение&lt;br /&gt;
≈	сравнение, НЕ равно ли значение&lt;br /&gt;
&amp;lt;	сравнение, меньше ли значение&lt;br /&gt;
&amp;gt;	сравнение, больше ли значение&lt;br /&gt;
&amp;lt;=	сравнение, меньше ли значение или равно&lt;br /&gt;
&amp;gt;=	сравнение, больше ли значение или равно&lt;br /&gt;
and	логический оператор И&lt;br /&gt;
not	логический оператор НЕ&lt;br /&gt;
or	логический оператор ИЛИ&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Всё. Теперь поехали дальше… Язык Lua Изначально создавался для работы с большими строчными базами данных, поэтому ВСЕ виды конструкций в языке — это типы данных, то есть по сути либо переменные, либо константы. Это касается так же и функций! '''Поэтому даже с функциями в Lua можно и нужно обращатся как с переменными.''' Далее. Касательно, собственно «привычных» переменных. '''Обычные переменные в Lua получают свой тип данных только в момент присвоения им значения (запомните, это важно).''' При этом, в Lua есть такой важный и полезный тип данных как nil. nil — Это «пустота», то есть отсутствие какого-либо значения. При этом этот тип используется компилятором скриптов для «сбора мусора», то есть для освобождения занимаемой памяти. Смотрите пример, я объясню подробнее, что это значит:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;local reminder_count&lt;br /&gt;
local exposure_count = 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тут у нас 2 локальные переменные, одна из которых просто создана, а вторая — создана и инициализарована. Если сейчас обратиться к переменной reminder_count, считав её значение, то мы получим в качестве значения — nil, то есть пустоту. Если же мы обратимся к переменной exposure_count — то получим, как и ожидали, 0 — так как мы это значение проинициализировали ЗАРАНЕЕ. '''При этом не надо путать nil и 0 — так как 0 — это всё-таки какая-то информация, а вот nil — это её полное отсутствие.''' Так вот, собственно, чем это чревато. Я уже сказал, что nil используется для сбора мусора. Вручную это работает так — когда вам переменная уже не нужна, вы просто пишете:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;test_variable = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И ваша переменная test_variable сотрётся из памяти, и любые ссылки на неё будут выдавать на выходе nil (типа нет такой переменной, «абонент вне зоны действия сети»). Ну так собственно вот, зачем я это всё рассказываю… '''если не определить значение переменной сразу, как это было сделано с переменной reminder_count, то любые попытки обратиться к ней внутри скрипта приведут к вылетам.''' Происходит это следующим образом: допустим, reminder_count — это у нас счётчик напоминаний. То есть мы о чём-то напоминаем игроку текстовыми сообщениями, и помечаем, сколько раз мы это сделали. Определили мы переменную именно так, как в примере выше, то есть не задав ей никакого значения. Значение же её должно присваиваться ниже по коду, после первого оповещения юзера. При этом у нас есть в коде обращение к этой переменной, по которому решается что делать дальше. Выглядит это примерно так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function check_antirad_supplies()&lt;br /&gt;
&lt;br /&gt;
	if auto_injection_active then&lt;br /&gt;
&lt;br /&gt;
		if antirad_check_delay == nil or game.get_game_time():diffSec(antirad_check_delay) &amp;gt; 60 then&lt;br /&gt;
&lt;br /&gt;
			if not db.actor:object(«antirad») and not (use_scientific_kit and db.actor:object(«medkit_scientic»)) then&lt;br /&gt;
&lt;br /&gt;
			--обратите внимание сюда&lt;br /&gt;
				if reminder_count == 0 or reminder_count &amp;gt;= mins_till_next_remind then&lt;br /&gt;
			--обратите внимание сюда&lt;br /&gt;
					if use_text then&lt;br /&gt;
						local news_text = «%c[255,160,160,160]Автоматическая система ввода медицинских&lt;br /&gt;
 препаратов\\n&amp;quot;..&amp;quot;%c[default]Напоминаю: %c[255,230,0,0]&lt;br /&gt;
Противорадиационные препараты отстутствуют! %c[default]&lt;br /&gt;
Автоматический ввод препаратов невозможен.»&lt;br /&gt;
  						db.actor:give_game_news(news_text, &amp;quot;ui\\ui_iconsTotal&amp;quot;, &lt;br /&gt;
Frect():set(0,188,83,47), 0, 3000)&lt;br /&gt;
  					end	&lt;br /&gt;
	  				if use_sounds then&lt;br /&gt;
				 		local snd_obj&lt;br /&gt;
						if use_custom_sounds then&lt;br /&gt;
							snd_obj = xr_sound.get_safe_sound_object([[HEV\no-anti-rad]])&lt;br /&gt;
						else&lt;br /&gt;
	  						snd_obj = xr_sound.get_safe_sound_object([[device\pda\pda_tip]])&lt;br /&gt;
						end&lt;br /&gt;
						if snd_obj then&lt;br /&gt;
							snd_obj:play_no_feedback(db.actor, sound_object.s2d, 0, vector(), 1.0)&lt;br /&gt;
						end&lt;br /&gt;
					end&lt;br /&gt;
					reminder_count = 1&lt;br /&gt;
				elseif reminder then&lt;br /&gt;
					reminder_count = reminder_count + 1&lt;br /&gt;
				end&lt;br /&gt;
&lt;br /&gt;
			else&lt;br /&gt;
				radiation_warning = true&lt;br /&gt;
				reminder_count = 0&lt;br /&gt;
			end&lt;br /&gt;
			antirad_check_delay = game.get_game_time()&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Во время игры всё это скорее всего отработает хорошо, но вот при загрузке… тут могут быть проблемы, так как скрипт при запуске может пролететь ту часть, где переменной reminder_count присваивается значение! В итоге при запуске вышеприведённой функции check_antirad_supplies() нас получится что reminder_count не существует — он равен nil, и в итоге проверка&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count == 0 or reminder_count &amp;gt;= mins_till_next_remind then&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
выдаст ошибку и приведёт к вылету! Почему? '''Да потому что НЕЛЬЗЯ сравнивать НИЧТО с вещественным значением.''' Именно поэтому, когда вы создаёте новую переменную, вы ОБЯЗАНЫ задать ей значение по умолчанию, даже если вы полностью уверены, что она нигде не будет использована до инициализации. Поверьте — в программировании бывает всё, и запросто может так случиться, что ваша переменная будет использована и спровоцирует вылет.&lt;br /&gt;
&lt;br /&gt;
Простой способ обойти такую кучу проверок и лишних инициализаций — использование or-оператора. пример&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if (reminder_count or 0) &amp;gt;= mins_till_next_remind then&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
если переменная равна nil, то выражение вычисляется дальше и получается равным 0, а это уже число и сравнение происходит безболезненно.&lt;br /&gt;
&lt;br /&gt;
Кроме вышеприведённой ситуации, возможен ещё один неприятный момент, связанный с определением переменных и значением nil. Суть его заключается в том, что при присваивании значений или операциями с переменными мы можем получить кучу проблем… покажу конкретнее каких:&lt;br /&gt;
&lt;br /&gt;
== «Смерть» переменных ==&lt;br /&gt;
&lt;br /&gt;
Положим, у нас с reminder_count происходят ещё какие-нибудь занимательные вещи, например мы его значение зачем-нибудь присваиваем другой переменной вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;antirad_check_delay = reminder_count&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ну и вот, если у нас reminder_count в момент присвоения ещё не имеет никакого значения, то мы получим занимательную штуку — у нас это выражение сработает как&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;antirad_check_delay = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате чего переменная antirad_check_delay «сдохнет» и будет деловито убрана «сборщиком мусора» из памяти, что моментально приведёт к вылету, когда в дальнейшем какая-то часть кода обратится к значению antirad_check_delay.&lt;br /&gt;
&lt;br /&gt;
== Ошибки деления на 0 ==&lt;br /&gt;
&lt;br /&gt;
Теперь положим что у нас reminder_count используется в каком-нибудь расчёте вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;antirad_check_delay = exposure_count/reminder_count&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Как мы помним, у нас reminder_count не инициализирована, и как следствие равна nil. В итоге мы получим попытку поделить exposure_count на ноль (в нашем случае на nil, что суть одно и то же в нашем случае) и получим «классический» программистский вылет из-за ошибки деления на ноль.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Ошибки присвоения ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Предположим что мы написали скрипт, который помогает выбирать оружие при наличии вблизи врагов. И вот, у нас в скрипте есть такой кусок кода, где NPC вынимает из рюкзака оружие:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function weapon_manager:set_weapon(wpn)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	local enemy = self.npc:best_enemy()&lt;br /&gt;
	if wpn then&lt;br /&gt;
		--обратите внимание сюда&lt;br /&gt;
		self.weapon_id = wpn:id()&lt;br /&gt;
		--обратите внимание сюда&lt;br /&gt;
		self:return_items(self.weapon_id)&lt;br /&gt;
	else&lt;br /&gt;
		printw(«set_wpn:weapon not exist»)&lt;br /&gt;
	end&lt;br /&gt;
	if self.modes.process_mode == &amp;quot;3&amp;quot; and enemy then&lt;br /&gt;
		for k, v in pairs(self.weapons) do&lt;br /&gt;
			for i, w in ipairs(v) do&lt;br /&gt;
				if w.id ≈ self.weapon_id then&lt;br /&gt;
					local item = level.object_by_id(w.id)&lt;br /&gt;
					if item and item:parent() and item:parent():id() == self.npc_id then&lt;br /&gt;
						printw(«set_weapon[%s]:process %s[%s]», self.npc:character_name(), w.id, w.sec)&lt;br /&gt;
						self:process_item(item)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if enemy then&lt;br /&gt;
		self.npc:set_item(object.idle, wpn)&lt;br /&gt;
	end&lt;br /&gt;
	self.weapons = nil&lt;br /&gt;
&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В помеченном выражении self.weapon_id = wpn:id(), и всё вроде бы отлично, так как стоит проверка if wpn then, однако иногда движок умудряется передать объект функциям некорректно, либо сам скриптер может перепутать при вызове игровой объект с объектом а-лайфа, и присвовение self.weapon_id = wpn:id() либо приравняет self.weapon_id к nil либо сразу приведёт к вылету из-за того что соответствующее свойство объекта не было обнаружено в его классе.&lt;br /&gt;
&lt;br /&gt;
== Как с этим бороться ==&lt;br /&gt;
&lt;br /&gt;
Бороться с такими ситуациями очень просто на самом деле.&lt;br /&gt;
Во-первых: ВСЕГДА ИНИЦИАЛИЗИРУЙТЕ ПЕРЕМЕННЫЕ. То есть вот такое описание переменной:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
НЕДОПУСТИМО! ВЫ ОБЯЗАТЕЛЬНО ДОЛЖНЫ ЗАДАТЬ ПЕРЕМЕННОЙ ЗНАЧЕНИЕ!&lt;br /&gt;
Если переменная числовая, сделайте это так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count = 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если строчная — то сделайте это так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count = &amp;quot;&amp;quot;&amp;lt;/code&amp;gt; (получится вместо nil пустая строка)&lt;br /&gt;
&lt;br /&gt;
Если логическая, то так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count = false&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В общем, делайте так, как вам удобнее, но ДЕЛАЙТЕ ОБЯЗАТЕЛЬНО!&lt;br /&gt;
&lt;br /&gt;
И во-вторых: ВСЕГДА ПРОВЕРЯЙТЕ ПЕРЕМЕННУЮ ПЕРЕД ИСПОЛЬЗОВАНИЕМ!&lt;br /&gt;
&lt;br /&gt;
То есть в кусках кода, чреватых вылетами (к ним относятся АБСОЛЮТНО ВСЕ части, где идёт работа с вещами или оружием) обязательно вставляйте проверки переменных на nil следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if wpn and wpn:id() then&lt;br /&gt;
	self.weapon_id = wpn:id()&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тогда сначала выполнится проверка, и если обе переменные имеют значение, то операция отработает, а если один из параметров пуст — то функция просто пропустит этот код. Тут не помешает ещё и проверку вставить на существование self.weapon_id, хотя это наверное уже моя параноя :)&lt;br /&gt;
&lt;br /&gt;
Кстати, что самое дурацкое — ошибки, приведённые выше, часто в лог попадают совершенно непохожим на ошибки скриптов образом. Например, ошибка присвоения из примера 3 приводила к вылету с сообщением «Assertion failed», и поймать её я смог только изучив внимательно предыдущие строки лога, где зафиксировались операции с вещами…&lt;br /&gt;
&lt;br /&gt;
Точно так же, если вы используете ЛЮБЫЕ математические операции например деление (но не только деление, всего остального тоже касается), тоже ОБЯЗАТЕЛЬНО поверяйте переменную. Вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count and reminder_count ≈ 0 then&lt;br /&gt;
	antirad_check_delay = exposure_count/reminder_count&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Таким образом потенциальные вылеты будут аккуратно изолированы, и больше не будут создавать проблем.&lt;br /&gt;
И ещё одно небольшое дополнение к этой же теме. Итак, вы вставили обходные проверки вот таким образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if wpn and wpn:id() then&lt;br /&gt;
   self.weapon_id = wpn:id() &lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Или таким:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count and reminder_count ≈ 0 then&lt;br /&gt;
   antirad_check_delay = exposure_count/reminder_count &lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И у вас в случае некорректного значения компилятор спокойно пропустил этот код, ничего не сделав ни с self.weapon_id (из первого примера), ни с antirad_check_delay (из второго). Однако вам всё-таки нужно, чтобы с ними что-то происходило, даже если проверяемые переменные неверны. Тогда я бы советовал вам дополнить этот обход отработкой нештатной ситуации. Делается это банально просто:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if wpn and wpn:id()then&lt;br /&gt;
   self.weapon_id = wpn:id() &lt;br /&gt;
elseif wpn == nil then&lt;br /&gt;
   --вставьте сюда код, что делать если wpn не существует&lt;br /&gt;
elseif wpn:id() == nil then&lt;br /&gt;
   --а сюда, если wpn:id() не существует&lt;br /&gt;
   --тут скорее всего перепутан объект, и можно попробовать&lt;br /&gt;
   --обратиться к wpn.id&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И для второго случая аналогично:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count and reminder_count ≈ 0 then&lt;br /&gt;
   antirad_check_delay = exposure_count/reminder_count &lt;br /&gt;
else&lt;br /&gt;
   --а тут что мы сделаем если reminder_count не существует&lt;br /&gt;
   --я бы сделал например вот так:&lt;br /&gt;
   antirad_check_delay = exposure_count/1&lt;br /&gt;
   --и все дела&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Более конкретная реализация зависит от того, что именно вы пытаетесь сделать — тут уже вам виднее…&lt;br /&gt;
&lt;br /&gt;
= Особенность вторая. Типичные ошибки при именовании функций, присвоении и инициализации переменных. =&lt;br /&gt;
&lt;br /&gt;
Типичная ошибка, приводящая к вылету:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function remove_item(remove_item)&lt;br /&gt;
     if remove_item~=nil then&lt;br /&gt;
          alife():release(alife():object(remove_item:id()), true)&lt;br /&gt;
          return true&lt;br /&gt;
     end&lt;br /&gt;
     return false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В этом коде агрумент функции совпадает с её именем, и в итоге функция пытается передать функции alife():release вместо указателя на вещь — указатель на саму себя. А так как у функции remove_item нет никакого id, код выпадает с ошибкой:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Arguments     : LUA error: ...\s.t.a.l.k.e.r\gamedata\scripts\test.script:486: attempt to call method 'id' (a nil value)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Следует помнить, что Lua — это язык прямого (JIT) компилирования, и он, как любой такой язык не проверяет ошибки при компиляции. Поэтому нужно очень аккуратно раздавать имена переменным и функциям, чтобы избежать подобных ошибок. Данную функцию нужно переписать следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function remove_item(item_to_remove)&lt;br /&gt;
     if item_to_remove then&lt;br /&gt;
          alife():release(alife():object(item_to_remove:id()), true)&lt;br /&gt;
          return true&lt;br /&gt;
     end&lt;br /&gt;
     return false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Однажды, отлаживая скрипт, натолкнулся на вот такой код:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function hit_callback(npc_id)&lt;br /&gt;
	if db.storage[npc_id].wounded ≈ nil then&lt;br /&gt;
		db.storage[npc_id].wounded.wound_manager:hit_callback()&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Казалось бы, всё верно, и значение проверяется перед использованием. Ан нет, скрипт вызывал вылет, причём именно на проверке. Методом тыка было выясено, что nil вызвавший вылет, был не в результате обработки, а был передан фукнции. Это был параметр npc_id.&lt;br /&gt;
&lt;br /&gt;
Пришлось сделать вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function hit_callback(npc_id)&lt;br /&gt;
	if npc_id and db.storage[npc_id].wounded ≈ nil then&lt;br /&gt;
		db.storage[npc_id].wounded.wound_manager:hit_callback()&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Введя предварительно проверку аргумента npc_id. Вывод — если ставите проверку, старайтесь проверять не только саму функцию, но и вначале — аргумент, который ей передаётся. Мало ли в каком виде он до вас доедет… :)&lt;br /&gt;
&lt;br /&gt;
Касательно именования переменных — не забывайте давать им атрибут local если вы не собираетесь их пользовать во внешних скриптах. Иначе, если попадётся где-то аналогичное имя — будет сбой логики или вылет, помните об этом.&lt;br /&gt;
&lt;br /&gt;
И ещё один совет — для самых начинающих:&lt;br /&gt;
&lt;br /&gt;
Когда пишете скрипты, не забывайте о соблюдении синтаксиса! То есть внимательно и вдумчиво ставьте в концах функций и оснований ключевые слова end — помните, что как отсутствие нужных end’ов, так и наличие лишних приводят к тому, что границы фукций сбиваются, скрипт не парсится обработчиком и вызывает при попытке своего вызова вылет, примерно вот такой:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Arguments : LUA error: ...g\s.t.a.l.k.e.r\gamedata\scripts\bind_stalker.script:75: attempt to index global 'test_main_new' (a nil value)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы вам проще было следить за синтаксисом и не допускать лишних end’ов, выстраивайте текст скриптов каскадными лесенками:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code &amp;gt;Начало_Работы&lt;br /&gt;
          Начало_Внуренней_Функции&lt;br /&gt;
                  Процедуры_Внутреней_Функции              &lt;br /&gt;
          Конец_Внутренней_Функции&lt;br /&gt;
Конец_Работы&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
..и пользуйтесь редакторами с подсветкой синтаксиса, такими как Notepad++ например. В результате каждый уровень вложенности находится на своём отступе. Вы всегда увидите, какую конструкцию нужно закрыть, а какую — нет. Я же например, делаю ещё проще — я, открывая новую функцию, сразу ставлю сразу в её конце, парой строк ниже, end, и потом уже пишу наполняющий её текст с отступами как в примере выше.&lt;br /&gt;
&lt;br /&gt;
И ещё — касательно жизни переменных: не забывайте, что определённые внутри проверок переменные живут только до выхода из проверки. То есть, например:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if check == 1 then&lt;br /&gt;
	local test = true&lt;br /&gt;
else&lt;br /&gt;
	local test = false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В данном случае переменная test создастся, но просуществует только до выхода из проверки, и дальше обращение к ней выдаст nil. Для того чтобы этого избежать, определение переменных надо выносить «за скобки» вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;local test = false; — инитим переменную и задаём сразу ей значение по умолчанию&lt;br /&gt;
&lt;br /&gt;
if check == 1 then&lt;br /&gt;
	test = true&lt;br /&gt;
else&lt;br /&gt;
	test = false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Особенность третья. Смертельные циклы по таблицам. =&lt;br /&gt;
&lt;br /&gt;
Если вы строите цикл по таблице следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;for i=1, table.getn(table_name) do&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''То никогда, ни при каких обстоятельствах не используйте внутри этого цикла удаление/добавление строк, т.е:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;table_name[i] = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
или&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;table.remove(table_name, index)&lt;br /&gt;
table.insert(table_name, index)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
применительно к таблице по которой гоняете цикл!!! При использовании это приводит к тому, что количество строк в таблице уменьшается, а цикл пытается получить из таблицы строки сверх существующего количества, что в итоге приводит к выходу цикла за отведённый ему диапазон памяти. Результатом будет веер самых разнообразных последствий, самое безобидное из которых это безлоговый вылет на рабочий стол, а серьёзное — сбой в работе а-лайфа с последующим боем сейвов! Тяжесть последствий зависит от того, какие действия вы выполняете внутри цикла кроме удаления строки.&lt;br /&gt;
&lt;br /&gt;
Чтобы избежать подобных ситуаций, стройте циклы по таблице лучше следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;for k, v in pairs(table_name)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
А удаление строк внутри них делайте вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;table_name[k] = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Циклы же for i=1, table.getn(table_name) do следует использовать '''только для операций, не изменяющих структуру изменяемой таблицы!'''&lt;br /&gt;
&lt;br /&gt;
= Особенность четвёртая. Как безопасно использовать коллбэки и таймерные события. =&lt;br /&gt;
&lt;br /&gt;
В скриптовом движке есть удобный способ реакции на события - использование коллбэков - процедурных вызовов, привязанных к определённым событиям в жизни игрового объекта - получению повреждений, спавну, смерти и т.д. и т.п. Это hit_callback, death_callback из xr_motivator и многие другие... Все моддеры очень широко и совершенно спокойно пользуются ими, совершенно забывая при этом о таком важном факте, что это - обработки реального времени, как и таймерные события. Что это значит? А собственно вот что... Возьмём для примера коллбэк смерти неписей, мою головную боль последнего времени:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;xr_motivator.script -&lt;br /&gt;
function motivator_binder:death_callback(victim, who)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это функция обрабатывает смерть неписей. Когда вызывается этот коллбек он начинает по очереди вызывать внутренние функции, расположенные в других модулях. Они разрегистрируют умершего непися в гулагах, спавнят в него лут, обновляют статистику, отключают его от доп. схем логики и т.д.&lt;br /&gt;
&lt;br /&gt;
Так вот, обычно, когда в функциях возникает конфликт параметров, бесконечный цикл, попытка индексации nil и т.д., функция вылетает со стандартным логом. '''Но только не в случае, когда она находится внутри коллбэка. Если функция внутри него, то в случае возникновении в ней любой нештатной ситуации, коллбэк наглухо виснет.''' Это происходит из-за того, что функции вызываются строго друг за другом, и каждая из них вызывается только тогда, когда её предшественница вернула управление коллбэку. В случае с death_callback опознать такого непися очень просто - в его трупе окажется фонарик, КПК и возможно ещё немного разных &amp;quot;мусорных&amp;quot; вещей, что говорит о том, что обработка его смерти повисла не дойдя даже до спавна лута. В подобной ситуации можно быть на 100% уверенным, что труп этот не был корректно разрегистрирован, и игра всё ещё считает его живым неписем. Кроме того, зависший коллбэк не освобождает стек (а он у Луа-подсистемы единый на все скрипты), что '''в итоге приводит к вылетам игры с переполнением памяти (вот она, реальная причина этих &amp;quot;родных&amp;quot; вылетов).''' Но было бы слишком хорошо, если бы всё ограничивалось этим... однако тут всё намного хуже... такие &amp;quot;зависшие&amp;quot; коллбэки, особенно если их произошло несколько подряд, очень серьёзно влияют на работу а-лайфа. В лучшем случае они, забивая, стек, мешают нормально работать схемам логики, в худшем вызывают зависания самого  а-лайфа (этот эффект, кстати, производят и сами трупы таких неписей, так как они, как мы помним, не разрегистрировались корректно). '''Основной итог таких событий - бой сейвов, сделанных после возникновения таких ситуаций.''' Если повис один коллбэк, то такие сейвы ещё через раз загружаются, если же несколько, и остановилась работа а-лайфа - всё, сейвы бьются наглухо и реанимации не подлежат.&lt;br /&gt;
&lt;br /&gt;
Поэтому, чтобы избежать такого развития событий, каждый раз, когда вы вносите в коллбэк новую функцию - проверьте её самым тщательным образом. Она не должна содержать никаких рекурсивных циклов, в ней обязательно должны быть проверки на валидность обрабатываемых объектов и значений, и обязательно должна быть обработка нештатных ситуаций - т.е. функция должна обязательно, в абсолютно любой ситуации вернуть управление коллбэку, так или иначе. В самом наихудшем случае - делайте как делали разработчики игры - вставляйте принудительный вылет на рабстол функцией abort - она позволяет передавать отладочное сообщение, и это всяко лучше чем незаметный бой сейвов. Если обработка оборвалась в самом начале, а ф-ция обязательно должна вернуть значение - заведите ей &amp;quot;безопасное&amp;quot; возвращаемое значение по-умолчанию, которое она будет выдавать, если всё пошло плохо. И никогда не пренебрегайте пошаговой отладкой коллбэков с выводом в лог, особенно когда пишете схемы логики - это критически важно для стабильности вашего мода.&lt;br /&gt;
&lt;br /&gt;
== Основные внутриигровые признаки зависания коллбэков типа hit_callback, death_callback ==&lt;br /&gt;
&lt;br /&gt;
1. В трупах попадаются фонарики, КПК, разный мусор и общий лут слишком богат.&lt;br /&gt;
&lt;br /&gt;
2. Частые вылеты во время интенсивных боёв с логами типа&lt;br /&gt;
    Sheduler tried to update object...&lt;br /&gt;
    smart_terrain:1145(1146)&lt;br /&gt;
    LUA: out of memory&lt;br /&gt;
    любой_модуль_логики:любая_cтрока - stack overflow&lt;br /&gt;
&lt;br /&gt;
3. Частые &amp;quot;родные&amp;quot; вылеты в момент смерти непися или попадания по нему&lt;br /&gt;
&lt;br /&gt;
4. Произвольно бьются сейвы во время сражений, выброса и других насыщенных действиями событий&lt;br /&gt;
&lt;br /&gt;
= Авторы =&lt;br /&gt;
Статья создана: [[Участник:Kamikazze|Kamikazze]]&lt;/div&gt;</summary>
		<author><name>Kamikazze</name></author>	</entry>

	<entry>
		<id>http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2</id>
		<title>Как писать скрипты, не приводящие к вылетам и бою сейвов</title>
		<link rel="alternate" type="text/html" href="http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2"/>
				<updated>2009-08-25T10:02:28Z</updated>
		
		<summary type="html">&lt;p&gt;Kamikazze: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Категория:Статьи участников]]&lt;br /&gt;
= Предисловие =&lt;br /&gt;
&lt;br /&gt;
'''Отладив несколько скриптов на предмет вылетов решил написать это небольшое руководство, чтобы помочь начинающим (да и опытным тоже) скриптописателям избежать потенциальных проблем.'''&lt;br /&gt;
&lt;br /&gt;
Сразу оговорюсь — я программист, поэтому к отладке скриптов подхожу со своей профессиональной точки зрения, и то что я тут изложу вам — сие для программиста есть непреложная истина, и если вы хотите уменьшить количество глюков — старайтесь придерживаться нижеприведённого стиля программирования.&lt;br /&gt;
&lt;br /&gt;
Для начала маленький экскурс в историю. Итак, скрипты в сталкере написаны на языке '''Lua''' — разработали его бразильцы, он очень гибок, легко встраивается в игры и поэтому очень часто используется для написания скриптов любой степени сложности. Так вот, у этого языка и его реализации в игре есть несколько исключительно важных особенностей, которые, при их несоблюдении, будут приводить к вылетам вашего скрипта. Итак:&lt;br /&gt;
&lt;br /&gt;
= Особенность первая. Типы данных и проблемы с nil. =&lt;br /&gt;
&lt;br /&gt;
Сначала сделаю маленькое отступление для не знакомых с основами скриптеров, чтобы понятнее было. Итак, в Lua используются следующие логические операторы:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
=	оператор присваивания&lt;br /&gt;
==	сравнение, равно ли значение&lt;br /&gt;
≈	сравнение, НЕ равно ли значение&lt;br /&gt;
&amp;lt;	сравнение, меньше ли значение&lt;br /&gt;
&amp;gt;	сравнение, больше ли значение&lt;br /&gt;
&amp;lt;=	сравнение, меньше ли значение или равно&lt;br /&gt;
&amp;gt;=	сравнение, больше ли значение или равно&lt;br /&gt;
and	логический оператор И&lt;br /&gt;
not	логический оператор НЕ&lt;br /&gt;
or	логический оператор ИЛИ&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Всё. Теперь поехали дальше… Язык Lua Изначально создавался для работы с большими строчными базами данных, поэтому ВСЕ виды конструкций в языке — это типы данных, то есть по сути либо переменные, либо константы. Это касается так же и функций! '''Поэтому даже с функциями в Lua можно и нужно обращатся как с переменными.''' Далее. Касательно, собственно «привычных» переменных. '''Обычные переменные в Lua получают свой тип данных только в момент присвоения им значения (запомните, это важно).''' При этом, в Lua есть такой важный и полезный тип данных как nil. nil — Это «пустота», то есть отсутствие какого-либо значения. При этом этот тип используется компилятором скриптов для «сбора мусора», то есть для освобождения занимаемой памяти. Смотрите пример, я объясню подробнее, что это значит:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;local reminder_count&lt;br /&gt;
local exposure_count = 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тут у нас 2 локальные переменные, одна из которых просто создана, а вторая — создана и инициализарована. Если сейчас обратиться к переменной reminder_count, считав её значение, то мы получим в качестве значения — nil, то есть пустоту. Если же мы обратимся к переменной exposure_count — то получим, как и ожидали, 0 — так как мы это значение проинициализировали ЗАРАНЕЕ. '''При этом не надо путать nil и 0 — так как 0 — это всё-таки какая-то информация, а вот nil — это её полное отсутствие.''' Так вот, собственно, чем это чревато. Я уже сказал, что nil используется для сбора мусора. Вручную это работает так — когда вам переменная уже не нужна, вы просто пишете:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;test_variable = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И ваша переменная test_variable сотрётся из памяти, и любые ссылки на неё будут выдавать на выходе nil (типа нет такой переменной, «абонент вне зоны действия сети»). Ну так собственно вот, зачем я это всё рассказываю… '''если не определить значение переменной сразу, как это было сделано с переменной reminder_count, то любые попытки обратиться к ней внутри скрипта приведут к вылетам.''' Происходит это следующим образом: допустим, reminder_count — это у нас счётчик напоминаний. То есть мы о чём-то напоминаем игроку текстовыми сообщениями, и помечаем, сколько раз мы это сделали. Определили мы переменную именно так, как в примере выше, то есть не задав ей никакого значения. Значение же её должно присваиваться ниже по коду, после первого оповещения юзера. При этом у нас есть в коде обращение к этой переменной, по которому решается что делать дальше. Выглядит это примерно так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function check_antirad_supplies()&lt;br /&gt;
&lt;br /&gt;
	if auto_injection_active then&lt;br /&gt;
&lt;br /&gt;
		if antirad_check_delay == nil or game.get_game_time():diffSec(antirad_check_delay) &amp;gt; 60 then&lt;br /&gt;
&lt;br /&gt;
			if not db.actor:object(«antirad») and not (use_scientific_kit and db.actor:object(«medkit_scientic»)) then&lt;br /&gt;
&lt;br /&gt;
			--обратите внимание сюда&lt;br /&gt;
				if reminder_count == 0 or reminder_count &amp;gt;= mins_till_next_remind then&lt;br /&gt;
			--обратите внимание сюда&lt;br /&gt;
					if use_text then&lt;br /&gt;
						local news_text = «%c[255,160,160,160]Автоматическая система ввода медицинских&lt;br /&gt;
 препаратов\\n&amp;quot;..&amp;quot;%c[default]Напоминаю: %c[255,230,0,0]&lt;br /&gt;
Противорадиационные препараты отстутствуют! %c[default]&lt;br /&gt;
Автоматический ввод препаратов невозможен.»&lt;br /&gt;
  						db.actor:give_game_news(news_text, &amp;quot;ui\\ui_iconsTotal&amp;quot;, &lt;br /&gt;
Frect():set(0,188,83,47), 0, 3000)&lt;br /&gt;
  					end	&lt;br /&gt;
	  				if use_sounds then&lt;br /&gt;
				 		local snd_obj&lt;br /&gt;
						if use_custom_sounds then&lt;br /&gt;
							snd_obj = xr_sound.get_safe_sound_object([[HEV\no-anti-rad]])&lt;br /&gt;
						else&lt;br /&gt;
	  						snd_obj = xr_sound.get_safe_sound_object([[device\pda\pda_tip]])&lt;br /&gt;
						end&lt;br /&gt;
						if snd_obj then&lt;br /&gt;
							snd_obj:play_no_feedback(db.actor, sound_object.s2d, 0, vector(), 1.0)&lt;br /&gt;
						end&lt;br /&gt;
					end&lt;br /&gt;
					reminder_count = 1&lt;br /&gt;
				elseif reminder then&lt;br /&gt;
					reminder_count = reminder_count + 1&lt;br /&gt;
				end&lt;br /&gt;
&lt;br /&gt;
			else&lt;br /&gt;
				radiation_warning = true&lt;br /&gt;
				reminder_count = 0&lt;br /&gt;
			end&lt;br /&gt;
			antirad_check_delay = game.get_game_time()&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Во время игры всё это скорее всего отработает хорошо, но вот при загрузке… тут могут быть проблемы, так как скрипт при запуске может пролететь ту часть, где переменной reminder_count присваивается значение! В итоге при запуске вышеприведённой функции check_antirad_supplies() нас получится что reminder_count не существует — он равен nil, и в итоге проверка&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count == 0 or reminder_count &amp;gt;= mins_till_next_remind then&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
выдаст ошибку и приведёт к вылету! Почему? '''Да потому что НЕЛЬЗЯ сравнивать НИЧТО с вещественным значением.''' Именно поэтому, когда вы создаёте новую переменную, вы ОБЯЗАНЫ задать ей значение по умолчанию, даже если вы полностью уверены, что она нигде не будет использована до инициализации. Поверьте — в программировании бывает всё, и запросто может так случиться, что ваша переменная будет использована и спровоцирует вылет.&lt;br /&gt;
&lt;br /&gt;
Простой способ обойти такую кучу проверок и лишних инициализаций — использование or-оператора. пример&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if (reminder_count or 0) &amp;gt;= mins_till_next_remind then&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
если переменная равна nil, то выражение вычисляется дальше и получается равным 0, а это уже число и сравнение происходит безболезненно.&lt;br /&gt;
&lt;br /&gt;
Кроме вышеприведённой ситуации, возможен ещё один неприятный момент, связанный с определением переменных и значением nil. Суть его заключается в том, что при присваивании значений или операциями с переменными мы можем получить кучу проблем… покажу конкретнее каких:&lt;br /&gt;
&lt;br /&gt;
== «Смерть» переменных ==&lt;br /&gt;
&lt;br /&gt;
Положим, у нас с reminder_count происходят ещё какие-нибудь занимательные вещи, например мы его значение зачем-нибудь присваиваем другой переменной вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;antirad_check_delay = reminder_count&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ну и вот, если у нас reminder_count в момент присвоения ещё не имеет никакого значения, то мы получим занимательную штуку — у нас это выражение сработает как&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;antirad_check_delay = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате чего переменная antirad_check_delay «сдохнет» и будет деловито убрана «сборщиком мусора» из памяти, что моментально приведёт к вылету, когда в дальнейшем какая-то часть кода обратится к значению antirad_check_delay.&lt;br /&gt;
&lt;br /&gt;
== Ошибки деления на 0 ==&lt;br /&gt;
&lt;br /&gt;
Теперь положим что у нас reminder_count используется в каком-нибудь расчёте вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;antirad_check_delay = exposure_count/reminder_count&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Как мы помним, у нас reminder_count не инициализирована, и как следствие равна nil. В итоге мы получим попытку поделить exposure_count на ноль (в нашем случае на nil, что суть одно и то же в нашем случае) и получим «классический» программистский вылет из-за ошибки деления на ноль.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Ошибки присвоения ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Предположим что мы написали скрипт, который помогает выбирать оружие при наличии вблизи врагов. И вот, у нас в скрипте есть такой кусок кода, где NPC вынимает из рюкзака оружие:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function weapon_manager:set_weapon(wpn)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	local enemy = self.npc:best_enemy()&lt;br /&gt;
	if wpn then&lt;br /&gt;
		--обратите внимание сюда&lt;br /&gt;
		self.weapon_id = wpn:id()&lt;br /&gt;
		--обратите внимание сюда&lt;br /&gt;
		self:return_items(self.weapon_id)&lt;br /&gt;
	else&lt;br /&gt;
		printw(«set_wpn:weapon not exist»)&lt;br /&gt;
	end&lt;br /&gt;
	if self.modes.process_mode == &amp;quot;3&amp;quot; and enemy then&lt;br /&gt;
		for k, v in pairs(self.weapons) do&lt;br /&gt;
			for i, w in ipairs(v) do&lt;br /&gt;
				if w.id ≈ self.weapon_id then&lt;br /&gt;
					local item = level.object_by_id(w.id)&lt;br /&gt;
					if item and item:parent() and item:parent():id() == self.npc_id then&lt;br /&gt;
						printw(«set_weapon[%s]:process %s[%s]», self.npc:character_name(), w.id, w.sec)&lt;br /&gt;
						self:process_item(item)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if enemy then&lt;br /&gt;
		self.npc:set_item(object.idle, wpn)&lt;br /&gt;
	end&lt;br /&gt;
	self.weapons = nil&lt;br /&gt;
&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В помеченном выражении self.weapon_id = wpn:id(), и всё вроде бы отлично, так как стоит проверка if wpn then, однако иногда движок умудряется передать объект функциям некорректно, либо сам скриптер может перепутать при вызове игоровой объект с объектов а-лайфа, и присвовение self.weapon_id = wpn:id() либо приравняет self.weapon_id к nil либо сразу приведёт к вылету из-за того что соответствующее свойство объекта не было обнаружено в его классе.&lt;br /&gt;
&lt;br /&gt;
== Как с этим бороться ==&lt;br /&gt;
&lt;br /&gt;
Бороться с такими ситуациями очень просто на самом деле.&lt;br /&gt;
Во-первых: ВСЕГДА ИНИЦИАЛИЗИРУЙТЕ ПЕРЕМЕННЫЕ. То есть вот такое описание переменной:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
НЕДОПУСТИМО! ВЫ ОБЯЗАТЕЛЬНО ДОЛЖНЫ ЗАДАТЬ ПЕРЕМЕННОЙ ЗНАЧЕНИЕ!&lt;br /&gt;
Если переменная числовая, сделайте это так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count = 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если строчная — то сделайте это так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count = &amp;quot;&amp;quot;&amp;lt;/code&amp;gt; (получится вместо nil пустая строка)&lt;br /&gt;
&lt;br /&gt;
Если логическая, то так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count = false&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В общем, делайте так, как вам удобнее, но ДЕЛАЙТЕ ОБЯЗАТЕЛЬНО!&lt;br /&gt;
&lt;br /&gt;
И во-вторых: ВСЕГДА ПРОВЕРЯЙТЕ ПЕРЕМЕННУЮ ПЕРЕД ИСПОЛЬЗОВАНИЕМ!&lt;br /&gt;
&lt;br /&gt;
То есть в кусках кода, чреватых вылетами (к ним относятся АБСОЛЮТНО ВСЕ части, где идёт работа с вещами или оружием) обязательно вставляйте проверки переменных на nil следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if wpn and wpn:id() then&lt;br /&gt;
	self.weapon_id = wpn:id()&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тогда сначала выполнится проверка, и если обе переменные имеют значение, то операция отработает, а если один из параметров пуст — то функция просто пропустит этот код. Тут не помешает ещё и проверку вставить на существование self.weapon_id, хотя это наверное уже моя параноя :)&lt;br /&gt;
&lt;br /&gt;
Кстати, что самое дурацкое — ошибки, приведённые выше, часто в лог попадают совершенно непохожим на ошибки скриптов образом. Например, ошибка присвоения из примера 3 приводила к вылету с сообщением «Assertion failed», и поймать её я смог только изучив внимательно предыдущие строки лога, где зафиксировались операции с вещами…&lt;br /&gt;
&lt;br /&gt;
Точно так же, если вы используете ЛЮБЫЕ математические операции например деление (но не только деление, всего остального тоже касается), тоже ОБЯЗАТЕЛЬНО поверяйте переменную. Вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count and reminder_count ≈ 0 then&lt;br /&gt;
	antirad_check_delay = exposure_count/reminder_count&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Таким образом потенциальные вылеты будут аккуратно изолированы, и больше не будут создавать проблем.&lt;br /&gt;
И ещё одно небольшое дополнение к этой же теме. Итак, вы вставили обходные проверки вот таким образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if wpn and wpn:id() then&lt;br /&gt;
   self.weapon_id = wpn:id() &lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Или таким:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count and reminder_count ≈ 0 then&lt;br /&gt;
   antirad_check_delay = exposure_count/reminder_count &lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И у вас в случае некорректного значения компилятор спокойно пропустил этот код, ничего не сделав ни с self.weapon_id (из первого примера), ни с antirad_check_delay (из второго). Однако вам всё-таки нужно, чтобы с ними что-то происходило, даже если проверяемые переменные неверны. Тогда я бы советовал вам дополнить этот обход отработкой нештатной ситуации. Делается это банально просто:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if wpn and wpn:id()then&lt;br /&gt;
   self.weapon_id = wpn:id() &lt;br /&gt;
elseif wpn == nil then&lt;br /&gt;
   --вставьте сюда код, что делать если wpn не существует&lt;br /&gt;
elseif wpn:id() == nil then&lt;br /&gt;
   --а сюда, если wpn:id() не существует&lt;br /&gt;
   --тут скорее всего перепутан объект, и можно попробовать&lt;br /&gt;
   --обратиться к wpn.id&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И для второго случая аналогично:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count and reminder_count ≈ 0 then&lt;br /&gt;
   antirad_check_delay = exposure_count/reminder_count &lt;br /&gt;
else&lt;br /&gt;
   --а тут что мы сделаем если reminder_count не существует&lt;br /&gt;
   --я бы сделал например вот так:&lt;br /&gt;
   antirad_check_delay = exposure_count/1&lt;br /&gt;
   --и все дела&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Более конкретная реализация зависит от того, что именно вы пытаетесь сделать — тут уже вам виднее…&lt;br /&gt;
&lt;br /&gt;
= Особенность вторая. Типичные ошибки при именовании функций, присвоении и инициализации переменных. =&lt;br /&gt;
&lt;br /&gt;
Типичная ошибка, приводящая к вылету:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function remove_item(remove_item)&lt;br /&gt;
     if remove_item~=nil then&lt;br /&gt;
          alife():release(alife():object(remove_item:id()), true)&lt;br /&gt;
          return true&lt;br /&gt;
     end&lt;br /&gt;
     return false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В этом коде агрумент функции совпадает с её именем, и в итоге функция пытается передать функции alife():release вместо указателя на вещь — указатель на саму себя. А так как у функции remove_item нет никакого id, код выпадает с ошибкой:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Arguments     : LUA error: ...\s.t.a.l.k.e.r\gamedata\scripts\test.script:486: attempt to call method 'id' (a nil value)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Следует помнить, что Lua — это язык прямого (JIT) компилирования, и он, как любой такой язык не проверяет ошибки при компиляции. Поэтому нужно очень аккуратно раздавать имена переменным и функциям, чтобы избежать подобных ошибок. Данную функцию нужно переписать следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function remove_item(item_to_remove)&lt;br /&gt;
     if item_to_remove then&lt;br /&gt;
          alife():release(alife():object(item_to_remove:id()), true)&lt;br /&gt;
          return true&lt;br /&gt;
     end&lt;br /&gt;
     return false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Однажды, отлаживая скрипт, натолкнулся на вот такой код:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function hit_callback(npc_id)&lt;br /&gt;
	if db.storage[npc_id].wounded ≈ nil then&lt;br /&gt;
		db.storage[npc_id].wounded.wound_manager:hit_callback()&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Казалось бы, всё верно, и значение проверяется перед использованием. Ан нет, скрипт вызывал вылет, причём именно на проверке. Методом тыка было выясено, что nil вызвавший вылет, был не в результате обработки, а был передан фукнции. Это был параметр npc_id.&lt;br /&gt;
&lt;br /&gt;
Пришлось сделать вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function hit_callback(npc_id)&lt;br /&gt;
	if npc_id and db.storage[npc_id].wounded ≈ nil then&lt;br /&gt;
		db.storage[npc_id].wounded.wound_manager:hit_callback()&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Введя предварительно проверку аргумента npc_id. Вывод — если ставите проверку, старайтесь проверять не только саму функцию, но и вначале — аргумент, который ей передаётся. Мало ли в каком виде он до вас доедет… :)&lt;br /&gt;
&lt;br /&gt;
Касательно именования переменных — не забывайте давать им атрибут local если вы не собираетесь их пользовать во внешних скриптах. Иначе, если попадётся где-то аналогичное имя — будет сбой логики или вылет, помните об этом.&lt;br /&gt;
&lt;br /&gt;
И ещё один совет — для самых начинающих:&lt;br /&gt;
&lt;br /&gt;
Когда пишете скрипты, не забывайте о соблюдении синтаксиса! То есть внимательно и вдумчиво ставьте в концах функций и оснований ключевые слова end — помните, что как отсутствие нужных end’ов, так и наличие лишних приводят к тому, что границы фукций сбиваются, скрипт не парсится обработчиком и вызывает при попытке своего вызова вылет, примерно вот такой:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Arguments : LUA error: ...g\s.t.a.l.k.e.r\gamedata\scripts\bind_stalker.script:75: attempt to index global 'test_main_new' (a nil value)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы вам проще было следить за синтаксисом и не допускать лишних end’ов, выстраивайте текст скриптов каскадными лесенками:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code &amp;gt;Начало_Работы&lt;br /&gt;
          Начало_Внуренней_Функции&lt;br /&gt;
                  Процедуры_Внутреней_Функции              &lt;br /&gt;
          Конец_Внутренней_Функции&lt;br /&gt;
Конец_Работы&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
..и пользуйтесь редакторами с подсветкой синтаксиса, такими как Notepad++ например. В результате каждый уровень вложенности находится на своём отступе. Вы всегда увидите, какую конструкцию нужно закрыть, а какую — нет. Я же например, делаю ещё проще — я, открывая новую функцию, сразу ставлю сразу в её конце, парой строк ниже, end, и потом уже пишу наполняющий её текст с отступами как в примере выше.&lt;br /&gt;
&lt;br /&gt;
И ещё — касательно жизни переменных: не забывайте, что определённые внутри проверок переменные живут только до выхода из проверки. То есть, например:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if check == 1 then&lt;br /&gt;
	local test = true&lt;br /&gt;
else&lt;br /&gt;
	local test = false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В данном случае переменная test создастся, но просуществует только до выхода из проверки, и дальше обращение к ней выдаст nil. Для того чтобы этого избежать, определение переменных надо выносить «за скобки» вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;local test = false; — инитим переменную и задаём сразу ей значение по умолчанию&lt;br /&gt;
&lt;br /&gt;
if check == 1 then&lt;br /&gt;
	test = true&lt;br /&gt;
else&lt;br /&gt;
	test = false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Особенность третья. Смертельные циклы по таблицам. =&lt;br /&gt;
&lt;br /&gt;
Если вы строите цикл по таблице следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;for i=1, table.getn(table_name) do&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''То никогда, ни при каких обстоятельствах не используйте внутри этого цикла удаление/добавление строк, т.е:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;table_name[i] = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
или&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;table.remove(table_name, index)&lt;br /&gt;
table.insert(table_name, index)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
применительно к таблице по которой гоняете цикл!!! При использовании это приводит к тому, что количество строк в таблице уменьшается, а цикл пытается получить из таблицы строки сверх существующего количества, что в итоге приводит к выходу цикла за отведённый ему диапазон памяти. Результатом будет веер самых разнообразных последствий, самое безобидное из которых это безлоговый вылет на рабочий стол, а серьёзное — сбой в работе а-лайфа с последующим боем сейвов! Тяжесть последствий зависит от того, какие действия вы выполняете внутри цикла кроме удаления строки.&lt;br /&gt;
&lt;br /&gt;
Чтобы избежать подобных ситуаций, стройте циклы по таблице лучше следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;for k, v in pairs(table_name)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
А удаление строк внутри них делайте вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;table_name[k] = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Циклы же for i=1, table.getn(table_name) do следует использовать '''только для операций, не изменяющих структуру изменяемой таблицы!'''&lt;br /&gt;
&lt;br /&gt;
= Особенность четвёртая. Как безопасно использовать коллбэки и таймерные события. =&lt;br /&gt;
&lt;br /&gt;
В скриптовом движке есть удобный способ реакции на события - использование коллбэков - процедурных вызовов, привязанных к определённым событиям в жизни игрового объекта - получению повреждений, спавну, смерти и т.д. и т.п. Это hit_callback, death_callback из xr_motivator и многие другие... Все моддеры очень широко и совершенно спокойно пользуются ими, совершенно забывая при этом о таком важном факте, что это - обработки реального времени, как и таймерные события. Что это значит? А собственно вот что... Возьмём для примера коллбэк смерти неписей, мою головную боль последнего времени:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;xr_motivator.script -&lt;br /&gt;
function motivator_binder:death_callback(victim, who)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это функция обрабатывает смерть неписей. Когда вызывается этот коллбек он начинает по очереди вызывать внутренние функции, расположенные в других модулях. Они разрегистрируют умершего непися в гулагах, спавнят в него лут, обновляют статистику, отключают его от доп. схем логики и т.д.&lt;br /&gt;
&lt;br /&gt;
Так вот, обычно, когда в функциях возникает конфликт параметров, бесконечный цикл, попытка индексации nil и т.д., функция вылетает со стандартным логом. '''Но только не в случае, когда она находится внутри коллбэка. Если функция внутри него, то в случае возникновении в ней любой нештатной ситуации, коллбэк наглухо виснет.''' Это происходит из-за того, что функции вызываются строго друг за другом, и каждая из них вызывается только тогда, когда её предшественница вернула управление коллбэку. В случае с death_callback опознать такого непися очень просто - в его трупе окажется фонарик, КПК и возможно ещё немного разных &amp;quot;мусорных&amp;quot; вещей, что говорит о том, что обработка его смерти повисла не дойдя даже до спавна лута. В подобной ситуации можно быть на 100% уверенным, что труп этот не был корректно разрегистрирован, и игра всё ещё считает его живым неписем. Кроме того, зависший коллбэк не освобождает стек (а он у Луа-подсистемы единый на все скрипты), что '''в итоге приводит к вылетам игры с переполнением памяти (вот она, реальная причина этих &amp;quot;родных&amp;quot; вылетов).''' Но было бы слишком хорошо, если бы всё ограничивалось этим... однако тут всё намного хуже... такие &amp;quot;зависшие&amp;quot; коллбэки, особенно если их произошло несколько подряд, очень серьёзно влияют на работу а-лайфа. В лучшем случае они, забивая, стек, мешают нормально работать схемам логики, в худшем вызывают зависания самого  а-лайфа (этот эффект, кстати, производят и сами трупы таких неписей, так как они, как мы помним, не разрегистрировались корректно). '''Основной итог таких событий - бой сейвов, сделанных после возникновения таких ситуаций.''' Если повис один коллбэк, то такие сейвы ещё через раз загружаются, если же несколько, и остановилась работа а-лайфа - всё, сейвы бьются наглухо и реанимации не подлежат.&lt;br /&gt;
&lt;br /&gt;
Поэтому, чтобы избежать такого развития событий, каждый раз, когда вы вносите в коллбэк новую функцию - проверьте её самым тщательным образом. Она не должна содержать никаких рекурсивных циклов, в ней обязательно должны быть проверки на валидность обрабатываемых объектов и значений, и обязательно должна быть обработка нештатных ситуаций - т.е. функция должна обязательно, в абсолютно любой ситуации вернуть управление коллбэку, так или иначе. В самом наихудшем случае - делайте как делали разработчики игры - вставляйте принудительный вылет на рабстол функцией abort - она позволяет передавать отладочное сообщение, и это всяко лучше чем незаметный бой сейвов. Если обработка оборвалась в самом начале, а ф-ция обязательно должна вернуть значение - заведите ей &amp;quot;безопасное&amp;quot; возвращаемое значение по-умолчанию, которое она будет выдавать, если всё пошло плохо. И никогда не пренебрегайте пошаговой отладкой коллбэков с выводом в лог, особенно когда пишете схемы логики - это критически важно для стабильности вашего мода.&lt;br /&gt;
&lt;br /&gt;
== Основные внутриигровые признаки зависания коллбэков типа hit_callback, death_callback ==&lt;br /&gt;
&lt;br /&gt;
1. В трупах попадаются фонарики, КПК, разный мусор и общий лут слишком богат.&lt;br /&gt;
&lt;br /&gt;
2. Частые вылеты во время интенсивных боёв с логами типа&lt;br /&gt;
    Sheduler tried to update object...&lt;br /&gt;
    smart_terrain:1145(1146)&lt;br /&gt;
    LUA: out of memory&lt;br /&gt;
    любой_модуль_логики:любая_cтрока - stack overflow&lt;br /&gt;
&lt;br /&gt;
3. Частые &amp;quot;родные&amp;quot; вылеты в момент смерти непися или попадания по нему&lt;br /&gt;
&lt;br /&gt;
4. Произвольно бьются сейвы во время сражений, выброса и других насыщенных действиями событий&lt;br /&gt;
&lt;br /&gt;
= Авторы =&lt;br /&gt;
Статья создана: [[Участник:Kamikazze|Kamikazze]]&lt;/div&gt;</summary>
		<author><name>Kamikazze</name></author>	</entry>

	<entry>
		<id>http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2</id>
		<title>Как писать скрипты, не приводящие к вылетам и бою сейвов</title>
		<link rel="alternate" type="text/html" href="http://stalkerin.gameru.net/wiki/index.php?title=%D0%9A%D0%B0%D0%BA_%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C_%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B,_%D0%BD%D0%B5_%D0%BF%D1%80%D0%B8%D0%B2%D0%BE%D0%B4%D1%8F%D1%89%D0%B8%D0%B5_%D0%BA_%D0%B2%D1%8B%D0%BB%D0%B5%D1%82%D0%B0%D0%BC_%D0%B8_%D0%B1%D0%BE%D1%8E_%D1%81%D0%B5%D0%B9%D0%B2%D0%BE%D0%B2"/>
				<updated>2009-08-25T08:45:57Z</updated>
		
		<summary type="html">&lt;p&gt;Kamikazze: Создание новой статьи&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''Отладив несколько скриптов на предмет вылетов решил написать это небольшое руководство, чтобы помочь скриптописателям избежать потенциальных проблем.'''&lt;br /&gt;
&lt;br /&gt;
Сразу оговорюсь — я программист, поэтому к отладке скриптов подхожу со своей профессиональной точки зрения, и то что я тут изложу вам — сие для программиста есть непреложная истина, и если вы хотите уменьшить количество глюков — старайтесь придерживаться нижеприведённого стиля программирования.&lt;br /&gt;
&lt;br /&gt;
Для начала маленький экскурс в историю. Итак, скрипты в сталкере написаны на языке '''Lua''' — разработали его бразильцы, он очень гибок, легко встраивается в игры и поэтому очень часто используется для написания скриптов любой степени сложности. Так вот, у этого языка есть несколько исключительно важных особенностей, которые, при их несоблюдении, будут приводить к вылетам вашего скрипта. Итак:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== '''Особенность первая. Типы данных и проблемы с nil.''' ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Сначала сделаю маленькое отступление, чтобы понятнее было. Итак, в Lua используются следующие операторы:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;= оператор присваивания&lt;br /&gt;
== сравнение, равно ли значение&lt;br /&gt;
≈ сравнение, НЕ равно ли значение&lt;br /&gt;
&amp;lt;		сравнение, меньше ли значение&lt;br /&gt;
&amp;gt; сравнение, больше ли значение&lt;br /&gt;
&amp;lt;=		сравнение, меньше ли значение или равно&lt;br /&gt;
&amp;gt;= сравнение, больше ли значение или равно&lt;br /&gt;
and логический оператор И&lt;br /&gt;
not логический оператор НЕ&lt;br /&gt;
or логический оператор ИЛИ&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Всё. Теперь поехали дальше… Язык Lua Изначально создавался для работы с большими строчными базами данных, поэтому ВСЕ виды конструкций в языке — это типы данных, то есть по сути либо переменные, либо константы. Это касается так же и функций! '''Поэтому даже с функциями в Lua можно и нужно обращатся как с переменными.''' Далее. Касательно, собственно «привычных» переменных. '''Обычные переменные в Lua получают свой тип данных только в момент присвоения им значения (запомните, это важно).''' При этом, в Lua есть такой важный и полезный тип данных как nil. nil — Это «пустота», то есть отсутствие какого-либо значения. При этом этот тип используется компилятором скриптов для «сбора мусора», то есть для освобождения занимаемой памяти. Смотрите пример, я объясню подробнее, что это значит:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;local reminder_count&lt;br /&gt;
local exposure_count = 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тут у нас 2 локальные переменные, одна из которых просто создана, а вторая — создана и инициализарована. Если сейчас обратиться к переменной reminder_count, считав её значение, то мы получим в качестве значения — nil, то есть пустоту. Если же мы обратимся к переменной exposure_count — то получим, как и ожидали, 0 — так как мы это значение проинициализировали ЗАРАНЕЕ. '''При этом не надо путать nil и 0 — так как 0 — это всё-таки какая-то информация, а вот nil — это её полное отсутствие.''' Так вот, собственно, чем это чревато. Я уже сказал, что nil используется для сбора мусора. Вручную это работает так — когда вам переменная уже не нужна, вы просто пишете:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;test_variable = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И ваша переменная test_variable сотрётся из памяти, и любые ссылки на неё будут выдавать на выходе nil (типа нет такой переменной, «абонент вне зоны действия сети»). Ну так собственно вот, зачем я это всё рассказываю… '''если не определить значение переменной сразу, как это было сделано с переменной reminder_count, то любые попытки обратиться к ней внутри скрипта приведут к вылетам.''' Происходит это следующим образом: допустим, reminder_count — это у нас счётчик напоминаний. То есть мы о чём-то напоминаем игроку текстовыми сообщениями, и помечаем, сколько раз мы это сделали. Определили мы переменную именно так, как в примере выше, то есть не задав ей никакого значения. Значение же её должно присваиваться ниже по коду, после первого оповещения юзера. При этом у нас есть в коде обращение к этой переменной, по которому решается что делать дальше. Выглядит это примерно так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function check_antirad_supplies()&lt;br /&gt;
&lt;br /&gt;
	if auto_injection_active then&lt;br /&gt;
&lt;br /&gt;
		if antirad_check_delay == nil or game.get_game_time():diffSec(antirad_check_delay) &amp;gt; 60 then&lt;br /&gt;
&lt;br /&gt;
			if not db.actor:object(«antirad») and not (use_scientific_kit and db.actor:object(«medkit_scientic»)) then&lt;br /&gt;
&lt;br /&gt;
			--обратите внимание сюда&lt;br /&gt;
				if reminder_count == 0 or reminder_count &amp;gt;= mins_till_next_remind then&lt;br /&gt;
			--обратите внимание сюда&lt;br /&gt;
					if use_text then&lt;br /&gt;
						local news_text = «%c[255,160,160,160]Автоматическая система ввода медицинских&lt;br /&gt;
 препаратов\\n&amp;quot;..&amp;quot;%c[default]Напоминаю: %c[255,230,0,0]&lt;br /&gt;
Противорадиационные препараты отстутствуют! %c[default]&lt;br /&gt;
Автоматический ввод препаратов невозможен.»&lt;br /&gt;
  						db.actor:give_game_news(news_text, &amp;quot;ui\\ui_iconsTotal&amp;quot;, &lt;br /&gt;
Frect():set(0,188,83,47), 0, 3000)&lt;br /&gt;
  					end	&lt;br /&gt;
	  				if use_sounds then&lt;br /&gt;
				 		local snd_obj&lt;br /&gt;
						if use_custom_sounds then&lt;br /&gt;
							snd_obj = xr_sound.get_safe_sound_object([[HEV\no-anti-rad]])&lt;br /&gt;
						else&lt;br /&gt;
	  						snd_obj = xr_sound.get_safe_sound_object([[device\pda\pda_tip]])&lt;br /&gt;
						end&lt;br /&gt;
						if snd_obj then&lt;br /&gt;
							snd_obj:play_no_feedback(db.actor, sound_object.s2d, 0, vector(), 1.0)&lt;br /&gt;
						end&lt;br /&gt;
					end&lt;br /&gt;
					reminder_count = 1&lt;br /&gt;
				elseif reminder then&lt;br /&gt;
					reminder_count = reminder_count + 1&lt;br /&gt;
				end&lt;br /&gt;
&lt;br /&gt;
			else&lt;br /&gt;
				radiation_warning = true&lt;br /&gt;
				reminder_count = 0&lt;br /&gt;
			end&lt;br /&gt;
			antirad_check_delay = game.get_game_time()&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Во время игры всё это скорее всего отработает хорошо, но вот при загрузке… тут могут быть проблемы, так как скрипт при запуске может пролететь ту часть, где переменной reminder_count присваивается значение! В итоге при запуске вышеприведённой функции check_antirad_supplies() нас получится что reminder_count не существует — он равен nil, и в итоге проверка&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count == 0 or reminder_count &amp;gt;= mins_till_next_remind then&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
выдаст ошибку и приведёт к вылету! Почему? '''Да потому что НЕЛЬЗЯ сравнивать НИЧТО с вещественным значением.''' Именно поэтому, когда вы создаёте новую переменную, вы ОБЯЗАНЫ задать ей значение по умолчанию, даже если вы полностью уверены, что она нигде не будет использована до инициализации. Поверьте — в программировании бывает всё, и запросто может так случиться, что ваша переменная будет использована и спровоцирует вылет.&lt;br /&gt;
&lt;br /&gt;
Простой способ обойти такую кучу проверок и лишних инициализаций — использование or-оператора. пример&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if (reminder_count or 0) &amp;gt;= mins_till_next_remind then&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
если переменная равна nil, то выражение вычисляется дальше и получается равным 0, а это уже число и сравнение происходит безболезненно.&lt;br /&gt;
&lt;br /&gt;
Кроме вышеприведённой ситуации, возможен ещё один неприятный момент, связанный с определением переменных и значением nil. Суть его заключается в том, что при присваивании значений или операциями с переменными мы можем получить кучу проблем… покажу конкретнее каких:&lt;br /&gt;
&lt;br /&gt;
'''1) «Убийство» переменных'''&lt;br /&gt;
&lt;br /&gt;
Положим, у нас с reminder_count происходят ещё какие-нибудь занимательные вещи, например мы его значение зачем-нибудь присваиваем другой переменной вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;antirad_check_delay = reminder_count&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ну и вот, если у нас reminder_count в момент присвоения ещё не имеет никакого значения, то мы получим занимательную штуку — у нас это выражение сработает как&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;antirad_check_delay = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате чего переменная antirad_check_delay «сдохнет» и будет деловито убрана «сборщиком мусора» из памяти, что моментально приведёт к вылету, когда в дальнейшем какая-то часть кода обратится к значению antirad_check_delay.&lt;br /&gt;
&lt;br /&gt;
'''2) Ошибки деления на 0.'''&lt;br /&gt;
&lt;br /&gt;
Теперь положим что у нас reminder_count используется в каком-нибудь расчёте вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;antirad_check_delay = exposure_count/reminder_count&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Как мы помним, у нас reminder_count не инициализирована, и как следствие равна nil. В итоге мы получим попытку поделить exposure_count на ноль (в нашем случае на nil, что суть одно и то же в нашем случае) и получим «классический» программистский вылет из-за ошибки деления на ноль.&lt;br /&gt;
&lt;br /&gt;
[b]3) Ошибки присвоения.[/b]&lt;br /&gt;
&lt;br /&gt;
Предположим что мы написали скрипт, который помогает выбирать оружие при наличии вблизи врагов. И вот, у нас в скрипте есть такой кусок кода, где NPC вынимает из рюкзака оружие:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function weapon_manager:set_weapon(wpn)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	local enemy = self.npc:best_enemy()&lt;br /&gt;
	if wpn then&lt;br /&gt;
		--обратите внимание сюда&lt;br /&gt;
		self.weapon_id = wpn:id()&lt;br /&gt;
		--обратите внимание сюда&lt;br /&gt;
		self:return_items(self.weapon_id)&lt;br /&gt;
	else&lt;br /&gt;
		printw(«set_wpn:weapon not exist»)&lt;br /&gt;
	end&lt;br /&gt;
	if self.modes.process_mode == &amp;quot;3&amp;quot; and enemy then&lt;br /&gt;
		for k, v in pairs(self.weapons) do&lt;br /&gt;
			for i, w in ipairs(v) do&lt;br /&gt;
				if w.id ≈ self.weapon_id then&lt;br /&gt;
					local item = level.object_by_id(w.id)&lt;br /&gt;
					if item and item:parent() and item:parent():id() == self.npc_id then&lt;br /&gt;
						printw(«set_weapon[%s]:process %s[%s]», self.npc:character_name(), w.id, w.sec)&lt;br /&gt;
						self:process_item(item)&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if enemy then&lt;br /&gt;
		self.npc:set_item(object.idle, wpn)&lt;br /&gt;
	end&lt;br /&gt;
	self.weapons = nil&lt;br /&gt;
&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В помеченном выражении self.weapon_id = wpn:id(), и всё вроде бы отлично, так как стоит проверка if wpn then, однако иногда движок умудряется передать объект функциям некорректно, либо сам скриптер может перепутать при вызове игоровой объект с объектов а-лайфа, и присвовение self.weapon_id = wpn:id() либо приравняет self.weapon_id к nil либо сразу приведёт к вылету из-за того что соответствующее свойство объекта не было обнаружено в его классе.&lt;br /&gt;
&lt;br /&gt;
'''Как с этим бороться.'''&lt;br /&gt;
&lt;br /&gt;
Бороться с такими ситуациями очень просто на самом деле.&lt;br /&gt;
Во-первых: ВСЕГДА ИНИЦИАЛИЗИРУЙТЕ ПЕРЕМЕННЫЕ. То есть вот такое описание переменной:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
НЕДОПУСТИМО! ВЫ ОБЯЗАТЕЛЬНО ДОЛЖНЫ ЗАДАТЬ ПЕРЕМЕННОЙ ЗНАЧЕНИЕ!&lt;br /&gt;
Если переменная числовая, сделайте это так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count = 0&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если строчная — то сделайте это так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count = &amp;quot;&amp;quot;&amp;lt;/code&amp;gt; (получится вместо nil пустая строка)&lt;br /&gt;
&lt;br /&gt;
Если логическая, то так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;local reminder_count = false&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В общем, делайте так, как вам удобнее, но ДЕЛАЙТЕ ОБЯЗАТЕЛЬНО!&lt;br /&gt;
&lt;br /&gt;
И во-вторых: ВСЕГДА ПРОВЕРЯЙТЕ ПЕРЕМЕННУЮ ПЕРЕД ИСПОЛЬЗОВАНИЕМ!&lt;br /&gt;
&lt;br /&gt;
То есть в кусках кода, чреватых вылетами (к ним относятся АБСОЛЮТНО ВСЕ части, где идёт работа с вещами или оружием) обязательно вставляйте проверки переменных на nil следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if wpn and wpn:id() then&lt;br /&gt;
	self.weapon_id = wpn:id()&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Тогда сначала выполнится проверка, и если обе переменные имеют значение, то операция отработает, а если один из параметров пуст — то функция просто пропустит этот код. Тут не помешает ещё и проверку вставить на существование self.weapon_id, хотя это наверное уже моя параноя :)&lt;br /&gt;
&lt;br /&gt;
Кстати, что самое дурацкое — ошибки, приведённые выше, часто в лог попадают совершенно непохожим на ошибки скриптов образом. Например, ошибка присвоения из примера 3 приводила к вылету с сообщением «Assertion failed», и поймать её я смог только изучив внимательно предыдущие строки лога, где зафиксировались операции с вещами…&lt;br /&gt;
&lt;br /&gt;
Точно так же, если вы используете ЛЮБЫЕ математические операции например деление (но не только деление, всего остального тоже касается), тоже ОБЯЗАТЕЛЬНО поверяйте переменную. Вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count and reminder_count ≈ 0 then&lt;br /&gt;
	antirad_check_delay = exposure_count/reminder_count&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Таким образом потенциальные вылеты будут аккуратно изолированы, и больше не будут создавать проблем.&lt;br /&gt;
И ещё одно небольшое дополнение к этой же теме. Итак, вы вставили обходные проверки вот таким образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if wpn and wpn:id() then&lt;br /&gt;
   self.weapon_id = wpn:id() &lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Или таким:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count and reminder_count ≈ 0 then&lt;br /&gt;
   antirad_check_delay = exposure_count/reminder_count &lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И у вас в случае некорректного значения компилятор спокойно пропустил этот код, ничего не сделав ни с self.weapon_id (из первого примера), ни с antirad_check_delay (из второго). Однако вам всё-таки нужно, чтобы с ними что-то происходило, даже если проверяемые переменные неверны. Тогда я бы советовал вам дополнить этот обход отработкой нештатной ситуации. Делается это банально просто:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if wpn and wpn:id()then&lt;br /&gt;
   self.weapon_id = wpn:id() &lt;br /&gt;
elseif wpn == nil then&lt;br /&gt;
   --вставьте сюда код, что делать если wpn не существует&lt;br /&gt;
elseif wpn:id() == nil then&lt;br /&gt;
   --а сюда, если wpn:id() не существует&lt;br /&gt;
   --тут скорее всего перепутан объект, и можно попробовать&lt;br /&gt;
   --обратиться к wpn.id&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
И для второго случая аналогично:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if reminder_count and reminder_count ≈ 0 then&lt;br /&gt;
   antirad_check_delay = exposure_count/reminder_count &lt;br /&gt;
else&lt;br /&gt;
   --а тут что мы сделаем если reminder_count не существует&lt;br /&gt;
   --я бы сделал например вот так:&lt;br /&gt;
   antirad_check_delay = exposure_count/1&lt;br /&gt;
   --и все дела&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Более конкретная реализация зависит от того, что именно вы пытаетесь сделать — тут уже вам виднее…&lt;br /&gt;
&lt;br /&gt;
== '''Особенность вторая. Типичные ошибки при именовании функций, присвоении и инициализации переменных.''' ==&lt;br /&gt;
&lt;br /&gt;
Типичная ошибка, приводящая к вылету:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function remove_item(remove_item)&lt;br /&gt;
     if remove_item~=nil then&lt;br /&gt;
          alife():release(alife():object(remove_item:id()), true)&lt;br /&gt;
          return true&lt;br /&gt;
     end&lt;br /&gt;
     return false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В этом коде агрумент функции совпадает с её именем, и в итоге функция пытается передать функции alife():release вместо указателя на вещь — указатель на саму себя. А так как у функции remove_item нет никакого id, код выпадает с ошибкой:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Arguments     : LUA error: ...\s.t.a.l.k.e.r\gamedata\scripts\test.script:486: attempt to call method 'id' (a nil value)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Следует помнить, что Lua — это язык прямого (JIT) компилирования, и он, как любой такой язык не проверяет ошибки при компиляции. Поэтому нужно очень аккуратно раздавать имена переменным и функциям, чтобы избежать подобных ошибок. Данную функцию нужно переписать следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function remove_item(item_to_remove)&lt;br /&gt;
     if item_to_remove then&lt;br /&gt;
          alife():release(alife():object(item_to_remove:id()), true)&lt;br /&gt;
          return true&lt;br /&gt;
     end&lt;br /&gt;
     return false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Однажды, отлаживая скрипт, натолкнулся на вот такой код:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function hit_callback(npc_id)&lt;br /&gt;
	if db.storage[npc_id].wounded ≈ nil then&lt;br /&gt;
		db.storage[npc_id].wounded.wound_manager:hit_callback()&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Казалось бы, всё верно, и значение проверяется перед использованием. Ан нет, скрипт вызывал вылет, причём именно на проверке. Методом тыка было выясено, что nil вызвавший вылет, был не в результате обработки, а был передан фукнции. Это был параметр npc_id.&lt;br /&gt;
&lt;br /&gt;
Пришлось сделать вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;function hit_callback(npc_id)&lt;br /&gt;
	if npc_id and db.storage[npc_id].wounded ≈ nil then&lt;br /&gt;
		db.storage[npc_id].wounded.wound_manager:hit_callback()&lt;br /&gt;
	end&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Введя предварительно проверку аргумента npc_id. Вывод — если ставите проверку, старайтесь проверять не только саму функцию, но и вначале — аргумент, который ей передаётся. Мало ли в каком виде он до вас доедет… :)&lt;br /&gt;
&lt;br /&gt;
Касательно именования переменных — не забывайте давать им атрибут local если вы не собираетесь их пользовать во внешних скриптах. Иначе, если попадётся где-то аналогичное имя — будет сбой логики или вылет, помните об этом.&lt;br /&gt;
&lt;br /&gt;
И ещё один совет — для самых начинающих:&lt;br /&gt;
&lt;br /&gt;
Когда пишете скрипты, не забывайте о соблюдении синтаксиса! То есть внимательно и вдумчиво ставьте в концах функций и оснований ключевые слова end — помните, что как отсутствие нужных end’ов, так и наличие лишних приводят к тому, что границы фукций сбиваются, скрипт не парсится обработчиком и вызывает при попытке своего вызова вылет, примерно вот такой:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Arguments : LUA error: ...g\s.t.a.l.k.e.r\gamedata\scripts\bind_stalker.script:75: attempt to index global 'test_main_new' (a nil value)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы вам проще было следить за синтаксисом и не допускать лишних end’ов, выстраивайте текст скриптов каскадными лесенками:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code &amp;gt;Начало_Работы&lt;br /&gt;
          Начало_Внуренней_Функции&lt;br /&gt;
                  Процедуры_Внутреней_Функции              &lt;br /&gt;
          Конец_Внутренней_Функции&lt;br /&gt;
Конец_Работы&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
..и пользуйтесь редакторами с подсветкой синтаксиса, такими как Notepad++ например. В результате каждый уровень вложенности находится на своём отступе. Вы всегда увидите, какую конструкцию нужно закрыть, а какую — нет. Я же например, делаю ещё проще — я, открывая новую функцию, сразу ставлю сразу в её конце, парой строк ниже, end, и потом уже пишу наполняющий её текст с отступами как в примере выше.&lt;br /&gt;
&lt;br /&gt;
И ещё — касательно жизни переменных: не забывайте, что определённые внутри проверок переменные живут только до выхода из проверки. То есть, например:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;if check == 1 then&lt;br /&gt;
	local test = true&lt;br /&gt;
else&lt;br /&gt;
	local test = false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В данном случае переменная test создастся, но просуществует только до выхода из проверки, и дальше обращение к ней выдаст nil. Для того чтобы этого избежать, определение переменных надо выносить «за скобки» вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;local test = false; — инитим переменную и задаём сразу ей значение по умолчанию&lt;br /&gt;
&lt;br /&gt;
if check == 1 then&lt;br /&gt;
	test = true&lt;br /&gt;
else&lt;br /&gt;
	test = false&lt;br /&gt;
end&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== '''Особенность третья. Смертельные циклы по таблицам.''' ==&lt;br /&gt;
&lt;br /&gt;
Если вы строите цикл по таблице следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;for i=1, table.getn(table_name) do&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''То никогда, ни при каких обстоятельствах не используйте внутри этого цикла удаление/добавление строк, т.е:'''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;table_name[i] = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
или&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;table.remove(table_name, index)&lt;br /&gt;
table.insert(table_name, index)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
применительно к таблице по которой гоняете цикл!!! При использовании это приводит к тому, что количество строк в таблице уменьшается, а цикл пытается получить из таблицы строки сверх существующего количества, что в итоге приводит к выходу цикла за отведённый ему диапазон памяти. Результатом будет веер самых разнообразных последствий, самое безобидное из которых это безлоговый вылет на рабочий стол, а серьёзное — сбой в работе а-лайфа с последующим боем сейвов! Тяжесть последствий зависит от того, какие действия вы выполняете внутри цикла кроме удаления строки.&lt;br /&gt;
&lt;br /&gt;
Чтобы избежать подобных ситуаций, стройте циклы по таблице лучше следующим образом:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;for k, v in pairs(table_name)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
А удаление строк внутри них делайте вот так:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code lua&amp;gt;table_name[k] = nil&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Циклы же for i=1, table.getn(table_name) do следует использовать '''только для операций, не изменяющих структуру изменяемой таблицы!'''&lt;br /&gt;
&lt;br /&gt;
== '''Особенность четвёртая. Как безопасно использовать коллбэки и таймерные события.''' ==&lt;br /&gt;
&lt;br /&gt;
В скриптовом движке есть удобный способ реакции на события - использование коллбэков - процедурных вызовов, привязанных к определённым событиям в жизни игрового объекта - получению повреждений, спавну, смерти и т.д. и т.п. Это hit_callback, death_callback из xr_motivator и многие другие... Все моддеры очень широко и совершенно спокойно пользуются ими, совершенно забывая при этом о таком важном факте, что это - обработки реального времени, как и таймерные события. Что это значит? А собственно вот что... Возьмём для примера коллбэк смерти неписей, мою головную боль последнего времени:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;xr_motivator.script -&lt;br /&gt;
function motivator_binder:death_callback(victim, who)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Это функция обрабатывает смерть неписей. Когда вызывается этот коллбек он начинает по очереди вызывать внутренние функции, расположенные в других модулях. Они разрегистрируют умершего непися в гулагах, спавнят в него лут, обновляют статистику, отключают его от доп. схем логики и т.д.&lt;br /&gt;
&lt;br /&gt;
Так вот, обычно, когда в функциях возникает конфликт параметров, бесконечный цикл, попытка индексации nil и т.д., функция вылетает со стандартным логом. '''Но только не в случае, когда она находится внутри коллбэка. Если функция внутри него, то в случае возникновении в ней любой нештатной ситуации, коллбэк наглухо виснет.''' Это происходит из-за того, что функции вызываются строго друг за другом, и каждая из них вызывается только тогда, когда её предшественница вернула управление коллбэку. В случае с death_callback опознать такого непися очень просто - в его трупе окажется фонарик, КПК и возможно ещё немного разных &amp;quot;мусорных&amp;quot; вещей, что говорит о том, что обработка его смерти повисла не дойдя даже до спавна лута. В подобной ситуации можно быть на 100% уверенным, что труп этот не был корректно разрегистрирован, и игра всё ещё считает его живым неписем. Кроме того, зависший коллбэк не освобождает стек (а он у Луа-подсистемы единый на все скрипты), что '''в итоге приводит к вылетам игры с переполнением памяти (вот она, реальная причина этих &amp;quot;родных&amp;quot; вылетов).''' Но было бы слишком хорошо, если бы всё ограничивалось этим... однако тут всё намного хуже... такие &amp;quot;зависшие&amp;quot; коллбэки, особенно если их произошло несколько подряд, очень серьёзно влияют на работу а-лайфа. В лучшем случае они, забивая, стек, мешают нормально работать схемам логики, в худшем вызывают зависания самого  а-лайфа (этот эффект, кстати, производят и сами трупы таких неписей, так как они, как мы помним, не разрегистрировались корректно). '''Основной итог таких событий - бой сейвов, сделанных после возникновения таких ситуаций.''' Если повис один коллбэк, то такие сейвы ещё через раз загружаются, если же несколько, и остановилась работа а-лайфа - всё, сейвы бьются наглухо и реанимации не подлежат.&lt;br /&gt;
&lt;br /&gt;
Поэтому, чтобы избежать такого развития событий, каждый раз, когда вы вносите в коллбэк новую функцию - проверьте её самым тщательным образом. Она не должна содержать никаких рекурсивных циклов, в ней обязательно должны быть проверки на валидность обрабатываемых объектов и значений, и обязательно должна быть обработка нештатных ситуаций - т.е. функция должна обязательно, в абсолютно любой ситуации вернуть управление коллбэку, так или иначе. В самом наихудшем случае - делайте как делали разработчики игры - вставляйте принудительный вылет на рабстол функцией abort - она позволяет передавать отладочное сообщение, и это всяко лучше чем незаметный бой сейвов. Если обработка оборвалась в самом начале, а ф-ция обязательно должна вернуть значение - заведите ей &amp;quot;безопасное&amp;quot; возвращаемое значение по-умолчанию, которое она будет выдавать, если всё пошло плохо. И никогда не пренебрегайте пошаговой отладкой коллбэков с выводом в лог, особенно когда пишете схемы логики - это критически важно для стабильности вашего мода.&lt;br /&gt;
&lt;br /&gt;
'''Основные внутриигровые признаки зависания коллбэков типа hit_callback, death_callback:'''&lt;br /&gt;
&lt;br /&gt;
1. В трупах попадаются фонарики, КПК, разный мусор и общий лут слишком богат.&lt;br /&gt;
&lt;br /&gt;
2. Частые вылеты во время интенсивных боёв с логами типа&lt;br /&gt;
    Sheduler tried to update object...&lt;br /&gt;
    smart_terrain:1145(1146)&lt;br /&gt;
    LUA: out of memory&lt;br /&gt;
    любой_модуль_логики:любая_cтрока - stack overflow&lt;br /&gt;
&lt;br /&gt;
3. Частые &amp;quot;родные&amp;quot; вылеты в момент смерти непися или попадания по нему&lt;br /&gt;
&lt;br /&gt;
4. Произвольно бьются сейвы во время сражений, выброса и других насыщенных действиями событий&lt;/div&gt;</summary>
		<author><name>Kamikazze</name></author>	</entry>

	</feed>