import bots from "../../schemas/bots.json"; import server from "../../schemas/server.json"; import catalog from "../../modules/catalog.json"; import type { Locale } from "@/i18n"; import type { ModuleSetting } from "@/lib/api"; export interface ModuleField { title: string; description: string; type: string; category: string; advanced: boolean; min?: number; max?: number; step?: number; options?: { value: string | number | boolean; label: string }[]; } const locales: Locale[] = ["en", "ru", "de", "fr", "es", "zh"]; const text = (values: string[], locale: Locale) => values[locales.indexOf(locale)] ?? values[0]; type Entry = { key: string; type: string; title: string[]; description: string[]; min?: number; max?: number; advanced?: boolean; options?: { value: string; label: string[] }[] }; export const moduleSettingDefinitions: Entry[] = []; const fields = moduleSettingDefinitions; function add(key: string, type: string, title: string[], description: string[], extra: Partial = {}) { fields.push({ key, type, title, description, ...extra }); } add("AiPlayerbot.RandomBotAutologin", "bool", ["Populate the world automatically", "Автоматически населять мир", "Welt automatisch bevölkern", "Peupler automatiquement le monde", "Poblar el mundo automáticamente", "自动填充世界"], ["Log random bots into the world when the server starts.", "Вводит случайных ботов в мир при запуске сервера.", "Meldet Zufallsbots beim Serverstart in der Welt an.", "Connecte les bots aléatoires au démarrage du serveur.", "Conecta bots aleatorios al iniciar el servidor.", "服务器启动时让随机机器人登录世界。"]); add("AiPlayerbot.MinRandomBots", "int", ["Minimum online bots", "Минимум ботов онлайн", "Mindestens aktive Bots", "Minimum de bots connectés", "Mínimo de bots conectados", "最少在线机器人"], ["Lower limit of the automatically selected online population. Must not exceed the maximum.", "Нижний предел автоматически выбираемого числа ботов онлайн. Не должен превышать максимум.", "Untergrenze der automatisch gewählten Bot-Zahl. Darf das Maximum nicht überschreiten.", "Limite basse du nombre de bots connectés. Ne doit pas dépasser le maximum.", "Límite inferior de bots conectados. No debe superar el máximo.", "自动选择的在线机器人数量下限,不得超过上限。"], { min: 0 }); add("AiPlayerbot.MaxRandomBots", "int", ["Maximum online bots", "Максимум ботов онлайн", "Maximal aktive Bots", "Maximum de bots connectés", "Máximo de bots conectados", "最多在线机器人"], ["Upper limit of the random bot population; more bots use more server resources.", "Верхний предел числа случайных ботов онлайн. Больше ботов — выше нагрузка на сервер.", "Obergrenze der Zufallsbots; mehr Bots benötigen mehr Serverressourcen.", "Limite haute des bots aléatoires ; plus de bots utilisent plus de ressources.", "Límite superior de bots aleatorios; más bots requieren más recursos del servidor.", "随机机器人在线数量上限,数量越多越占用服务器资源。"], { min: 0 }); add("AiPlayerbot.RandomBotAccountCount", "int", ["Random bot accounts", "Аккаунтов случайных ботов", "Zufallsbot-Konten", "Comptes de bots aléatoires", "Cuentas de bots aleatorios", "随机机器人账号数量"], ["0 calculates the required accounts automatically. Each account contains multiple bots.", "0 — рассчитывать нужное число аккаунтов автоматически. Один аккаунт содержит несколько ботов.", "0 berechnet die benötigten Konten automatisch. Ein Konto enthält mehrere Bots.", "0 calcule automatiquement les comptes nécessaires. Chaque compte contient plusieurs bots.", "0 calcula automáticamente las cuentas necesarias. Cada cuenta contiene varios bots.", "0自动计算所需账号,每个账号包含多个机器人。"], { min: 0 }); add("AiPlayerbot.RandomBotMinLevel", "int", ["Minimum bot level", "Минимальный уровень ботов", "Minimale Bot-Stufe", "Niveau minimum des bots", "Nivel mínimo de bots", "最低机器人等级"], ["Lowest level used when generating random bots.", "Нижний уровень при создании случайных ботов.", "Niedrigste Stufe beim Erstellen von Zufallsbots.", "Niveau le plus bas lors de la génération des bots.", "Nivel más bajo al generar bots aleatorios.", "生成随机机器人时的最低等级。"], { min: 0, max: 80 }); add("AiPlayerbot.RandomBotMaxLevel", "int", ["Maximum bot level", "Максимальный уровень ботов", "Maximale Bot-Stufe", "Niveau maximum des bots", "Nivel máximo de bots", "最高机器人等级"], ["Highest level used when generating random bots. Keep within the server level cap.", "Верхний уровень при создании случайных ботов. Не превышайте максимальный уровень сервера.", "Höchste Stufe beim Erstellen von Zufallsbots. Server-Stufenlimit beachten.", "Niveau le plus haut lors de la génération. Respectez la limite du serveur.", "Nivel máximo al generar bots. Respeta el límite del servidor.", "生成机器人的最高等级,不应超过服务器等级上限。"], { min: 0, max: 80 }); add("AiPlayerbot.SyncLevelWithPlayers", "bool", ["Match player levels", "Подстраивать уровень под игроков", "Spielerstufen anpassen", "Adapter aux niveaux des joueurs", "Ajustar al nivel de jugadores", "匹配玩家等级"], ["Adjust random bot levels to the levels of real players.", "Подстраивает уровни случайных ботов под уровни настоящих игроков.", "Passt Zufallsbot-Stufen an echte Spieler an.", "Adapte le niveau des bots aux joueurs réels.", "Ajusta los niveles de bots a jugadores reales.", "根据真实玩家调整随机机器人等级。"]); add("AiPlayerbot.RandomBotInvitePlayer", "bool", ["Bots invite players", "Боты приглашают игроков", "Bots laden Spieler ein", "Les bots invitent les joueurs", "Los bots invitan jugadores", "机器人邀请玩家"], ["Allow random bots to invite nearby players into groups.", "Разрешает случайным ботам приглашать игроков поблизости в группу.", "Zufallsbots dürfen nahe Spieler in Gruppen einladen.", "Permet aux bots d’inviter les joueurs proches dans un groupe.", "Permite a bots invitar a jugadores cercanos a grupos.", "允许随机机器人邀请附近玩家组队。"]); add("AiPlayerbot.RandomBotJoinLfg", "bool", ["Join dungeon finder", "Участвовать в поиске подземелий", "Dungeonsuche nutzen", "Participer à la recherche de donjon", "Participar en buscador de mazmorras", "加入地下城查找器"], ["Allow random bots to participate in dungeon matchmaking.", "Разрешает случайным ботам участвовать в подборе группы для подземелий.", "Zufallsbots dürfen an der Dungeonsuche teilnehmen.", "Autorise les bots à participer à la recherche de donjon.", "Permite a bots participar en el buscador de mazmorras.", "允许随机机器人参与地下城匹配。"]); add("AiPlayerbot.RandomBotJoinBG", "bool", ["Join battlegrounds", "Участвовать в полях боя", "Schlachtfeldern beitreten", "Rejoindre les champs de bataille", "Unirse a campos de batalla", "加入战场"], ["Allow random bots to enter battlegrounds.", "Разрешает случайным ботам входить на поля боя.", "Zufallsbots dürfen Schlachtfelder betreten.", "Autorise les bots à entrer sur les champs de bataille.", "Permite a bots entrar en campos de batalla.", "允许随机机器人进入战场。"]); add("AiPlayerbot.RandomBotAutoJoinBG", "bool", ["Queue for battlegrounds automatically", "Автоматически вставать в очередь на поля боя", "Automatisch für Schlachtfelder anmelden", "Inscription automatique aux champs de bataille", "Cola automática para campos de batalla", "自动排队战场"], ["Queue random bots without waiting for a player to queue first.", "Ставит ботов в очередь, не дожидаясь первого игрока.", "Meldet Bots an, ohne auf die Anmeldung eines Spielers zu warten.", "Inscrit les bots sans attendre un premier joueur.", "Pone bots en cola sin esperar a un jugador.", "无需等待玩家先排队,即可让机器人排队。"]); add("AiPlayerbot.RandomBotTalk", "bool", ["Bot chat", "Сообщения ботов", "Bot-Chat", "Discussion des bots", "Chat de bots", "机器人聊天"], ["Allow random bots to send chat messages.", "Разрешает случайным ботам отправлять сообщения в чат.", "Zufallsbots dürfen Chatnachrichten senden.", "Autorise les messages des bots dans le chat.", "Permite mensajes de bots en el chat.", "允许随机机器人发送聊天消息。"]); add("AiPlayerbot.RandomBotEmote", "bool", ["Bot emotes", "Эмоции ботов", "Bot-Emotes", "Émotes des bots", "Gestos de bots", "机器人表情"], ["Allow random bots to perform emotes.", "Разрешает случайным ботам использовать эмоции.", "Zufallsbots dürfen Emotes verwenden.", "Autorise les émotes des bots.", "Permite a bots usar gestos.", "允许随机机器人使用表情动作。"]); add("AiPlayerbot.RandomBotSuggestDungeons", "bool", ["Suggest dungeons", "Предлагать подземелья", "Dungeons vorschlagen", "Suggérer des donjons", "Sugerir mazmorras", "建议地下城"], ["Allow bots to suggest dungeons to players in chat.", "Разрешает ботам предлагать игрокам подземелья в чате.", "Bots dürfen Spielern im Chat Dungeons vorschlagen.", "Autorise les suggestions de donjons dans le chat.", "Permite sugerir mazmorras a jugadores en el chat.", "允许机器人在聊天中向玩家建议地下城。"]); add("AiPlayerbot.CoaClassesOnly", "bool", ["Only CoA classes", "Только классы CoA", "Nur CoA-Klassen", "Classes CoA uniquement", "Solo clases CoA", "仅CoA职业"], ["Use Conquest of Azeroth classes for generated bots.", "Использует классы Conquest of Azeroth для создаваемых ботов.", "Nutzt CoA-Klassen für erstellte Bots.", "Utilise les classes CoA pour les bots générés.", "Usa clases CoA para bots generados.", "生成机器人时使用CoA职业。"]); add("AiPlayerbot.CoaSmartHeal", "bool", ["Smart healing", "Умное лечение", "Intelligente Heilung", "Soins intelligents", "Sanación inteligente", "智能治疗"], ["Use CoA healing priorities to choose targets and spells.", "Использует приоритеты лечения CoA для выбора целей и заклинаний.", "Nutzt CoA-Heilprioritäten für Ziele und Zauber.", "Utilise les priorités de soins CoA pour les cibles et sorts.", "Usa prioridades de sanación CoA para objetivos y hechizos.", "根据CoA治疗优先级选择目标和法术。"]); add("AiPlayerbot.CoaSmartTank", "bool", ["Smart tanking", "Умное танкование", "Intelligentes Tanken", "Tank intelligent", "Tanque inteligente", "智能坦克"], ["Use CoA tank priorities for threat and survival.", "Использует приоритеты танков CoA для угрозы и выживания.", "Nutzt CoA-Tankprioritäten für Bedrohung und Überleben.", "Utilise les priorités de tank CoA pour menace et survie.", "Usa prioridades de tanque CoA para amenaza y supervivencia.", "使用CoA坦克优先级控制仇恨和生存。"]); add("AiPlayerbot.CoaSpecRotations", "bool", ["CoA specialization rotations", "Ротации специализаций CoA", "CoA-Spezialisierungsrotationen", "Rotations des spécialisations CoA", "Rotaciones de especialización CoA", "CoA专精循环"], ["Use authored specialization rotations alongside automatic spell selection.", "Использует готовые ротации специализаций вместе с автоматическим выбором заклинаний.", "Nutzt Spezialisierungsrotationen neben automatischer Zauberauswahl.", "Utilise les rotations de spécialisation avec la sélection automatique des sorts.", "Usa rotaciones de especialización junto a selección automática de hechizos.", "在自动选法术的基础上使用专精循环。"]); add("AiPlayerbot.CoaInterruptCoordination", "bool", ["Coordinate interrupts", "Согласовывать прерывания", "Unterbrechungen koordinieren", "Coordonner les interruptions", "Coordinar interrupciones", "协调打断"], ["Coordinate bot interrupts so several bots do not interrupt the same cast.", "Согласовывает прерывания ботов, чтобы несколько ботов не тратили их на одно заклинание.", "Koordiniert Unterbrechungen, damit Bots nicht denselben Zauber unterbrechen.", "Coordonne les interruptions pour éviter de viser tous le même sort.", "Coordina interrupciones para evitar gastar varias en el mismo lanzamiento.", "协调打断,避免多个机器人同时打断同一个施法。"]); add("AiPlayerbot.MaxAddedBots", "int", ["Bots controlled by one player", "Ботов под управлением игрока", "Bots pro Spieler", "Bots contrôlés par joueur", "Bots controlados por jugador", "每位玩家控制的机器人"], ["Maximum number of bots a player can control at once.", "Максимум ботов, которыми игрок может управлять одновременно.", "Maximale gleichzeitig steuerbare Bots pro Spieler.", "Nombre maximal de bots contrôlés simultanément par joueur.", "Máximo de bots controlados a la vez por jugador.", "玩家同时控制的机器人数量上限。"], { min: 0 }); add("AiPlayerbot.BotAutologin", "bool", ["Log in player alts automatically", "Автоматически вводить альтов игрока", "Spieler-Alts automatisch anmelden", "Connecter automatiquement les personnages secondaires", "Conectar personajes alternativos automáticamente", "自动登录玩家小号"], ["Log in other characters on the player’s account as bots when the player logs in.", "При входе игрока вводит других персонажей его аккаунта как ботов.", "Meldet andere Charaktere des Spielerkontos beim Login als Bots an.", "Connecte les autres personnages du compte comme bots à la connexion.", "Conecta otros personajes de la cuenta como bots al entrar el jugador.", "玩家登录时,将同账号其他角色作为机器人登录。"]); add("AiPlayerbot.DisabledWithoutRealPlayer", "bool", ["Only populate while players are online", "Вводить ботов только при наличии игроков", "Bots nur bei aktiven Spielern", "Bots seulement avec des joueurs connectés", "Bots solo con jugadores conectados", "仅有玩家在线时填充机器人"], ["Log random bots out when no real players remain online.", "Выводит случайных ботов, когда на сервере нет настоящих игроков.", "Meldet Zufallsbots ab, wenn keine echten Spieler online sind.", "Déconnecte les bots quand aucun joueur réel ne reste connecté.", "Desconecta bots cuando no quedan jugadores reales.", "没有真实玩家在线时退出随机机器人。"]); add("AiPlayerbot.AddClassCommand", "bool", ["Players can create bots by class", "Игроки могут создавать ботов по классу", "Spieler können Klassenbots erstellen", "Les joueurs peuvent créer des bots par classe", "Jugadores pueden crear bots por clase", "玩家可按职业创建机器人"], ["Allow the addclass command for players; disabled restricts it to game masters.", "Разрешает игрокам команду addclass. При выключении она доступна только ГМ.", "Erlaubt addclass für Spieler; deaktiviert nur für Spielleiter.", "Autorise addclass aux joueurs ; sinon réservé aux maîtres de jeu.", "Permite addclass a jugadores; desactivado solo para maestros de juego.", "允许玩家使用addclass命令,关闭后仅GM可用。"]); add("AiPlayerbot.KeepAltsInGroup", "bool", ["Keep alts in the group", "Оставлять альтов в группе", "Alts in der Gruppe behalten", "Garder les personnages secondaires en groupe", "Mantener personajes alternativos en el grupo", "保留小号在队伍中"], ["Keep alt bots in the group after their owner leaves.", "Оставляет альтов-ботов в группе после выхода владельца.", "Behält Alt-Bots in der Gruppe, wenn ihr Besitzer geht.", "Garde les bots secondaires après le départ du propriétaire.", "Mantiene bots alternativos cuando sale su dueño.", "主人离队后,让小号机器人留在队伍中。"]); add("AiPlayerbot.CoaRecruitSameFaction", "bool", ["Recruit bots of your faction", "Нанимать ботов своей фракции", "Bots der eigenen Fraktion anwerben", "Recruter les bots de votre faction", "Reclutar bots de tu facción", "招募同阵营机器人"], ["Restrict CoA role recruitment to the player’s faction.", "Ограничивает найм по роли CoA фракцией игрока.", "Beschränkt die CoA-Rollenanwerbung auf die Spielerfraktion.", "Limite le recrutement CoA à la faction du joueur.", "Limita el reclutamiento CoA a la facción del jugador.", "将CoA按职责招募限制为玩家同阵营。"]); add("AiPlayerbot.CoaLfgBots", "bool", ["CoA bots fill dungeon groups", "Боты CoA заполняют группы подземелий", "CoA-Bots füllen Dungeongruppen", "Les bots CoA complètent les groupes de donjon", "Bots CoA completan grupos de mazmorras", "CoA机器人补齐地下城队伍"], ["Allow CoA bots to fill missing roles in dungeon finder groups.", "Разрешает ботам CoA занимать недостающие роли в группах поиска подземелий.", "Erlaubt CoA-Bots fehlende Rollen in Dungeonsuche-Gruppen zu füllen.", "Autorise les bots CoA à compléter les rôles manquants.", "Permite a bots CoA cubrir roles faltantes en grupos del buscador.", "允许CoA机器人补齐地下城查找器队伍的缺失职责。"]); add("AiPlayerbot.RandomBotXPRate", "float", ["Bot experience multiplier", "Множитель опыта ботов", "Bot-Erfahrungsmultiplikator", "Multiplicateur d’expérience des bots", "Multiplicador de experiencia de bots", "机器人经验倍率"], ["1 uses the normal experience rate for random bots.", "1 — обычная скорость получения опыта случайными ботами.", "1 nutzt die normale Erfahrungsrate für Zufallsbots.", "1 conserve le gain d’expérience normal des bots aléatoires.", "1 usa la experiencia normal de bots aleatorios.", "1使用随机机器人的正常经验获取倍率。"], { min: 0 }); add("AiPlayerbot.FollowDistance", "float", ["Follow distance", "Дистанция следования", "Folgeabstand", "Distance de suivi", "Distancia de seguimiento", "跟随距离"], ["Distance bots maintain while following their owner.", "Расстояние до владельца при следовании.", "Abstand zum Besitzer beim Folgen.", "Distance maintenue en suivant le propriétaire.", "Distancia mantenida al seguir al dueño.", "跟随主人时保持的距离。"], { min: 0 }); const scaling = "CoAContentScaling."; add(scaling + "LFG.AllowPartialGroups", "bool", ["Enter with the current party", "Входить текущим составом", "Mit aktueller Gruppe betreten", "Entrer avec le groupe actuel", "Entrar con el grupo actual", "以当前队伍进入"], ["Enter Dungeon Finder with 1–5 players without waiting for a full party. Requires group scaling. BotFill keeps filling empty slots.", "Вход через ЛФГ составом 1–5 игроков без ожидания полной группы. Требует скейлинга группы. BotFill продолжает заполнять свободные места.", "Dungeonbrowser mit 1–5 Spielern ohne volle Gruppe. Erfordert Gruppenanpassung. BotFill füllt freie Plätze.", "Entrée avec 1 à 5 joueurs sans attendre un groupe complet. Nécessite l’adaptation au groupe. BotFill remplit les places libres.", "Entrar con 1–5 jugadores sin esperar un grupo completo. Requiere ajuste al grupo. BotFill llena los huecos.", "以1至5名玩家进入,无需等待满员。需要队伍缩放。BotFill仍会填补空位。"]); add(scaling + "World.Leech.Enable", "bool", ["Open-world life steal", "Отхил от урона в открытом мире", "Lebensraub in der offenen Welt", "Vol de vie en monde ouvert", "Robo de vida en mundo abierto", "开放世界吸血"], ["Heal from your damage to open-world creatures. Excludes players, their pets and instances. Requires the module; group scaling and solo assistance are optional.", "Восстанавливает здоровье от вашего урона существам открытого мира. Исключает игроков, их питомцев и инстансы. Требует включения модуля; скейлинг группы и соло-помощь необязательны.", "Heilt durch eigenen Schaden an Weltkreaturen. Ohne Spieler, deren Begleiter und Instanzen. Erfordert das Modul; Gruppenanpassung und Solohilfe sind optional.", "Soigne grâce aux dégâts aux créatures du monde ouvert. Exclut joueurs, familiers et instances. Nécessite le module ; adaptation au groupe et aide solo facultatives.", "Cura con tu daño a criaturas del mundo abierto. Excluye jugadores, mascotas e instancias. Requiere el módulo; ajuste al grupo y ayuda individual opcionales.", "对开放世界生物造成伤害时恢复生命。不适用于玩家、玩家宠物和副本。需要启用模块,队伍缩放和单人辅助可选。"]); add(scaling + "World.Leech.Percent", "float", ["Life steal (%)", "Отхил от нанесённого урона (%)", "Lebensraub (%)", "Vol de vie (%)", "Robo de vida (%)", "吸血比例(%)"], ["Percentage of damage returned as health: 0–100. 10 means 10%. Requires open-world life steal. Overkill gives no extra healing.", "Процент урона, возвращаемый здоровьем: 0–100. Значение 10 означает 10%. Требует включения отхила в открытом мире. Избыточный урон при добивании не увеличивает отхил.", "Schadensanteil als Heilung: 0–100. 10 bedeutet 10%. Erfordert Welt-Lebensraub. Überzähliger Schaden heilt nicht zusätzlich.", "Pourcentage des dégâts rendu en vie : 0–100. 10 signifie 10%. Nécessite le vol de vie. Les dégâts excédentaires ne soignent pas davantage.", "Porcentaje del daño recuperado como salud: 0–100. 10 significa 10%. Requiere robo de vida. El daño excedente no aporta curación adicional.", "将伤害的0至100%转化为生命。10表示10%。需要启用吸血。过量伤害不会额外治疗。"], { min: 0, max: 100 }); add(scaling + "Difficulty.DamageMultiplier", "range", ["Enemy damage", "Урон противников", "Gegnerschaden", "Dégâts ennemis", "Daño enemigo", "敌人伤害"], ["Dungeon and raid damage after group scaling and solo assistance. 100% keeps the normal balance; lower is easier. Applies to everyone on this server. Does not change health, loot or instant-kill mechanics.", "Урон в подземельях и рейдах после скейлинга группы и соло-помощи. 100% — обычный баланс, ниже — легче. Действует для всех игроков сервера. Не меняет здоровье, добычу и механики мгновенной смерти.", "Schaden in Dungeons und Schlachtzügen nach Gruppenanpassung und Solohilfe. 100% ist normal, weniger ist leichter. Gilt für alle Spieler. Ändert weder Gesundheit noch Beute oder Soforttötungen.", "Dégâts en donjon et raid après adaptation au groupe et aide solo. 100% conserve l’équilibrage normal ; moins est plus facile. Pour tous les joueurs. Ne change ni la vie, ni le butin, ni les morts instantanées.", "Daño en mazmorras y bandas tras el ajuste del grupo y la ayuda individual. 100% mantiene el equilibrio normal; menos es más fácil. Para todos los jugadores. No cambia salud, botín ni muerte instantánea.", "队伍缩放和单人辅助后的地下城与团队副本伤害。100%为正常,降低后更容易。对全服玩家生效,不改变生命值、掉落或即死机制。"], { min: 0.25, max: 2 }); add(scaling + "SoloAssist.Mode", "enum", ["Solo assistance", "Помощь в соло", "Solohilfe", "Aide solo", "Ayuda individual", "单人辅助"], ["Additional damage reduction when the scaling target is one player.", "Дополнительное снижение урона, когда расчёт сложности рассчитан на одного игрока.", "Zusätzliche Schadensreduktion bei einem Skalierungsziel von einem Spieler.", "Réduction supplémentaire quand la difficulté est calculée pour un joueur.", "Reducción adicional cuando la dificultad se calcula para un jugador.", "难度目标为一名玩家时额外减伤。"], { options: [ { value: "None", label: ["None", "Без помощи", "Keine", "Aucune", "Ninguna", "无"] }, { value: "Light", label: ["Light (−15%)", "Лёгкая (−15%)", "Leicht (−15%)", "Légère (−15%)", "Ligera (−15%)", "轻度(−15%)"] }, { value: "Full", label: ["Full (−30%)", "Полная (−30%)", "Voll (−30%)", "Complète (−30%)", "Completa (−30%)", "完整(−30%)"] }, ] }); add(scaling + "GroupScaling.Enable", "bool", ["Scale to group size", "Подстраивать под размер группы", "An Gruppengröße anpassen", "Adapter à la taille du groupe", "Ajustar al tamaño del grupo", "按队伍人数缩放"], ["Adjust enemy health and damage to the party size. Required for damage difficulty and solo assistance.", "Подстраивает здоровье и урон врагов под группу. Необходимо для регулировки урона и соло-помощи.", "Passt Gesundheit und Schaden an die Gruppe an. Für Schadensregler und Solohilfe erforderlich.", "Adapte la vie et les dégâts au groupe. Requis pour le réglage des dégâts et l’aide solo.", "Ajusta salud y daño al grupo. Necesario para el control de daño y la ayuda individual.", "按队伍人数调整敌人生命和伤害。伤害调节和单人辅助需要启用此项。"]); add(scaling + "GroupScaling.AllowSoloRaids", "bool", ["Enter raids solo", "Входить в рейды одному", "Schlachtzüge solo betreten", "Entrer seul en raid", "Entrar solo en bandas", "单人进入团队副本"], ["Allow raid entry without a raid group.", "Разрешает вход без рейдовой группы.", "Erlaubt den Eintritt ohne Schlachtzugsgruppe.", "Autorise l’entrée sans groupe de raid.", "Permite entrar sin grupo de banda.", "允许无需团队即可进入团队副本。"]); add(scaling + "AdaptiveMechanics.Enable", "bool", ["Adapt encounter mechanics", "Адаптировать механики боёв", "Kampfmechaniken anpassen", "Adapter les mécaniques", "Adaptar mecánicas", "调整战斗机制"], ["Adapt supported encounters for smaller groups.", "Адаптирует поддерживаемые бои для малых групп.", "Passt unterstützte Kämpfe für kleine Gruppen an.", "Adapte les combats pris en charge aux petits groupes.", "Adapta los encuentros compatibles a grupos pequeños.", "为小队调整受支持的战斗。"]); add(scaling + "Rewards.ScaleLootCount", "bool", ["Scale loot quantity", "Подстраивать количество добычи", "Beutemenge anpassen", "Adapter la quantité de butin", "Ajustar cantidad de botín", "缩放掉落数量"], ["Adjust drops to group size, keeping at least one drop.", "Подстраивает добычу под группу, сохраняя хотя бы один предмет.", "Passt Beute an die Gruppe an, mindestens ein Gegenstand bleibt.", "Adapte le butin au groupe, avec au moins un objet.", "Ajusta el botín al grupo, con al menos un objeto.", "按队伍人数调整掉落,至少保留一件。"]); add(scaling + "ScaleItems", "bool", ["Scale equipment", "Масштабировать снаряжение", "Ausrüstung skalieren", "Adapter l’équipement", "Ajustar equipo", "缩放装备"], ["Adjust equipment stats to the selected progression level bands.", "Подстраивает характеристики снаряжения под уровни прогрессии.", "Passt Ausrüstungswerte an die Fortschrittsstufen an.", "Adapte les statistiques aux niveaux de progression.", "Ajusta estadísticas a los niveles de progresión.", "按所选进度等级范围调整装备属性。"]); add(scaling + "LFG.DefaultMode", "enum", ["Dungeon Finder group", "Группа для поиска подземелий", "Dungeonbrowser-Gruppe", "Groupe de recherche de donjons", "Grupo del buscador", "地下城查找器组队"], ["Default queue mode. Players can override it with .lfgmode.", "Режим очереди по умолчанию. Игрок может изменить его командой .lfgmode.", "Standard-Warteschlange. Spieler können sie mit .lfgmode ändern.", "Mode de file par défaut. Modifiable avec .lfgmode.", "Cola predeterminada. Se puede cambiar con .lfgmode.", "默认排队模式,玩家可通过 .lfgmode 更改。"], { options: [ { value: "Matchmaking", label: ["Find players", "Искать игроков", "Spieler suchen", "Chercher des joueurs", "Buscar jugadores", "寻找玩家"] }, { value: "BotFill", label: ["Fill with bots", "Дополнить ботами", "Mit Bots auffüllen", "Compléter avec des bots", "Completar con bots", "机器人补位"] }, { value: "CurrentParty", label: ["Current party / solo", "Текущая группа / соло", "Aktuelle Gruppe / solo", "Groupe actuel / solo", "Grupo actual / solo", "当前队伍 / 单人"] }, ] }); add(scaling + "LFG.DefaultChallengeSize", "int", ["Challenge group size", "Размер группы для расчёта сложности", "Gruppengröße für die Schwierigkeit", "Taille du groupe pour la difficulté", "Tamaño del grupo para dificultad", "难度目标人数"], ["0 follows actual party size. 1–40 simulates that many players. Players can override with .lfgchallenge.", "0 — реальный размер группы. 1–40 — сложность как для указанного числа игроков. Игрок может изменить через .lfgchallenge.", "0 nutzt die tatsächliche Gruppe. 1–40 simuliert diese Spielerzahl. Mit .lfgchallenge änderbar.", "0 suit le groupe réel. 1–40 simule ce nombre de joueurs. Modifiable avec .lfgchallenge.", "0 usa el grupo real. 1–40 simula ese número de jugadores. Modificable con .lfgchallenge.", "0按实际人数,1–40模拟指定人数。玩家可通过 .lfgchallenge 更改。"], { min: 0, max: 40 }); add(scaling + "GroupScaling.LockOnEncounterStart", "bool", ["Lock scaling during combat", "Фиксировать скейлинг на время боя", "Skalierung im Kampf sperren", "Figer l’adaptation en combat", "Fijar ajuste durante combate", "战斗期间锁定缩放"], ["Joining or leaving during a boss fight does not change its scaling.", "Вход и выход игроков во время боя с боссом не меняют скейлинг.", "Beitreten oder Verlassen während eines Bosskampfs ändert die Skalierung nicht.", "Les arrivées et départs pendant un boss ne changent pas l’adaptation.", "Entrar o salir durante un jefe no cambia el ajuste.", "首领战期间玩家进出不会改变缩放。"], { advanced: true }); add(scaling + "Progression.Mode", "enum", ["Progression boundaries", "Границы прогрессии", "Fortschrittsgrenzen", "Limites de progression", "Límites de progresión", "进度边界"], ["Automatic boundaries follow the level cap and enabled expansions. Custom uses the levels below.", "Автоматические границы зависят от максимального уровня и включённых дополнений. Ручные используют уровни ниже.", "Automatisch folgt der Maximalstufe und Erweiterungen. Benutzerdefiniert nutzt die folgenden Stufen.", "Automatique suit le niveau maximal et les extensions. Manuel utilise les niveaux ci-dessous.", "Automático sigue el nivel máximo y las expansiones. Manual usa los niveles siguientes.", "自动边界按等级上限和启用的资料片计算,自定义使用以下等级。"], { advanced: true, options: [ { value: "Auto", label: ["Automatic", "Автоматически", "Automatisch", "Automatique", "Automático", "自动"] }, { value: "Custom", label: ["Custom", "Вручную", "Benutzerdefiniert", "Manuel", "Manual", "自定义"] }, ] }); for (const era of ["Classic", "Tbc"]) add(scaling + `Progression.${era}End`, "int", [`${era} last level`, `Последний уровень ${era}`, `${era}-Endstufe`, `Dernier niveau ${era}`, `Último nivel ${era}`, `${era} 最高等级`], ["Used in custom progression. 0 chooses automatically.", "Для ручной прогрессии. 0 — автоматически.", "Für benutzerdefinierten Fortschritt. 0 wählt automatisch.", "Pour la progression manuelle. 0 choisit automatiquement.", "Para progresión manual. 0 elige automáticamente.", "用于自定义进度,0为自动。"], { min: 0, max: 80, advanced: true }); add(scaling + "Debug", "bool", ["Detailed scaling logs", "Подробные логи скейлинга", "Ausführliche Skalierungsprotokolle", "Journaux détaillés", "Registros detallados", "详细缩放日志"], ["Log scaling calculations for troubleshooting.", "Записывает расчёты скейлинга для диагностики.", "Protokolliert Berechnungen zur Fehleranalyse.", "Enregistre les calculs pour le diagnostic.", "Registra cálculos para diagnóstico.", "记录缩放计算以排查问题。"], { advanced: true }); const enchantDescription = ["Tell players about the enchanter NPC when they log in.", "Сообщает игрокам о НПС-зачарователе при входе в игру.", "Informiert Spieler beim Einloggen über den Verzauberer-NPC.", "Informe les joueurs du PNJ enchanteur à la connexion.", "Informa a los jugadores del PNJ encantador al iniciar sesión.", "玩家登录时提示附魔NPC。"]; add("Enchanter.CostCopper", "int", ["Price per enchantment (copper)", "Цена зачарования (медь)", "Preis pro Verzauberung (Kupfer)", "Prix par enchantement (cuivre)", "Precio por encantamiento (cobre)", "每次附魔价格(铜币)"], ["0 is free. 10,000 copper = 1 gold.", "0 — бесплатно. 10 000 меди = 1 золото.", "0 ist kostenlos. 10.000 Kupfer = 1 Gold.", "0 est gratuit. 10 000 cuivre = 1 or.", "0 es gratis. 10 000 cobre = 1 oro.", "0为免费。10000铜币=1金币。"], { min: 0 }); add("Enchanter.Announce", "bool", ["Announce on login", "Объявлять при входе", "Beim Einloggen ankündigen", "Annonce à la connexion", "Anunciar al iniciar sesión", "登录时公告"], enchantDescription); for (const [key, title, desc] of [ ["MessageTimer", ["Speech interval (milliseconds)", "Интервал реплик (миллисекунды)", "Sprechintervall (Millisekunden)", "Intervalle de parole (millisecondes)", "Intervalo de habla (milisegundos)", "发言间隔(毫秒)"], ["0 disables speech; 60,000 milliseconds = 1 minute.", "0 отключает реплики; 60 000 мс = 1 минута.", "0 deaktiviert Sprache; 60.000 ms = 1 Minute.", "0 désactive la parole ; 60 000 ms = 1 minute.", "0 desactiva el habla; 60 000 ms = 1 minuto.", "0关闭发言;60000毫秒=1分钟。"]], ["NumPhrases", ["Number of phrases", "Количество реплик", "Anzahl der Sätze", "Nombre de phrases", "Número de frases", "台词数量"], ["Number of configured phrases the NPC can choose from. 0 disables phrases.", "Количество настроенных реплик НПС. 0 отключает реплики.", "Anzahl verfügbarer Sätze. 0 deaktiviert sie.", "Nombre de phrases disponibles. 0 les désactive.", "Número de frases disponibles. 0 las desactiva.", "NPC可选台词数量,0关闭。"]], ["EmoteSpell", ["Attention spell ID", "ID заклинания для привлечения внимания", "Aufmerksamkeitszauber-ID", "ID du sort d’attention", "ID del hechizo de atención", "吸引注意的法术ID"], ["Spell cast by the NPC. 0 disables it.", "Заклинание НПС. 0 отключает его.", "Zauber des NPCs. 0 deaktiviert ihn.", "Sort du PNJ. 0 le désactive.", "Hechizo del PNJ. 0 lo desactiva.", "NPC施放的法术,0关闭。"]], ["EmoteCommand", ["Speech gesture ID", "ID жеста при разговоре", "Gesten-ID beim Sprechen", "ID du geste de parole", "ID del gesto al hablar", "发言动作ID"], ["0 disables gestures; 3 is a wave.", "0 отключает жесты; 3 — помахать рукой.", "0 deaktiviert Gesten; 3 ist Winken.", "0 désactive les gestes ; 3 est un salut.", "0 desactiva gestos; 3 es saludar.", "0关闭动作;3为挥手。"]], ] as [string, string[], string[]][]) add(`Enchanter.${key}`, "int", title, desc, { min: 0, advanced: true }); for (let n = 1; n <= 3; n++) add(`EC.P${n}`, "string", [`NPC phrase ${n}`, `Реплика НПС ${n}`, `NPC-Satz ${n}`, `Phrase du PNJ ${n}`, `Frase del PNJ ${n}`, `NPC台词 ${n}`], ["Text spoken by the NPC. Keep the enclosing quotes.", "Текст реплики НПС. Сохраните кавычки вокруг текста.", "Gesprochener Text. Anführungszeichen beibehalten.", "Texte prononcé. Conservez les guillemets.", "Texto hablado. Conserva las comillas.", "NPC发言内容,请保留外层引号。"], { advanced: true }); add("CoACustomRaces.BotsEnable", "bool", ["Bots can be custom races", "Боты кастомных рас", "Bots benutzerdefinierter Völker", "Bots de races personnalisées", "Bots de razas personalizadas", "允许机器人为自定义种族"], [ "Allow Playerbots to spawn as custom races (experimental). Off excludes all custom races; on allows stable custom races.", "Разрешает ботам-игрокам появляться кастомными расами (экспериментально). Выключено — исключает все кастомные расы, включено — разрешает стабильные.", "Erlaubt Spieler-Bots, benutzerdefinierte Völker zu nutzen (experimentell). Aus schließt alle benutzerdefinierten Völker aus; An erlaubt stabile.", "Autorise les bots joueurs à s’incarner avec des races personnalisées (expérimental). Désactivé exclut toutes les races personnalisées ; activé autorise les stables.", "Permite a los bots jugadores crearse con razas personalizadas (experimental). Desactivado excluye todas las razas personalizadas; activado permite las estables.", "允许玩家机器人扮演自定义种族(实验性)。关闭将排除所有自定义种族,开启允许稳定的自定义种族。" ] ); const ah = "AuctionHouseBot."; add(ah + "GUIDs", "string", ["Auction characters", "Персонажи аукциона", "Auktionscharaktere", "Personnages d’enchères", "Personajes de subasta", "拍卖角色"], ["Leave 0 and the server creates and uses its own auction character. To use your own, enter GUIDs separated by commas, without spaces; use dedicated offline characters, not companion bots.", "Оставьте 0, и сервер сам создаст и будет использовать своего персонажа аукциона. Чтобы использовать своих, укажите GUID через запятую без пробелов; нужны отдельные офлайн-персонажи, не боты-компаньоны.", "Bei 0 legt der Server selbst einen Auktionscharakter an und nutzt ihn. Eigene Charaktere: GUIDs durch Kommas, ohne Leerzeichen; separate Offline-Charaktere verwenden, keine Begleiter-Bots.", "Laissez 0 : le serveur crée et utilise son propre personnage d’enchères. Pour les vôtres, indiquez des GUID séparés par des virgules sans espaces ; personnages dédiés hors ligne, pas des bots compagnons.", "Deja 0 y el servidor crea y usa su propio personaje de subastas. Para usar los tuyos, indica GUID separados por comas sin espacios; personajes dedicados desconectados, no bots acompañantes.", "保持0时由服务器自动创建并使用拍卖角色。要使用自己的角色,请以逗号分隔填写GUID,不加空格;使用专用离线角色,勿用伙伴机器人。"], { advanced: true }); add(ah + "EnableSeller", "bool", ["Post auctions", "Выставлять лоты", "Auktionen einstellen", "Publier des enchères", "Publicar subastas", "发布拍卖"], ["Let the auction characters sell items. The module switch is also required.", "Разрешает персонажам продавать предметы. Нужен включённый модуль.", "Erlaubt Verkäufe. Der Modulschalter muss ebenfalls aktiv sein.", "Autorise les ventes. Le module doit aussi être activé.", "Permite vender. También requiere el módulo activado.", "允许拍卖角色出售物品,还需启用模块。"]); add(ah + "CoA.ObtainableItemsOnly", "bool", ["Only obtainable items", "Только доступные в игре предметы", "Nur erhältliche Gegenstände", "Objets accessibles uniquement", "Solo objetos obtenibles", "仅可获取物品"], ["List items from world loot, vendors and professions. Other item filters still apply.", "Выставляет предметы из добычи, торговцев и профессий. Остальные фильтры продолжают действовать.", "Listet Gegenstände aus Beute, Händlern und Berufen. Weitere Filter gelten weiterhin.", "Publie les objets de butin, vendeurs et métiers. Les autres filtres s’appliquent.", "Publica objetos de botín, vendedores y profesiones. Los demás filtros siguen activos.", "发布来自掉落、商人和专业的物品,其他筛选仍生效。"]); add(ah + "ItemsPerCycle", "int", ["Listings per cycle", "Лотов за цикл", "Angebote pro Durchlauf", "Annonces par cycle", "Anuncios por ciclo", "每轮上架数量"], ["Maximum items posted during one auction update.", "Максимум предметов за одно обновление аукциона.", "Maximale Angebote pro Auktionsaktualisierung.", "Nombre maximal d’objets par mise à jour.", "Máximo de objetos por actualización.", "每轮拍卖更新最多上架的物品数量。"], { min: 0 }); for (const [kind, titles] of [ ["Buy", ["Buying interval (minutes)", "Интервал покупки (минуты)", "Kaufintervall (Minuten)", "Intervalle d’achat (minutes)", "Intervalo de compra (minutos)", "购买间隔(分钟)"]], ["Sell", ["Selling interval (minutes)", "Интервал продажи (минуты)", "Verkaufsintervall (Minuten)", "Intervalle de vente (minutes)", "Intervalo de venta (minutos)", "出售间隔(分钟)"]], ] as [string, string[]][]) add(ah + `MinutesBetween${kind}Cycle`, "string", titles, ["A number for a fixed interval, or min:max for a random interval (e.g. 3:15).", "Число для фиксированного интервала или мин:макс для случайного (например, 3:15).", "Zahl für ein festes Intervall oder min:max für ein zufälliges (z. B. 3:15).", "Un nombre pour un délai fixe, ou min:max pour un délai aléatoire (ex. 3:15).", "Número para intervalo fijo o mín:máx para uno aleatorio (p. ej. 3:15).", "固定间隔填数字,随机间隔填最小:最大,如3:15。"]); for (const [faction, names] of [ ["Alliance", ["Alliance", "Альянс", "Allianz", "Alliance", "Alianza", "联盟"]], ["Horde", ["Horde", "Орда", "Horde", "Horde", "Horda", "部落"]], ["Neutral", ["Neutral", "Нейтральный аукцион", "Neutral", "Neutre", "Neutral", "中立"]], ] as [string, string[]][]) for (const bound of ["Min", "Max"]) { const maximum = bound === "Max"; add(ah + `${faction}.${bound}Items`, "int", [ `${names[0]}: ${maximum ? "maximum" : "minimum"} listings`, `${names[1]}: ${maximum ? "максимум" : "минимум"} лотов`, `${names[2]}: ${maximum ? "maximale" : "minimale"} Angebote`, `${names[3]} : annonces ${maximum ? "maximales" : "minimales"}`, `${names[4]}: anuncios ${maximum ? "máximos" : "mínimos"}`, `${names[5]}:${maximum ? "最多" : "最少"}上架数量`, ], ["Target listing count for this auction house. Keep the minimum no higher than the maximum.", "Целевое число лотов этого аукциона. Минимум не должен превышать максимум.", "Zielanzahl für dieses Auktionshaus. Minimum nicht höher als Maximum einstellen.", "Nombre cible pour cet hôtel des ventes. Le minimum ne doit pas dépasser le maximum.", "Cantidad objetivo para esta casa de subastas. El mínimo no debe superar el máximo.", "此拍卖行的目标上架数量,最小值不得超过最大值。"], { min: 0 }); } add(ah + "Buyer.Enabled", "bool", ["Buy player auctions", "Покупать лоты игроков", "Spielerauktionen kaufen", "Acheter les enchères des joueurs", "Comprar subastas de jugadores", "购买玩家拍卖"], ["The auctioneer bot buys listings.", "Бот-аукционист покупает лоты.", "Der Auktionator-Bot kauft Angebote.", "Le bot commissaire-priseur achète des annonces.", "El bot subastador compra anuncios.", "拍卖机器人会购买拍卖品。"]); add(ah + "Buyer.BuyCandidatesPerBuyCycle", "int", ["Purchase candidates per cycle", "Кандидатов на покупку за цикл", "Kaufkandidaten pro Durchlauf", "Achats potentiels par cycle", "Candidatos de compra por ciclo", "每轮购买候选数量"], ["How many auctions the buyer considers per cycle.", "Сколько лотов покупатель рассматривает за цикл.", "Anzahl geprüfter Auktionen pro Durchlauf.", "Nombre d’enchères examinées par cycle.", "Subastas examinadas por ciclo.", "买家每轮考虑的拍卖数量。"], { min: 0 }); add(ah + "Buyer.AcceptablePriceModifier", "float", ["Buyer price multiplier", "Множитель цены покупателя", "Kaufpreisfaktor", "Multiplicateur du prix d’achat", "Multiplicador del precio de compra", "买家价格倍率"], ["1 uses the calculated price. Higher values allow more expensive purchases.", "1 — расчётная цена. Большие значения допускают более дорогие покупки.", "1 nutzt den berechneten Preis. Höhere Werte erlauben teurere Käufe.", "1 utilise le prix calculé. Une valeur supérieure autorise des achats plus chers.", "1 usa el precio calculado. Valores mayores permiten compras más caras.", "1使用计算价格,增大后允许更高价购买。"], { min: 0 }); add(ah + "MaxBuyoutPriceInCopper", "int", ["Maximum buyout (copper)", "Максимальная цена выкупа (медь)", "Maximaler Sofortkauf (Kupfer)", "Rachat maximal (cuivre)", "Compra máxima (cobre)", "最高一口价(铜币)"], ["Upper price limit. 10,000 copper = 1 gold.", "Верхний предел цены. 10 000 меди = 1 золото.", "Preisobergrenze. 10.000 Kupfer = 1 Gold.", "Prix maximal. 10 000 cuivre = 1 or.", "Precio máximo. 10 000 cobre = 1 oro.", "价格上限,10000铜币=1金币。"], { min: 0 }); for (const [key, titles] of [ ["Buyer.AlwaysBidMaxCalculatedPrice", ["Bid the calculated maximum", "Ставить расчётный максимум", "Berechnetes Maximum bieten", "Enchérir au maximum calculé", "Pujar el máximo calculado", "按计算最高价出价"]], ["Buyer.PreventOverpayingForVendorItems", ["Avoid overpaying for vendor items", "Не переплачивать за товары торговцев", "Händlerwaren nicht überbezahlen", "Éviter de surpayer les objets de vendeurs", "Evitar sobrepagar objetos de vendedores", "避免高价购买商人物品"]], ["Buyer.BidAgainstPlayers", ["Compete with player bids", "Конкурировать со ставками игроков", "Mit Spielergeboten konkurrieren", "Concurrencer les enchères des joueurs", "Competir con pujas de jugadores", "与玩家竞价"]], ] as [string, string[]][]) add(ah + key, "bool", titles, ["Controls the auction buyer’s bidding behaviour.", "Настраивает поведение покупателя при торгах.", "Steuert das Bietverhalten des Käufers.", "Règle le comportement d’enchères de l’acheteur.", "Controla las pujas del comprador.", "控制拍卖买家的出价行为。"]); for (const [prefix, labels, bounds, titles] of [ ["ListedItemLevelRestrict", ["Filter by item level", "Фильтр по уровню предметов", "Nach Gegenstandsstufe filtern", "Filtrer par niveau d’objet", "Filtrar por nivel de objeto", "按物品等级筛选"], ["MinItemLevel", "MaxItemLevel"], ["Item level", "Уровень предметов", "Gegenstandsstufe", "Niveau d’objet", "Nivel de objeto", "物品等级"]], ["EquipItemUseOrEquipLevelRestrict", ["Filter by required player level", "Фильтр по требуемому уровню игрока", "Nach benötigter Spielerstufe filtern", "Filtrer par niveau requis", "Filtrar por nivel requerido", "按角色需求等级筛选"], ["MinLevel", "MaxLevel"], ["Required level", "Требуемый уровень", "Benötigte Stufe", "Niveau requis", "Nivel requerido", "需求等级"]], ] as [string, string[], string[], string[]][]) { const desc = ["Only list items within these limits when this filter is enabled.", "При включённом фильтре выставляет только предметы в этих пределах.", "Bei aktivem Filter nur Gegenstände innerhalb dieser Grenzen einstellen.", "Lorsque le filtre est actif, publier uniquement les objets dans ces limites.", "Con filtro activo, publicar solo objetos dentro de estos límites.", "启用筛选后仅发布范围内的物品。"]; add(ah + prefix + ".Enabled", "bool", labels, desc); bounds.forEach((bound, i) => add(ah + prefix + "." + bound, "int", [`${titles[0]}: ${i ? "maximum" : "minimum"}`, `${titles[1]}: ${i ? "максимум" : "минимум"}`, `${titles[2]}: ${i ? "Maximum" : "Minimum"}`, `${titles[3]} : ${i ? "maximum" : "minimum"}`, `${titles[4]}: ${i ? "máximo" : "mínimo"}`, `${titles[5]}:${i ? "最大" : "最小"}`], desc, { min: 0 })); } export function moduleEnableKey(id: string) { return catalog.modules.find(m => m.id === id)?.enable_key; } export const auctionCategories: Record = { Consumable: ["Consumables", "Расходники", "Verbrauchsgüter", "Consommables", "Consumibles", "消耗品"], Container: ["Bags", "Сумки", "Taschen", "Sacs", "Bolsas", "背包"], Weapon: ["Weapons", "Оружие", "Waffen", "Armes", "Armas", "武器"], Gem: ["Gems", "Самоцветы", "Edelsteine", "Gemmes", "Gemas", "宝石"], Armor: ["Armor", "Броня", "Rüstung", "Armures", "Armaduras", "护甲"], Reagent: ["Spell reagents", "Реагенты заклинаний", "Zauberreagenzien", "Composants de sorts", "Componentes de hechizos", "施法材料"], Projectile: ["Ammunition", "Боеприпасы", "Munition", "Munitions", "Munición", "弹药"], TradeGood: ["Crafting materials", "Материалы для профессий", "Handwerksmaterialien", "Matériaux d’artisanat", "Materiales de profesión", "专业材料"], Generic: ["Generic items", "Общие предметы", "Allgemeine Gegenstände", "Objets génériques", "Objetos genéricos", "通用物品"], Recipe: ["Recipes", "Рецепты", "Rezepte", "Recettes", "Recetas", "配方"], Quiver: ["Quivers and ammo pouches", "Колчаны и подсумки", "Köcher und Munitionstaschen", "Carquois et gibernes", "Carcajes y bolsas de munición", "箭袋与弹药包"], Quest: ["Quest items", "Предметы заданий", "Questgegenstände", "Objets de quête", "Objetos de misión", "任务物品"], Key: ["Keys", "Ключи", "Schlüssel", "Clés", "Llaves", "钥匙"], Misc: ["Miscellaneous", "Разное", "Verschiedenes", "Divers", "Varios", "杂项"], Glyph: ["Glyphs", "Символы", "Glyphen", "Glyphes", "Glifos", "雕文"], }; export const auctionQualities: Record = { Poor: ["Poor", "Низкое", "Schlecht", "Médiocre", "Pobre", "粗糙"], Normal: ["Common", "Обычное", "Gewöhnlich", "Commun", "Común", "普通"], Uncommon: ["Uncommon", "Необычное", "Ungewöhnlich", "Inhabituel", "Poco común", "优秀"], Rare: ["Rare", "Редкое", "Selten", "Rare", "Raro", "精良"], Epic: ["Epic", "Эпическое", "Episch", "Épique", "Épico", "史诗"], Legendary: ["Legendary", "Легендарное", "Legendär", "Légendaire", "Legendario", "传说"], Artifact: ["Artifact", "Артефакт", "Artefakt", "Artefact", "Artefacto", "神器"], Heirloom: ["Heirloom", "Наследуемое", "Erbstück", "Héritage", "Reliquia", "传家宝"], }; export const auctionCategoryLabel = (category: string, locale: Locale) => text(auctionCategories[category], locale); export const auctionQualityLabel = (quality: string, locale: Locale) => text(auctionQualities[quality], locale); export const auctionWeightKey = (key: string) => /^AuctionHouseBot\.ListProportion\.Category([A-Za-z]+)\.Quality([A-Za-z]+)$/.exec(key); export function auctionCategorySettings(items: ModuleSetting[], category: string) { return items.filter(item => auctionWeightKey(item.key)?.[1] === category); } /** Turn a type off across all qualities; on restores weights or selects one supported common quality. */ export function auctionTypeChanges(items: ModuleSetting[], on: boolean, remembered: Record = {}): Record { if (!on) return Object.fromEntries(items.map(item => [item.key, "0"])); const restored = Object.fromEntries(items.map(item => [item.key, remembered[item.key] ?? item.default ?? "0"])); if (!Object.values(restored).some(value => Number(value) > 0)) { const fallback = items.find(item => auctionWeightKey(item.key)?.[2] === "Normal") ?? items[0]; if (fallback) restored[fallback.key] = "1"; } return restored; } for (const [category, categoryNames] of Object.entries(auctionCategories)) { for (const [quality, qualityNames] of Object.entries(auctionQualities)) add( `AuctionHouseBot.ListProportion.Category${category}.Quality${quality}`, "int", categoryNames.map((name, index) => `${name}: ${qualityNames[index]}`), ["Relative listing weight, not a percentage. 0 excludes this type and quality. Larger values make it more frequent among new listings.", "Относительный вес, а не процент. 0 исключает этот тип и качество. Чем больше значение, тем чаще они встречаются среди новых лотов.", "Relatives Gewicht, kein Prozentwert. 0 schließt Typ und Qualität aus. Höhere Werte erzeugen häufiger neue Angebote.", "Poids relatif, pas un pourcentage. 0 exclut ce type et cette qualité. Une valeur plus élevée augmente leur fréquence.", "Peso relativo, no porcentaje. 0 excluye este tipo y calidad. Valores mayores aumentan su frecuencia en nuevas subastas.", "相对权重,并非百分比。0排除此类型与品质,数值越大越常出现在新拍卖中。"], { min: 0, max: 1000, advanced: true }, ); } export function moduleField(key: string, locale: Locale, metadata?: ModuleSetting["field"]): ModuleField | undefined { const translated = locale !== "en" ? fields.find(f => f.key === key) : undefined; if (metadata) { // A fixed list of values is edited as a drop-down; the values stay strings, as they are written to the config. const options = metadata.choices?.length ? metadata.choices.map(c => ({ value: String(c.value), label: c.label })) : undefined; return { title: translated ? text(translated.title, locale) : metadata.title, description: translated ? text(translated.description, locale) : metadata.description, type: options ? "enum" : metadata.type, category: metadata.group, advanced: false, min: metadata.min ?? undefined, max: metadata.max ?? undefined, ...(options ? { options } : {}) } as ModuleField; } const field = fields.find(f => f.key === key); if (field) return { ...field, title: text(field.title, locale), description: text(field.description, locale), category: "general", advanced: field.advanced ?? false, step: field.type === "range" ? 0.05 : undefined, options: field.options?.map(o => ({ value: o.value, label: text(o.label, locale) })) }; const existing = [...bots.settings, ...server.settings].find(s => s.key === key); return existing ? { ...existing, options: "options" in existing ? existing.options : undefined } as ModuleField : undefined; } /** Older configs accept numeric aliases; keep their effective selection when opening the editor. */ export function canonicalModuleValue(key: string, value: string): string { const v = value.trim().replace(/^"|"$/g, ""); if (key === scaling + "SoloAssist.Mode") return ({ "0": "None", "1": "Light", "2": "Full", none: "None", light: "Light", full: "Full" } as Record)[v.toLowerCase()] ?? v; if (key === scaling + "LFG.DefaultMode") return ({ "0": "Matchmaking", "1": "BotFill", "2": "CurrentParty", matchmaking: "Matchmaking", botfill: "BotFill", currentparty: "CurrentParty" } as Record)[v.toLowerCase()] ?? v; if (key === scaling + "Progression.Mode") return v.toLowerCase() === "auto" ? "Auto" : v.toLowerCase() === "custom" ? "Custom" : v; return v; }