Загрузка данных
// Модуль управляемой формы внешней обработки "МиниРПГ"
// (ТабличныйДокумент, процедурная генерация, преследование, бой, босс)
//
// Реквизит формы: Поле (тип ТабличныйДокумент)
// Команды формы: Вверх, Вниз, Влево, Вправо, НачатьЗаново,
// Способность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);
КонецПроцедуры
// Кнопка «Начать заново»: новое подземелье и новый герой (класс выбирается снова).
&НаКлиенте
Процедура НачатьЗаново(Команда)
НачатьИгру();
КонецПроцедуры
// Двойной щелчок по строке предмета в сумке.
// Событие ОбработкаРасшифровки поля формы «Поле».
&НаКлиенте
Процедура ПолеОбработкаРасшифровки(Элемент, Расшифровка, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
Если ТипЗнч(Расшифровка) = Тип("Число") Тогда
ИспользоватьПредмет(Расшифровка);
ИначеЕсли ТипЗнч(Расшифровка) = Тип("Строка") Тогда
ОбработатьТорговлю(Расшифровка);
КонецЕсли;
КонецПроцедуры