diff --git a/admin-web/index.html b/admin-web/index.html index 440f20b..fff3cbd 100644 --- a/admin-web/index.html +++ b/admin-web/index.html @@ -85,9 +85,9 @@ contentQuests: [], contentGearRows: [], contentGearEditor: null, - contentQuestEditor: null, contentEnemies: [], contentEnemyEditor: null, + contentQuestEditor: null, gearFilterSlot: "", gearFilterRarity: "", gearFilterSubtype: "", @@ -117,11 +117,8 @@ /** Matches model.AllBuffTypes / AllDebuffTypes (admin manual apply). */ const ADMIN_BUFF_TYPES = ["rush", "rage", "shield", "luck", "resurrection", "heal", "power_potion", "war_cry"]; const ADMIN_DEBUFF_TYPES = ["poison", "freeze", "burn", "stun", "slow", "weaken", "ice_slow"]; - /** model.SpecialAbility — чекбоксы в редакторе врагов */ - const ADMIN_ENEMY_SPECIAL_ABILITIES = [ - "burn", "slow", "critical", "poison", "freeze", "ice_slow", "stun", "dodge", - "regen", "burst", "chain_lightning", "summon" - ]; + /** model.SpecialAbility — enemy template abilities (DB text[]). */ + const ADMIN_ENEMY_ABILITIES = ["burn", "slow", "critical", "poison", "freeze", "ice_slow", "stun", "dodge", "regen", "burst", "chain_lightning", "summon"]; function e(v) { return String(v ?? "").replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """); } function authHeader() { return `Basic ${btoa(`${state.auth.username}:${state.auth.password}`)}`; } @@ -968,93 +965,53 @@ render(); } async function loadContentGearBase() { const data = await api("content/gear-base"); state.contentGearRows = data.gear || []; render(); } - async function loadContentQuests() { const data = await api("content/quests"); state.contentQuests = data.quests || []; render(); } async function loadContentEnemies() { const data = await api("content/enemies"); state.contentEnemies = data.enemies || []; render(); } - function openContentEnemyEditorByType(enemyType) { - const row = (state.contentEnemies || []).find(x => x.type === enemyType); + async function reloadEnemyTemplatesOnly() { + await api("content/enemies/reload", { method: "POST", body: "{}" }); + setMessage("Enemy templates reloaded from DB into server memory"); + } + function openContentEnemyEditorByType(type) { + const row = (state.contentEnemies || []).find(x => x.type === type); if (!row) return; state.contentEnemyEditor = Object.assign({}, row, { - _abilities: new Set(row.specialAbilities || []) + _abilitiesText: Array.isArray(row.specialAbilities) ? row.specialAbilities.join(", ") : "" }); render(); } function closeContentEnemyEditor() { state.contentEnemyEditor = null; render(); } async function saveContentEnemy() { - const ed = state.contentEnemyEditor; - if (!ed) return; - const type = String(ed.type || "").trim(); - const maxHp = Number(document.getElementById("cem-maxHp").value || 0); + const typ = document.getElementById("me-type")?.value?.trim(); + if (!typ) return; + const maxHp = Number(document.getElementById("me-maxHp").value || 0); + const abText = document.getElementById("me-abilities").value || ""; + const specialAbilities = abText.split(",").map(s => s.trim()).filter(Boolean); const body = { - type, - name: document.getElementById("cem-name").value.trim(), - maxHp, + id: Number(document.getElementById("me-id").value || 0), + type: typ, + name: document.getElementById("me-name").value, hp: maxHp, - attack: Number(document.getElementById("cem-attack").value || 0), - defense: Number(document.getElementById("cem-defense").value || 0), - speed: Number(document.getElementById("cem-speed").value || 0), - critChance: Number(document.getElementById("cem-critChance").value || 0), - minLevel: Number(document.getElementById("cem-minLevel").value || 1), - maxLevel: Number(document.getElementById("cem-maxLevel").value || 1), - xpReward: Number(document.getElementById("cem-xpReward").value || 0), - goldReward: Number(document.getElementById("cem-goldReward").value || 0), - isElite: !!document.getElementById("cem-isElite").checked, - specialAbilities: ADMIN_ENEMY_SPECIAL_ABILITIES.filter(a => document.getElementById("cem-ab-" + a).checked) + maxHp, + attack: Number(document.getElementById("me-attack").value || 0), + defense: Number(document.getElementById("me-defense").value || 0), + speed: Number(document.getElementById("me-speed").value || 0), + critChance: Number(document.getElementById("me-critChance").value || 0), + minLevel: Number(document.getElementById("me-minLevel").value || 1), + maxLevel: Number(document.getElementById("me-maxLevel").value || 100), + xpReward: Number(document.getElementById("me-xpReward").value || 0), + goldReward: Number(document.getElementById("me-goldReward").value || 0), + specialAbilities, + isElite: !!document.getElementById("me-isElite")?.checked }; - await api("content/enemies/" + encodeURIComponent(type), { method: "PUT", body: JSON.stringify(body) }); + await api(`content/enemies/${encodeURIComponent(typ)}`, { method: "PUT", body: JSON.stringify(body) }); state.contentEnemyEditor = null; await loadContentEnemies(); - setMessage("Enemy template saved; in-memory templates reloaded"); - } - async function reloadEnemyTemplatesOnly() { - await api("content/enemies/reload", { method: "POST", body: "{}" }); - setMessage("Enemy templates reloaded from DB"); - } - function contentEnemyEditorHtml() { - const ed = state.contentEnemyEditor; - if (!ed) return ""; - const abs = ed._abilities instanceof Set ? ed._abilities : new Set(ed.specialAbilities || []); - const abChecks = ADMIN_ENEMY_SPECIAL_ABILITIES.map(a => - `` - ).join(""); - return ` -
Запись в таблице enemies. После сохранения сервер подставляет hp = maxHp и перезагружает шаблоны в памяти.
- -Таблица enemies. Поле type не меняется (ключ). После сохранения шаблоны подхватываются в память сервера.
+ +Допустимые теги: ${e(abHint)}
+ + +Чтение/запись таблицы enemies. Изменения применяются к новым встречам; активный бой использует уже созданный экземпляр.
+ + +| ID | type | name | elite | lvl | HP | atk/def | spd | XP/gold | abilities | + |
|---|---|---|---|---|---|---|---|---|---|---|
| Нажмите «Обновить из БД» | ||||||||||
Таблица enemies: базовые статы, уровни, награды, способности. Изменения после «Save» сразу попадают в память процесса. Новые типы добавляются миграциями/сидом, не из этой формы.
- - -| ID | Type | Name | Class | Levels | maxHP | Atk/Def | Spd | Crit | XP/Au | Abilities | - |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Нет данных — нажмите «Обновить из БД» | |||||||||||