Skip to content

Автозаселение странствующего торговца: вынести выбор комнаты из движка в конфиг #3773

Description

@bylins

Что сейчас

Странствующий торговец (моб 1901, держатель магазина BandageShop из lib/cfg/economics/shops.xml) расставляется по миру целиком из кода — town_shop_keepers(), src/gameplay/economics/shop_ext.cpp:554, вызывается один раз на буте из db.cpp:1525:

if (GetZoneRooms(world[ch->in_room]->zone_rn, &rnum_start, &rnum_end)) {
    CharData *mob = ReadMobile(1901, kVirtual);
    if (mob) {
        PlaceCharToRoom(mob, number(rnum_start, rnum_end));
    }
}

Функция идёт по всем рентёрам и в каждую зону с рентой сажает одного торговца в случайную комнату диапазона зоны. В данных этого моба нет нигде: резета моба 1901 нет ни в одной зоне, внум зашит ровно в одном месте во всём движке — в строке выше. shops.xml задаёт только ассортимент, но не место.

Следствие — #3770: торговец оказался в комнате 9052 «На дубе» (зона 90), у которой нет ни одного выхода, лезут туда командным триггером. Он там появился при буте и уйти не может.

В #3771 выбор комнаты ограничен фильтром (есть выход, не служебка %100 == 99, не смерть-комната, не kNoEntryMob, не комната богов/дом/клан/арена). Это заплатка на существующем механизме: критерий по-прежнему живёт в .cpp.

Что предлагается

Вынести автозаселение в конфиг: моб, область действия, количество на зону и критерий пригодной комнаты — данные, а не константы в коде. Движок остаётся исполнителем предиката, но перестаёт быть источником политики.

По сути это не новая сущность: town_shop_keepers() и так представляет собой один захардкоженный резет. Отличий от штатного механизма четыре, и все — минусы:

  • исполняется однократно на буте, а не на каждом резете зоны: убили торговца или он ушёл — до перезагрузки в городе продавца бинтов нет;
  • внум зашит в код;
  • критерий комнаты зашит в код;
  • идёт мимо штатных счётчиков резета.

Прецедент такого конфига в проекте уже есть — lib/cfg/specials.xml, где мобы привязываются к спецпроцедурам списком, без строчки в коде.

Масштаб: под kRent в specials.xml 75 мобов, это примерно 70 зон, то есть столько же торговцев.

Подводные камни

1. Счётчики резета. Команда M (db.cpp:2913) имеет вид 'M' <flag> <MobVnum> <max_in_world> <RoomVnum> <max_in_room> и при исполнении проверяет mob_index[rnum].total_online < max_in_world и, если задан, CountMobsInRoom(...) < max_in_room. Один внум на все города это выдерживает, если генератор сам выставит max_in_world = число городов и max_in_room = 1. Отдельные мобы-клоны на каждый город не нужны.

2. Бродячесть. У моба 1901 нет ни kSentinel, ни kStayZone — он уходит из города и может уйти из зоны совсем. При резетном подходе, пока он жив где-то в лесу, max_in_world занят, и в опустевшем городе новый не появится. Лечится флагом kStayZone в прототипе — одна правка в зоне 19, а не в семидесяти. Нужно решить, хотим ли мы вообще, чтобы он бродил между зонами.

3. Сгенерированные резеты и OLC. zone_table[].cmd — сырой C-массив (zone.h:53), а zedit_save_to_disk() (zedit.cpp:434) пишет зону в файл именно из него. Если генератор допишет команды в этот массив, они утекут в файл зоны при первом же сохранении зоны билдером. Нужен признак «команда сгенерирована движком, не сохранять» — и это, похоже, единственная настоящая работа во всей затее. Альтернатива — не трогать cmd вовсе и держать сгенерированные заселения отдельным списком, который исполняется на резете зоны рядом со штатными командами.

4. Формат предиката. Соблазн сделать язык выражений; вероятно, избыточно. Хватит перечислений: требуется хотя бы один выход, чёрный список флагов комнаты, исключение vnum % 100 == 99, возможно белый список секторов. Плюс возможность прибить место списком конкретных комнат для отдельной зоны — тогда билдер решает точно, а дефолт остаётся «случайная подходящая».

5. Совместимость. Пока конфига нет или он пуст — поведение не должно меняться. И надо решить, что делать, если в зоне не нашлось ни одной подходящей комнаты: сейчас в #3771 зона пропускается с записью в SYSLOG.

6. Зоны-подземелья и служебки. Текущая функция отсекает зоны с внумом ≤ 39 и комнату рентёра %100 == 99; при переезде в конфиг эти правила должны быть выражены явно, иначе потеряются.

Набросок реализации

Пометка: это черновик по итогам разбора #3770, его надо пересмотреть критически — особенно пункты 2 и 3, там возможны решения лучше.

  1. Новый конфиг (например, lib/cfg/mechanics/autopopulate.xml со схемой рядом): записи вида «моб — область действия — сколько на зону — критерий комнаты — необязательный список фиксированных комнат по зонам».
  2. Загрузчик конфига при буте, рядом с остальными lib/cfg.
  3. Область действия «зоны, где есть моб со спецпроцедурой kRent» — то же условие, что сейчас в town_shop_keepers(), но выраженное в конфиге, а не в коде.
  4. Исполнение на резете зоны, а не только на буте, чтобы торговец восстанавливался. Механизм — либо генерируемые команды резета с признаком «не сохранять», либо отдельный список заселений, исполняемый на резете (см. подводный камень 3).
  5. Предикат комнаты — фильтр из fix(shops): stop dropping the wandering trader into dead rooms (#3770) #3771, но параметры берутся из конфига.
  6. town_shop_keepers() и захардкоженный внум 1901 удаляются.
  7. Флаг kStayZone мобу 1901 — если решим, что торговец не должен уходить из города.

Связано: #3770, #3771.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions