diff --git a/internal/pzconfig/sandbox.go b/internal/pzconfig/sandbox.go index a35a04e..be34040 100644 --- a/internal/pzconfig/sandbox.go +++ b/internal/pzconfig/sandbox.go @@ -13,6 +13,9 @@ import ( type SandboxEntry struct { Path string `json:"path"` Value string `json:"value"` + // Comment — строки "--" перед параметром: игра расписывает там смысл + // значения и расшифровку вариантов. + Comment string `json:"comment,omitempty"` } // Sandbox — разбор _SandboxVars.lua. Файл генерируется игрой и имеет @@ -24,6 +27,8 @@ type Sandbox struct { lines []string // index сопоставляет путь параметра с номером строки. index map[string]int + // comments хранит описание параметра по его пути. + comments map[string]string // crlf — файл был с виндовыми переводами строк; сохраняем как было. crlf bool } @@ -35,16 +40,24 @@ var ( // ParseSandbox разбирает содержимое SandboxVars.lua. func ParseSandbox(data []byte) *Sandbox { - s := &Sandbox{index: make(map[string]int)} + s := &Sandbox{index: make(map[string]int), comments: make(map[string]string)} raw := string(data) s.crlf = strings.Contains(raw, "\r\n") text := strings.ReplaceAll(raw, "\r\n", "\n") s.lines = strings.Split(text, "\n") var stack []string + // Комментарии копим до ближайшего параметра, как и в .ini. + var comment []string + for n, line := range s.lines { trimmed := strings.TrimSpace(line) - if trimmed == "" || strings.HasPrefix(trimmed, "--") { + if strings.HasPrefix(trimmed, "--") { + comment = append(comment, strings.TrimSpace(strings.TrimPrefix(trimmed, "--"))) + continue + } + if trimmed == "" { + comment = nil continue } if sandboxCloseRe.MatchString(line) { @@ -66,6 +79,7 @@ func ParseSandbox(data []byte) *Sandbox { } else { stack = stack[:0] } + comment = nil continue } path := name @@ -73,6 +87,10 @@ func ParseSandbox(data []byte) *Sandbox { path = strings.Join(stack, ".") + "." + name } s.index[path] = n + if len(comment) > 0 { + s.comments[path] = strings.Join(comment, " ") + } + comment = nil } return s } @@ -99,7 +117,11 @@ func (s *Sandbox) Entries() []SandboxEntry { if !ok { continue } - out = append(out, SandboxEntry{Path: path, Value: s.valueAt(n)}) + out = append(out, SandboxEntry{ + Path: path, + Value: s.valueAt(n), + Comment: s.comments[path], + }) } return out } diff --git a/web/static/app.js b/web/static/app.js index 6b90ad7..5e524ba 100644 --- a/web/static/app.js +++ b/web/static/app.js @@ -476,31 +476,165 @@ $("#console-command").addEventListener("keydown", (event) => { // Конфиг сервера // --------------------------------------------------------------------------- -// Подсказки к самым востребованным параметрам: остальные показываются как есть. +// Подписи и описания параметров servertest.ini. Игра пишет свои комментарии +// на языке сервера, и на Linux они почти всегда английские, поэтому панель +// держит собственные: ключ -> [подпись, описание]. Чего здесь нет, панель +// покажет с описанием из самого конфига. const CONFIG_HINTS = { - PublicName: "Название сервера в списке серверов", - PublicDescription: "Описание сервера", - Public: "Показывать сервер в публичном списке (true/false)", - Password: "Пароль для входа на сервер (пусто — без пароля)", - MaxPlayers: "Максимум игроков", - DefaultPort: "Игровой порт (UDP), по умолчанию 16261", - UDPPort: "Второй игровой порт, по умолчанию 16262", - PVP: "Разрешить бой между игроками", - PauseEmpty: "Ставить мир на паузу, когда никого нет онлайн", - GlobalChat: "Общий чат для всего сервера", - Open: "Пускать игроков без записи в белом списке", - ServerWelcomeMessage: "Приветственное сообщение", - SaveWorldEveryMinutes: "Как часто сохранять мир, минут", - BackupsOnStart: "Делать бэкап силами самой игры при старте", - RCONPort: "Порт RCON", - RCONPassword: "Пароль RCON", - AdminSafehouse: "Разрешить админам входить в чужие убежища", - SafetySystem: "Система защиты от PVP-урона", - DisplayUserName: "Показывать ники над персонажами", - SpawnPoint: "Точка появления новых игроков (x,y,z)", - Mods: "Список модов — редактируется во вкладке «Моды»", - WorkshopItems: "Моды мастерской — редактируется во вкладке «Моды»", - Map: "Карты — редактируется во вкладке «Моды»", + PublicName: ["Название сервера", "Показывается в списке серверов игры и Steam."], + PublicDescription: ["Описание сервера", "Текст под названием в списке серверов. Перенос строки — ."], + Public: ["Показывать в списке серверов", "Сервер со Steam-авторизацией виден в браузере Steam в любом случае."], + Open: ["Пускать без белого списка", "Если выключено, учётные записи создаёт администратор вручную."], + MaxPlayers: ["Максимум игроков", "Администраторы не считаются. Выше 32 игроков возможны просадки."], + ServerWelcomeMessage: ["Приветственное сообщение", "Показывается в чате сразу после входа. Поддерживает и цвета ."], + ResetID: ["Идентификатор сброса", "Меняется при мягком сбросе мира: клиенты с другим значением создают нового персонажа."], + ServerPlayerID: ["Идентификатор персонажей", "По нему игра отличает персонажей этого сервера от чужих."], + Seed: ["Зерно генерации мира", "Чтобы сменить, впишите новое значение и удалите map_worldgen.bin в папке мира."], + server_browser_announced_ip: ["Объявляемый IP", "Нужен, когда у машины несколько адресов и в список серверов должен попасть определённый."], + Password: ["Пароль сервера", "Пусто — вход без пароля."], + MaxAccountsPerUser: ["Учёток на игрока", "Сколько персонажей может завести один Steam-аккаунт. 0 — без ограничения."], + DenyLoginOnOverloadedServer: ["Не пускать при перегрузке", "Отказывать во входе, когда сервер не справляется с нагрузкой."], + DropOffWhiteListAfterDeath: ["Убирать из белого списка после смерти", "Погибший игрок теряет доступ и должен получить его заново."], + LoginQueueEnabled: ["Очередь на вход", "Лишние игроки ждут освободившегося места вместо отказа."], + LoginQueueConnectTimeout: ["Таймаут очереди, сек", "Сколько ждать подключения игрока, дошедшего до начала очереди."], + AllowCoop: ["Разрешить разделённый экран", "Несколько игроков с одного клиента."], + AllowNonAsciiUsername: ["Ники не только латиницей", "Разрешить кириллицу и другие не-ASCII символы в никах."], + SteamScoreboard: ["Показывать Steam-имена", "Отображать имена и аватары Steam в списке игроков."], + SteamVAC: ["Защита Steam VAC", "Включает античит-защиту Valve."], + DoLuaChecksum: ["Сверять контрольные суммы Lua", "Отключать игроков с изменёнными Lua-файлами."], + DefaultPort: ["Игровой порт (UDP)", "По умолчанию 16261. Должен быть открыт на firewall."], + UDPPort: ["Второй игровой порт", "По умолчанию 16262. Тоже должен быть открыт."], + RCONPort: ["Порт RCON", "Порт для внешнего управления консолью сервера."], + RCONPassword: ["Пароль RCON", "Пусто — RCON выключен. Панели он не нужен: она пишет прямо в консоль."], + PingLimit: ["Предел пинга, мс", "Игроков с задержкой выше выкидывает. Ниже 100 ставить не стоит."], + UPnP: ["Пробрасывать порты через UPnP", "Автоматически открыть порты на роутере, если тот поддерживает UPnP."], + MaxPacketsPerSecond: ["Пакетов в секунду", "Ограничение сетевого потока на игрока."], + ClientCommandFilter: ["Фильтр команд клиента", "Список команд, которые не пишутся в журнал."], + MultiplayerStatisticsPeriod: ["Период сбора статистики", "Как часто писать сетевую статистику. 0 — не собирать."], + PVP: ["Разрешить бой между игроками", "Игроки могут ранить и убивать друг друга."], + PVPLogToolChat: ["Писать PVP в чат админов", "Сообщать администраторам о PVP-стычках в чате."], + PVPLogToolFile: ["Писать PVP в файл", "Вести журнал PVP-стычек на диске."], + SafetySystem: ["Система безопасности", "Урон игроку проходит, только когда оба сняли защиту."], + ShowSafety: ["Показывать значок защиты", "Иконка над головой показывает, снята ли защита."], + SafetyToggleTimer: ["Задержка переключения, сек", "Сколько секунд занимает снятие или включение защиты."], + SafetyCooldownTimer: ["Откат переключения, сек", "Через сколько секунд защиту можно переключить снова."], + SafetyDisconnectDelay: ["Задержка выхода в PVP, сек", "Сколько игрок остаётся в мире после отключения во время боя."], + PVPMeleeDamageModifier: ["Урон в ближнем бою, %", "Множитель урона игрок-игрок для оружия ближнего боя."], + PVPFirearmDamageModifier: ["Урон от огнестрела, %", "Множитель урона игрок-игрок для огнестрельного оружия."], + PVPMeleeWhileHitReaction: ["Удары во время реакции на попадание", "Разрешить бить игрока, пока тот отшатывается от предыдущего удара."], + KnockedDownAllowed: ["Разрешить сбивать с ног", "Игроки могут сбивать друг друга с ног."], + SneakModeHideFromOtherPlayers: ["Скрытность прячет от игроков", "В режиме скрытности игрока не видят другие игроки."], + PlayerBumpPlayer: ["Толкать других игроков", "Столкновения игроков между собой."], + UsePhysicsHitReaction: ["Физическая реакция на удар", "Включает физику отдачи при попадании."], + CarEngineAttractionModifier: ["Шум двигателя для зомби", "Насколько сильно двигатель привлекает зомби."], + Map: ["Карты", "Список карт через точку с запятой. Первой идёт карта мода, последней — базовая."], + SpawnPoint: ["Точка появления", "Координаты x,y,z. Нули — стандартные точки появления карты."], + SpawnItems: ["Стартовые предметы", "Список предметов, которые получает новый персонаж."], + SaveWorldEveryMinutes: ["Сохранять мир каждые, мин", "0 — сохранять только при выходе игроков и остановке сервера."], + PauseEmpty: ["Пауза без игроков", "Игровое время останавливается, когда на сервере никого нет."], + NoFire: ["Запретить огонь", "Полностью отключает распространение огня."], + AllowDestructionBySledgehammer: ["Разрешить кувалду", "Игроки могут ломать стены и объекты кувалдой."], + SledgehammerOnlyInSafehouse: ["Кувалда только в убежище", "Ломать можно лишь внутри своего убежища."], + PlayerRespawnWithSelf: ["Возрождение у своего трупа", "Разрешить появляться на месте гибели прошлого персонажа."], + PlayerRespawnWithOther: ["Возрождение у товарища", "Разрешить появляться рядом с другим игроком."], + FastForwardMultiplier: ["Ускорение времени", "Во сколько раз ускоряется время при перемотке."], + RemovePlayerCorpsesOnCorpseRemoval: ["Убирать и трупы игроков", "Автоочистка трупов затрагивает и погибших игроков."], + TrashDeleteAll: ["Кнопка «удалить всё»", "Разрешить очищать контейнер целиком одним действием."], + BloodSplatLifespanDays: ["Кровь исчезает через, дней", "0 — пятна крови остаются навсегда."], + ItemNumbersLimitPerContainer: ["Предметов в контейнере", "Ограничение на количество предметов. 0 — без ограничения."], + SafehousePreventsLootRespawn: ["Убежище блокирует респавн лута", "В зданиях-убежищах лут заново не появляется."], + SwitchZombiesOwnershipEachUpdate: ["Передавать зомби каждый такт", "Меняет схему распределения зомби между клиентами."], + UltraSpeedDoesnotAffectToAnimals: ["Перемотка не ускоряет животных", "На максимальной скорости времени животные живут в обычном темпе."], + SleepAllowed: ["Разрешить сон", "Игроки могут спать; на сервере с несколькими игроками время не перематывается."], + SleepNeeded: ["Требовать сон", "Усталость накапливается и требует сна."], + SpeedLimit: ["Ограничение скорости", "Предел скорости транспорта, км/ч."], + BackupsCount: ["Сколько копий хранить", "Резервные копии, которые делает сама игра."], + BackupsOnStart: ["Копия при запуске", "Игра делает бэкап мира при каждом старте сервера."], + BackupsOnVersionChange: ["Копия при смене версии", "Бэкап перед первым запуском на новой версии игры."], + BackupsPeriod: ["Период копий, мин", "0 — периодические бэкапы игры выключены. У панели есть свои."], + Mods: ["Включённые моды", "Список Mod ID в порядке загрузки. Правится на вкладке «Моды»."], + WorkshopItems: ["Загружаемые из мастерской", "Список Workshop ID. Правится на вкладке «Моды»."], + GlobalChat: ["Общий чат", "Сообщения видят все игроки на сервере."], + ChatStreams: ["Доступные каналы чата", "Список каналов: s — сказать, r — рация, a — админ, w — крик и так далее."], + VoiceEnable: ["Голосовой чат", "Разрешить голосовое общение."], + VoiceMinDistance: ["Слышимость вблизи", "Расстояние, на котором голос слышен без затухания."], + VoiceMaxDistance: ["Предел слышимости", "Расстояние, дальше которого голос не слышен."], + Voice3D: ["Объёмный звук голоса", "Голос слышен с направления, откуда говорит игрок."], + ChatMessageCharacterLimit: ["Длина сообщения", "Максимум символов в одном сообщении чата."], + ChatMessageSlowModeTime: ["Пауза между сообщениями, сек", "Защита от флуда: минимальный интервал между сообщениями."], + AnnounceDeath: ["Объявлять о смертях", "Сообщать в чат о гибели игроков."], + AnnounceAnimalDeath: ["Объявлять о гибели животных", "Сообщать в чат, когда погибает домашнее животное."], + BanKickGlobalSound: ["Звук при бане и кике", "Проигрывать звук всем при исключении игрока."], + BadWordListFile: ["Файл со списком брани", "Путь к списку запрещённых слов."], + GoodWordListFile: ["Файл с исключениями", "Слова, которые фильтр пропускает."], + BadWordPolicy: ["Что делать с бранью", "Заменять слово или блокировать сообщение целиком."], + BadWordReplacement: ["Чем заменять", "Текст, на который меняется запрещённое слово."], + DisplayUserName: ["Ники над головами", "Показывать имена игроков в мире."], + ShowFirstAndLastName: ["Имя и фамилия", "Показывать полное имя персонажа вместо ника."], + UsernameDisguises: ["Маскировка ника", "Ник скрывается, когда лицо игрока закрыто маской или шлемом."], + HideDisguisedUserName: ["Прятать ник замаскированных", "У замаскированного игрока имя не показывается вовсе."], + MouseOverToSeeDisplayName: ["Имя по наведению", "Ник виден, только если навести на игрока курсор."], + HidePlayersBehindYou: ["Скрывать игроков за спиной", "Не показывать тех, кто вне поля зрения персонажа."], + MapRemotePlayerVisibility: ["Игроки на карте", "Показывать ли других игроков на внутриигровой карте."], + ShowCoordinates: ["Показывать координаты", "Координаты видны игрокам в интерфейсе."], + DisableScoreboard: ["Отключить таблицу игроков", "Скрыть список игроков сервера."], + HideAdminsInPlayerList: ["Прятать администраторов", "Администраторы не видны в списке игроков."], + PlayerSafehouse: ["Игроки могут ставить убежища", "Разрешить обычным игрокам объявлять здание своим."], + AdminSafehouse: ["Убежища только у админов", "Объявлять убежища могут лишь администраторы."], + SafehouseAllowTrepass: ["Пускать посторонних", "Разрешить входить в чужое убежище."], + SafehouseAllowFire: ["Огонь в убежище", "Разрешить возгорания на территории убежища."], + SafehouseAllowLoot: ["Брать вещи в убежище", "Разрешить посторонним забирать предметы из убежища."], + SafehouseAllowRespawn: ["Возрождение в убежище", "Члены убежища появляются в нём после гибели."], + SafehouseDaySurvivedToClaim: ["Дней до права на убежище", "Сколько дней надо прожить, прежде чем объявлять здание своим."], + SafeHouseRemovalTime: ["Освобождение убежища, ч", "Через сколько часов отсутствия владельца убежище снимается."], + SafehouseAllowNonResidential: ["Нежилые здания под убежище", "Разрешить занимать магазины, склады и прочие нежилые постройки."], + SafehouseDisableDisguises: ["Без маскировки в убежище", "Маскировка не действует на территории убежища."], + MaxSafezoneSize: ["Размер безопасной зоны", "Максимальная площадь убежища в клетках."], + DisableSafehouseWhenOwnerConnected: ["Снимать защиту при владельце", "Пока владелец в игре, защита убежища не действует."], + DisableSafehouseWhenPlayerConnected: ["Снимать защиту при игроках", "Защита не действует, пока внутри есть игроки."], + Faction: ["Разрешить фракции", "Игроки могут создавать группировки."], + FactionDaySurvivedToCreate: ["Дней до создания фракции", "Сколько дней нужно прожить, чтобы основать фракцию."], + FactionPlayersRequiredForTag: ["Игроков для метки фракции", "Сколько участников нужно, чтобы у фракции появился тег."], + War: ["Разрешить войны фракций", "Фракции могут объявлять друг другу войну."], + WarStartDelay: ["Задержка начала войны", "Через сколько времени после объявления начинается война."], + WarDuration: ["Длительность войны", "Сколько длится объявленная война."], + WarSafehouseHitPoints: ["Прочность убежищ в войне", "Сколько урона выдерживают постройки во время войны."], + DisableRadioStaff: ["Скрыть рацию персонала", "Отключить служебный радиоканал."], + DisableRadioAdmin: ["Скрыть рацию админов", "Отключить радиоканал администраторов."], + DisableRadioGM: ["Скрыть рацию гейм-мастеров", "Отключить радиоканал GM."], + DisableRadioOverseer: ["Скрыть рацию наблюдателей", "Отключить радиоканал наблюдателей."], + DisableRadioModerator: ["Скрыть рацию модераторов", "Отключить радиоканал модераторов."], + DisableRadioInvisible: ["Скрыть рацию невидимых", "Отключить радиоканал скрытых персонажей."], + DiscordEnable: ["Связь с Discord", "Пересылать чат сервера в Discord и обратно."], + DiscordToken: ["Токен бота", "Токен Discord-бота, через которого идёт связь."], + DiscordChannel: ["Канал чата", "Название канала для игрового чата."], + DiscordChannelID: ["Идентификатор канала", "Числовой ID канала Discord."], + DiscordChatChannel: ["Канал чата", "Канал, куда пересылается игровой чат."], + DiscordLogChannel: ["Канал журнала", "Канал для служебных сообщений сервера."], + DiscordCommandChannel: ["Канал команд", "Канал, из которого принимаются команды."], + WebhookAddress: ["Адрес вебхука", "Куда отправлять события сервера по HTTP."], + AntiCheatSafety: ["Античит: система защиты", "Проверка попыток обойти защиту от урона."], + AntiCheatSpeed: ["Античит: скорость", "Проверка неестественно быстрого перемещения."], + AntiCheatNoClip: ["Античит: проход сквозь стены", "Проверка перемещения через препятствия."], + AntiCheatHit: ["Античит: попадания", "Проверка ударов с недостижимого расстояния."], + AntiCheatPacketException: ["Античит: битые пакеты", "Реакция на некорректные сетевые пакеты."], + AntiCheatPermission: ["Античит: права", "Проверка попыток выполнить действия без прав."], + AntiCheatXP: ["Античит: опыт", "Проверка неестественного роста навыков."], + AntiCheatSafeHouse: ["Античит: убежища", "Проверка обхода защиты убежищ."], + AntiCheatPlayer: ["Античит: персонаж", "Проверка изменённых характеристик персонажа."], + AntiCheatChecksum: ["Античит: контрольные суммы", "Сверка файлов игры у клиента."], + ClientActionLogs: ["Журнал действий клиента", "Какие действия игроков записывать в журнал."], + PerkLogs: ["Журнал навыков", "Записывать изменения уровней навыков."], + DisableVehicleTowing: ["Запретить буксировку", "Машины нельзя цеплять друг к другу."], + DisableTrailerTowing: ["Запретить прицепы", "Прицепы нельзя цеплять к машинам."], + DisableBurntTowing: ["Не буксировать сгоревшие", "Сгоревший транспорт нельзя буксировать."], + + AutoCreateUserInWhiteList: ["Заводить учётки автоматически", "Игрок при первом входе сам создаёт логин и пароль."], + ConstructionPreventsLootRespawn: ["Постройки блокируют респавн лута", "В зданиях, где игроки что-то построили, лут заново не появляется."], + HoursForLootRespawn: ["Респавн лута через, часов", "0 — лут не появляется заново."], + MaxItemsForLootRespawn: ["Предметов при респавне", "Сколько предметов возвращается в контейнер при обновлении лута."], + KickFastPlayers: ["Выкидывать за скорость", "Отключать игроков, движущихся неестественно быстро. Может срабатывать ложно."], + MinutesPerPage: ["Минут на страницу", "Сколько игрового времени уходит на чтение одной страницы книги."], }; const BOOLEAN_VALUES = new Set(["true", "false"]); @@ -608,6 +742,194 @@ const CONFIG_GROUPS = [ }, ]; +// Подписи параметров песочницы. Подробное описание игра пишет сама (в Build 42 +// с расшифровкой каждого значения), но на языке сервера — обычно английском, +// поэтому подпись даём свою, а описание берём из файла. +const SANDBOX_HINTS = { + Zombies: ["Количество зомби"], + Distribution: ["Распределение зомби"], + ZombiesRespawn: ["Восстановление популяции"], + ZombieRespawn: ["Восстановление популяции"], + ZombieMigrate: ["Миграция зомби"], + ZombieVoronoiNoise: ["Неравномерность расселения"], + ZombieAttractionMultiplier: ["Притяжение на шум"], + ZombieHealthImpact: ["Здоровье зомби"], + ZombiePopLootEffect: ["Влияние популяции на лут"], + MultiHitZombies: ["Удар по нескольким зомби"], + RearVulnerability: ["Уязвимость со спины"], + SirenEffectsZombies: ["Сирены привлекают зомби"], + RallyGroupSize: ["Размер скопления"], + RallyTravelDistance: ["Дальность схода в группу"], + RallyGroupSeparation: ["Расстояние между группами"], + RallyGroupRadius: ["Плотность группы"], + HoursForCorpseRemoval: ["Трупы исчезают через, часов"], + DecayingCorpseHealthImpact: ["Вред от разлагающихся трупов"], + BloodLevel: ["Количество крови"], + BloodSplatLifespanDays: ["Кровь исчезает через, дней"], + DayLength: ["Длина игрового дня"], + StartYear: ["Год начала"], + StartMonth: ["Месяц начала"], + StartDay: ["День начала"], + StartTime: ["Время начала"], + DayNightCycle: ["Смена дня и ночи"], + NightLength: ["Длина ночи"], + NightDarkness: ["Темнота ночью"], + TimeSinceApo: ["Времени с начала эпидемии"], + WaterShut: ["Отключение воды"], + ElecShut: ["Отключение электричества"], + WaterShutModifier: ["Дней до отключения воды"], + ElecShutModifier: ["Дней до отключения света"], + Alarm: ["Частота сигнализаций"], + AlarmDecay: ["Затухание сигнализаций"], + AlarmDecayModifier: ["Скорость затухания сигнализаций"], + LockedHouses: ["Запертые дома"], + ErosionSpeed: ["Скорость зарастания"], + ErosionDays: ["Дней до полного зарастания"], + EndRegen: ["Восстановление выносливости"], + Helicopter: ["Вертолёт"], + MetaEvent: ["Случайные события"], + SleepingEvent: ["Ночные события"], + AnnotatedMapChance: ["Шанс найти карту с пометками"], + VehicleStoryChance: ["Частота историй с транспортом"], + ZoneStoryChance: ["Частота историй на местности"], + SurvivorHouseChance: ["Частота домов выживших"], + MaximumLootedBuildingRooms: ["Комнат в разграбленном здании"], + ClimateCycle: ["Смена времён года"], + FogCycle: ["Частота туманов"], + Temperature: ["Температура"], + TemperatureModifier: ["Поправка температуры"], + Rain: ["Дожди"], + RainModifier: ["Поправка дождей"], + MaxFogIntensity: ["Плотность тумана"], + MaxRainFxIntensity: ["Сила дождя"], + EnableSnowOnGround: ["Снег на земле"], + LootRespawn: ["Обновление лута"], + SeenHoursPreventLootRespawn: ["Часов после осмотра до респавна"], + HoursForLootRespawn: ["Респавн лута через, часов"], + MaxItemsForLootRespawn: ["Предметов при респавне"], + ConstructionPreventsLootRespawn: ["Постройки блокируют респавн"], + RollsMultiplier: ["Множитель бросков лута"], + FoodLoot: ["Еда"], + CannedFoodLoot: ["Консервы"], + LiteratureLoot: ["Книги и журналы"], + SurvivalGearsLoot: ["Снаряжение"], + MedicalLoot: ["Медикаменты"], + WeaponLoot: ["Оружие"], + RangedWeaponLoot: ["Огнестрельное оружие"], + AmmoLoot: ["Патроны"], + MechanicsLoot: ["Автозапчасти"], + OtherLoot: ["Прочее"], + SkillBookLoot: ["Учебники навыков"], + RecipeResourceLoot: ["Материалы для рецептов"], + RemoveStoryLoot: ["Убирать сюжетный лут"], + RemoveZombieLoot: ["Убирать лут с зомби"], + MaximumLooted: ["Предел разграбленности"], + DaysUntilMaximumLooted: ["Дней до предела разграбленности"], + RuralLooted: ["Разграбленность сельской местности"], + MaximumDiminishedLoot: ["Предел оскудения лута"], + DaysUntilMaximumDiminishedLoot: ["Дней до оскудения лута"], + XpMultiplier: ["Множитель опыта"], + XpMultiplierAffectsPassive: ["Опыт и пассивные навыки"], + StatsDecrease: ["Скорость падения характеристик"], + Endurance: ["Выносливость"], + Nutrition: ["Учитывать питание"], + StarterKit: ["Стартовый набор"], + InjurySeverity: ["Тяжесть травм"], + BoneFracture: ["Переломы"], + CharacterFreePoints: ["Свободных очков персонажа"], + ConstructionBonusPoints: ["Бонус к строительству"], + NightVisionEnabled: ["Ночное зрение"], + InjuredHealingModifier: ["Скорость заживления"], + MuscleStrainFactor: ["Растяжение мышц"], + DiscomfortFactor: ["Дискомфорт"], + WoundInfectionFactor: ["Заражение ран"], + NegativeTraitsPenalty: ["Штраф отрицательных черт"], + EnablePoisoning: ["Отравления"], + ClothingDegradation: ["Износ одежды"], + AllClothesUnlocked: ["Вся одежда доступна"], + NoBlackClothes: ["Без чёрной одежды"], + EasyClimbing: ["Лёгкое перелезание"], + AttackBlockMovements: ["Атака блокирует движение"], + MetaKnowledge: ["Знание рецептов заранее"], + SeeNotLearntRecipe: ["Видеть неизученные рецепты"], + LiteratureCooldown: ["Перерыв между чтением"], + MinutesPerPage: ["Минут на страницу"], + LevelForMediaXPCutoff: ["Предел опыта с кассет"], + LevelForDismantleXPCutoff: ["Предел опыта с разборки"], + FoodRotSpeed: ["Скорость порчи еды"], + FridgeFactor: ["Эффективность холодильников"], + MaggotSpawn: ["Появление личинок"], + EnableTaintedWaterText: ["Помечать грязную воду"], + FishAbundance: ["Обилие рыбы"], + NatureAbundance: ["Обилие даров природы"], + PlantResilience: ["Живучесть растений"], + PlantAbundance: ["Урожайность"], + Farming: ["Скорость роста"], + FarmingSpeedNew: ["Скорость земледелия"], + FarmingAmountNew: ["Урожай с грядки"], + CompostTime: ["Время компостирования"], + KillInsideCrops: ["Губить посевы в помещении"], + PlantGrowingSeasons: ["Сезонность посевов"], + PlaceDirtAboveground: ["Грядки на любом уровне"], + ClayLakeChance: ["Глина у озёр"], + ClayRiverChance: ["Глина у рек"], + DaysForRottenFoodRemoval: ["Дней до удаления гнили"], + AidPackage: ["Сброс гуманитарной помощи"], + AnimalStatsModifier: ["Характеристики животных"], + AnimalMetaStatsModifier: ["Дикие животные"], + AnimalPregnancyTime: ["Срок беременности"], + AnimalAgeModifier: ["Скорость взросления"], + AnimalMilkIncModifier: ["Надой молока"], + AnimalWoolIncModifier: ["Настриг шерсти"], + AnimalRanchChance: ["Частота ферм"], + AnimalGrassRegrowTime: ["Отрастание травы"], + AnimalMetaPredator: ["Хищники"], + AnimalMatingSeason: ["Сезон спаривания"], + AnimalEggHatch: ["Вылупление яиц"], + AnimalSoundAttractZombies: ["Животные привлекают зомби"], + AnimalTrackChance: ["Частота следов"], + AnimalPathChance: ["Частота троп"], + MaximumRatIndex: ["Предел числа крыс"], + DaysUntilMaximumRatIndex: ["Дней до предела крыс"], + CarSpawnRate: ["Частота машин"], + ChanceHasGas: ["Шанс бензина в баке"], + InitialGas: ["Бензина в баке"], + CarGasConsumption: ["Расход топлива"], + LockedCar: ["Запертые машины"], + CarGeneralCondition: ["Состояние машин"], + CarDamageOnImpact: ["Урон машине при ударе"], + DamageToPlayerFromHitByACar: ["Урон игроку от наезда"], + TrafficJam: ["Пробки на дорогах"], + CarAlarm: ["Автосигнализации"], + PlayerDamageFromCrash: ["Урон при аварии"], + SirenShutoffHours: ["Сирена смолкает через, часов"], + RecentlySurvivorVehicles: ["Машины выживших"], + EnableVehicles: ["Транспорт в игре"], + VehicleEasyUse: ["Упрощённое управление"], + MassInvasion: ["Массовое нашествие"], + FuelStationGas: ["Топливо на заправках"], + FuelStationGasInfinite: ["Бесконечное топливо"], + FuelStationGasMin: ["Минимум топлива на заправке"], + FuelStationGasMax: ["Максимум топлива на заправке"], + FuelStationGasEmptyChance: ["Шанс пустой заправки"], + FirearmUseDamageChance: ["Износ при стрельбе"], + FirearmNoiseMultiplier: ["Громкость выстрелов"], + FirearmJamMultiplier: ["Частота осечек"], + FirearmMoodleMultiplier: ["Влияние состояния на меткость"], + FirearmWeatherMultiplier: ["Влияние погоды на меткость"], + FirearmHeadGearEffect: ["Влияние головного убора"], + Generator: ["Расход топлива генератора"], + GeneratorSpawning: ["Частота генераторов"], + GeneratorFuelConsumption: ["Расход генератора"], + GeneratorTileRange: ["Радиус питания генератора"], + GeneratorVerticalPowerRange: ["Этажей питания генератора"], + AllowExteriorGenerator: ["Генератор на улице"], + FireSpread: ["Распространение огня"], + MaximumFireFuelHours: ["Часов горения топлива"], + LightBulbLifespan: ["Срок службы лампочек"], + VERSION: ["Версия набора настроек"], +}; + // Категории верхнеуровневых параметров песочницы. Вложенные (ZombieLore.Speed // и подобные) группируются сами — по имени таблицы, в которой лежат. const SANDBOX_GROUPS = [ @@ -823,8 +1145,18 @@ function renderGroupedFields(containerSelector, entries, groups, hints) { const fields = document.createElement("div"); fields.className = "fields"; group.items.forEach((entry) => { - fields.appendChild(buildConfigField( - entry.key, entry.value, hints ? hints[entry.key] : null, entry.description)); + // Свой словарь важнее комментария из файла: игра пишет его на языке + // сервера, а он почти всегда английский. + let hint = hints ? hints[entry.key] : null; + // Нумерованные проверки античита описывать поимённо нечем: игра не + // раскрывает, что стоит за каждым номером. + if (!hint && entry.key.startsWith("AntiCheatProtectionType")) { + hint = ["Античит: проверка " + entry.key.replace("AntiCheatProtectionType", "№"), + "Одна из встроенных проверок. Отключайте только при ложных срабатываниях."]; + } + const label = Array.isArray(hint) ? hint[0] : hint; + const description = (Array.isArray(hint) && hint[1]) || entry.description; + fields.appendChild(buildConfigField(entry.key, entry.value, label, description)); }); section.appendChild(fields); container.appendChild(section); @@ -993,8 +1325,12 @@ async function loadSandbox() { refreshFieldSaveState(SAVE_TARGETS[1]); return; } - const entries = data.entries.map((entry) => ({ key: entry.path, value: entry.value })); - renderGroupedFields("#sandbox-fields", entries, SANDBOX_GROUPS, null); + const entries = data.entries.map((entry) => ({ + key: entry.path, + value: entry.value, + description: entry.comment, + })); + renderGroupedFields("#sandbox-fields", entries, SANDBOX_GROUPS, SANDBOX_HINTS); applyFieldFilter("#sandbox-filter", "#sandbox-fields"); refreshFieldSaveState(SAVE_TARGETS[1]); }