# Боты: что сделано и как этим пользоваться

## SPP 3 (03.09.2026): боты реально играют

Население — headless-игроки (настоящие `Player` без клиента). Сборка живёт в git (`README.md`), деплой — `build.ps1`. Что делает каждый бот в течение дня, и как это проверить:

**Гринд.** Спот по своему уровню (96 зон, полосы уровней посчитаны по реальным спавнам датапака, `tools/fix_zones.py`), цель — моб не выше уровня+1 и подальше от стаи, автошоты своего грейда, скиллы по профилю класса (`FakePlayerCombat.xml`, 43 класса), самобафы и «городские» бафы (WW/Shield/Might/Empower/Acumen…, обновляются, когда спали), бижа своего грейда, зелья при HP < 45 %, отдых сидя при HP < 60 % / MP < 25 %, ответный удар любому, кто напал, бегство от моба на 7+ уровней выше, поводок к споту (1500 — идёт назад, 7000 — телепорт). Дварфы спойлят и свипают. Смерть — 15 с лежит, встаёт в ближайшем городе, продаёт хлам, докупает расходники, через 20–60 с возвращается на спот.

**Прокачка настоящая**: опыт с реальных убийств, левел-ап реальный, профы на 20/40/76 (`FakePlayerProfessionChange`) — бот берёт следующий класс своей ветки, получает скиллы, говорит об этом; при переходе тира прощается, идёт в город (продажа, покупки, бафы) и телепортируется в зону нового уровня.

**Экономика настоящая**: свой инвентарь, своя адена. Хлам (дешевле 1500 или стопки) продаётся НПС за полцены, дорогой дроп идёт в **лавку** (реальный `PrivateStoreType.SELL`, купить можно) в городе своего уровня; после лавки — покупка сосок/стрел/банок на заработанное и возврат на спот. Стартовый набор шотов — подарок один раз, дальше только за своё.

**Осады** (`FakePlayerSieges`). По расписанию `SiegeSchedule.xml` бот-кланы с ≥3 членами 40+ онлайн регистрируются атакующими на замки бот-кланов (по 2 клана на замок, `FakePlayerSiegeAttackerClans`; на замки игроков — только при `FakePlayerSiegeAttackPlayerCastles = True`). Лидеры кланов подгружаются в мир заранее. В начале осады атакующие собираются за внешними воротами, защитники — у артефакта. Атакующие бьют врагов, ломают ворота, идут к артефакту, лидер клана кастует Печать Правителя (3 минуты, остальные прикрывают) — замок переходит настоящей механикой движка. Крики в крик-чат: начало, ворота, печать, победа/поражение. Форс: `do=siege&arg=Giran`, `do=siegeend`, `do=siegeinfo`.

**Пати.** Соклановцы одного уровня стартуют на одном споте и собираются в настоящие пати (до 6), держатся лидера, ассистят его цель, хилеры лечат. С игроком: «го пати» → бот **приглашает сам** (обычное окно приглашения), инвайт игроку бот принимает.

**Чат — один движок на всех.** Шёпот и общий чат идут через `FakePlayerUnderstanding`: интенты с опечатками, память (кто убивал/помогал/соклановец/враг по кв), позиция на 15 минут (второй раз «я ж сказал: …»), молчание на первое сообщение незнакомца, реакция на капс, зеркалирование стиля, идиолект (свой смех, паразит, сленг, мат у трети), факты контекста (план: фарм/торговля/осада/переезд, событие: ап, ганг, профа, конец осады) и слоты (`{zone} {level} {mob} {town} {crowd} {drop} {clan} {enemyclan} {castle}`). Боты **разговаривают друг с другом**, когда рядом есть живой игрок: здороваются при встрече, задают вопросы (`bot_talk`: «как фарм», «дроп есть?», «какой лвл», «го в пати», «пк ходят?», «клан есть?»…), отвечают через тот же разбор интентов (до 3 реплик в цепочке), подхватывают жалобы и вопросы, поздравляют с лвлом, сплетничают. Зов на помощь при проигрываемом пвп — соклановцы в радиусе 1500 подтягиваются. LLM (Ollama) по умолчанию только для фраз без интента (`FakePlayerLlmPriority = seams`), `llm` — наоборот.

**Проверка, что они это делают, а не делают вид**: `do=stats` (сумма по населению), `do=stats&name=Ник` (один бот: убийства, опыт, лут, продано/куплено/адена, лавки, касты, отдыхи, реплики, пати, переезды, профы, осады, последнее действие), `/api/stats` (JSON), строка `FakePlayerTelemetry: last N min -> kills=+… exp=+… loot=+…` в логе раз в `FakePlayerStatsLogMinutes`, `FakePlayerChatLog = True` — каждая реплика в лог с каналом и швом. Оффлайн: `tools/run_harness.sh` (движок чата на реальных XML), `python tools/check_data.py` (пулы, слоты, швы, скиллы профилей).

Настройки — секция «SPP additions» в `game\config\Custom\FakePlayers.ini`: `FakePlayerLlmPriority`, `FakePlayerBotDialogues`, `FakePlayerEconomy`, `FakePlayerProfessionChange`, `FakePlayerSieges`, `FakePlayerSiegeAttackerClans`, `FakePlayerSiegeAttackPlayerCastles`, `FakePlayerStatsLogMinutes`, `FakePlayerChatLog`.

Ограничения: NPC-режим ботов (`FakePlayerProgression = True`) не поддерживается — шаблонов `fpc_spp_*` в сервере нет; смертность у магов и на спавне в центре стай выше, чем у живых (см. `AUDIT.md`).

---

Сборка от 2026-08-25 (пак `server_interlude.zip`). Население 1–76 с настоящей прокачкой, всё включено по умолчанию.

## Что в паке

**Геодата** — 172 региона `.l2j` уже лежат в `game\data\geodata`, `PathFinding = 2` включён. Боты и мобы обходят стены и ходят по реальному рельефу.

**5000 персонажей, уровни 1–76, 34 зоны** (замер: ~22% одного слабого ядра на все 5000, масштаб почти бесплатный) — от стартовых полей всех рас (Talking Island, Elven Forest, Shadow Land, Orc Lands, Dwarven Valley) через Wasteland, Cruma, Execution Grounds, Sea of Spores, Plains of Glory до Blazing Swamp, Wall of Argos, Ketra и Varka. У каждого — ник, пол, внешность, класс из 11 архетипов (гладиатор, паладин, разрушитель, кинжальщики, лучники, три школы магов, бишоп) и шмот по грейду. Стартовые уровни раскиданы по всей кривой, так что мир населён сразу.

**Настоящая прокачка.** Боты получают опыт с реально убитых мобов (реальные exp-значения мобов из датапака, рейт `FakePlayerXpRate`, по умолчанию x3). Уровень и опыт хранятся в таблице `fake_player_progress` и переживают рестарт. 8 тиров (1–14, 15–24, 25–34, 35–44, 45–54, 55–63, 64–71, 72–76): при переходе тира бот пишет в чат «апнулся, ушёл за шмотом», исчезает и через 1–3 минуты появляется в зоне своего нового уровня — с новой профой (варриор → глад → дуэлист) и новым грейдом шмота (NG → D → C → B → A → S). Кап 76. То есть встреченный на Talking Island новичок через пару дней реально будет фармить в Cruma, а потом в Blazing Swamp.

**Четыре характера** (параметр `fpcPersonality`):

- `ganker` (~25%) — фармит и нападает на игроков в радиусе 1000, кидает дерзкую реплику. Ганкают на любом уровне, включая стартовые поля — но только там, куда бот такого уровня и расы мог реально прийти.
- `pkk` (~10%) — охотник на ПК: белых не трогает, но нападает на фиолетовых (PvP-флаг) и красных (карма). Убил ганкера у новичков — жди комментарий про правосудие.
- `helper` (~15%) — видит твой бой — прибегает и помогает, комментирует.
- `neutral` (~50%) — мирно фармит и болтает. Все, кроме ганкеров, тоже сами бьют мобов (иначе бы не качались).

**Правдоподобность появления**: стартовые локации расовые — на Talking Island качаются только люди, в Elven Forest эльфы, у орков орки и т.д. (бот другой расы «не мог туда прийти»). С 15-го уровня все смешиваются на континенте. Уровень бота всегда соответствует зоне.

**Торговля.** Периодически бот «уходит в город» (по уровню и расе: новички — в свою деревню, 40+ — Гиран/Орен/Аден...), садится и открывает лавку: скупку — если ему «нужно что-то», или продажу — если перед этим успешно нафармил. Раз в 3 минуты кричит зазывалку в торговый канал (текст генерирует LLM). Через час лавка закрывается, бот возвращается на спот, следующая торговля через ~2–2.5 часа. Настройки в `FakePlayers.ini`: `FakePlayerTrading`, `FakePlayerTradeDuration`, `FakePlayerTradeCooldown`, `FakePlayerTradeShoutInterval`, `FakePlayerTradeChance`. Лавки декоративные: купить/продать им ничего нельзя (это NPC без настоящего инвентаря) — но выглядят и кричат как живые.

**Мир живёт без тебя**: `GridsAlwaysOn = True` — AI работает во всех регионах, боты фармят и качаются, даже когда тебя нет рядом. Это дороже по CPU; если сервер начнёт тормозить — выключи в `General.ini`, тогда качаться будут только зоны рядом с тобой.

**LLM-чат через Ollama.** Боты пишут по-русски: тонт при ганге, комментарий при помощи, бытовой трёп когда ты рядом, ответ если ты что-то сказал в общий чат возле них (шанс 40%), ответ в личку (шёпот `"ник текст`). Если Ollama не запущена — молча откатываются на шаблонные ответы из `FakePlayerChatData.xml`, сервер работает как обычно.

## Рынок, титулы, гербы

**Лавки торгуют настоящим лутом.** Боты подбирают дроп со своих убийств (свой лут, чужой не трогают) и идут в город, только когда действительно есть что продать; в лавку выставляются реальные вещи из инвентаря по цене от справочной ±. Никаких выданных из воздуха товаров и никакого лимита — торгуют те, кто нафармил.

**Название лавки собирается из частей** (пулы `store_goods` / `store_price` / `store_mood`): «соски дёшево налетай», «C грейд по рынку», «дроп оптом не торгуюсь» — тон зависит от характера. Подпись теперь реально видна: раньше не хватало broadcast'а `PrivateStoreMsgSell`.

**Титулы вместо PK/PKK.** Над ником — то, что пишут живые игроки: «ищу клан», «куплю адену», «afk», «Гиран», «рб хантер», у большинства пусто. Характер больше нигде не светится.

**Гербы кланов и альянсов** генерируются процедурно (DXT1 DDS 16×12 и 8×12, как требует клиент — 224 и 176 байт при лимитах 256/192): шесть геральдических стилей (полосы, четверти, шеврон, рамка, диагональ, шахматка) и восемь палитр, детерминированно от названия клана. 150 кланов сведены в ~19 альянсов, у каждого свой герб. Сид `db_installer\sql\spp_crest_seed.sql` применяется автоматически (entrypoint теперь накатывает все `spp_*.sql`).

## Мир только из headless-игроков (текущая схема)

NPC-боты выключены (`FakePlayerProgression = False`), население — 500 headless-игроков (`FakePlayerHeadlessCount`), выбранных случайно из тех же 5000 сид-персонажей, то есть с разбросом по всем уровням 1–76. Каждый расселяется в зону по своему уровню и расе (та же таблица зон, что была у NPC — она грузится всегда, даже когда NPC-население отключено). Стоковый бот Evi из поставки Mobius отключён.

Хочешь другое число — правь `FakePlayerHeadlessCount` и пересобирай образ. Замер: 500 headless на слабом ядре песочницы — 3.2% CPU, +0.5 ГБ RAM; на нормальной машине запас большой, но помни, что Player в бою дороже NPC.

Вернуть гибрид (NPC-статисты + элитные headless) — поставить `FakePlayerProgression = True` и уменьшить `FakePlayerHeadlessCount`: тогда headless берут только «хвост» сид-персонажей, а остальные живут как NPC.

## Headless-игроки (настоящая пати) — прототип

`FakePlayerHeadlessCount` в `FakePlayers.ini` (по умолчанию 30) поднимает N ботов не как NPC, а как настоящие `Player`-инстансы без клиента — поднятые из тех же сид-персонажей (берутся старшие charId, прогрессия их не трогает). Они фармят у Гирана. Ключевое: **их можно позвать в настоящую пати** — жмёшь invite, бот через 1–2 сек сам принимает, появляется окно группы, идёт опыт, боты держатся рядом с лидером и ассистят его цель; после смерти встают через 15 сек.

Это цена «быть настоящим игроком»: для движка они игроки, потому что это класс `Player`, а не NPC с флагом.

**Весь функционал NPC-ботов перенесён на headless.** Они берут свою личность из того же сида (`FakePlayerProgression.xml`): характер (ganker/pkk/helper/neutral), раса, клан, домашняя зона. И дальше умеют всё то же самое:

- **речь**: те же швы + LLM с той же персоной; тонты при ганге, реплики ПКК, ассист, бытовой трёп, левел-ап, прощание при переезде;
- **понимание**: шёпот и общий чат проходят через тот же разбор интентов — оскорбил ганкера, он нападёт; попросил помощи, побежит; спросил «что нового» — расскажет слух;
- **память и слухи**: помнят, кто их убивал и кто помогал; смерть headless-бота видят соседние боты и разносят историю с искажением;
- **прогрессия настоящая**: они получают реальный опыт с мобов (они же игроки), при переходе тира сами прощаются и телепортируются в зону своего уровня — по той же таблице зон, что у NPC;
- **пати между собой**: соклановцы поблизости собираются в **настоящие** L2-пати (не имитация);
- **дашборд**: видны на карте отдельным значком (★, обведены золотым), фильтруются как обычные боты.

**Что они умеют как игроки** (добавлено после первого прототипа):

- **клан**: при спавне восстанавливается членство из БД — крест над ником, участие в клановых войнах;
- **торговля**: раз в 0.5–1.5 часа бот телепортируется на площадь Гирана, получает товар (соски, D-грейд), выставляет **настоящую приватную лавку** с ценником и садится на 20–60 минут. У такой лавки реально можно купить — это полноценный `PrivateStoreType.SELL`, а не декорация как у NPC-торговцев;
- **PvP**: если рядом игрок или другой headless из клана-врага — атакует, со всеми штатными последствиями (флаг, карма, PvP-счётчик).

**Замер (песочница, 2 слабых ядра, без геодаты, изолированно от NPC-ботов):**

| headless | CPU | RAM |
|---|---|---|
| 0 | 1.3% | 1.81 ГБ |
| 500 | 3.2% | 2.29 ГБ |

То есть ~1 МБ и почти ноль CPU на одного простаивающего headless-игрока. Но честные оговорки: замер без геодаты (pathfinding выключен) и в основном на фарме — активный бой у Player-инстанса дороже NPC (пересчёт статов, тики эффектов, апдейты пати, броадкаст пакетов), так что под нагрузкой с геодатой цена вырастет. Вывод: **30–100 headless-игроков рядом с 5000 NPC — комфортно; тысячи headless — нет** (это подтверждает гибридную архитектуру). И: авто-принятие пати написано по образцу штатного accept, но вживую с клиентом я его не проверял — это прототип, первым делом протести окно группы у себя.

## Речь, память, слухи

**Композитные фразы.** Реплика собирается из кусочков, а характер решает, какие кусочки вообще войдут:

- `game\data\FakePlayerPools.xml` — 26 пулов по 12 кусочков (приветствия, обращение по нику, слово-паразит, угроза, насмешка, похвальба, предложение помощи, зов в пати, бытовуха, жалоба, встречный вопрос, хвосты-смешки и т.п.). Пул с суффиксом характера (`hello.helper`, `threat.pkk`) перекрывает базовый.
- `game\data\FakePlayerPhrases.xml` — шаблоны сборки: `<part pool="hello" chance="100" skip="ganker"/>`. То есть ганкер никогда не здоровается, а сразу насмехается; хелпер здоровается развёрнуто и добавляет встречный вопрос; ПКК отвечает сухо.

Из тех же частей выходит «прив, {player} чё как» у дружелюбного и «слышь {player} ща будет больно кек» у пкшера. Если для ключа шаблона нет — используется плоский шов из `FakePlayerChatLines.xml`, так что старые ключи продолжают работать. Проверить руками: на дашборде блок «Генератор фраз» — вводишь ключ и характер, получаешь 8 разных сборок.

**Плоские швы** (`game\data\FakePlayerChatLines.xml`): 194 реплики в 14 событийных швах со слотами `{target}/{player}/{boss}/{town}/{level}/{msg}` — редактируются без пересборки. Анти-повтор: каждый бот помнит свои последние 12 реплик и не повторяется.

**Память** (`fake_player_memory`): бот помнит, кто его убивал (обида) и кто помогал добить его моба (благодарность). При встрече узнаёт («опа, а вот и обидчик»), LLM-разговоры тоже пишутся в память.

**Слухи** — порт системы из «Округи»: смерть бота видят соседние боты (свидетели), истории гуляют между ботами при встречах с искажением (уверенность падает, пересказы копятся, масштаб растёт: «гангнул» → «вырезал всех на точке» → «пол сервера в страхе держит»). Рассказывая тебе, бот выдаёт источник хеджем: «сам видел как ты…» / «говорят ты…» / «брешут будто ты…» / «уже легенды ходят как ты…». Стоя рядом с двумя ботами, можно подслушать сплетню о себе. Всё в `fake_player_rumor`, переживает рестарт, и всё это скармливается LLM в персону (с пометкой «пересказ N раз, можешь ошибаться»).

## Группы, рейды, олимпиада

**Сквады.** Ганкеры и охотники на ПК ходят группами по 2–4 (свой клан, свой уровень, своя раса): лидер ведёт маршрут, остальные держатся рядом и вместе входят в бой (соседние фейки и так ассистят друг другу в радиусе 600). Итого ~500 бродячих групп: ганг встречается пати, и ответка от ПКК тоже приходит пати.

**Рейды на боссов.** Каждые 30–90 минут собирается пати из 5 ботов 60+ и идёт убивать живого рейд-боссa (55–80 lvl): телепорт к боссу («со стороны» выглядит как приход группы), боевой клич в чат, бой до смерти босса или 25 минут, потом расходятся по спотам. В логе: `Raid event started/finished`. Гранд-боссы (Баюм и т.п.) не трогаются.

**Олимпиада — иллюзия, честно.** Настоящие бои олимпиады требуют персонажей-игроков, NPC-боты в них участвовать не могут — это фундаментальное ограничение. Что сделано вместо: 60 ботов записаны в нобли с очками/статистикой боёв, 12 из них — действующие Герои (по классам). Списки ноблей и героев в игре настоящие. Чего нет: самих матчей и ротации героев по месяцам (могу добавить «виртуальную ротацию» очков позже).

## Кланы, войны, холлы, замки (pre-seed)

12 бот-кланов в базе (RedSyndicate и GopStop — пкшеры, KarmaPolice и NightWatch — охотники на ПК, Academka — новички, IronPact/OldSchool — хай-энд...). ~85% ботов состоят в кланах и носят клан-теги; все 200 существуют и как офлайн-персонажи, так что списки членов кланов в игре настоящие. Между кланами прописано 8 взаимных войн. 30 из 34 аукционных клан-холлов заняты бот-кланами (аренда оплачена), 4 свободны для тебя. Замки Глудио, Дион, Гиран и Аден принадлежат бот-кланам (кресты над городами).

**Война с бот-кланом работает по-настоящему**: объяви войну (например, RedSyndicate) — и каждый бот этого клана при встрече будет нападать на членов твоего клана, с боевыми выкриками. Осады замков ботами пока не отыгрываются.

Сид идемпотентный (`db_installer\sql\spp_clan_seed.sql`), применяется автоматически при старте login-контейнера; занятые игроком холлы не трогает.

## Включение Ollama

1. Поставь Ollama: https://ollama.com/download
2. `ollama pull gemma3`
3. Ollama должна слушать на 127.0.0.1:11434 (по умолчанию так и есть).

Всё. Сервер сам начнёт ходить в неё. Модель, язык, тайминги и шансы — в `game\config\Custom\FakePlayers.ini`, секция «SPP additions».

## Настройка

`game\config\Custom\FakePlayers.ini`:

- `FakePlayerXpRate = 3` — скорость прокачки ботов (реальный опыт мобов × рейт).
- `FakePlayerProgression = True` — вся система прокачки; False = статичное население.
- `FakePlayerAggroPlayers = True` — ганкеры нападают на игроков. False = мирные боты.
- `FakePlayerAmbientChatChance / CombatChatChance / LocalChatReplyChance` — болтливость.
- `FakePlayerLlmModel` — любая модель Ollama (gemma3, qwen3, llama3.1...).

Сбросить прогресс всех ботов: `DROP TABLE fake_player_progress;` в базе — при следующем старте пересоздастся со стартовыми уровнями.

Своих персонажей добавлять в `game\data\FakePlayerProgression.xml` (секция `character`: id, имя, характер, стартовый уровень) — но для нового id нужны и 8 тир-шаблонов в `game\data\stats\npcs\custom\fpc_spp_*.xml` (id = 82000 + (charId−1)×8 + тир). Проще попросить меня перегенерировать население с нужными параметрами.

## Что изменено в исходниках (src_mobius_interlude.zip)

Новые файлы:

- `gameserver/managers/FakePlayerLlmService.java` — асинхронный клиент Ollama (без зависимостей, семафор на 2 запроса, санитизация ответов).
- `gameserver/managers/FakePlayerAiTaskManager.java` — «мозг»: тик раз в 5 сек, тонты при ганге, ассист и фарм для не-агрессивных, бытовой чат.
- `gameserver/managers/FakePlayerProgressionManager.java` — прокачка: опыт с киллов, тиры, миграция между зонами, движение по точкам зоны, персист в БД.

Правки:

- `FakePlayerChatManager.java` — LLM-путь с фолбэком на паттерны, `manageLocalChat`/`manageEventChat`/`broadcastToNearby`, персона бота из шаблона.
- `FakePlayersConfig.java` — новые ключи конфига.
- `Attackable.java` — хук начисления опыта боту при убийстве моба.
- `GameServer.java` — старт менеджеров.
- `data/scripts/handlers/chat/channels/ChatGeneral.java` — реакция ботов на общий чат.

Данные генерируются скриптом (1600 шаблонов в `fpc_spp_00..07.xml`, зоны/опыт/персонажи в `FakePlayerProgression.xml`). Таблица опыта построена из реальных exp-значений мобов датапака: уровень ≈ 60 убитых мобов своего уровня.

Пересборка: `ant` из корня `L2J_Mobius_CT_0_Interlude` (нужны JDK 25 и Ant).

## Проверено в песочнице

Полный цикл на тестовом рейте x1000: за 2,5 минуты 192 из 200 ботов получили опыт с реальных киллов, +2066 уровней суммарно, миграции между зонами и сохранение в БД отработали, 0 исключений в логе.

## Ограничения текущей версии

Уровень бота в бою определяется тиром (статы шаблона), а не точным уровнем из БД — внутри тира бот дерётся одинаково. Хилеры-helper-ы бьют моба, а не хилят тебя. Ганкеры бросают погоню, если ты убежал далеко от их зоны. Пати с ботами собрать нельзя — они NPC; управляемые пати-боты — следующий большой этап.
