Загрузка данных


// Модуль управляемой формы внешней обработки "МиниРПГ"
// (ТабличныйДокумент, процедурная генерация, преследование, бой, босс)
//
// Реквизит формы:  Поле  (тип ТабличныйДокумент)
// Команды формы:   Вверх, Вниз, Влево, Вправо, НачатьЗаново,
//                  Способность1, Способность2 (кнопки получат имена ФормаСпособность1/2)
//
// Легенда карты:  #  стена,  .  пол,  $  сундук (золото + случайный лут),
//                 *  сундук некроманта,  !  зелье (+15 HP)
// Враги в карте не хранятся: это отдельный массив структур.
//
// Генерация: на пустой карте случайно размещаются комнаты (без наложения),
// соседние по порядку создания комнаты соединяются коридорами с одним поворотом.
// Герой появляется у левого края карты (клетка E — вход/выход из подземелья),
// некромант стоит в самой дальней от входа комнате.
// В остальных комнатах случайные враги, сундуки и зелья.
//
// Туман войны: герой видит только клетки в радиусе 1 вокруг себя (3x3, включая
// диагонали). Увиденные клетки запоминаются (карта Открыто) и рисуются тусклыми,
// неисследованное закрыто туманом. Врагов видно только в радиусе 1 от героя.
//
// Ход: после каждого шага героя или удара по врагу ходят все враги.
// Все враги, включая призванных скелетов, преследуют героя, если он в радиусе
// 4 клеток (по большей из разностей координат) и в прямой видимости (стены
// закрывают обзор), и бьют, когда подошли вплотную по горизонтали или вертикали.
// Обычные скелеты (не призванные) стреляют из лука на 3 клетки по прямой
// (горизонталь/вертикаль), если линия выстрела свободна, урон 4.
// Некромант стоит на месте, просыпается в радиусе 6 (стены ему не мешают)
// и призывает скелетов.
//
// Яд: каждый удар слизня отравляет героя на 4 хода (по 1 HP в начале фазы
// врагов). Повторный удар обновляет срок. Зелье лечения снимает яд.
//
// Уровень большой (70x36, до 16 комнат), генерация процедурная.
//
// Локации: игра начинается в деревне (небольшая карта: забор #, дома H, деревья T, пруд P,
// вход в подземелье D в северной ограде). Шаг в D полностью меняет карту на
// подземелье, шаг в E (левый край подземелья) возвращает в деревню. Подземелье
// генерируется один раз, при выходе его состояние (карта, враги, туман, сундуки)
// сохраняется, при повторном входе загружается то же самое. Враги остаются в
// подземелье и не могут выйти. Деревня безопасна. Кнопка «Начать заново» создаёт
// новую деревню и новое подземелье (и снова предлагает выбрать класс).
//
// Жители деревни: староста (квест «Тени под деревней»), оружейник и бронник
// (покупка и продажа: двойной щелчок по строке магазина), четверо простых жителей
// с репликами-заглушками. Шаг в жителя = разговор (ход не тратится). Жители
// случайно делают шаг раз в 2 хода героя, не заходят в подземелье и не подходят
// близко ко входу. Торговцы и староста топчутся у дверей своих домов (отходят не дальше 1 клетки).
// Квест: староста просит принести записку некроманта, за неё даёт 100 золота и
// «Таинственный ключ» (от другого подземелья, его используют позже). Список
// квестов выводится под легендой справа.
//
// Этажи подземелья: 1 этаж (выход E в деревню). После сдачи квеста в случайной
// комнате 1 этажа появляется люк L: с «Таинственным ключом» в сумке шаг в люк
// проваливает на 2 этаж (демоны и тёмный владыка), где на левом краю лестница вверх «<». Люк
// и оба этажа сохраняются, можно спускаться и подниматься сколько угодно.
// 2 этаж: адское отродье (бьёт вблизи и поджигает), пиромант (огненный шар на
// 4 клетки), демон (броня). Босс — тёмный владыка: он неуязвим, пока живы два его
// стража: багряный палач (вампиризм) и грозовой жнец (молния по линиям и
// диагоналям на 3 клетки). Когда стражи пали, владыка впадает в ярость. После
// победы над ним в деревне торговцы получают ассортимент до 4 уровня.
// Лекарь в деревне лечит за золото: цена зависит от недостающего HP.
// Когда герой берёт письмо из сундука некроманта, звучит голос из подземелья и
// 1 этаж заполняется драуграми (сильнее бронированных скелетов).
//
// Бронированный скелет: имеет броню (поглощает часть
// урона героя), повышенный урон и бьёт только вблизи (не стреляет). Спавнится реже
// обычных скелетов.
//
// Инвентарь: герой стартует с оружием и бронёй 1 уровня. Из обычных сундуков, кроме
// золота, может выпасть оружие или броня 1-2 уровня (или ничего). В сундуке на месте
// смерти некроманта гарантированы «Письмо некроманту» и оружие или броня 3 уровня.
// Оружие прибавляет атаку, броня снижает входящий урон (минимум 1 HP; на яд броня
// не действует). Двойной щелчок по предмету в сумке надевает его (тратит ход).
// После смерти некроманта игра продолжается; новое подземелье: «Начать заново».
//
// КЛАССЫ (выбираются в начале партии): Воин, Маг, Вор, Стрелок.
//   Воин:    50 HP. Щит (урон вдвое меньше, 3 хода), Мощный удар (x2, игнорирует броню).
//   Маг:     30 HP, мана. Огненный шар (дальний, игнорирует броню), Ледяная волна
//            (урон по площади + заморозка).
//   Вор:     36 HP. Бьёт дважды (по 70% урона), пока он идёт, враги и яд пропускают
//            каждый второй шаг, удар в спину x3. Тень (невидимость), Метательные ножи.
//   Стрелок: 34 HP, фокус, крит 25%. Меткий выстрел (x2), Град стрел (по площади).
// Оружие и броня называются по классу (меч/посох/кинжал/лук), лут и ассортимент
// торговцев подстраиваются под класс; магам и стрелкам оружейник продаёт зелья ресурса.
// Ресурс (мана/фокус) растёт на 1 за ход, восстанавливается зельем, лекарем и уровнем.

&НаКлиенте
Перем Карта;

&НаКлиенте
Перем ГероX;

&НаКлиенте
Перем ГероY;

&НаКлиенте
Перем Герой;

&НаКлиенте
Перем Враги;

&НаКлиенте
Перем Сообщение;

&НаКлиенте
Перем Генератор;

// Массив строк того же размера, что Карта: "1" клетку уже видели, "0" туман.
&НаКлиенте
Перем Открыто;

// Локации: "Деревня" и "Подземелье". Карта, Враги, Открыто, ГероX, ГероY всегда
// относятся к текущей локации, другая хранится в сохранённом состоянии.
&НаКлиенте
Перем Локация;

&НаКлиенте
Перем ДеревняСост;

&НаКлиенте
Перем ПодземельеСост;

// Где герой появляется в подземелье (клетка рядом с выходом E).
&НаКлиенте
Перем ВходX;

&НаКлиенте
Перем ВходY;

// Жители деревни (массив структур), столбец входа в подземелье в деревне,
// счётчик ходов в деревне (жители ходят раз в 2 хода), открытый магазин, квест.
&НаКлиенте
Перем Жители;

&НаКлиенте
Перем ДеревняВходX;

&НаКлиенте
Перем ХодыДеревни;

&НаКлиенте
Перем Торговец;

// Состояние квеста старосты: "Нет", "Активен", "Сдан".
&НаКлиенте
Перем КвестСостояние;

// Комнаты текущего этажа подземелья (структуры X, Y, Ширина, Высота, ЦентрX, ЦентрY).
&НаКлиенте
Перем КомнатыЭтажа;

// Сохранённое состояние 2 этажа; люк на 1 этаже (ЛюкX = 0: люка ещё нет);
// был ли герой на 2 этаже.
&НаКлиенте
Перем Подземелье2Сост;

&НаКлиенте
Перем ЛюкX;

&НаКлиенте
Перем ЛюкY;

&НаКлиенте
Перем ВторойЭтажПосещён;

// Побеждён ли тёмный владыка; оповещены ли торговцы о новом товаре.
&НаКлиенте
Перем ВладыкаПобеждён;

&НаКлиенте
Перем ТорговцыОповещены;

// Класс героя в текущей партии: "Воин", "Маг", "Вор", "Стрелок".
&НаКлиенте
Перем КлассГероя;

// Тип последнего действия героя: "Шаг" или "" (нужен вору: после шага враги ходят через раз).
&НаКлиенте
Перем ТипХода;

&НаКлиенте
Процедура ПриОткрытии(Отказ)

	// Привязываем обработчик двойного щелчка по предмету программно
	// (то же, что назначить событие ОбработкаРасшифровки поля «Поле» в конфигураторе).
	Попытка
		Элементы.Поле.УстановитьДействие("ОбработкаРасшифровки", "ПолеОбработкаРасшифровки");
	Исключение
	КонецПопытки;

	НачатьИгру();

КонецПроцедуры

&НаКлиенте
Функция РадиусПреследования()
	Возврат 4;
КонецФункции

&НаКлиенте
Функция ДальностьВыстрела()
	Возврат 3;
КонецФункции

&НаКлиенте
Функция УронВыстрела()
	Возврат 5;
КонецФункции

&НаКлиенте
Функция УронЯда()
	Возврат 1;
КонецФункции

&НаКлиенте
Функция ДлительностьЯда()
	Возврат 4;
КонецФункции

// Горение: урон за ход и длительность (от адского отродья и ярости владыки).
&НаКлиенте
Функция УронОжога()
	Возврат 2;
КонецФункции

&НаКлиенте
Функция ДлительностьОжога()
	Возврат 3;
КонецФункции

// Огненный шар пироманта: дальность и урон.
&НаКлиенте
Функция ДальностьОгненногоШара()
	Возврат 4;
КонецФункции

&НаКлиенте
Функция УронОгненногоШара()
	Возврат 9;
КонецФункции

// Лечение зелья: на 2 этаже зелья крепче.
&НаКлиенте
Функция ЛечениеЗелья()
	Возврат ?(Локация = "Подземелье, 2 этаж", 30, 15);
КонецФункции

&НаКлиенте
Функция ШиринаУровня()
	Возврат 70;
КонецФункции

&НаКлиенте
Функция ВысотаУровня()
	Возврат 40;
КонецФункции

&НаКлиенте
Функция МаксКомнат()
	Возврат 16;
КонецФункции

&НаКлиенте
Функция ШиринаДеревни()
	Возврат 48;
КонецФункции

&НаКлиенте
Функция ВысотаДеревни()
	Возврат 30;
КонецФункции

// ---------------------------------------------------------------------------
// Предметы и инвентарь
// ---------------------------------------------------------------------------

// Предмет: структура Вид ("Меч", "Броня", "Письмо", "Ключ", "Зелье"), Уровень, Название.
// Внутри игры Вид "Меч" означает оружие класса (меч, посох, кинжал, лук),
// Вид "Броня" — доспех класса.
// Функции ниже доступны и на клиенте, и на сервере (нужны для отрисовки).

&НаКлиентеНаСервереБезКонтекста
Функция ВместимостьСумки()
	Возврат 8;
КонецФункции

// Цена покупки оружия, брони или зелья у торговца (0 для прочих предметов).
&НаКлиентеНаСервереБезКонтекста
Функция ЦенаПокупки(Вид, Уровень)

	Если Вид = "Меч" Тогда
		Если Уровень = 1 Тогда
			Возврат 30;
		ИначеЕсли Уровень = 2 Тогда
			Возврат 120;
		ИначеЕсли Уровень = 3 Тогда
			Возврат 400;
		КонецЕсли;
		Возврат 900;
	ИначеЕсли Вид = "Броня" Тогда
		Если Уровень = 1 Тогда
			Возврат 30;
		ИначеЕсли Уровень = 2 Тогда
			Возврат 100;
		ИначеЕсли Уровень = 3 Тогда
			Возврат 350;
		КонецЕсли;
		Возврат 800;
	ИначеЕсли Вид = "Зелье" Тогда
		Возврат 25;
	КонецЕсли;

	Возврат 0;

КонецФункции

// Цена, за которую торговец выкупает предмет героя: половина цены покупки.
// 0 = предмет не продаётся (письмо, ключ).
&НаКлиентеНаСервереБезКонтекста
Функция ЦенаПродажи(Предмет)

	Возврат Цел(ЦенаПокупки(Предмет.Вид, Предмет.Уровень) / 2);

КонецФункции

// Опыт, нужный для следующего уровня.
&НаКлиентеНаСервереБезКонтекста
Функция ОпытДляУровня(Уровень)
	Возврат Уровень * 15;
КонецФункции

// Бонус к атаке от оружия.
&НаКлиентеНаСервереБезКонтекста
Функция БонусАтаки(Уровень)

	Если Уровень = 1 Тогда
		Возврат 1;
	ИначеЕсли Уровень = 2 Тогда
		Возврат 3;
	ИначеЕсли Уровень = 3 Тогда
		Возврат 5;
	ИначеЕсли Уровень = 4 Тогда
		Возврат 8;
	КонецЕсли;

	Возврат 0;

КонецФункции

// Сколько урона поглощает броня.
&НаКлиентеНаСервереБезКонтекста
Функция БонусЗащиты(Уровень)

	Если Уровень = 1 Тогда
		Возврат 1;
	ИначеЕсли Уровень = 2 Тогда
		Возврат 2;
	ИначеЕсли Уровень = 3 Тогда
		Возврат 4;
	ИначеЕсли Уровень = 4 Тогда
		Возврат 6;
	КонецЕсли;

	Возврат 0;

КонецФункции

&НаКлиентеНаСервереБезКонтекста
Функция НазваниеПредмета(Вид, Уровень, Класс)

	Если Вид = "Ключ" Тогда
		Возврат "Таинственный ключ";
	ИначеЕсли Вид = "Письмо" Тогда
		Возврат "Письмо некроманту";
	ИначеЕсли Вид = "Зелье" Тогда
		Возврат ?(Класс = "Маг", "Зелье маны", "Зелье концентрации");
	КонецЕсли;

	Если Вид = "Меч" Тогда
		Если Класс = "Маг" Тогда
			Названия = "Сучковатый посох,Посох чародея,Посох мага,Посох архимага";
		ИначеЕсли Класс = "Вор" Тогда
			Названия = "Ржавый кинжал,Стальной кинжал,Мифриловый стилет,Теневые клинки";
		ИначеЕсли Класс = "Стрелок" Тогда
			Названия = "Охотничий лук,Составной лук,Мифриловый лук,Эльфийский лук";
		Иначе
			Названия = "Ржавый меч,Стальной меч,Мифриловый меч,Адамантовый клинок";
		КонецЕсли;
	Иначе
		Если Класс = "Маг" Тогда
			Названия = "Роба ученика,Роба чародея,Мантия мага,Мантия архимага";
		ИначеЕсли Класс = "Вор" Тогда
			Названия = "Кожаная куртка,Усиленная куртка,Плащ теней,Плащ ночи";
		ИначеЕсли Класс = "Стрелок" Тогда
			Названия = "Охотничий жилет,Кожаный доспех,Лесной доспех,Эльфийский доспех";
		Иначе
			Названия = "Кожаная броня,Кольчуга,Латы,Адамантовые латы";
		КонецЕсли;
	КонецЕсли;

	Массив = СтрРазделить(Названия, ",");
	Возврат Массив[Мин(Макс(Уровень, 1), 4) - 1];

КонецФункции

&НаКлиентеНаСервереБезКонтекста
Функция ОписаниеПредмета(Предмет)

	Если Предмет.Вид = "Меч" Тогда
		Возврат Предмет.Название + " (ур. " + Предмет.Уровень + ", атака +" + БонусАтаки(Предмет.Уровень) + ")";
	ИначеЕсли Предмет.Вид = "Броня" Тогда
		Возврат Предмет.Название + " (ур. " + Предмет.Уровень + ", защита " + БонусЗащиты(Предмет.Уровень) + ")";
	ИначеЕсли Предмет.Вид = "Зелье" Тогда
		Возврат Предмет.Название + " (+15 к ресурсу)";
	КонецЕсли;

	Возврат Предмет.Название + " (особый предмет)";

КонецФункции

// ---------------------------------------------------------------------------
// Классы и способности (клиент + сервер: нужны и для логики, и для отрисовки)
// ---------------------------------------------------------------------------

&НаКлиентеНаСервереБезКонтекста
Функция ПараметрыКласса(Класс)

	П = Новый Структура("HP, АтакаМин, АтакаМакс, Мана, РостHP, РостМаны, Ресурс, Описание");

	Если Класс = "Маг" Тогда
		П.HP = 30;
		П.АтакаМин = 1;
		П.АтакаМакс = 3;
		П.Мана = 24;
		П.РостHP = 3;
		П.РостМаны = 4;
		П.Ресурс = "Мана";
		П.Описание = "30 HP, мана 24. Огненный шар и ледяная волна. Слаб в ближнем бою.";
	ИначеЕсли Класс = "Вор" Тогда
		П.HP = 36;
		П.АтакаМин = 2;
		П.АтакаМакс = 4;
		П.Мана = 0;
		П.РостHP = 4;
		П.РостМаны = 0;
		П.Ресурс = "";
		П.Описание = "36 HP. Бьёт дважды, враги ходят через раз, пока вы бежите. Удар в спину x3. Тень и метательные ножи.";
	ИначеЕсли Класс = "Стрелок" Тогда
		П.HP = 34;
		П.АтакаМин = 2;
		П.АтакаМакс = 4;
		П.Мана = 14;
		П.РостHP = 4;
		П.РостМаны = 2;
		П.Ресурс = "Фокус";
		П.Описание = "34 HP, фокус 14, крит 25%. Меткий выстрел и град стрел.";
	Иначе
		П.HP = 50;
		П.АтакаМин = 2;
		П.АтакаМакс = 5;
		П.Мана = 0;
		П.РостHP = 7;
		П.РостМаны = 0;
		П.Ресурс = "";
		П.Описание = "50 HP, самый прочный. Щит и мощный удар сквозь броню.";
	КонецЕсли;

	Возврат П;

КонецФункции

&НаКлиентеНаСервереБезКонтекста
Функция ДанныеСпособности(Класс, Номер)

	Д = Новый Структура("Имя, Стоимость, Перезарядка, Описание");

	Если Класс = "Маг" Тогда
		Если Номер = 1 Тогда
			Д.Имя = "Огненный шар";
			Д.Стоимость = 6;
			Д.Перезарядка = 0;
			Д.Описание = "урон ближайшему врагу в 5 клетках, игнорирует броню";
		Иначе
			Д.Имя = "Ледяная волна";
			Д.Стоимость = 9;
			Д.Перезарядка = 5;
			Д.Описание = "урон всем врагам в 2 клетках и заморозка на 2 хода";
		КонецЕсли;
	ИначеЕсли Класс = "Вор" Тогда
		Если Номер = 1 Тогда
			Д.Имя = "Тень";
			Д.Стоимость = 0;
			Д.Перезарядка = 10;
			Д.Описание = "невидимость на 4 хода, первый удар из тени x3";
		Иначе
			Д.Имя = "Метательные ножи";
			Д.Стоимость = 0;
			Д.Перезарядка = 3;
			Д.Описание = "два броска по ближайшему врагу в 4 клетках";
		КонецЕсли;
	ИначеЕсли Класс = "Стрелок" Тогда
		Если Номер = 1 Тогда
			Д.Имя = "Меткий выстрел";
			Д.Стоимость = 3;
			Д.Перезарядка = 0;
			Д.Описание = "двойной урон по ближайшему врагу в 6 клетках";
		Иначе
			Д.Имя = "Град стрел";
			Д.Стоимость = 8;
			Д.Перезарядка = 6;
			Д.Описание = "обычный урон всем врагам в 3 клетках";
		КонецЕсли;
	Иначе
		Если Номер = 1 Тогда
			Д.Имя = "Щит";
			Д.Стоимость = 0;
			Д.Перезарядка = 8;
			Д.Описание = "получаемый урон вдвое меньше, 3 хода";
		Иначе
			Д.Имя = "Мощный удар";
			Д.Стоимость = 0;
			Д.Перезарядка = 4;
			Д.Описание = "двойной урон соседнему врагу, игнорирует броню";
		КонецЕсли;
	КонецЕсли;

	Возврат Д;

КонецФункции

// Начало игры: выбор класса. Сама партия стартует в НачатьИгруСКлассом.
&НаКлиенте
Процедура НачатьИгру()

	Генератор = Новый ГенераторСлучайныхЧисел;
	ВыбратьКласс();

КонецПроцедуры

&НаКлиенте
Процедура ВыбратьКласс()

	Список = Новый СписокЗначений;
	Для Каждого Имя Из СтрРазделить("Воин,Маг,Вор,Стрелок", ",") Цикл
		Список.Добавить(Имя, Имя + " — " + ПараметрыКласса(Имя).Описание);
	КонецЦикла;

	Список.ПоказатьВыборЭлемента(Новый ОписаниеОповещения("ПослеВыбораКласса", ЭтотОбъект), "Выберите класс героя");

КонецПроцедуры

&НаКлиенте
Процедура ПослеВыбораКласса(Результат, ДопПараметры) Экспорт

	// Закрыли окно без выбора: берём воина.
	НачатьИгруСКлассом(?(Результат = Неопределено, "Воин", Результат.Значение));

КонецПроцедуры

&НаКлиенте
Процедура НачатьИгруСКлассом(Класс)

	КлассГероя = Класс;
	ТипХода = "";
	П = ПараметрыКласса(Класс);

	// Яд: сколько ходов ещё действует (0 = нет яда).
	// Победа: некромант убит (игра при этом продолжается).
	// Меч, Броня: надетые предметы (оружие и доспех класса); Сумка: массив предметов.
	// Мана: ресурс мага (мана) и стрелка (фокус); Щит, Невидим: ходов до конца эффекта;
	// КД1, КД2: перезарядка способностей; ШагиВора: счётчик шагов вора.
	Герой = Новый Структура("HP, МаксHP, АтакаМин, АтакаМакс, Уровень, Опыт, Золото, Жив, Победа, Яд, Горение, Меч, Броня, Сумка, Класс, Мана, МаксМана, Щит, Невидим, КД1, КД2, ШагиВора");
	Герой.HP = П.HP;
	Герой.МаксHP = П.HP;
	Герой.АтакаМин = П.АтакаМин;
	Герой.АтакаМакс = П.АтакаМакс;
	Герой.Уровень = 1;
	Герой.Опыт = 0;
	Герой.Золото = 0;
	Герой.Жив = Истина;
	Герой.Победа = Ложь;
	Герой.Яд = 0;
	Герой.Горение = 0;
	Герой.Меч = НовыйПредмет("Меч", 1);
	Герой.Броня = НовыйПредмет("Броня", 1);
	Герой.Сумка = Новый Массив;
	Герой.Класс = Класс;
	Герой.Мана = П.Мана;
	Герой.МаксМана = П.Мана;
	Герой.Щит = 0;
	Герой.Невидим = 0;
	Герой.КД1 = 0;
	Герой.КД2 = 0;
	Герой.ШагиВора = 0;

	// Игра начинается в деревне, подземелье появится при первом входе.
	ПодземельеСост = Неопределено;
	ДеревняСост = Неопределено;
	Подземелье2Сост = Неопределено;
	ЛюкX = 0;
	ЛюкY = 0;
	ВторойЭтажПосещён = Ложь;
	ВладыкаПобеждён = Ложь;
	ТорговцыОповещены = Ложь;
	КомнатыЭтажа = Неопределено;
	Локация = "Деревня";
	КвестСостояние = "Нет";
	Торговец = Неопределено;
	СгенерироватьДеревню(ШиринаДеревни(), ВысотаДеревни());

	Сообщение = "Вы в деревне, ваш класс: " + Класс + ". Вход в подземелье: тёмная арка в северной ограде. Шагните в жителя, чтобы поговорить с ним.";

	Отрисовать();

КонецПроцедуры

// ---------------------------------------------------------------------------
// Деревня и переходы между локациями
// ---------------------------------------------------------------------------

// Клетка, через которую нельзя пройти: стена или забор (#), дома (H, S, W, A, M), дерево (T), пруд (P).
&НаКлиенте
Функция Непроходимо(Символ)

	Возврат Символ = "#" Или Символ = "H" Или Символ = "S" Или Символ = "W" Или Символ = "A" Или Символ = "M" Или Символ = "T" Или Символ = "P";

КонецФункции

// Деревня: забор по краю, вход в подземелье (D) в середине северной ограды,
// дома (три особых: S староста, W оружейная, A бронная), деревья и жители.
// Дома стоят южнее северной рощи (y >= 7), колонки у входа остаются свободными.
// Заполняет Карта, Открыто (всё видно), Враги (пусто), Жители, ГероX, ГероY.
&НаКлиенте
Процедура СгенерироватьДеревню(ШиринаКарты, ВысотаКарты)

	Сетка = Новый Массив;
	Для ТY = 1 По ВысотаКарты Цикл
		СтрокаСетки = Новый Массив;
		Для ТX = 1 По ШиринаКарты Цикл
			Если ТY = 1 Или ТY = ВысотаКарты Или ТX = 1 Или ТX = ШиринаКарты Тогда
				СтрокаСетки.Добавить("#");
			Иначе
				СтрокаСетки.Добавить(".");
			КонецЕсли;
		КонецЦикла;
		Сетка.Добавить(СтрокаСетки);
	КонецЦикла;

	ВходДеревни = Цел(ШиринаКарты / 2);
	ДеревняВходX = ВходДеревни;
	Сетка[0][ВходДеревни - 1] = "D";

	// Дома 3x2: до 10 штук, без наложения, с зазором в одну клетку.
	// Первые три: дом старосты, оружейная, бронная. Клетка под их дверью свободна.
	Дома = Новый Массив;
	Двери = Новый Массив;
	Попытки = 0;
	Пока Попытки < 500 И Дома.Количество() < 10 Цикл

		Попытки = Попытки + 1;

		ДомX = Генератор.СлучайноеЧисло(3, ШиринаКарты - 5);
		ДомY = Генератор.СлучайноеЧисло(7, ВысотаКарты - 4);

		// Не загораживать проход к входу (±2 колонки от него).
		Если ДомX + 2 >= ВходДеревни - 2 И ДомX <= ВходДеревни + 2 Тогда
			Продолжить;
		КонецЕсли;

		Пересекается = Ложь;
		Для Каждого Другой Из Дома Цикл
			Разделены = (ДомX + 4 <= Другой.X) Или (Другой.X + 4 <= ДомX)
				Или (ДомY + 3 <= Другой.Y) Или (Другой.Y + 3 <= ДомY);
			Если Не Разделены Тогда
				Пересекается = Истина;
				Прервать;
			КонецЕсли;
		КонецЦикла;

		Если Пересекается Тогда
			Продолжить;
		КонецЕсли;

		НомерДома = Дома.Количество();
		Дома.Добавить(Новый Структура("X, Y", ДомX, ДомY));

		Если НомерДома = 0 Тогда
			СимволДома = "S";
		ИначеЕсли НомерДома = 1 Тогда
			СимволДома = "W";
		ИначеЕсли НомерДома = 2 Тогда
			СимволДома = "A";
		ИначеЕсли НомерДома = 3 Тогда
			СимволДома = "M";
		Иначе
			СимволДома = "H";
		КонецЕсли;

		Для ТY = ДомY По ДомY + 1 Цикл
			Для ТX = ДомX По ДомX + 2 Цикл
				Сетка[ТY - 1][ТX - 1] = СимволДома;
			КонецЦикла;
		КонецЦикла;

		Если НомерДома <= 3 Тогда
			Двери.Добавить(Новый Структура("X, Y", ДомX + 1, ДомY + 2));
			Сетка[ДомY + 1][ДомX] = "d"; // временная метка: деревья сюда не ставим
		КонецЕсли;

	КонецЦикла;

	// Нужны минимум четыре дома (староста, оружейник, бронник, лекарь), иначе генерируем заново.
	Если Дома.Количество() < 4 Тогда
		СгенерироватьДеревню(ШиринаКарты, ВысотаКарты);
		Возврат;
	КонецЕсли;

	// Пруд (P): овал примерно 5-7 на 3-4 клетки, с зазором в клетку от домов и забора,
	// не на колонках у входа. Рыбак Олаф живёт рядом с ним.
	ПрудX = 0;
	ПрудY = 0;
	ПрудШирина = 0;
	ПрудВысота = 0;
	Для НомерПопытки = 1 По 200 Цикл

		ШиринаПруда = Генератор.СлучайноеЧисло(5, 7);
		ВысотаПруда = Генератор.СлучайноеЧисло(3, 4);
		ПX = Генератор.СлучайноеЧисло(3, ШиринаКарты - ШиринаПруда - 1);
		ПY = Генератор.СлучайноеЧисло(3, ВысотаКарты - ВысотаПруда - 2);

		// Не загораживать проход к входу.
		Если ПX + ШиринаПруда - 1 >= ВходДеревни - 2 И ПX <= ВходДеревни + 2 Тогда
			Продолжить;
		КонецЕсли;

		// Область вместе с зазором в одну клетку должна быть свободной травой.
		Свободно = Истина;
		Для ТY = ПY - 1 По ПY + ВысотаПруда Цикл
			Для ТX = ПX - 1 По ПX + ШиринаПруда Цикл
				Если Сетка[ТY - 1][ТX - 1] <> "." Тогда
					Свободно = Ложь;
				КонецЕсли;
			КонецЦикла;
		КонецЦикла;

		Если Не Свободно Тогда
			Продолжить;
		КонецЕсли;

		Для ТY = ПY По ПY + ВысотаПруда - 1 Цикл
			Для ТX = ПX По ПX + ШиринаПруда - 1 Цикл
				Сетка[ТY - 1][ТX - 1] = "P";
			КонецЦикла;
		КонецЦикла;

		// Скругляем углы.
		Сетка[ПY - 1][ПX - 1] = ".";
		Сетка[ПY - 1][ПX + ШиринаПруда - 2] = ".";
		Сетка[ПY + ВысотаПруда - 2][ПX - 1] = ".";
		Сетка[ПY + ВысотаПруда - 2][ПX + ШиринаПруда - 2] = ".";

		ПрудX = ПX;
		ПрудY = ПY;
		ПрудШирина = ШиринаПруда;
		ПрудВысота = ВысотаПруда;
		Прервать;

	КонецЦикла;

	// Деревья: до 40 штук на свободной траве, колонки у входа не занимаем.
	Деревьев = 0;
	Для НомерПопытки = 1 По 300 Цикл

		Если Деревьев >= 40 Тогда
			Прервать;
		КонецЕсли;

		ДеревоX = Генератор.СлучайноеЧисло(2, ШиринаКарты - 1);
		ДеревоY = Генератор.СлучайноеЧисло(2, ВысотаКарты - 1);

		Если АбсЧисла(ДеревоX - ВходДеревни) <= 1 Тогда
			Продолжить;
		КонецЕсли;

		Если Сетка[ДеревоY - 1][ДеревоX - 1] <> "." Тогда
			Продолжить;
		КонецЕсли;

		Сетка[ДеревоY - 1][ДеревоX - 1] = "T";
		Деревьев = Деревьев + 1;

	КонецЦикла;

	Для Каждого Дверь Из Двери Цикл
		Сетка[Дверь.Y - 1][Дверь.X - 1] = ".";
	КонецЦикла;

	Карта = Новый Массив;
	Открыто = Новый Массив;
	Для Каждого СтрокаСетки Из Сетка Цикл
		ТекстСтроки = "";
		ТекстОткрыто = "";
		Для Каждого Символ Из СтрокаСетки Цикл
			ТекстСтроки = ТекстСтроки + Символ;
			ТекстОткрыто = ТекстОткрыто + "1"; // в деревне тумана нет
		КонецЦикла;
		Карта.Добавить(ТекстСтроки);
		Открыто.Добавить(ТекстОткрыто);
	КонецЦикла;

	Враги = Новый Массив;

	// Герой стоит у южной ограды под входом.
	ГероX = ВходДеревни;
	ГероY = ВысотаКарты - 2;

	// Жители. Староста и торговцы стоят у дверей своих домов и далеко от них не уходят.
	Жители = Новый Массив;
	ХодыДеревни = 0;
	Торговец = Неопределено;

	Жители.Добавить(НовыйЖитель("Староста Бран", "Староста", Двери[0].X, Двери[0].Y, 1,
		"Путник... погоди. Слышишь, как стихла деревня? Раньше к вечеру здесь пели и смеялись дети, а теперь двери запирают с первыми сумерками.~"
		+ "С тех пор как под северной оградой разверзлась тёмная арка, по ночам оттуда ползёт нечисть. Слизни травят колодцы, мёртвые лучники бьют стрелами в окна, и никто не смеет выйти за порог до рассвета.~"
		+ "Старики шепчутся, что внизу хозяйничает некромант. Спустись, одолей его и принеси мне любую записку, что найдёшь при нём: хочу знать, кто он и чего добивается. Деревня будет у тебя в долгу."));

	Жители.Добавить(НовыйЖитель("Оружейник Торвин", "Оружейник", Двери[1].X, Двери[1].Y, 1,
		"Сталь не лжёт, путник. Смотри, что у меня есть.~"
		+ "Добрый клинок дороже золота: он хотя бы возвращается домой вместе с тобой.~"
		+ "Принесёшь трофеи из подземелья, выкуплю по честной цене."));

	Жители.Добавить(НовыйЖитель("Бронник Хельга", "Бронник", Двери[2].X, Двери[2].Y, 1,
		"Броня должна быть тяжёлой настолько, чтобы ты вернулся.~"
		+ "Стрелы скелетов бьют метко: хорошая кольчуга порой решает всё.~"
		+ "Лишнюю броню из подземелья я выкуплю, не стесняйся."));

	Жители.Добавить(НовыйЖитель("Лекарь Эйра", "Лекарь", Двери[3].X, Двери[3].Y, 1,
		"Присаживайся, герой. Я зашью любую рану, лишь бы золота хватило.~"
		+ "Яд, огонь, переломы... за этот месяц у меня не было ни одного скучного дня.~"
		+ "Цена зависит от того, сколько здоровья ты оставил внизу."));

	Простые = Новый Массив;
	Простые.Добавить(Новый Структура("Имя, Реплики", "Бабка Марта",
		"Каждую ночь слышу скрежет у северной арки. Не к добру это, милок.~"
		+ "Была бы я помоложе, показала бы им! А так только молюсь за вас.~"
		+ "Староста у нас мудрый, да только и он ночами не спит."));
	Простые.Добавить(Новый Структура("Имя, Реплики", "Мальчишка Тим",
		"Вы правда ходили туда?! А скелеты настоящие? А они стреляют?~"
		+ "Как вырасту, тоже стану героем! Только мама не пускает.~"
		+ "Говорят, у слизня яд липкий. Фу!"));
	Простые.Добавить(Новый Структура("Имя, Реплики", "Рыбак Олаф",
		"Раньше в пруду рыба играла, а теперь вода мутная. Всё из-за той дыры.~"
		+ "Сети чиню, а сам одним глазом на арку. Мало ли что.~"
		+ "Принесёшь в деревню мир, выпьем за твоё здоровье."));
	Простые.Добавить(Новый Структура("Имя, Реплики", "Пекарь Эда",
		"Хлеб остыл, а покупателей нет: все сидят по домам.~"
		+ "Вернёшься целым, угощу пирогом.~"
		+ "Говорят, внизу тёмный маг. Страшно и подумать."));

	Для Каждого Данные Из Простые Цикл
		Для НомерПопытки = 1 По 300 Цикл
			Если Данные.Имя = "Рыбак Олаф" И ПрудШирина > 0 И НомерПопытки <= 100 Тогда
				// Рыбак сначала ищет место у пруда.
				ЖX = Генератор.СлучайноеЧисло(ПрудX - 2, ПрудX + ПрудШирина + 1);
				ЖY = Генератор.СлучайноеЧисло(ПрудY - 2, ПрудY + ПрудВысота + 1);
			Иначе
				ЖX = Генератор.СлучайноеЧисло(2, ШиринаКарты - 1);
				ЖY = Генератор.СлучайноеЧисло(2, ВысотаКарты - 1);
			КонецЕсли;
			Если ЖительМожетСтать(Неопределено, ЖX, ЖY) Тогда
				Жители.Добавить(НовыйЖитель(Данные.Имя, "Житель", ЖX, ЖY, 0, Данные.Реплики));
				Прервать;
			КонецЕсли;
		КонецЦикла;
	КонецЦикла;

КонецПроцедуры

// Житель: Реплики разделены символом ~, Номер — какая реплика прозвучит следующей.
// Привязка: радиус от начальной точки (0 = бродит где угодно).
&НаКлиенте
Функция НовыйЖитель(Имя, Роль, X, Y, Привязка, РепликиТекст)

	Житель = Новый Структура("Имя, Роль, X, Y, ДомX, ДомY, Привязка, Реплики, Номер");
	Житель.Имя = Имя;
	Житель.Роль = Роль;
	Житель.X = X;
	Житель.Y = Y;
	Житель.ДомX = X;
	Житель.ДомY = Y;
	Житель.Привязка = Привязка;
	Житель.Реплики = СтрРазделить(РепликиТекст, "~");
	Житель.Номер = 0;

	Возврат Житель;

КонецФункции

&НаКлиенте
Функция НайтиЖителя(X, Y)

	Если Жители = Неопределено Тогда
		Возврат Неопределено;
	КонецЕсли;

	Для Каждого Житель Из Жители Цикл
		Если Житель.X = X И Житель.Y = Y Тогда
			Возврат Житель;
		КонецЕсли;
	КонецЦикла;

	Возврат Неопределено;

КонецФункции

// Может ли житель стоять в клетке: не стена и не вход, не рядом со входом в
// подземелье (зона 9 клеток в ширину и 5 в глубину), не герой, не другой житель,
// и в пределах привязки к дому.
&НаКлиенте
Функция ЖительМожетСтать(Житель, X, Y)

	Если X < 2 Или Y < 2 Или Y > Карта.Количество() - 1 Или X > СтрДлина(Карта[0]) - 1 Тогда
		Возврат Ложь;
	КонецЕсли;

	Символ = Сред(Карта[Y - 1], X, 1);
	Если Непроходимо(Символ) Или Символ = "D" Тогда
		Возврат Ложь;
	КонецЕсли;

	Если Макс(АбсЧисла(X - ДеревняВходX), Y - 1) <= 4 Тогда
		Возврат Ложь;
	КонецЕсли;

	Если X = ГероX И Y = ГероY Тогда
		Возврат Ложь;
	КонецЕсли;

	Если НайтиЖителя(X, Y) <> Неопределено Тогда
		Возврат Ложь;
	КонецЕсли;

	Если Житель <> Неопределено И Житель.Привязка > 0
		И Макс(АбсЧисла(X - Житель.ДомX), АбсЧисла(Y - Житель.ДомY)) > Житель.Привязка Тогда
		Возврат Ложь;
	КонецЕсли;

	Возврат Истина;

КонецФункции

// Жители делают случайный шаг (или стоят) раз в 2 хода героя.
&НаКлиенте
Процедура ХодЖителей()

	ХодыДеревни = ХодыДеревни + 1;
	Если ХодыДеревни % 2 <> 0 Тогда
		Возврат;
	КонецЕсли;

	Для Каждого Житель Из Жители Цикл

		Направление = Генератор.СлучайноеЧисло(0, 4);
		ШагX = 0;
		ШагY = 0;

		Если Направление = 1 Тогда
			ШагX = 1;
		ИначеЕсли Направление = 2 Тогда
			ШагX = -1;
		ИначеЕсли Направление = 3 Тогда
			ШагY = 1;
		ИначеЕсли Направление = 4 Тогда
			ШагY = -1;
		КонецЕсли;

		Если ШагX = 0 И ШагY = 0 Тогда
			Продолжить;
		КонецЕсли;

		Если ЖительМожетСтать(Житель, Житель.X + ШагX, Житель.Y + ШагY) Тогда
			Житель.X = Житель.X + ШагX;
			Житель.Y = Житель.Y + ШагY;
		КонецЕсли;

	КонецЦикла;

КонецПроцедуры

// Следующая реплика жителя по кругу.
&НаКлиенте
Функция СледующаяРеплика(Житель)

	Реплика = Житель.Реплики[Житель.Номер];
	Житель.Номер = (Житель.Номер + 1) % Житель.Реплики.Количество();

	Возврат Реплика;

КонецФункции

// Герой шагнул в жителя: разговор. Ход не тратится.
&НаКлиенте
Процедура Поговорить(Житель)

	Торговец = Неопределено;

	Если Житель.Роль = "Староста" Тогда
		РазговорСоСтаростой(Житель);
	ИначеЕсли Житель.Роль = "Оружейник" Или Житель.Роль = "Бронник" Или Житель.Роль = "Лекарь" Тогда
		Торговец = Житель;
		Сообщение = Житель.Имя + ": " + СледующаяРеплика(Житель);
	Иначе
		Сообщение = Житель.Имя + ": " + СледующаяРеплика(Житель);
	КонецЕсли;

КонецПроцедуры

&НаКлиенте
Функция НайтиВСумке(Вид)

	Для Инд = 0 По Герой.Сумка.ВГраница() Цикл
		Если Герой.Сумка[Инд].Вид = Вид Тогда
			Возврат Инд;
		КонецЕсли;
	КонецЦикла;

	Возврат -1;

КонецФункции

// Староста: три реплики вступления (каждое обращение — следующая), после третьей
// квест принят. Потом: напоминание, сдача записки, благодарность.
&НаКлиенте
Процедура РазговорСоСтаростой(Житель)

	Если КвестСостояние = "Нет" Тогда

		Сообщение = Житель.Имя + ": " + Житель.Реплики[Житель.Номер];
		Житель.Номер = Житель.Номер + 1;

		Если Житель.Номер >= Житель.Реплики.Количество() Тогда
			КвестСостояние = "Активен";
			Сообщение = Сообщение + " [Новое задание: «Тени под деревней»]";
		КонецЕсли;

	ИначеЕсли КвестСостояние = "Активен" Тогда

		ИндексПисьма = НайтиВСумке("Письмо");

		Если ИндексПисьма < 0 Тогда
			Сообщение = Житель.Имя + ": Ты уже спускался вниз? Ищи записку некроманта: она должна лежать в сундуке на месте его гибели.";
		Иначе
			Герой.Сумка.Удалить(ИндексПисьма);
			КвестСостояние = "Сдан";
			Герой.Золото = Герой.Золото + 100;
			Сообщение = Житель.Имя + " развернул письмо, и лицо его потемнело. «Это не угроза. Это ключ к чему-то большему... Ты послужил деревне, герой. Возьми награду: сто золотых и вот это. Я нашёл этот ключ много лет назад у самой арки, ещё до того, как из неё поползла нечисть. Хранил и сам не знал зачем, а рука всё тянулась выбросить: от него веет такой жутью, что собаки воют по ночам, а молоко киснет в кувшинах. Он от другого подземелья, куда страшнее нашего. Береги его.» [Задание выполнено, +100 зол.]";
			ВзятьПредмет(НовыйПредмет("Ключ", 0), Истина);
			ПоставитьЛюк();
			Сообщение = Сообщение + " Едва ключ ложится в ладонь, ты чувствуешь, как далеко под землёй что-то изменилось: дрогнули камни под северной оградой, по деревне прокатился низкий гул, будто в глубине медленно повернулся исполинский замок. Там, внизу, открылось то, чего раньше не было.";
		КонецЕсли;

	Иначе

		Сообщение = Житель.Имя + ": Спасибо тебе, герой. Ключ береги: его замок ещё ждёт тебя.";

	КонецЕсли;

КонецПроцедуры

// Товары торговца: структуры Вид, Уровень. После 2 этажа ассортимент до 4 уровня.
// Названия оружия и брони зависят от класса героя (см. НазваниеПредмета);
// магам и стрелкам оружейник дополнительно продаёт зелье ресурса.
&НаКлиенте
Функция Ассортимент(Роль)

	Товары = Новый Массив;

	Если Роль = "Оружейник" Или Роль = "Бронник" Тогда

		Вид = ?(Роль = "Оружейник", "Меч", "Броня");

		Если ВторойЭтажПосещён Тогда
			Для Уровень = 2 По 4 Цикл
				Товары.Добавить(Новый Структура("Вид, Уровень", Вид, Уровень));
			КонецЦикла;
		Иначе
			Для Уровень = 1 По 2 Цикл
				Товары.Добавить(Новый Структура("Вид, Уровень", Вид, Уровень));
			КонецЦикла;
		КонецЕсли;

		Если Роль = "Оружейник" И (КлассГероя = "Маг" Или КлассГероя = "Стрелок") Тогда
			Товары.Добавить(Новый Структура("Вид, Уровень", "Зелье", 1));
		КонецЕсли;

	КонецЕсли;

	Возврат Товары;

КонецФункции

// Цена лечения у лекаря: 1,5 золота за каждый недостающий HP (+10, если есть яд или огонь).
&НаКлиенте
Функция ЦенаЛечения()

	Недостаток = Герой.МаксHP - Герой.HP;
	Цена = Цел(Недостаток * 3 / 2);

	Если Герой.Яд > 0 Или Герой.Горение > 0 Тогда
		Цена = Цена + 10;
	КонецЕсли;

	Возврат Цена;

КонецФункции

// Магазин закрывается, если герой отошёл от торговца дальше чем на 2 клетки
// или покинул деревню.
&НаКлиенте
Процедура ПроверитьТорговца()

	Если Торговец = Неопределено Тогда
		Возврат;
	КонецЕсли;

	Если Локация <> "Деревня"
		Или Макс(АбсЧисла(Торговец.X - ГероX), АбсЧисла(Торговец.Y - ГероY)) > 2 Тогда
		Торговец = Неопределено;
	КонецЕсли;

КонецПроцедуры

// Строки панели магазина для отрисовки: Текст, Код (для двойного щелчка), Заголовок.
// Код: "К<номер>" — купить товар, "П<номер>" — продать предмет из сумки.
&НаКлиенте
Функция СтрокиМагазина()

	Строки = Новый Массив;

	Если Торговец = Неопределено Тогда
		Возврат Строки;
	КонецЕсли;

	Строки.Добавить(Новый Структура("Текст, Код, Заголовок",
		?(Торговец.Роль = "Лекарь", "ЛЕЧЕНИЕ: ", "МАГАЗИН: ") + Торговец.Имя + "   у вас " + Герой.Золото + " зол.   (двойной щелчок по строке: купить или продать)",
		Неопределено, Истина));

	// Лекарь: одна строка «лечить».
	Если Торговец.Роль = "Лекарь" Тогда

		Цена = ЦенаЛечения();

		Если Цена = 0 Тогда
			Строки.Добавить(Новый Структура("Текст, Код, Заголовок", "Вы здоровы: лечение не требуется.", Неопределено, Ложь));
		Иначе
			Недостаток = Герой.МаксHP - Герой.HP;
			Строки.Добавить(Новый Структура("Текст, Код, Заголовок",
				"Лечить: +" + Недостаток + " HP" + ?(Герой.Яд > 0 Или Герой.Горение > 0, " и снять яд и огонь", "")
				+ " — " + Цена + " зол.", "Л", Ложь));
		КонецЕсли;

		Возврат Строки;

	КонецЕсли;

	Товары = Ассортимент(Торговец.Роль);
	Для Инд = 0 По Товары.ВГраница() Цикл
		Предмет = НовыйПредмет(Товары[Инд].Вид, Товары[Инд].Уровень);
		Строки.Добавить(Новый Структура("Текст, Код, Заголовок",
			"Купить: " + ОписаниеПредмета(Предмет) + " — " + ЦенаПокупки(Предмет.Вид, Предмет.Уровень) + " зол.",
			"К" + XMLСтрока(Инд), Ложь));
	КонецЦикла;

	Для Инд = 0 По Герой.Сумка.ВГраница() Цикл
		Предмет = Герой.Сумка[Инд];
		Цена = ЦенаПродажи(Предмет);
		Если Цена > 0 Тогда
			Строки.Добавить(Новый Структура("Текст, Код, Заголовок",
				"Продать: " + ОписаниеПредмета(Предмет) + " — " + Цена + " зол.",
				"П" + XMLСтрока(Инд), Ложь));
		КонецЕсли;
	КонецЦикла;

	Возврат Строки;

КонецФункции

// Двойной щелчок по строке магазина.
&НаКлиенте
Процедура ОбработатьТорговлю(Код)

	Если Торговец = Неопределено Или Не Герой.Жив Тогда
		Возврат;
	КонецЕсли;

	Действие = Лев(Код, 1);
	Номер = Число(Сред(Код, 2));

	Если Действие = "К" Тогда

		Товары = Ассортимент(Торговец.Роль);
		Если Номер > Товары.ВГраница() Тогда
			Возврат;
		КонецЕсли;

		Предмет = НовыйПредмет(Товары[Номер].Вид, Товары[Номер].Уровень);
		Цена = ЦенаПокупки(Предмет.Вид, Предмет.Уровень);

		Если Герой.Золото < Цена Тогда
			Сообщение = "Не хватает золота: нужно " + Цена + ", у вас " + Герой.Золото + ".";
		ИначеЕсли Герой.Сумка.Количество() >= ВместимостьСумки() Тогда
			Сообщение = "Сумка полна: продайте или наденьте что-нибудь.";
		Иначе
			Герой.Золото = Герой.Золото - Цена;
			Герой.Сумка.Добавить(Предмет);
			Сообщение = "Куплено: " + ОписаниеПредмета(Предмет) + " за " + Цена + " зол.";
		КонецЕсли;

	ИначеЕсли Действие = "Л" Тогда

		Цена = ЦенаЛечения();

		Если Цена = 0 Тогда
			Сообщение = Торговец.Имя + ": Ты здоров, лечить нечего.";
		ИначеЕсли Герой.Золото < Цена Тогда
			Сообщение = Торговец.Имя + ": Нужно " + Цена + " зол., а у тебя " + Герой.Золото + ". Возвращайся с монетой.";
		Иначе
			Герой.Золото = Герой.Золото - Цена;
			Герой.HP = Герой.МаксHP;
			Герой.Мана = Герой.МаксМана;
			Герой.Яд = 0;
			Герой.Горение = 0;
			Сообщение = Торговец.Имя + " накладывает повязки и шепчет над отваром: раны затянулись, яд и огонь ушли. [-" + Цена + " зол.]";
		КонецЕсли;

	ИначеЕсли Действие = "П" Тогда

		Если Номер > Герой.Сумка.ВГраница() Тогда
			Возврат;
		КонецЕсли;

		Предмет = Герой.Сумка[Номер];
		Цена = ЦенаПродажи(Предмет);

		Если Цена = 0 Тогда
			Сообщение = "Это не продаётся.";
		Иначе
			Герой.Сумка.Удалить(Номер);
			Герой.Золото = Герой.Золото + Цена;
			Сообщение = "Продано: " + ОписаниеПредмета(Предмет) + " за " + Цена + " зол.";
		КонецЕсли;

	КонецЕсли;

	Отрисовать();

КонецПроцедуры

// Список квестов под легендой: первая строка — заголовок.
&НаКлиенте
Функция СтрокиКвестов()

	Строки = Новый Массив;
	Строки.Добавить("КВЕСТЫ");

	Если КвестСостояние = "Нет" Тогда
		Строки.Добавить("Пока нет заданий.");
		Строки.Добавить("Поговорите со старостой.");
	ИначеЕсли КвестСостояние = "Активен" Тогда
		Строки.Добавить("Тени под деревней");
		Строки.Добавить("Одолеть некроманта и отнести старосте");
		Строки.Добавить("его записку.");
		Строки.Добавить("Некромант повержен: " + ?(Герой.Победа, "да", "нет"));
		Строки.Добавить("Записка в сумке: " + ?(НайтиВСумке("Письмо") >= 0, "да", "нет"));
	Иначе
		Строки.Добавить("Тени под деревней: выполнено");
		Строки.Добавить("Тайна ключа");
		Если ВладыкаПобеждён Тогда
			Строки.Добавить("Тёмный владыка повержен.");
			Строки.Добавить("Но из глубин слышен смех...");
		ИначеЕсли ВторойЭтажПосещён Тогда
			Строки.Добавить("Одолейте стражей, затем тёмного владыку.");
			Строки.Добавить("Пока стражи живы, владыка неуязвим.");
		Иначе
			Строки.Добавить("В подземелье открылся люк. Найдите его");
			Строки.Добавить("и спуститесь с ключом на 2 этаж.");
		КонецЕсли;
	КонецЕсли;

	Возврат Строки;

КонецФункции

// Состояние текущего этажа подземелья: сохраняется при уходе с этажа.
&НаКлиенте
Функция СохранитьЭтаж()

	Возврат Новый Структура("Карта, Открыто, Враги, ВходX, ВходY, Комнаты",
		Карта, Открыто, Враги, ВходX, ВходY, КомнатыЭтажа);

КонецФункции

&НаКлиенте
Процедура ЗагрузитьЭтаж(Состояние)

	Карта = Состояние.Карта;
	Открыто = Состояние.Открыто;
	Враги = Состояние.Враги;
	ВходX = Состояние.ВходX;
	ВходY = Состояние.ВходY;
	КомнатыЭтажа = Состояние.Комнаты;

КонецПроцедуры

// Шаг в клетку D: запоминаем деревню и переходим в подземелье (1 этаж).
// Этаж генерируется только при первом входе.
&НаКлиенте
Процедура ВойтиВПодземелье()

	ДеревняСост = Новый Структура("Карта, Открыто, Враги, ГероX, ГероY", Карта, Открыто, Враги, ГероX, ГероY);

	Если ПодземельеСост = Неопределено Тогда

		// Заполняет Карта, Открыто, Враги, ВходX, ВходY, КомнатыЭтажа и ставит героя у входа.
		СгенерироватьУровень(ШиринаУровня(), ВысотаУровня(), 1);
		Сообщение = "Вы спускаетесь в тёмное подземелье: видны только соседние клетки. Где-то в нём ждёт некромант.";

	Иначе

		ЗагрузитьЭтаж(ПодземельеСост);
		ГероX = ВходX;
		ГероY = ВходY;
		Сообщение = "Вы снова спускаетесь в подземелье: всё осталось, как вы его оставили.";

	КонецЕсли;

	Локация = "Подземелье";
	ОткрытьОкрестность();

КонецПроцедуры

// Шаг в клетку E: сохраняем 1 этаж (враги остаются в нём) и возвращаемся в деревню.
&НаКлиенте
Процедура ВыйтиИзПодземелья()

	ПодземельеСост = СохранитьЭтаж();

	Карта = ДеревняСост.Карта;
	Открыто = ДеревняСост.Открыто;
	Враги = ДеревняСост.Враги;
	ГероX = ДеревняСост.ГероX;
	ГероY = ДеревняСост.ГероY;

	Локация = "Деревня";
	Сообщение = "Вы вышли из подземелья в деревню. Подземелье сохранится таким, каким вы его оставили.";

	Если ВторойЭтажПосещён И Не ТорговцыОповещены Тогда
		ТорговцыОповещены = Истина;
		Сообщение = Сообщение + " Слух о том, что вы побывали на нижнем этаже, облетел деревню: торговцы выложили лучший товар, до 4 уровня.";
	КонецЕсли;

КонецПроцедуры

// Шаг в люк с ключом: сохраняем 1 этаж и проваливаемся на 2 этаж.
// 2 этаж генерируется один раз (мобы, сундуки, зелья и босс — тёмный владыка).
&НаКлиенте
Процедура СпуститьсяНаВторойЭтаж()

	ПодземельеСост = СохранитьЭтаж();

	Если Подземелье2Сост = Неопределено Тогда

		СгенерироватьУровень(ШиринаУровня(), ВысотаУровня(), 2);
		Сообщение = "Замок щёлкает, люк распахивается, и пол уходит из-под ног. Вы падаете во тьму и приземляетесь на горячий камень второго этажа. Воздух пахнет серой и жжёной костью, а в темноте шевелится что-то многочисленное.";

	Иначе

		ЗагрузитьЭтаж(Подземелье2Сост);
		ГероX = ВходX;
		ГероY = ВходY;
		Сообщение = "Вы снова проваливаетесь во тьму второго этажа.";

	КонецЕсли;

	ВторойЭтажПосещён = Истина;
	Локация = "Подземелье, 2 этаж";
	ОткрытьОкрестность();

КонецПроцедуры

// Шаг на лестницу (<): сохраняем 2 этаж и поднимаемся на 1 этаж рядом с люком.
&НаКлиенте
Процедура ПоднятьсяНаПервыйЭтаж()

	Подземелье2Сост = СохранитьЭтаж();
	ЗагрузитьЭтаж(ПодземельеСост);

	Место = МестоРядомСЛюком();
	ГероX = Место.X;
	ГероY = Место.Y;

	Локация = "Подземелье";
	Сообщение = "Вы карабкаетесь по скользким ступеням и выбираетесь наверх, рядом с люком первого этажа.";
	ОткрытьОкрестность();

КонецПроцедуры

// Свободная клетка пола рядом с люком (сначала вплотную, затем в радиусе 2).
&НаКлиенте
Функция МестоРядомСЛюком()

	Если ЛюкX = 0 Тогда
		Возврат Новый Структура("X, Y", ВходX, ВходY);
	КонецЕсли;

	// Герой ещё числится в координатах 2 этажа: временно ставим его на люк.
	ГероX = ЛюкX;
	ГероY = ЛюкY;

	Для Радиус = 1 По 2 Цикл
		Для СмещениеY = -Радиус По Радиус Цикл
			Для СмещениеX = -Радиус По Радиус Цикл

				ТX = ЛюкX + СмещениеX;
				ТY = ЛюкY + СмещениеY;

				Если ТX < 1 Или ТY < 1 Или ТY > Карта.Количество() Или ТX > СтрДлина(Карта[0]) Тогда
					Продолжить;
				КонецЕсли;

				Если Сред(Карта[ТY - 1], ТX, 1) = "." И КлеткаСвободна(ТX, ТY) Тогда
					Возврат Новый Структура("X, Y", ТX, ТY);
				КонецЕсли;

			КонецЦикла;
		КонецЦикла;
	КонецЦикла;

	Возврат Новый Структура("X, Y", ВходX, ВходY);

КонецФункции

// После сдачи квеста: в случайной комнате 1 этажа (кроме стартовой) появляется люк.
// Клетка люка сразу отмечается на карте как увиденная.
&НаКлиенте
Процедура ПоставитьЛюк()

	Если ЛюкX <> 0 Или ПодземельеСост = Неопределено Тогда
		Возврат;
	КонецЕсли;

	Состояние = ПодземельеСост;
	Комнаты = Состояние.Комнаты;

	Для НомерПопытки = 1 По 300 Цикл

		Комната = Комнаты[Генератор.СлучайноеЧисло(1, Комнаты.ВГраница())];
		X = Генератор.СлучайноеЧисло(Комната.X + 1, Комната.X + Комната.Ширина - 2);
		Y = Генератор.СлучайноеЧисло(Комната.Y + 1, Комната.Y + Комната.Высота - 2);

		Если Сред(Состояние.Карта[Y - 1], X, 1) <> "." Тогда
			Продолжить;
		КонецЕсли;

		Занят = Ложь;
		Для Каждого Враг Из Состояние.Враги Цикл
			Если Враг.X = X И Враг.Y = Y Тогда
				Занят = Истина;
				Прервать;
			КонецЕсли;
		КонецЦикла;

		Если Занят Тогда
			Продолжить;
		КонецЕсли;

		СтрокаКарты = Состояние.Карта[Y - 1];
		Состояние.Карта[Y - 1] = Лев(СтрокаКарты, X - 1) + "L" + Сред(СтрокаКарты, X + 1);

		СтрокаОткрыто = Состояние.Открыто[Y - 1];
		Состояние.Открыто[Y - 1] = Лев(СтрокаОткрыто, X - 1) + "1" + Сред(СтрокаОткрыто, X + 1);

		ЛюкX = X;
		ЛюкY = Y;
		Возврат;

	КонецЦикла;

КонецПроцедуры

// Свободная клетка пола комнаты не ближе 6 клеток к герою (для усиленных мобов).
&НаКлиенте
Функция СвободнаяКлеткаДляМоба(Комната)

	Для НомерПопытки = 1 По 30 Цикл

		X = Генератор.СлучайноеЧисло(Комната.X, Комната.X + Комната.Ширина - 1);
		Y = Генератор.СлучайноеЧисло(Комната.Y, Комната.Y + Комната.Высота - 1);

		Если Сред(Карта[Y - 1], X, 1) <> "." Тогда
			Продолжить;
		КонецЕсли;

		Если Не КлеткаСвободна(X, Y) Тогда
			Продолжить;
		КонецЕсли;

		Если Макс(АбсЧисла(X - ГероX), АбсЧисла(Y - ГероY)) <= 5 Тогда
			Продолжить;
		КонецЕсли;

		Возврат Новый Структура("X, Y", X, Y);

	КонецЦикла;

	Возврат Неопределено;

КонецФункции

// После письма некроманта 1 этаж заполняется драуграми: в каждой комнате 1-2.
&НаКлиенте
Процедура НаселитьДрауграми()

	Для Каждого Комната Из КомнатыЭтажа Цикл

		ЧислоМобов = ?(Генератор.СлучайноеЧисло(1, 100) <= 40, 2, 1);

		Для Номер = 1 По ЧислоМобов Цикл
			Точка = СвободнаяКлеткаДляМоба(Комната);
			Если Точка <> Неопределено Тогда
				Враги.Добавить(НовыйВраг("Драугр", Точка.X, Точка.Y));
			КонецЕсли;
		КонецЦикла;

	КонецЦикла;

КонецПроцедуры

// ---------------------------------------------------------------------------
// Генерация уровня
// ---------------------------------------------------------------------------

&НаКлиенте
Процедура СгенерироватьУровень(ШиринаКарты, ВысотаКарты, Этаж)

	// Сетка: массив строк, каждая строка массив символов. Сначала всё стены.
	Сетка = Новый Массив;
	Для ТY = 1 По ВысотаКарты Цикл
		СтрокаСетки = Новый Массив;
		Для ТX = 1 По ШиринаКарты Цикл
			СтрокаСетки.Добавить("#");
		КонецЦикла;
		Сетка.Добавить(СтрокаСетки);
	КонецЦикла;

	// Комнаты: до МаксКомнат() штук, размером 4-10 на 3-6, между комнатами минимум одна стена.
	Комнаты = Новый Массив;
	Попытки = 0;
	Пока Попытки < 1000 И Комнаты.Количество() < МаксКомнат() Цикл

		Попытки = Попытки + 1;

		ШиринаКомнаты = Генератор.СлучайноеЧисло(4, 10);
		ВысотаКомнаты = Генератор.СлучайноеЧисло(3, 6);
		КомнатаX = Генератор.СлучайноеЧисло(2, ШиринаКарты - ШиринаКомнаты);
		КомнатаY = Генератор.СлучайноеЧисло(2, ВысотаКарты - ВысотаКомнаты);

		Пересекается = Ложь;
		Для Каждого Другая Из Комнаты Цикл
			Разделены = (КомнатаX + ШиринаКомнаты + 1 <= Другая.X)
				Или (Другая.X + Другая.Ширина + 1 <= КомнатаX)
				Или (КомнатаY + ВысотаКомнаты + 1 <= Другая.Y)
				Или (Другая.Y + Другая.Высота + 1 <= КомнатаY);
			Если Не Разделены Тогда
				Пересекается = Истина;
				Прервать;
			КонецЕсли;
		КонецЦикла;

		Если Пересекается Тогда
			Продолжить;
		КонецЕсли;

		Комната = Новый Структура("X, Y, Ширина, Высота, ЦентрX, ЦентрY");
		Комната.X = КомнатаX;
		Комната.Y = КомнатаY;
		Комната.Ширина = ШиринаКомнаты;
		Комната.Высота = ВысотаКомнаты;
		Комната.ЦентрX = КомнатаX + Цел(ШиринаКомнаты / 2);
		Комната.ЦентрY = КомнатаY + Цел(ВысотаКомнаты / 2);
		Комнаты.Добавить(Комната);

	КонецЦикла;

	// Если комнат слишком мало (почти невозможно), просто пробуем сгенерировать заново.
	Если Комнаты.Количество() < 8 Тогда
		СгенерироватьУровень(ШиринаКарты, ВысотаКарты, Этаж);
		Возврат;
	КонецЕсли;

	// Стартовая комната (индекс 0) — самая левая: вход в подземелье на левом краю карты.
	ИндексЛевой = 0;
	Для Инд = 1 По Комнаты.ВГраница() Цикл
		Если Комнаты[Инд].X < Комнаты[ИндексЛевой].X Тогда
			ИндексЛевой = Инд;
		КонецЕсли;
	КонецЦикла;
	Если ИндексЛевой <> 0 Тогда
		Временная = Комнаты[0];
		Комнаты[0] = Комнаты[ИндексЛевой];
		Комнаты[ИндексЛевой] = Временная;
	КонецЕсли;

	// Вырезаем комнаты.
	Для Каждого Комната Из Комнаты Цикл
		Для ТY = Комната.Y По Комната.Y + Комната.Высота - 1 Цикл
			Для ТX = Комната.X По Комната.X + Комната.Ширина - 1 Цикл
				Сетка[ТY - 1][ТX - 1] = ".";
			КонецЦикла;
		КонецЦикла;
	КонецЦикла;

	// Коридоры: каждая комната соединяется с предыдущей, поворот в случайную сторону.
	Для Инд = 1 По Комнаты.ВГраница() Цикл

		Предыдущая = Комнаты[Инд - 1];
		Текущая = Комнаты[Инд];

		Если Генератор.СлучайноеЧисло(0, 1) = 0 Тогда
			ПрорубитьОтрезок(Сетка, Предыдущая.ЦентрX, Предыдущая.ЦентрY, Текущая.ЦентрX, Предыдущая.ЦентрY);
			ПрорубитьОтрезок(Сетка, Текущая.ЦентрX, Предыдущая.ЦентрY, Текущая.ЦентрX, Текущая.ЦентрY);
		Иначе
			ПрорубитьОтрезок(Сетка, Предыдущая.ЦентрX, Предыдущая.ЦентрY, Предыдущая.ЦентрX, Текущая.ЦентрY);
			ПрорубитьОтрезок(Сетка, Предыдущая.ЦентрX, Текущая.ЦентрY, Текущая.ЦентрX, Текущая.ЦентрY);
		КонецЕсли;

	КонецЦикла;

	// Вход/выход (E) в левой границе карты и коридор до центра стартовой комнаты.
	// Герой появляется на клетке рядом с выходом.
	ВходY = Комнаты[0].ЦентрY;
	ПрорубитьОтрезок(Сетка, 1, ВходY, Комнаты[0].ЦентрX, ВходY);
	Сетка[ВходY - 1][0] = ?(Этаж = 2, "<", "E"); // на 2 этаже вместо выхода лестница вверх
	ВходX = 2;
	ГероX = ВходX;
	ГероY = ВходY;

	Враги = Новый Массив;

	// Босс, враги и предметы: набор зависит от этажа (1 — некромант, 2 — тёмный владыка).
	// Босс: в комнате, самой дальней от старта (по сумме разностей координат).
	ИндексБосса = 1;
	МаксДистанция = -1;
	Для Инд = 1 По Комнаты.ВГраница() Цикл
		Дистанция = АбсЧисла(Комнаты[Инд].ЦентрX - ГероX) + АбсЧисла(Комнаты[Инд].ЦентрY - ГероY);
		Если Дистанция > МаксДистанция Тогда
			МаксДистанция = Дистанция;
			ИндексБосса = Инд;
		КонецЕсли;
	КонецЦикла;

	Враги = Новый Массив;
	Если Этаж = 1 Тогда
		Враги.Добавить(НовыйВраг("Некромант", Комнаты[ИндексБосса].ЦентрX, Комнаты[ИндексБосса].ЦентрY));
	Иначе
		// Тёмный владыка в центре зала, по бокам два стража.
		ЦентрБоссаX = Комнаты[ИндексБосса].ЦентрX;
		ЦентрБоссаY = Комнаты[ИндексБосса].ЦентрY;
		Враги.Добавить(НовыйВраг("Тёмный владыка", ЦентрБоссаX, ЦентрБоссаY));
		Враги.Добавить(НовыйВраг("Багряный палач", ЦентрБоссаX - 1, ЦентрБоссаY));
		Враги.Добавить(НовыйВраг("Грозовой жнец", ЦентрБоссаX + 1, ЦентрБоссаY));
	КонецЕсли;

	// Обычные враги: 2-3 в каждой комнате, кроме стартовой и комнаты босса.
	// Тип каждого: 30% скелет, 18% бронированный скелет, иначе слизень.
	Для Инд = 1 По Комнаты.ВГраница() Цикл

		Если Инд = ИндексБосса Тогда
			Продолжить;
		КонецЕсли;

		ЧислоВрагов = Генератор.СлучайноеЧисло(2, 3);
		Для Номер = 1 По ЧислоВрагов Цикл

			БросокТипа = Генератор.СлучайноеЧисло(1, 100);
			Если Этаж = 1 Тогда
				Тип = "Слизень";
				Если БросокТипа <= 30 Тогда
					Тип = "Скелет";
				ИначеЕсли БросокТипа <= 48 Тогда
					Тип = "Бронированный скелет";
				КонецЕсли;
			Иначе
				// 2 этаж: 40% адское отродье, 35% пиромант, 25% демон.
				Тип = "Демон";
				Если БросокТипа <= 40 Тогда
					Тип = "Адское отродье";
				ИначеЕсли БросокТипа <= 75 Тогда
					Тип = "Пиромант";
				КонецЕсли;
			КонецЕсли;

			Точка = СлучайнаяКлеткаКомнаты(Сетка, Комнаты[Инд]);
			Если Точка <> Неопределено Тогда
				Враги.Добавить(НовыйВраг(Тип, Точка.X, Точка.Y));
			КонецЕсли;

		КонецЦикла;

	КонецЦикла;

	// Предметы: сундук с шансом 50% и зелье с шансом 30% в каждой комнате.
	Для Каждого Комната Из Комнаты Цикл

		Если Генератор.СлучайноеЧисло(1, 100) <= 50 Тогда
			Точка = СлучайнаяКлеткаКомнаты(Сетка, Комната);
			Если Точка <> Неопределено Тогда
				Сетка[Точка.Y - 1][Точка.X - 1] = "$";
			КонецЕсли;
		КонецЕсли;

		Если Генератор.СлучайноеЧисло(1, 100) <= 30 Тогда
			Точка = СлучайнаяКлеткаКомнаты(Сетка, Комната);
			Если Точка <> Неопределено Тогда
				Сетка[Точка.Y - 1][Точка.X - 1] = "!";
			КонецЕсли;
		КонецЕсли;

	КонецЦикла;

	// Собираем карту в массив строк.
	Карта = Новый Массив;
	Для Каждого СтрокаСетки Из Сетка Цикл
		ТекстСтроки = "";
		Для Каждого Символ Из СтрокаСетки Цикл
			ТекстСтроки = ТекстСтроки + Символ;
		КонецЦикла;
		Карта.Добавить(ТекстСтроки);
	КонецЦикла;

	// Ничего не исследовано: "0" = туман, "1" = клетку уже видели.
	Открыто = Новый Массив;
	Для Каждого СтрокаКарты Из Карта Цикл
		ТекстСтроки = "";
		Для Позиция = 1 По СтрДлина(СтрокаКарты) Цикл
			ТекстСтроки = ТекстСтроки + "0";
		КонецЦикла;
		Открыто.Добавить(ТекстСтроки);
	КонецЦикла;

	КомнатыЭтажа = Комнаты;

КонецПроцедуры

// Вырезает прямой отрезок (горизонтальный или вертикальный) в сетке.
&НаКлиенте
Процедура ПрорубитьОтрезок(Сетка, X1, Y1, X2, Y2)

	ШагX = ЗнакЧисла(X2 - X1);
	ШагY = ЗнакЧисла(Y2 - Y1);

	ТX = X1;
	ТY = Y1;

	Пока Истина Цикл

		Сетка[ТY - 1][ТX - 1] = ".";

		Если ТX = X2 И ТY = Y2 Тогда
			Прервать;
		КонецЕсли;

		ТX = ТX + ШагX;
		ТY = ТY + ШагY;

	КонецЦикла;

КонецПроцедуры

// Случайная пустая клетка пола в комнате (без героя, врагов и предметов).
// Возвращает структуру X, Y или Неопределено, если за 30 попыток не нашлась.
&НаКлиенте
Функция СлучайнаяКлеткаКомнаты(Сетка, Комната)

	Для НомерПопытки = 1 По 30 Цикл

		X = Генератор.СлучайноеЧисло(Комната.X, Комната.X + Комната.Ширина - 1);
		Y = Генератор.СлучайноеЧисло(Комната.Y, Комната.Y + Комната.Высота - 1);

		Если Сетка[Y - 1][X - 1] <> "." Тогда
			Продолжить;
		КонецЕсли;

		Если X = ГероX И Y = ГероY Тогда
			Продолжить;
		КонецЕсли;

		Если НайтиИндексВрага(X, Y) >= 0 Тогда
			Продолжить;
		КонецЕсли;

		Возврат Новый Структура("X, Y", X, Y);

	КонецЦикла;

	Возврат Неопределено;

КонецФункции

// ---------------------------------------------------------------------------
// Враги
// ---------------------------------------------------------------------------

// Параметры врагов задаются здесь: HP, диапазон урона, награда.
&НаКлиенте
Функция НовыйВраг(Тип, X, Y)

	Враг = Новый Структура("Тип, X, Y, HP, МаксHP, АтакаМин, АтакаМакс, Опыт, Золото, Призванный, Таймер, Проснулся, Заметил, Броня, Заморожен");
	Враг.Тип = Тип;
	Враг.X = X;
	Враг.Y = Y;
	Враг.Заметил = Ложь;
	Враг.Призванный = Ложь;
	Враг.Таймер = 0;
	Враг.Проснулся = Ложь;
	Враг.Броня = 0; // сколько урона героя поглощает за удар
	Враг.Заморожен = 0; // сколько ходов враг ещё пропустит (ледяная волна мага)

	Если Тип = "Некромант" Тогда
		Враг.МаксHP = 60;
		Враг.АтакаМин = 4;
		Враг.АтакаМакс = 7;
		Враг.Опыт = 40;
		Враг.Золото = 50;
	ИначеЕсли Тип = "Адское отродье" Тогда
		Враг.МаксHP = 34;
		Враг.АтакаМин = 6;
		Враг.АтакаМакс = 10;
		Враг.Опыт = 30;
		Враг.Золото = 20;
	ИначеЕсли Тип = "Пиромант" Тогда
		Враг.МаксHP = 28;
		Враг.АтакаМин = 4;
		Враг.АтакаМакс = 6;
		Враг.Опыт = 35;
		Враг.Золото = 25;
	ИначеЕсли Тип = "Демон" Тогда
		Враг.МаксHP = 60;
		Враг.АтакаМин = 9;
		Враг.АтакаМакс = 13;
		Враг.Опыт = 55;
		Враг.Золото = 35;
		Враг.Броня = 4;
	ИначеЕсли Тип = "Багряный палач" Тогда
		Враг.МаксHP = 75;
		Враг.АтакаМин = 8;
		Враг.АтакаМакс = 12;
		Враг.Опыт = 90;
		Враг.Золото = 60;
		Враг.Броня = 2;
	ИначеЕсли Тип = "Грозовой жнец" Тогда
		Враг.МаксHP = 55;
		Враг.АтакаМин = 10;
		Враг.АтакаМакс = 14;
		Враг.Опыт = 90;
		Враг.Золото = 60;
		Враг.Броня = 1;
	ИначеЕсли Тип = "Тёмный владыка" Тогда
		Враг.МаксHP = 170;
		Враг.АтакаМин = 11;
		Враг.АтакаМакс = 16;
		Враг.Опыт = 300;
		Враг.Золото = 250;
		Враг.Броня = 3;
	ИначеЕсли Тип = "Драугр" Тогда
		Враг.МаксHP = 45;
		Враг.АтакаМин = 7;
		Враг.АтакаМакс = 11;
		Враг.Опыт = 45;
		Враг.Золото = 25;
		Враг.Броня = 3;
	ИначеЕсли Тип = "Бронированный скелет" Тогда
		Враг.МаксHP = 26;
		Враг.АтакаМин = 5;
		Враг.АтакаМакс = 8;
		Враг.Опыт = 25;
		Враг.Золото = 15;
		Враг.Броня = 2;
	ИначеЕсли Тип = "Скелет" Тогда
		Враг.МаксHP = 16;
		Враг.АтакаМин = 3;
		Враг.АтакаМакс = 5;
		Враг.Опыт = 12;
		Враг.Золото = 8;
	Иначе // Слизень
		Враг.МаксHP = 10;
		Враг.АтакаМин = 2;
		Враг.АтакаМакс = 4;
		Враг.Опыт = 5;
		Враг.Золото = 3;
	КонецЕсли;

	Враг.HP = Враг.МаксHP;

	Возврат Враг;

КонецФункции

&НаКлиенте
Функция ИмяВрага(Враг)

	Если Враг.Призванный Тогда
		Возврат "Призванный скелет";
	КонецЕсли;

	Возврат Враг.Тип;

КонецФункции

&НаКлиенте
Функция НайтиИндексВрага(X, Y)

	Для Инд = 0 По Враги.ВГраница() Цикл
		Если Враги[Инд].X = X И Враги[Инд].Y = Y Тогда
			Возврат Инд;
		КонецЕсли;
	КонецЦикла;

	Возврат -1;

КонецФункции

// Клетка свободна: не стена, не герой и не другой враг.
&НаКлиенте
Функция КлеткаСвободна(X, Y)

	Если X < 1 Или Y < 1 Или Y > Карта.Количество() Или X > СтрДлина(Карта[0]) Тогда
		Возврат Ложь;
	КонецЕсли;

	Если Сред(Карта[Y - 1], X, 1) = "#" Или Сред(Карта[Y - 1], X, 1) = "E"
		Или Сред(Карта[Y - 1], X, 1) = "<" Или Сред(Карта[Y - 1], X, 1) = "L" Тогда // враги не выходят и не спускаются
		Возврат Ложь;
	КонецЕсли;

	Если X = ГероX И Y = ГероY Тогда
		Возврат Ложь;
	КонецЕсли;

	Возврат НайтиИндексВрага(X, Y) < 0;

КонецФункции

// В 1С нет встроенной функции модуля числа.
&НаКлиенте
Функция АбсЧисла(Число)

	Если Число < 0 Тогда
		Возврат -Число;
	КонецЕсли;

	Возврат Число;

КонецФункции

&НаКлиенте
Функция ЗнакЧисла(Число)

	Если Число > 0 Тогда
		Возврат 1;
	ИначеЕсли Число < 0 Тогда
		Возврат -1;
	КонецЕсли;

	Возврат 0;

КонецФункции

// Прямая видимость между двумя клетками (алгоритм Брезенхэма).
// Стены закрывают обзор; сами конечные клетки не проверяются.
&НаКлиенте
Функция ЕстьПрямаяВидимость(X1, Y1, X2, Y2)

	ДельтаX = АбсЧисла(X2 - X1);
	ДельтаY = АбсЧисла(Y2 - Y1);
	ШагX = ЗнакЧисла(X2 - X1);
	ШагY = ЗнакЧисла(Y2 - Y1);
	Ошибка = ДельтаX - ДельтаY;

	ТX = X1;
	ТY = Y1;

	Пока Не (ТX = X2 И ТY = Y2) Цикл

		Ошибка2 = 2 * Ошибка;

		Если Ошибка2 > -ДельтаY Тогда
			Ошибка = Ошибка - ДельтаY;
			ТX = ТX + ШагX;
		КонецЕсли;

		Если Ошибка2 < ДельтаX Тогда
			Ошибка = Ошибка + ДельтаX;
			ТY = ТY + ШагY;
		КонецЕсли;

		Если ТX = X2 И ТY = Y2 Тогда
			Прервать;
		КонецЕсли;

		Если Сред(Карта[ТY - 1], ТX, 1) = "#" Тогда
			Возврат Ложь;
		КонецЕсли;

	КонецЦикла;

	Возврат Истина;

КонецФункции

// Линия выстрела (строго по горизонтали или вертикали) свободна:
// между скелетом и героем нет ни стен, ни других врагов.
&НаКлиенте
Функция ЛинияВыстрелаСвободна(Враг)

	ШагX = ЗнакЧисла(ГероX - Враг.X);
	ШагY = ЗнакЧисла(ГероY - Враг.Y);

	ТX = Враг.X + ШагX;
	ТY = Враг.Y + ШагY;

	Пока Не (ТX = ГероX И ТY = ГероY) Цикл

		Если Сред(Карта[ТY - 1], ТX, 1) = "#" Тогда
			Возврат Ложь;
		КонецЕсли;

		Если НайтиИндексВрага(ТX, ТY) >= 0 Тогда
			Возврат Ложь;
		КонецЕсли;

		ТX = ТX + ШагX;
		ТY = ТY + ШагY;

	КонецЦикла;

	Возврат Истина;

КонецФункции

// ---------------------------------------------------------------------------
// Ход героя и бой
// ---------------------------------------------------------------------------

&НаКлиенте
Процедура Сдвиг(DX, DY)

	// До выбора класса героя нет; после смерти герой не ходит:
	// новую игру начинает только кнопка «Начать заново».
	Если Герой = Неопределено Или Не Герой.Жив Тогда
		Возврат;
	КонецЕсли;

	Сообщение = "";
	ТипХода = "";

	НовX = ГероX + DX;
	НовY = ГероY + DY;
	Символ = Сред(Карта[НовY - 1], НовX, 1);

	Если Непроходимо(Символ) Тогда
		Возврат;
	КонецЕсли;

	// Переходы между локациями: полная смена карты, враги не ходят.
	Если Символ = "D" Тогда
		ВойтиВПодземелье();
		Отрисовать();
		Возврат;
	ИначеЕсли Символ = "E" Тогда
		ВыйтиИзПодземелья();
		Отрисовать();
		Возврат;
	ИначеЕсли Символ = "<" Тогда
		ПоднятьсяНаПервыйЭтаж();
		Отрисовать();
		Возврат;
	ИначеЕсли Символ = "L" Тогда
		Если НайтиВСумке("Ключ") < 0 Тогда
			Сообщение = "Люк закрыт тяжёлым замком с чужими рунами. Без ключа его не открыть.";
		Иначе
			СпуститьсяНаВторойЭтаж();
		КонецЕсли;
		Отрисовать();
		Возврат;
	КонецЕсли;

	// Шаг в жителя деревни: разговор (ход не тратится, жители стоят на месте).
	Если Локация = "Деревня" Тогда
		Житель = НайтиЖителя(НовX, НовY);
		Если Житель <> Неопределено Тогда
			Поговорить(Житель);
			Отрисовать();
			Возврат;
		КонецЕсли;
	КонецЕсли;

	ИндексВрага = НайтиИндексВрага(НовX, НовY);

	Если ИндексВрага >= 0 Тогда

		Бой(ИндексВрага);

	Иначе

		ГероX = НовX;
		ГероY = НовY;
		ТипХода = "Шаг";
		ОткрытьОкрестность();

		Если Символ = "$" Тогда
			ОткрытьСундук();
			УбратьСимвол(НовX, НовY);
		ИначеЕсли Символ = "*" Тогда
			Если Локация = "Подземелье, 2 этаж" Тогда
				ОткрытьСундукВладыки();
			Иначе
				ОткрытьСундукНекроманта();
			КонецЕсли;
			УбратьСимвол(НовX, НовY);
		ИначеЕсли Символ = "!" Тогда
			Лечение = Мин(ЛечениеЗелья(), Герой.МаксHP - Герой.HP);
			Герой.HP = Герой.HP + Лечение;
			Сообщение = "Зелье: +" + Лечение + " HP.";
			Если Герой.МаксМана > 0 Тогда
				Герой.Мана = Мин(Герой.МаксМана, Герой.Мана + 10);
				Сообщение = Сообщение + " (+10 к ресурсу)";
			КонецЕсли;
			Если Герой.Яд > 0 Или Герой.Горение > 0 Тогда
				Герой.Яд = 0;
				Герой.Горение = 0;
				Сообщение = Сообщение + " Яд и огонь погашены.";
			КонецЕсли;
			УбратьСимвол(НовX, НовY);
		КонецЕсли;

	КонецЕсли;

	// Ход врагов: после каждого шага или удара героя.
	ЗавершитьХод();

	Отрисовать();

КонецПроцедуры

// Конец хода героя: действует яд, затем ходят враги, затем тают эффекты.
// Вор: каждый второй шаг враги и яд пропускают (он двигается вдвое быстрее).
&НаКлиенте
Процедура ЗавершитьХод()

	ПропуститьВрагов = Ложь;
	Если Герой.Класс = "Вор" И ТипХода = "Шаг" И Локация <> "Деревня" Тогда
		Герой.ШагиВора = Герой.ШагиВора + 1;
		ПропуститьВрагов = (Герой.ШагиВора % 2 = 1);
	КонецЕсли;

	Если Не ПропуститьВрагов Тогда
		Если Герой.Жив Тогда
			ЯдДействует();
			Если Герой.Жив Тогда
				ХодВрагов();
			КонецЕсли;
		КонецЕсли;
		ТикСостояний();
	КонецЕсли;

	// В деревне жители делают шаг раз в 2 хода героя.
	Если Локация = "Деревня" Тогда
		ХодЖителей();
	КонецЕсли;

КонецПроцедуры

// Конец хода: тают щит, невидимость и перезарядки, восстанавливается ресурс (+1 за ход).
&НаКлиенте
Процедура ТикСостояний()

	Если Герой.Щит > 0 Тогда
		Герой.Щит = Герой.Щит - 1;
	КонецЕсли;

	Если Герой.Невидим > 0 Тогда
		Герой.Невидим = Герой.Невидим - 1;
	КонецЕсли;

	Если Герой.КД1 > 0 Тогда
		Герой.КД1 = Герой.КД1 - 1;
	КонецЕсли;

	Если Герой.КД2 > 0 Тогда
		Герой.КД2 = Герой.КД2 - 1;
	КонецЕсли;

	Если Герой.Мана < Герой.МаксМана Тогда
		Герой.Мана = Герой.Мана + 1;
	КонецЕсли;

КонецПроцедуры

&НаКлиенте
Функция НовыйПредмет(Вид, Уровень)

	Предмет = Новый Структура("Вид, Уровень, Название");
	Предмет.Вид = Вид;
	Предмет.Уровень = Уровень;
	Предмет.Название = НазваниеПредмета(Вид, Уровень, КлассГероя);

	Возврат Предмет;

КонецФункции

// Кладёт предмет в сумку. Если сумка полна, находка пропадает
// (кроме Принудительно = Истина: так кладутся награды босса).
&НаКлиенте
Процедура ВзятьПредмет(Предмет, Принудительно)

	Если Не Принудительно И Герой.Сумка.Количество() >= ВместимостьСумки() Тогда
		Сообщение = Сообщение + " Сумка полна, находка потеряна: " + ОписаниеПредмета(Предмет) + ".";
		Возврат;
	КонецЕсли;

	Герой.Сумка.Добавить(Предмет);
	Сообщение = Сообщение + " Найдено: " + ОписаниеПредмета(Предмет) + "!";

КонецПроцедуры

// Обычный сундук: 10 золота + лут: 55% ничего, 30% предмет 1 ур., 15% предмет 2 ур.
&НаКлиенте
Процедура ОткрытьСундук()

	Бросок = Генератор.СлучайноеЧисло(1, 100);
	Уровень = 0;

	Если Локация = "Подземелье, 2 этаж" Тогда

		// 2 этаж: 25 золота; 40% ничего, 35% предмет 2 ур., 25% предмет 3 ур.
		Герой.Золото = Герой.Золото + 25;
		Сообщение = "Сундук: +25 золота.";
		Если Бросок <= 25 Тогда
			Уровень = 3;
		ИначеЕсли Бросок <= 60 Тогда
			Уровень = 2;
		КонецЕсли;

	Иначе

		Герой.Золото = Герой.Золото + 10;
		Сообщение = "Сундук: +10 золота.";
		Если Бросок <= 15 Тогда
			Уровень = 2;
		ИначеЕсли Бросок <= 45 Тогда
			Уровень = 1;
		КонецЕсли;

	КонецЕсли;

	Если Уровень > 0 Тогда
		Вид = ?(Генератор.СлучайноеЧисло(0, 1) = 0, "Меч", "Броня");
		ВзятьПредмет(НовыйПредмет(Вид, Уровень), Ложь);
	КонецЕсли;

КонецПроцедуры

// Сундук тёмного владыки: золото, предмет 4 уровня и парный предмет 3 уровня.
&НаКлиенте
Процедура ОткрытьСундукВладыки()

	Герой.Золото = Герой.Золото + 250;
	Сообщение = "Сундук тёмного владыки: +250 золота.";

	Вид = ?(Генератор.СлучайноеЧисло(0, 1) = 0, "Меч", "Броня");
	Другой = ?(Вид = "Меч", "Броня", "Меч");
	ВзятьПредмет(НовыйПредмет(Вид, 4), Истина);
	ВзятьПредмет(НовыйПредмет(Другой, 3), Истина);

КонецПроцедуры

// Сундук на месте смерти некроманта: золото, письмо и оружие или броня 3 уровня.
&НаКлиенте
Процедура ОткрытьСундукНекроманта()

	Герой.Золото = Герой.Золото + 50;
	Сообщение = "Сундук некроманта: +50 золота.";

	ВзятьПредмет(НовыйПредмет("Письмо", 0), Истина);

	Вид = ?(Генератор.СлучайноеЧисло(0, 1) = 0, "Меч", "Броня");
	ВзятьПредмет(НовыйПредмет(Вид, 3), Истина);

	// Голос из подземелья: теперь оно заполняется усиленными мобами.
	Сообщение = Сообщение + " Едва письмо ложится в сумку, из глубины подземелья раздаётся голос, от которого стынет кровь: «Ты не выберешься отсюда... никогда!» Стены вздрагивают, и в каждом зале просыпаются мёртвые.";
	НаселитьДрауграми();

КонецПроцедуры

// Двойной щелчок по предмету в сумке: надеть (старое снаряжение уходит в сумку)
// или выпить зелье ресурса.
&НаКлиенте
Процедура ИспользоватьПредмет(Инд)

	Если Не Герой.Жив Тогда
		Возврат;
	КонецЕсли;

	Если Инд < 0 Или Инд > Герой.Сумка.ВГраница() Тогда
		Возврат;
	КонецЕсли;

	Предмет = Герой.Сумка[Инд];

	Если Предмет.Вид = "Письмо" Тогда
		Сообщение = "Письмо запечатано чёрным воском. Его стоит показать старосте.";
		Отрисовать();
		Возврат;
	ИначеЕсли Предмет.Вид = "Ключ" Тогда
		Сообщение = "Тяжёлый ключ с чужими рунами. Он подойдёт к двери другого подземелья.";
		Отрисовать();
		Возврат;
	ИначеЕсли Предмет.Вид = "Зелье" Тогда

		Если Герой.МаксМана = 0 Тогда
			Сообщение = "Это зелье вам ни к чему: у вас нет ни маны, ни фокуса.";
			Отрисовать();
			Возврат;
		КонецЕсли;

		Восстановлено = Мин(15, Герой.МаксМана - Герой.Мана);
		Герой.Мана = Герой.Мана + Восстановлено;
		Герой.Сумка.Удалить(Инд);
		Сообщение = "Вы выпили " + Предмет.Название + ": +" + Восстановлено + " к ресурсу.";
		ТипХода = "";
		ЗавершитьХод();
		Отрисовать();
		Возврат;

	КонецЕсли;

	Если Предмет.Вид = "Меч" Тогда
		Старый = Герой.Меч;
		Герой.Меч = Предмет;
	Иначе
		Старый = Герой.Броня;
		Герой.Броня = Предмет;
	КонецЕсли;

	Герой.Сумка[Инд] = Старый;

	Сообщение = "Вы надели: " + ОписаниеПредмета(Предмет) + ". В сумку убрано: " + ОписаниеПредмета(Старый) + ".";

	// Смена снаряжения занимает ход (чтобы убрать, удали вызов ЗавершитьХод).
	ТипХода = "";
	ЗавершитьХод();

	Отрисовать();

КонецПроцедуры

// Урон после брони: не меньше 1. Щит воина уменьшает урон вдвое.
&НаКлиенте
Функция УронСУчётомБрони(Урон)

	Результат = Макс(1, Урон - БонусЗащиты(Герой.Броня.Уровень));

	Если Герой.Щит > 0 Тогда
		Результат = Макс(1, Цел(Результат / 2));
	КонецЕсли;

	Возврат Результат;

КонецФункции

// Атака героя: базовая + бонус оружия.
&НаКлиенте
Функция ОбщаяАтака()

	Возврат Генератор.СлучайноеЧисло(Герой.АтакаМин, Герой.АтакаМакс) + БонусАтаки(Герой.Меч.Уровень);

КонецФункции

// Наносит урон врагу, пишет в Сообщение и обрабатывает смерть (награды, боссы).
// Возвращает "ранен", "убит" или "щит" (владыка защищён стражами).
&НаКлиенте
Функция ПрименитьУрон(ИндексВрага, Знач Урон, ИгнорБрони, Действие, Пометка)

	Враг = Враги[ИндексВрага];
	Имя = ИмяВрага(Враг);

	// Тёмный владыка неуязвим, пока живы его стражи.
	Если Враг.Тип = "Тёмный владыка" И ЕстьСтражи() Тогда
		Сообщение = Сообщение + " " + Действие + ": удар тонет в багровом щите тёмного владыки. Пока живы его стражи, ему нельзя причинить вреда.";
		Возврат "щит";
	КонецЕсли;

	// Броня врага поглощает часть урона (но не меньше 1 HP за удар).
	Урон = Макс(1, Урон);
	Поглощено = 0;
	Если Не ИгнорБрони Тогда
		Поглощено = Мин(Враг.Броня, Урон - 1);
	КонецЕсли;
	Урон = Урон - Поглощено;

	Враг.HP = Враг.HP - Урон;
	Сообщение = Сообщение + " " + Действие + ": " + Имя + " -" + Урон + " HP" + Пометка
		+ ?(Поглощено > 0, " (броня поглотила " + Поглощено + ")", "");

	Если Враг.HP > 0 Тогда
		Сообщение = Сообщение + " [" + Враг.HP + "/" + Враг.МаксHP + "].";
		Возврат "ранен";
	КонецЕсли;

	Враги.Удалить(ИндексВрага);
	Сообщение = Сообщение + ". " + Имя + " повержен";

	Если Враг.Опыт > 0 Тогда
		Герой.Опыт = Герой.Опыт + Враг.Опыт;
		Герой.Золото = Герой.Золото + Враг.Золото;
		Сообщение = Сообщение + ": +" + Враг.Опыт + " оп., +" + Враг.Золото + " зол.";
	Иначе
		Сообщение = Сообщение + ".";
	КонецЕсли;

	ПроверитьУровень();

	Если Враг.Тип = "Некромант" Тогда
		УбратьПризванных();
		Герой.Победа = Истина;
		УстановитьСимвол(Враг.X, Враг.Y, "*");
		Сообщение = Сообщение + " ПОБЕДА! На месте некроманта остался особый сундук. Можно дочистить подземелье или начать заново кнопкой.";
	КонецЕсли;

	// Пал последний страж: щит владыки гаснет.
	Если (Враг.Тип = "Багряный палач" Или Враг.Тип = "Грозовой жнец") И Не ЕстьСтражи() Тогда
		Сообщение = Сообщение + " Багровый щит вокруг тёмного владыки трескается и гаснет: теперь он уязвим, и ярость его не знает границ!";
	КонецЕсли;

	Если Враг.Тип = "Тёмный владыка" Тогда
		ВладыкаПобеждён = Истина;
		УстановитьСимвол(Враг.X, Враг.Y, "*");
		Сообщение = Сообщение + " ПОБЕДА! Тёмный владыка рассыпается пеплом, оставив сундук. Но откуда-то из глубин ещё доносится тихий смех...";
	КонецЕсли;

	Возврат "убит";

КонецФункции

// Обычная атака шагом во врага. Вор бьёт дважды (по 70% урона) и режет в спину
// (x3), если враг его не заметил или герой невидим. Стрелок критует в 25% случаев.
// Ответный удар врага происходит в фазе ХодВрагов, если он выжил.
&НаКлиенте
Процедура Бой(ИндексВрага)

	Враг = Враги[ИндексВрага];
	ВрагX = Враг.X;
	ВрагY = Враг.Y;

	Класс = Герой.Класс;
	Ударов = ?(Класс = "Вор", 2, 1);
	ШансКрита = ?(Класс = "Стрелок", 25, 15);
	Спина = (Класс = "Вор" И (Герой.Невидим > 0 Или (Не Враг.Заметил И Не Враг.Проснулся)));

	Для НомерУдара = 1 По Ударов Цикл

		Индекс = НайтиИндексВрага(ВрагX, ВрагY);
		Если Индекс < 0 Тогда
			Прервать;
		КонецЕсли;

		Урон = ОбщаяАтака();
		Если Класс = "Вор" Тогда
			Урон = Макс(1, Окр(Урон * 7 / 10, 0));
		КонецЕсли;

		Пометка = "";
		Если Генератор.СлучайноеЧисло(1, 100) <= ШансКрита Тогда
			Урон = Урон * 2;
			Пометка = " (крит!)";
		КонецЕсли;

		Если Спина И НомерУдара = 1 Тогда
			Урон = Урон * 3;
			Пометка = Пометка + " (удар в спину!)";
		КонецЕсли;

		Если ПрименитьУрон(Индекс, Урон, Ложь, "Вы бьёте", Пометка) <> "ранен" Тогда
			Прервать;
		КонецЕсли;

	КонецЦикла;

	// Атака выдаёт вора: невидимость спадает.
	Герой.Невидим = 0;

КонецПроцедуры

// ---------------------------------------------------------------------------
// Способности классов
// ---------------------------------------------------------------------------

// Индекс ближайшего врага в радиусе (по большей из разностей) и в прямой видимости.
// -1, если целей нет.
&НаКлиенте
Функция НайтиЦель(Дальность)

	Лучший = -1;
	ЛучшаяДистанция = 1000;

	Для Инд = 0 По Враги.ВГраница() Цикл

		Враг = Враги[Инд];
		Дистанция = Макс(АбсЧисла(Враг.X - ГероX), АбсЧисла(Враг.Y - ГероY));

		Если Дистанция > Дальность Или Дистанция >= ЛучшаяДистанция Тогда
			Продолжить;
		КонецЕсли;

		Если Не ЕстьПрямаяВидимость(ГероX, ГероY, Враг.X, Враг.Y) Тогда
			Продолжить;
		КонецЕсли;

		Лучший = Инд;
		ЛучшаяДистанция = Дистанция;

	КонецЦикла;

	Возврат Лучший;

КонецФункции

// Все враги в радиусе и в прямой видимости (массив самих структур-врагов).
&НаКлиенте
Функция ВрагиВРадиусе(Радиус)

	Результат = Новый Массив;

	Для Каждого Враг Из Враги Цикл
		Если Макс(АбсЧисла(Враг.X - ГероX), АбсЧисла(Враг.Y - ГероY)) <= Радиус
			И ЕстьПрямаяВидимость(ГероX, ГероY, Враг.X, Враг.Y) Тогда
			Результат.Добавить(Враг);
		КонецЕсли;
	КонецЦикла;

	Возврат Результат;

КонецФункции

// Применяет эффект способности. Истина = способность сработала (тратится ход и ресурс),
// Ложь = не было цели (ничего не тратится, причина в Сообщение).
&НаКлиенте
Функция СработалаСпособность(Класс, Номер)

	Бонус = БонусАтаки(Герой.Меч.Уровень);

	Если Класс = "Воин" Тогда

		Если Номер = 1 Тогда
			Герой.Щит = 3;
			Сообщение = Сообщение + " Вы поднимаете щит: урон по вам вдвое меньше (3 хода).";
			Возврат Истина;
		КонецЕсли;

		// Мощный удар: враг в одной из четырёх соседних клеток.
		Индекс = -1;
		Для Каждого Смещение Из СтрРазделить("1 0,-1 0,0 1,0 -1", ",") Цикл
			Части = СтрРазделить(Смещение, " ");
			Индекс = НайтиИндексВрага(ГероX + Число(Части[0]), ГероY + Число(Части[1]));
			Если Индекс >= 0 Тогда
				Прервать;
			КонецЕсли;
		КонецЦикла;

		Если Индекс < 0 Тогда
			Сообщение = "Рядом нет врага для мощного удара.";
			Возврат Ложь;
		КонецЕсли;

		ПрименитьУрон(Индекс, ОбщаяАтака() * 2, Истина, "Мощный удар", "");
		Возврат Истина;

	ИначеЕсли Класс = "Маг" Тогда

		Если Номер = 1 Тогда

			Цель = НайтиЦель(5);
			Если Цель < 0 Тогда
				Сообщение = "Нет цели: нужен враг в прямой видимости не дальше 5 клеток.";
				Возврат Ложь;
			КонецЕсли;

			Урон = Генератор.СлучайноеЧисло(8, 12) + Герой.Уровень * 2 + Бонус * 2;
			ПрименитьУрон(Цель, Урон, Истина, "Огненный шар", "");
			Возврат Истина;

		КонецЕсли;

		Цели = ВрагиВРадиусе(2);
		Если Цели.Количество() = 0 Тогда
			Сообщение = "Рядом нет врагов для ледяной волны (радиус 2).";
			Возврат Ложь;
		КонецЕсли;

		Для Каждого Цель Из Цели Цикл
			Индекс = НайтиИндексВрага(Цель.X, Цель.Y);
			Если Индекс < 0 Тогда
				Продолжить;
			КонецЕсли;
			Результат = ПрименитьУрон(Индекс, 4 + Герой.Уровень + Бонус, Истина, "Ледяная волна", "");
			Если Результат = "ранен" И Цель.Тип <> "Тёмный владыка" Тогда
				Цель.Заморожен = 3; // пропустит этот и следующий ход
			КонецЕсли;
		КонецЦикла;

		Возврат Истина;

	ИначеЕсли Класс = "Вор" Тогда

		Если Номер = 1 Тогда
			Герой.Невидим = 4;
			Сообщение = Сообщение + " Вы растворяетесь в тени: враги теряют вас из виду (4 хода). Первый удар из тени x3.";
			Возврат Истина;
		КонецЕсли;

		Цель = НайтиЦель(4);
		Если Цель < 0 Тогда
			Сообщение = "Нет цели: нужен враг в прямой видимости не дальше 4 клеток.";
			Возврат Ложь;
		КонецЕсли;

		ЦельX = Враги[Цель].X;
		ЦельY = Враги[Цель].Y;
		Для Нож = 1 По 2 Цикл
			Индекс = НайтиИндексВрага(ЦельX, ЦельY);
			Если Индекс < 0 Тогда
				Прервать;
			КонецЕсли;
			Если ПрименитьУрон(Индекс, Окр(ОбщаяАтака() * 8 / 10, 0), Ложь, "Нож " + Нож, "") <> "ранен" Тогда
				Прервать;
			КонецЕсли;
		КонецЦикла;

		Возврат Истина;

	ИначеЕсли Класс = "Стрелок" Тогда

		Если Номер = 1 Тогда

			Цель = НайтиЦель(6);
			Если Цель < 0 Тогда
				Сообщение = "Нет цели: нужен враг в прямой видимости не дальше 6 клеток.";
				Возврат Ложь;
			КонецЕсли;

			ПрименитьУрон(Цель, ОбщаяАтака() * 2 + Герой.Уровень, Ложь, "Меткий выстрел", "");
			Возврат Истина;

		КонецЕсли;

		Цели = ВрагиВРадиусе(3);
		Если Цели.Количество() = 0 Тогда
			Сообщение = "Нет целей для града стрел (радиус 3).";
			Возврат Ложь;
		КонецЕсли;

		Для Каждого Цель Из Цели Цикл
			Индекс = НайтиИндексВрага(Цель.X, Цель.Y);
			Если Индекс >= 0 Тогда
				ПрименитьУрон(Индекс, ОбщаяАтака(), Ложь, "Град стрел", "");
			КонецЕсли;
		КонецЦикла;

		Возврат Истина;

	КонецЕсли;

	Возврат Ложь;

КонецФункции

&НаКлиенте
Процедура ИспользоватьСпособность(Номер)

	Если Герой = Неопределено Или Не Герой.Жив Тогда
		Возврат;
	КонецЕсли;

	Если Локация = "Деревня" Тогда
		Сообщение = "В деревне способности не нужны.";
		Отрисовать();
		Возврат;
	КонецЕсли;

	Данные = ДанныеСпособности(Герой.Класс, Номер);
	Перезарядка = ?(Номер = 1, Герой.КД1, Герой.КД2);

	Если Перезарядка > 0 Тогда
		Сообщение = Данные.Имя + " ещё перезаряжается (ходов: " + Перезарядка + ").";
		Отрисовать();
		Возврат;
	КонецЕсли;

	Если Герой.Мана < Данные.Стоимость Тогда
		Сообщение = "Не хватает ресурса (" + ПараметрыКласса(Герой.Класс).Ресурс + "): нужно " + Данные.Стоимость
			+ ", есть " + Герой.Мана + ".";
		Отрисовать();
		Возврат;
	КонецЕсли;

	Сообщение = "";
	ТипХода = "";

	Если Не СработалаСпособность(Герой.Класс, Номер) Тогда
		Отрисовать();
		Возврат;
	КонецЕсли;

	Герой.Мана = Герой.Мана - Данные.Стоимость;

	ЗавершитьХод();

	// Перезарядка ставится после хода, чтобы этот ход её не «съел».
	Если Номер = 1 Тогда
		Герой.КД1 = Данные.Перезарядка;
	Иначе
		Герой.КД2 = Данные.Перезарядка;
	КонецЕсли;

	Отрисовать();

КонецПроцедуры

&НаКлиенте
Процедура ПроверитьСмертьГероя()

	Если Герой.HP <= 0 Тогда
		Герой.HP = 0;
		Герой.Жив = Ложь;
		Сообщение = Сообщение + " Вы погибли. Нажмите «Начать заново», чтобы создать новое подземелье.";
	КонецЕсли;

КонецПроцедуры

&НаКлиенте
Процедура ПроверитьУровень()

	П = ПараметрыКласса(Герой.Класс);

	Пока Герой.Опыт >= ОпытДляУровня(Герой.Уровень) Цикл
		Герой.Опыт = Герой.Опыт - ОпытДляУровня(Герой.Уровень);
		Герой.Уровень = Герой.Уровень + 1;
		Герой.МаксHP = Герой.МаксHP + П.РостHP;
		Герой.HP = Герой.МаксHP;
		Герой.МаксМана = Герой.МаксМана + П.РостМаны;
		Герой.Мана = Герой.МаксМана;
		Герой.АтакаМин = Герой.АтакаМин + 1;
		Герой.АтакаМакс = Герой.АтакаМакс + 1;
		Сообщение = Сообщение + " Новый уровень: " + Герой.Уровень + "!";
	КонецЦикла;

КонецПроцедуры

&НаКлиенте
Процедура УбратьСимвол(X, Y)

	СтрокаКарты = Карта[Y - 1];
	Карта[Y - 1] = Лев(СтрокаКарты, X - 1) + "." + Сред(СтрокаКарты, X + 1);

КонецПроцедуры

&НаКлиенте
Процедура УстановитьСимвол(X, Y, Символ)

	СтрокаКарты = Карта[Y - 1];
	Карта[Y - 1] = Лев(СтрокаКарты, X - 1) + Символ + Сред(СтрокаКарты, X + 1);

КонецПроцедуры

// Открывает клетки в радиусе 1 вокруг героя (включая диагонали) и запоминает их.
&НаКлиенте
Процедура ОткрытьОкрестность()

	Для СмещениеY = -1 По 1 Цикл
		Для СмещениеX = -1 По 1 Цикл

			ТX = ГероX + СмещениеX;
			ТY = ГероY + СмещениеY;

			Если ТY < 1 Или ТY > Открыто.Количество() Или ТX < 1 Или ТX > СтрДлина(Открыто[0]) Тогда
				Продолжить;
			КонецЕсли;

			СтрокаОткрыто = Открыто[ТY - 1];
			Открыто[ТY - 1] = Лев(СтрокаОткрыто, ТX - 1) + "1" + Сред(СтрокаОткрыто, ТX + 1);

		КонецЦикла;
	КонецЦикла;

КонецПроцедуры

// ---------------------------------------------------------------------------
// Ход врагов
// ---------------------------------------------------------------------------

&НаКлиенте
Процедура ХодВрагов()

	// Фиксируем количество: скелеты, призванные в этот ход, начнут ходить со следующего.
	Количество = Враги.Количество();

	Для Инд = 0 По Количество - 1 Цикл

		Враг = Враги[Инд];

		// Замороженный враг пропускает ход.
		Если Враг.Заморожен > 0 Тогда
			Враг.Заморожен = Враг.Заморожен - 1;
			Продолжить;
		КонецЕсли;

		// Невидимого героя (вор в тени) враги не замечают, пока он не вплотную.
		Если Герой.Невидим > 0 И АбсЧисла(ГероX - Враг.X) + АбсЧисла(ГероY - Враг.Y) > 1 Тогда
			Враг.Заметил = Ложь;
			Продолжить;
		КонецЕсли;

		Если Враг.Тип = "Некромант" Тогда
			ХодНекроманта(Враг);
		ИначеЕсли Враг.Тип = "Тёмный владыка" Тогда
			ХодВладыки(Враг);
		Иначе
			ХодПреследователя(Враг, РадиусПреследования()); // и обычные, и призванные скелеты
		КонецЕсли;

		Если Не Герой.Жив Тогда
			Прервать;
		КонецЕсли;

	КонецЦикла;

КонецПроцедуры

// Удар врага по герою.
&НаКлиенте
Процедура УдарВрага(Враг)

	Урон = УронСУчётомБрони(Генератор.СлучайноеЧисло(Враг.АтакаМин, Враг.АтакаМакс));
	Герой.HP = Герой.HP - Урон;
	Глагол = ?(Враг.Тип = "Грозовой жнец", "разит молнией", "бьёт");
	Сообщение = Сообщение + " " + ИмяВрага(Враг) + " " + Глагол + ": -" + Урон + " HP.";

	// Багряный палач лечится на нанесённый урон (вампиризм).
	Если Враг.Тип = "Багряный палач" Тогда
		Лечение = Мин(Урон, Враг.МаксHP - Враг.HP);
		Если Лечение > 0 Тогда
			Враг.HP = Враг.HP + Лечение;
			Сообщение = Сообщение + " Палач пьёт вашу кровь (+" + Лечение + " HP ему)!";
		КонецЕсли;
	КонецЕсли;

	ПроверитьСмертьГероя();

	Если Герой.Жив Тогда
		Если Враг.Тип = "Слизень" Тогда
			// Слизень отравляет: срок яда обновляется до полного.
			Герой.Яд = ДлительностьЯда();
			Сообщение = Сообщение + " Вы отравлены (ходов: " + Герой.Яд + ")!";
		ИначеЕсли Враг.Тип = "Адское отродье" Тогда
			// Адское отродье поджигает.
			Герой.Горение = ДлительностьОжога();
			Сообщение = Сообщение + " Вы охвачены огнём (ходов: " + Герой.Горение + ")!";
		КонецЕсли;
	КонецЕсли;

КонецПроцедуры

// Урон от яда: срабатывает перед ходом врагов, поэтому свежий яд
// начинает действовать со следующего хода и длится ровно ДлительностьЯда() ходов.
&НаКлиенте
Процедура ЯдДействует()

	// Яд.
	Если Герой.Яд > 0 Тогда
		Герой.Яд = Герой.Яд - 1;
		Герой.HP = Герой.HP - УронЯда();
		Сообщение = Сообщение + " Яд: -" + УронЯда() + " HP"
			+ ?(Герой.Яд > 0, " (ещё ходов: " + Герой.Яд + ").", " (яд выветрился).");
		ПроверитьСмертьГероя();
	КонецЕсли;

	// Горение (броня не защищает).
	Если Герой.Жив И Герой.Горение > 0 Тогда
		Герой.Горение = Герой.Горение - 1;
		Герой.HP = Герой.HP - УронОжога();
		Сообщение = Сообщение + " Горение: -" + УронОжога() + " HP"
			+ ?(Герой.Горение > 0, " (ещё ходов: " + Герой.Горение + ").", " (пламя погасло).");
		ПроверитьСмертьГероя();
	КонецЕсли;

КонецПроцедуры

// Выстрел обычного скелета: игрок видит, откуда и с какого расстояния прилетело.
&НаКлиенте
Процедура ВыстрелСкелета(Враг, ДельтаX, ДельтаY)

	Расстояние = Макс(АбсЧисла(ДельтаX), АбсЧисла(ДельтаY));

	Урон = УронСУчётомБрони(УронВыстрела());
	Герой.HP = Герой.HP - Урон;
	Сообщение = Сообщение + " Скелет (" + НаправлениеНаВрага(ДельтаX, ДельтаY) + ", "
		+ Расстояние + " кл.) стреляет из лука: стрела попала в вас, -" + Урон + " HP!";
	ПроверитьСмертьГероя();

КонецПроцедуры

// Где находится враг относительно героя.
// ДельтаX, ДельтаY: координаты героя минус координаты врага.
&НаКлиенте
Функция НаправлениеНаВрага(ДельтаX, ДельтаY)

	Если АбсЧисла(ДельтаX) >= АбсЧисла(ДельтаY) Тогда
		Возврат ?(ДельтаX > 0, "слева", "справа");
	КонецЕсли;

	Возврат ?(ДельтаY > 0, "сверху", "снизу");

КонецФункции

// Преследователь: если герой дальше Радиуса или за стеной, стоит на месте;
// если рядом по горизонтали/вертикали, бьёт; обычный скелет стреляет по прямой;
// иначе делает шаг к герою.
&НаКлиенте
Процедура ХодПреследователя(Враг, Радиус)

	ДельтаX = ГероX - Враг.X;
	ДельтаY = ГероY - Враг.Y;

	// Дальше радиуса или герой за стеной: враг его не видит.
	Если Макс(АбсЧисла(ДельтаX), АбсЧисла(ДельтаY)) > Радиус
		Или Не ЕстьПрямаяВидимость(Враг.X, Враг.Y, ГероX, ГероY) Тогда
		Враг.Заметил = Ложь; // потерял героя: при следующей встрече заметит снова
		Возврат;
	КонецЕсли;

	// Предупреждение: враг только что заметил героя (о призванных сообщает сам призыв).
	Если Не Враг.Заметил Тогда
		Враг.Заметил = Истина;
		Если Не Враг.Призванный Тогда
			Сообщение = Сообщение + " " + ИмяВрага(Враг) + " замечает вас ("
				+ НаправлениеНаВрага(ДельтаX, ДельтаY) + ")!";
		КонецЕсли;
	КонецЕсли;

	Если АбсЧисла(ДельтаX) + АбсЧисла(ДельтаY) = 1 Тогда
		УдарВрага(Враг);
		Возврат;
	КонецЕсли;

	// Грозовой жнец бьёт молнией по прямой и по диагонали на 3 клетки (стены закрывают).
	Если Враг.Тип = "Грозовой жнец"
		И (ДельтаX = 0 Или ДельтаY = 0 Или АбсЧисла(ДельтаX) = АбсЧисла(ДельтаY))
		И Макс(АбсЧисла(ДельтаX), АбсЧисла(ДельтаY)) <= 3 Тогда
		УдарМолнии(Враг, ДельтаX, ДельтаY);
		Возврат;
	КонецЕсли;

	// Пиромант бросает огненный шар по прямой на 4 клетки.
	Если Враг.Тип = "Пиромант"
		И (ДельтаX = 0 Или ДельтаY = 0)
		И Макс(АбсЧисла(ДельтаX), АбсЧисла(ДельтаY)) <= ДальностьОгненногоШара()
		И ЛинияВыстрелаСвободна(Враг) Тогда
		ОгненныйШар(Враг, ДельтаX, ДельтаY);
		Возврат;
	КонецЕсли;

	// Обычный скелет стреляет, если герой на одной линии в пределах дальности.
	Если Враг.Тип = "Скелет" И Не Враг.Призванный
		И (ДельтаX = 0 Или ДельтаY = 0)
		И Макс(АбсЧисла(ДельтаX), АбсЧисла(ДельтаY)) <= ДальностьВыстрела()
		И ЛинияВыстрелаСвободна(Враг) Тогда
		ВыстрелСкелета(Враг, ДельтаX, ДельтаY);
		Возврат;
	КонецЕсли;

	ШагX = ЗнакЧисла(ДельтаX);
	ШагY = ЗнакЧисла(ДельтаY);

	// Сначала пробуем идти по оси с большим расстоянием, потом по другой.
	Если АбсЧисла(ДельтаX) >= АбсЧисла(ДельтаY) Тогда
		Если ШагX <> 0 И КлеткаСвободна(Враг.X + ШагX, Враг.Y) Тогда
			Враг.X = Враг.X + ШагX;
			Возврат;
		КонецЕсли;
		Если ШагY <> 0 И КлеткаСвободна(Враг.X, Враг.Y + ШагY) Тогда
			Враг.Y = Враг.Y + ШагY;
		КонецЕсли;
	Иначе
		Если ШагY <> 0 И КлеткаСвободна(Враг.X, Враг.Y + ШагY) Тогда
			Враг.Y = Враг.Y + ШагY;
			Возврат;
		КонецЕсли;
		Если ШагX <> 0 И КлеткаСвободна(Враг.X + ШагX, Враг.Y) Тогда
			Враг.X = Враг.X + ШагX;
		КонецЕсли;
	КонецЕсли;

КонецПроцедуры

// Молния грозового жнеца: попадает по линии и диагонали, наносит урон его атаки.
&НаКлиенте
Процедура УдарМолнии(Враг, ДельтаX, ДельтаY)

	Расстояние = Макс(АбсЧисла(ДельтаX), АбсЧисла(ДельтаY));
	Направление = ?(ДельтаX = 0 Или ДельтаY = 0, НаправлениеНаВрага(ДельтаX, ДельтаY), "по диагонали");

	Урон = УронСУчётомБрони(Генератор.СлучайноеЧисло(Враг.АтакаМин, Враг.АтакаМакс));
	Герой.HP = Герой.HP - Урон;
	Сообщение = Сообщение + " Грозовой жнец (" + Направление + ", " + Расстояние
		+ " кл.) бьёт молнией: -" + Урон + " HP!";
	ПроверитьСмертьГероя();

КонецПроцедуры

// Огненный шар пироманта.
&НаКлиенте
Процедура ОгненныйШар(Враг, ДельтаX, ДельтаY)

	Расстояние = Макс(АбсЧисла(ДельтаX), АбсЧисла(ДельтаY));

	Урон = УронСУчётомБрони(УронОгненногоШара());
	Герой.HP = Герой.HP - Урон;
	Сообщение = Сообщение + " Пиромант (" + НаправлениеНаВрага(ДельтаX, ДельтаY) + ", "
		+ Расстояние + " кл.) бросает огненный шар: -" + Урон + " HP!";
	ПроверитьСмертьГероя();

КонецПроцедуры

// Живы ли стражи тёмного владыки (палач или жнец).
&НаКлиенте
Функция ЕстьСтражи()

	Для Каждого Враг Из Враги Цикл
		Если Враг.Тип = "Багряный палач" Или Враг.Тип = "Грозовой жнец" Тогда
			Возврат Истина;
		КонецЕсли;
	КонецЦикла;

	Возврат Ложь;

КонецФункции

// Тёмный владыка стоит на месте. Просыпается, когда герой ближе 7 клеток, бьёт
// вплотную и раз в несколько ходов обрушивает теневую волну на героя в радиусе 3.
// Пока живы стражи, он неуязвим (см. ПрименитьУрон); когда стражи пали, впадает в ярость:
// волна чаще и сильнее, и она поджигает.
&НаКлиенте
Процедура ХодВладыки(Враг)

	ДельтаX = ГероX - Враг.X;
	ДельтаY = ГероY - Враг.Y;
	Дистанция = Макс(АбсЧисла(ДельтаX), АбсЧисла(ДельтаY));
	Ярость = Не ЕстьСтражи();

	Если Не Враг.Проснулся Тогда
		Если Дистанция <= 7 Тогда
			Враг.Проснулся = Истина;
			Враг.Таймер = 3;
			Сообщение = Сообщение + " Тёмный владыка поднимает голову: «Ещё один смертный жаждет моего покоя...»";
		КонецЕсли;
		Возврат;
	КонецЕсли;

	Если АбсЧисла(ДельтаX) + АбсЧисла(ДельтаY) = 1 Тогда
		УдарВрага(Враг);
		Если Не Герой.Жив Тогда
			Возврат;
		КонецЕсли;
	КонецЕсли;

	Враг.Таймер = Враг.Таймер - 1;
	Если Враг.Таймер > 0 Тогда
		Возврат;
	КонецЕсли;

	Если Дистанция > 3 Или Не ЕстьПрямаяВидимость(Враг.X, Враг.Y, ГероX, ГероY) Тогда
		Враг.Таймер = 1;
		Возврат;
	КонецЕсли;

	Урон = УронСУчётомБрони(?(Ярость, 14, 10));
	Герой.HP = Герой.HP - Урон;
	Сообщение = Сообщение + " Тёмный владыка обрушивает теневую волну: -" + Урон + " HP!";
	ПроверитьСмертьГероя();

	Если Герой.Жив И Ярость Тогда
		Герой.Горение = ДлительностьОжога();
		Сообщение = Сообщение + " Тёмное пламя прожигает вас (ходов: " + Герой.Горение + ")!";
	КонецЕсли;

	Враг.Таймер = ?(Ярость, 3, 5);

КонецПроцедуры

// Некромант: просыпается, когда герой ближе 6 клеток (стены не мешают), затем
// бьёт стоящего вплотную героя и раз в несколько ходов призывает скелетов.
&НаКлиенте
Процедура ХодНекроманта(Враг)

	ДельтаX = ГероX - Враг.X;
	ДельтаY = ГероY - Враг.Y;
	Дистанция = Макс(АбсЧисла(ДельтаX), АбсЧисла(ДельтаY));

	Если Не Враг.Проснулся Тогда
		Если Дистанция <= 6 Тогда
			Враг.Проснулся = Истина;
			Враг.Таймер = 2; // первый призыв через два хода
			Сообщение = Сообщение + " Некромант замечает вас!";
		КонецЕсли;
		Возврат;
	КонецЕсли;

	// Ближний бой, если герой рядом.
	Если АбсЧисла(ДельтаX) + АбсЧисла(ДельтаY) = 1 Тогда
		УдарВрага(Враг);
		Если Не Герой.Жив Тогда
			Возврат;
		КонецЕсли;
	КонецЕсли;

	Враг.Таймер = Враг.Таймер - 1;
	Если Враг.Таймер > 0 Тогда
		Возврат;
	КонецЕсли;

	// Одновременно не больше трёх призванных скелетов.
	Если ЧислоПризванных() >= 3 Тогда
		Враг.Таймер = 1;
		Возврат;
	КонецЕсли;

	// Ищем свободную клетку рядом с некромантом (включая диагонали).
	Для СмещениеY = -1 По 1 Цикл
		Для СмещениеX = -1 По 1 Цикл

			Если СмещениеX = 0 И СмещениеY = 0 Тогда
				Продолжить;
			КонецЕсли;

			НовX = Враг.X + СмещениеX;
			НовY = Враг.Y + СмещениеY;

			Если КлеткаСвободна(НовX, НовY) Тогда
				Скелет = НовыйВраг("Скелет", НовX, НовY);
				Скелет.Призванный = Истина;
				Скелет.Опыт = 0;
				Скелет.Золото = 0;
				Враги.Добавить(Скелет);
				Сообщение = Сообщение + " Некромант призывает скелета!";
				Враг.Таймер = 3; // пауза до следующего призыва
				Возврат;
			КонецЕсли;

		КонецЦикла;
	КонецЦикла;

	// Места нет: попробует ещё раз на следующем ходу.
	Враг.Таймер = 1;

КонецПроцедуры

&НаКлиенте
Функция ЧислоПризванных()

	Результат = 0;

	Для Каждого Враг Из Враги Цикл
		Если Враг.Призванный Тогда
			Результат = Результат + 1;
		КонецЕсли;
	КонецЦикла;

	Возврат Результат;

КонецФункции

&НаКлиенте
Процедура УбратьПризванных()

	Инд = Враги.ВГраница();

	Пока Инд >= 0 Цикл
		Если Враги[Инд].Призванный Тогда
			Враги.Удалить(Инд);
		КонецЕсли;
		Инд = Инд - 1;
	КонецЦикла;

	Сообщение = Сообщение + " Призванные скелеты рассыпаются в прах.";

КонецПроцедуры

// ---------------------------------------------------------------------------
// Отрисовка
// ---------------------------------------------------------------------------

&НаКлиенте
Процедура Отрисовать()

	ПроверитьТорговца();

	// Подписи кнопок способностей берутся из класса героя.
	Попытка
		Элементы.ФормаСпособность1.Заголовок = ДанныеСпособности(Герой.Класс, 1).Имя;
		Элементы.ФормаСпособность2.Заголовок = ДанныеСпособности(Герой.Класс, 2).Имя;
	Исключение
	КонецПопытки;

	ОтрисоватьНаСервере(Карта, ГероX, ГероY, Герой, Враги, Сообщение, Открыто, Локация,
		Жители, СтрокиМагазина(), СтрокиКвестов());

	// Фокус на кнопку, а не на поле с картой: иначе поле может перехватывать
	// нажатия, и горячие клавиши команд (WASD) не срабатывают.
	Попытка
		ТекущийЭлемент = Элементы.ФормаВверх;
	Исключение
	КонецПопытки;

КонецПроцедуры

// Единый источник цветов: карта и легенда берут их отсюда.
&НаСервереБезКонтекста
Функция ЦветОбъекта(Имя)

	Если Имя = "Герой" Тогда
		Возврат Новый Цвет(255, 215, 0);
	ИначеЕсли Имя = "Погибший герой" Тогда
		Возврат Новый Цвет(60, 60, 60);
	ИначеЕсли Имя = "Стена" Тогда
		Возврат Новый Цвет(90, 90, 90);
	ИначеЕсли Имя = "Сундук" Тогда
		Возврат Новый Цвет(205, 133, 63);
	ИначеЕсли Имя = "Зелье" Тогда
		Возврат Новый Цвет(230, 120, 190);
	ИначеЕсли Имя = "Сундук некроманта" Тогда
		Возврат Новый Цвет(255, 90, 40);
	ИначеЕсли Имя = "Пол" Тогда
		Возврат Новый Цвет(235, 235, 235);
	ИначеЕсли Имя = "Слизень" Тогда
		Возврат Новый Цвет(60, 180, 75);
	ИначеЕсли Имя = "Скелет" Тогда
		Возврат Новый Цвет(245, 238, 175);
	ИначеЕсли Имя = "Бронированный скелет" Тогда
		Возврат Новый Цвет(95, 140, 200);
	ИначеЕсли Имя = "Призванный скелет" Тогда
		Возврат Новый Цвет(190, 170, 230);
	ИначеЕсли Имя = "Некромант" Тогда
		Возврат Новый Цвет(120, 40, 160);
	ИначеЕсли Имя = "Туман" Тогда
		Возврат Новый Цвет(15, 15, 20);
	ИначеЕсли Имя = "Выход" Тогда
		Возврат Новый Цвет(110, 190, 255);
	ИначеЕсли Имя = "Трава" Тогда
		Возврат Новый Цвет(110, 185, 90);
	ИначеЕсли Имя = "Забор" Тогда
		Возврат Новый Цвет(140, 105, 70);
	ИначеЕсли Имя = "Дом" Тогда
		Возврат Новый Цвет(170, 70, 60);
	ИначеЕсли Имя = "Дерево" Тогда
		Возврат Новый Цвет(35, 115, 50);
	ИначеЕсли Имя = "Вход в подземелье" Тогда
		Возврат Новый Цвет(45, 25, 65);
	ИначеЕсли Имя = "Дом старосты" Тогда
		Возврат Новый Цвет(150, 120, 200);
	ИначеЕсли Имя = "Оружейная" Тогда
		Возврат Новый Цвет(200, 110, 40);
	ИначеЕсли Имя = "Бронная" Тогда
		Возврат Новый Цвет(110, 140, 170);
	ИначеЕсли Имя = "Староста" Тогда
		Возврат Новый Цвет(60, 60, 200);
	ИначеЕсли Имя = "Оружейник" Тогда
		Возврат Новый Цвет(255, 150, 0);
	ИначеЕсли Имя = "Бронник" Тогда
		Возврат Новый Цвет(80, 200, 220);
	ИначеЕсли Имя = "Житель" Тогда
		Возврат Новый Цвет(240, 200, 170);
	ИначеЕсли Имя = "Пруд" Тогда
		Возврат Новый Цвет(70, 130, 210);
	ИначеЕсли Имя = "Драугр" Тогда
		Возврат Новый Цвет(70, 115, 125);
	ИначеЕсли Имя = "Адское отродье" Тогда
		Возврат Новый Цвет(225, 60, 25);
	ИначеЕсли Имя = "Пиромант" Тогда
		Возврат Новый Цвет(200, 40, 130);
	ИначеЕсли Имя = "Демон" Тогда
		Возврат Новый Цвет(140, 20, 30);
	ИначеЕсли Имя = "Багряный палач" Тогда
		Возврат Новый Цвет(190, 0, 60);
	ИначеЕсли Имя = "Грозовой жнец" Тогда
		Возврат Новый Цвет(60, 120, 255);
	ИначеЕсли Имя = "Тёмный владыка" Тогда
		Возврат Новый Цвет(70, 0, 110);
	ИначеЕсли Имя = "Лазарет" Тогда
		Возврат Новый Цвет(225, 235, 250);
	ИначеЕсли Имя = "Лекарь" Тогда
		Возврат Новый Цвет(255, 255, 255);
	ИначеЕсли Имя = "Люк" Тогда
		Возврат Новый Цвет(150, 90, 40);
	ИначеЕсли Имя = "Лестница вверх" Тогда
		Возврат Новый Цвет(180, 230, 140);
	КонецЕсли;

	Возврат Новый Цвет(255, 0, 255);

КонецФункции

// Тусклые цвета для клеток, которые герой видел раньше, но не видит сейчас.
&НаСервереБезКонтекста
Функция ЦветПамяти(Имя)

	Если Имя = "Стена" Тогда
		Возврат Новый Цвет(45, 45, 52);
	ИначеЕсли Имя = "Сундук" Тогда
		Возврат Новый Цвет(110, 72, 35);
	ИначеЕсли Имя = "Зелье" Тогда
		Возврат Новый Цвет(125, 65, 105);
	ИначеЕсли Имя = "Сундук некроманта" Тогда
		Возврат Новый Цвет(140, 55, 30);
	ИначеЕсли Имя = "Выход" Тогда
		Возврат Новый Цвет(55, 95, 128);
	ИначеЕсли Имя = "Люк" Тогда
		Возврат Новый Цвет(90, 55, 25);
	ИначеЕсли Имя = "Лестница вверх" Тогда
		Возврат Новый Цвет(95, 125, 80);
	КонецЕсли;

	Возврат Новый Цвет(105, 105, 115); // пол

КонецФункции

&НаСервере
Процедура ОтрисоватьНаСервере(Знач ТекКарта, Знач ТекX, Знач ТекY, Знач ТекГерой, Знач ТекВраги, Знач ТекСообщение, Знач ТекОткрыто, Знач ТекЛокация, Знач ТекЖители, Знач ТекМагазин, Знач ТекКвесты)

	Высота = ТекКарта.Количество();
	Ширина = СтрДлина(ТекКарта[0]);

	// Справа от карты: зазор (1 колонка), цветной образец (1) и подпись легенды (17 колонок).
	ШиринаВсего = Ширина + 19;

	Поле.Очистить();
	Поле.Область("C1:C" + ШиринаВсего).ШиринаКолонки = 2;
	Поле.Область("C" + (Ширина + 1) + ":C" + (Ширина + 1)).ШиринаКолонки = 1;
	Поле.Область("R1:R" + Высота).ВысотаСтроки = 7;
	Поле.Область("R" + (Высота + 1) + ":R" + (Высота + 1)).ВысотаСтроки = 3;

	// Карта.
	Для Y = 1 По Высота Цикл
		Для X = 1 По Ширина Цикл

			Символ = Сред(ТекКарта[Y - 1], X, 1);
			Ячейка = Поле.Область(Y, X, Y, X);

			// Неисследованная клетка закрыта туманом.
			Если Сред(ТекОткрыто[Y - 1], X, 1) <> "1" Тогда
				Ячейка.ЦветФона = ЦветОбъекта("Туман");
				Продолжить;
			КонецЕсли;

			// Клетка в радиусе 1 от героя видна сейчас, остальные исследованные только помнятся.
			// В деревне видно всё.
			Видна = (ТекЛокация = "Деревня") Или (X >= ТекX - 1 И X <= ТекX + 1 И Y >= ТекY - 1 И Y <= ТекY + 1);

			Если ТекЛокация = "Деревня" Тогда
				Если Символ = "#" Тогда
					Имя = "Забор";
				ИначеЕсли Символ = "H" Тогда
					Имя = "Дом";
				ИначеЕсли Символ = "T" Тогда
					Имя = "Дерево";
				ИначеЕсли Символ = "P" Тогда
					Имя = "Пруд";
				ИначеЕсли Символ = "S" Тогда
					Имя = "Дом старосты";
				ИначеЕсли Символ = "W" Тогда
					Имя = "Оружейная";
				ИначеЕсли Символ = "A" Тогда
					Имя = "Бронная";
				ИначеЕсли Символ = "M" Тогда
					Имя = "Лазарет";
				ИначеЕсли Символ = "D" Тогда
					Имя = "Вход в подземелье";
				Иначе
					Имя = "Трава";
				КонецЕсли;
			ИначеЕсли Символ = "#" Тогда
				Имя = "Стена";
			ИначеЕсли Символ = "$" Тогда
				Имя = "Сундук";
			ИначеЕсли Символ = "!" Тогда
				Имя = "Зелье";
			ИначеЕсли Символ = "*" Тогда
				Имя = "Сундук некроманта";
			ИначеЕсли Символ = "E" Тогда
				Имя = "Выход";
			ИначеЕсли Символ = "<" Тогда
				Имя = "Лестница вверх";
			ИначеЕсли Символ = "L" Тогда
				Имя = "Люк";
			Иначе
				Имя = "Пол";
			КонецЕсли;

			Если Видна Тогда
				Ячейка.ЦветФона = ЦветОбъекта(Имя);
			Иначе
				Ячейка.ЦветФона = ЦветПамяти(Имя);
			КонецЕсли;

		КонецЦикла;
	КонецЦикла;

	// Жители деревни.
	Если ТекЛокация = "Деревня" Тогда
		Для Каждого Житель Из ТекЖители Цикл
			Поле.Область(Житель.Y, Житель.X, Житель.Y, Житель.X).ЦветФона = ЦветОбъекта(Житель.Роль);
		КонецЦикла;
	КонецЕсли;

	// Враги.
	Для Каждого Враг Из ТекВраги Цикл

		// Врагов видно только в радиусе 1 от героя.
		Если Враг.X < ТекX - 1 Или Враг.X > ТекX + 1 Или Враг.Y < ТекY - 1 Или Враг.Y > ТекY + 1 Тогда
			Продолжить;
		КонецЕсли;

		Ячейка = Поле.Область(Враг.Y, Враг.X, Враг.Y, Враг.X);
		Если Враг.Призванный Тогда
			Ячейка.ЦветФона = ЦветОбъекта("Призванный скелет");
		Иначе
			Ячейка.ЦветФона = ЦветОбъекта(Враг.Тип);
		КонецЕсли;
	КонецЦикла;

	// Герой: жёлтый кубик (тёмный, если погиб).
	Ячейка = Поле.Область(ТекY, ТекX, ТекY, ТекX);
	Если ТекГерой.Жив Тогда
		Ячейка.ЦветФона = ЦветОбъекта("Герой");
	Иначе
		Ячейка.ЦветФона = ЦветОбъекта("Погибший герой");
	КонецЕсли;

	// Легенда справа от карты: цветной образец + подпись, по одной строке на пункт.
	Легенда = Новый Массив;
	Если ТекЛокация = "Деревня" Тогда
		Легенда.Добавить(Новый Структура("Цвет, Текст", Неопределено, "Легенда: деревня"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Герой", "Герой (жёлтый кубик)"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Забор", "Забор"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Дом", "Дом"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Дом старосты", "Дом старосты"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Оружейная", "Оружейная (оружие)"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Бронная", "Бронная (броня)"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Лазарет", "Лазарет (лечение за золото)"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Дерево", "Дерево"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Пруд", "Пруд"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Вход в подземелье", "Вход в подземелье (тёмная арка)"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", Неопределено, ""));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Староста", "Староста (задания)"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Оружейник", "Оружейник"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Бронник", "Бронник"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Лекарь", "Лекарь"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Житель", "Житель"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", Неопределено, ""));
		Легенда.Добавить(Новый Структура("Цвет, Текст", Неопределено, "Шаг в жителя = разговор"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", Неопределено, "Двойной клик: купить, продать, надеть"));
	Иначе
		Легенда.Добавить(Новый Структура("Цвет, Текст", Неопределено, "Легенда"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Герой", "Герой (жёлтый кубик)"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Стена", "Стена"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Выход", "Выход в деревню (подземелье сохр.)"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Люк", "Люк на 2 этаж (нужен ключ)"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Лестница вверх", "Лестница на 1 этаж (со 2 этажа)"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Сундук", "Сундук: золото, иногда лут"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Сундук некроманта", "Сундук босса (ур. 3-4)"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Зелье", "Зелье: +15 HP (2 эт.: +30), лечит яд и огонь"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Слизень", "Слизень (яд: 1 HP, 4 хода)"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Скелет", "Скелет (стреляет, 3 кл.)"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Бронированный скелет", "Бронированный скелет (броня 2)"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Драугр", "Драугр (после некроманта, броня 3)"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Адское отродье", "Адское отродье (поджигает)"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Пиромант", "Пиромант (огненный шар, 4 кл.)"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Демон", "Демон (броня 4)"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Багряный палач", "Багряный палач (вампиризм)"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Грозовой жнец", "Грозовой жнец (молния, 3 кл.)"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Тёмный владыка", "Тёмный владыка (щит, пока живы стражи)"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Призванный скелет", "Призванный скелет (без наград)"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Некромант", "Некромант (босс)"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", "Туман", "Туман: не исследовано"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", Неопределено, ""));
		Легенда.Добавить(Новый Структура("Цвет, Текст", Неопределено, "Шаг во врага = атака"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", Неопределено, "Враги преследуют: радиус 4"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", Неопределено, "Враги не видят через стены"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", Неопределено, "Видимость: 1 клетка"));
		Легенда.Добавить(Новый Структура("Цвет, Текст", Неопределено, "Двойной клик: надеть предмет"));
	КонецЕсли;

	Для Инд = 0 По Мин(Легенда.ВГраница(), Высота - 1) Цикл

		Пункт = Легенда[Инд];
		СтрокаЛегенды = Инд + 1;

		Если Пункт.Цвет <> Неопределено Тогда
			Поле.Область(СтрокаЛегенды, Ширина + 2, СтрокаЛегенды, Ширина + 2).ЦветФона = ЦветОбъекта(Пункт.Цвет);
		КонецЕсли;

		Область = Поле.Область(СтрокаЛегенды, Ширина + 3, СтрокаЛегенды, ШиринаВсего);
		Область.Объединить();
		Область.Текст = Пункт.Текст;
		Область.Шрифт = Новый Шрифт(Область.Шрифт, , 6); // мелкий шрифт: строки карты низкие
		Область.ГоризонтальноеПоложение = ГоризонтальноеПоложение.Лево;
		Область.ВертикальноеПоложение = ВертикальноеПоложение.Центр;

	КонецЦикла;

	// Список квестов под легендой.
	СтрокаКвеста = Легенда.Количество() + 2;
	Для Каждого ТекстКвеста Из ТекКвесты Цикл

		Если СтрокаКвеста > Высота Тогда
			Прервать;
		КонецЕсли;

		Область = Поле.Область(СтрокаКвеста, Ширина + 2, СтрокаКвеста, ШиринаВсего);
		Область.Объединить();
		Область.Текст = ТекстКвеста;
		Область.Шрифт = Новый Шрифт(Область.Шрифт, , 7, ТекстКвеста = "КВЕСТЫ");
		Область.ГоризонтальноеПоложение = ГоризонтальноеПоложение.Лево;
		Область.ВертикальноеПоложение = ВертикальноеПоложение.Центр;

		СтрокаКвеста = СтрокаКвеста + 1;

	КонецЦикла;

	// Полоска HP героя: по одной ячейке на колонку карты.
	СтрокаHP = Высота + 2;
	Заполнено = Окр(ТекГерой.HP / ТекГерой.МаксHP * Ширина, 0, РежимОкругления.Окр15как20);
	Для X = 1 По Ширина Цикл
		Ячейка = Поле.Область(СтрокаHP, X, СтрокаHP, X);
		Если X <= Заполнено Тогда
			Если ТекГерой.Горение > 0 Тогда
				Ячейка.ЦветФона = Новый Цвет(240, 130, 30); // горит: полоска оранжевая
			ИначеЕсли ТекГерой.Яд > 0 Тогда
				Ячейка.ЦветФона = Новый Цвет(90, 170, 60); // отравлен: полоска зелёная
			Иначе
				Ячейка.ЦветФона = Новый Цвет(210, 40, 40);
			КонецЕсли;
		Иначе
			Ячейка.ЦветФона = Новый Цвет(200, 200, 200);
		КонецЕсли;
	КонецЦикла;
	Поле.Область("R" + СтрокаHP + ":R" + СтрокаHP).ВысотаСтроки = 4;

	// Строка статуса и сообщение боя (объединённые ячейки на всю ширину).
	СтрокаСтатуса = Высота + 3;
	Область = Поле.Область(СтрокаСтатуса, 1, СтрокаСтатуса, ШиринаВсего);
	Область.Объединить();
	Область.Текст = ТекГерой.Класс + "   " + ТекЛокация + "   Ур. " + ТекГерой.Уровень + "   HP " + ТекГерой.HP + "/" + ТекГерой.МаксHP
		+ ?(ТекГерой.МаксМана > 0, "   " + ПараметрыКласса(ТекГерой.Класс).Ресурс + " " + ТекГерой.Мана + "/" + ТекГерой.МаксМана, "")
		+ "   Опыт " + ТекГерой.Опыт + "/" + ОпытДляУровня(ТекГерой.Уровень)
		+ "   Золото " + ТекГерой.Золото
		+ "   Атака " + (ТекГерой.АтакаМин + БонусАтаки(ТекГерой.Меч.Уровень))
		+ "-" + (ТекГерой.АтакаМакс + БонусАтаки(ТекГерой.Меч.Уровень))
		+ "   Защита " + БонусЗащиты(ТекГерой.Броня.Уровень)
		+ ?(ТекГерой.Щит > 0, "   ЩИТ: ещё ходов " + ТекГерой.Щит, "")
		+ ?(ТекГерой.Невидим > 0, "   НЕВИДИМ: ещё ходов " + ТекГерой.Невидим, "")
		+ ?(ТекГерой.Яд > 0, "   ЯД: ещё ходов " + ТекГерой.Яд, "")
		+ ?(ТекГерой.Горение > 0, "   ГОРИТ: ещё ходов " + ТекГерой.Горение, "");
	Область.ГоризонтальноеПоложение = ГоризонтальноеПоложение.Лево;
	Поле.Область("R" + СтрокаСтатуса + ":R" + СтрокаСтатуса).ВысотаСтроки = 10;

	СтрокаСообщения = Высота + 4;
	Область = Поле.Область(СтрокаСообщения, 1, СтрокаСообщения, ШиринаВсего);
	Область.Объединить();
	Область.Текст = ТекСообщение;
	Область.ГоризонтальноеПоложение = ГоризонтальноеПоложение.Лево;
	Область.РазмещениеТекста = ТипРазмещенияТекстаТабличногоДокумента.Переносить;
	Поле.Область("R" + СтрокаСообщения + ":R" + СтрокаСообщения).ВысотаСтроки = 52;

	// Инвентарь в самом низу: снаряжение, способности и сумка. У строк предметов в сумке
	// расшифровка = индекс предмета: двойной щелчок вызывает ОбработкаРасшифровки.
	// Панель магазина (если открыта) идёт над снаряжением, инвентарь остаётся внизу.
	СтрокаМагазина = Высота + 5;
	Для Каждого Пункт Из ТекМагазин Цикл
		Область = ВывестиСтрокуИнвентаря(СтрокаМагазина, ШиринаВсего, Пункт.Текст, Пункт.Заголовок);
		Если Пункт.Код <> Неопределено Тогда
			Область.Расшифровка = Пункт.Код;
		КонецЕсли;
		СтрокаМагазина = СтрокаМагазина + 1;
	КонецЦикла;

	СтрокаИнв = СтрокаМагазина;
	ВывестиСтрокуИнвентаря(СтрокаИнв, ШиринаВсего, "СНАРЯЖЕНИЕ", Истина);
	ВывестиСтрокуИнвентаря(СтрокаИнв + 1, ШиринаВсего, "Оружие: " + ОписаниеПредмета(ТекГерой.Меч), Ложь);
	ВывестиСтрокуИнвентаря(СтрокаИнв + 2, ШиринаВсего, "Броня:  " + ОписаниеПредмета(ТекГерой.Броня), Ложь);

	Для Номер = 1 По 2 Цикл
		ДанныеСп = ДанныеСпособности(ТекГерой.Класс, Номер);
		Ожидание = ?(Номер = 1, ТекГерой.КД1, ТекГерой.КД2);
		ТекстСп = "Способность " + Номер + ": " + ДанныеСп.Имя + " — " + ДанныеСп.Описание
			+ ?(ДанныеСп.Стоимость > 0, ", цена " + ДанныеСп.Стоимость, "")
			+ ?(ДанныеСп.Перезарядка > 0, ", перезарядка " + ДанныеСп.Перезарядка, "")
			+ ?(Ожидание > 0, "   [ждать ходов: " + Ожидание + "]", "   [готово]");
		ВывестиСтрокуИнвентаря(СтрокаИнв + 2 + Номер, ШиринаВсего, ТекстСп, Ложь);
	КонецЦикла;

	КолвоВСумке = ТекГерой.Сумка.Количество();
	КолвоСтрок = Макс(ВместимостьСумки(), КолвоВСумке);
	ВывестиСтрокуИнвентаря(СтрокаИнв + 5, ШиринаВсего,
		"СУМКА (" + КолвоВСумке + "/" + ВместимостьСумки() + ")   двойной щелчок по предмету: надеть или выпить (тратит ход)", Истина);

	Для Инд = 0 По КолвоСтрок - 1 Цикл
		СтрокаПредмета = СтрокаИнв + 6 + Инд;
		Если Инд < КолвоВСумке Тогда
			Область = ВывестиСтрокуИнвентаря(СтрокаПредмета, ШиринаВсего,
				XMLСтрока(Инд + 1) + ". " + ОписаниеПредмета(ТекГерой.Сумка[Инд]), Ложь);
			Область.Расшифровка = Инд;
		Иначе
			ВывестиСтрокуИнвентаря(СтрокаПредмета, ШиринаВсего, XMLСтрока(Инд + 1) + ". —", Ложь);
		КонецЕсли;
	КонецЦикла;

КонецПроцедуры

// Строка инвентаря: объединённая ячейка на всю ширину. Возвращает область.
&НаСервере
Функция ВывестиСтрокуИнвентаря(Строка, ШиринаВсего, Текст, Заголовок)

	Область = Поле.Область(Строка, 1, Строка, ШиринаВсего);
	Область.Объединить();
	Область.Текст = Текст;
	Область.ГоризонтальноеПоложение = ГоризонтальноеПоложение.Лево;
	Область.ВертикальноеПоложение = ВертикальноеПоложение.Центр;

	Если Заголовок Тогда
		Область.ЦветФона = Новый Цвет(215, 215, 225);
	КонецЕсли;

	Поле.Область("R" + Строка + ":R" + Строка).ВысотаСтроки = 10;

	Возврат Область;

КонецФункции

&НаКлиенте
Процедура Вверх(Команда)
	Сдвиг(0, -1);
КонецПроцедуры

&НаКлиенте
Процедура Вниз(Команда)
	Сдвиг(0, 1);
КонецПроцедуры

&НаКлиенте
Процедура Влево(Команда)
	Сдвиг(-1, 0);
КонецПроцедуры

&НаКлиенте
Процедура Вправо(Команда)
	Сдвиг(1, 0);
КонецПроцедуры

// Способности классов: команды формы «Способность1» и «Способность2»
// (сочетания клавиш назначаются в конфигураторе, например Q и E).
&НаКлиенте
Процедура Способность1(Команда)
	ИспользоватьСпособность(1);
КонецПроцедуры

&НаКлиенте
Процедура Способность2(Команда)
	ИспользоватьСпособность(2);
КонецПроцедуры

// Кнопка «Начать заново»: новое подземелье и новый герой (класс выбирается снова).
&НаКлиенте
Процедура НачатьЗаново(Команда)
	НачатьИгру();
КонецПроцедуры

// Двойной щелчок по строке предмета в сумке.
// Событие ОбработкаРасшифровки поля формы «Поле».
&НаКлиенте
Процедура ПолеОбработкаРасшифровки(Элемент, Расшифровка, СтандартнаяОбработка)

	СтандартнаяОбработка = Ложь;

	Если ТипЗнч(Расшифровка) = Тип("Число") Тогда
		ИспользоватьПредмет(Расшифровка);
	ИначеЕсли ТипЗнч(Расшифровка) = Тип("Строка") Тогда
		ОбработатьТорговлю(Расшифровка);
	КонецЕсли;

КонецПроцедуры