admin debugg

master
Denis Ranneft 1 month ago
parent 16287bb25b
commit aab12c1567

@ -112,6 +112,10 @@
};
state._confirmAction = null;
/** 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"];
function e(v) { return String(v ?? "").replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;"); }
function authHeader() { return `Basic ${btoa(`${state.auth.username}:${state.auth.password}`)}`; }
function setMessage(text) { state.message = text; render(); }
@ -1185,6 +1189,25 @@
async function claimQuest(questId) { await api(`heroes/${state.selectedHeroId}/quests/${questId}/claim`, { method: "POST", body: "{}" }); await loadHero(state.selectedHeroId); }
async function abandonQuest(questId) { await api(`heroes/${state.selectedHeroId}/quests/${questId}`, { method: "DELETE" }); await loadHero(state.selectedHeroId); }
async function applyHeroBuffAdmin() {
if (!state.selectedHeroId) { setMessage("Сначала выберите героя"); return; }
const buffType = document.getElementById("hero-admin-buff-type")?.value;
if (!buffType) { setMessage("Выберите тип баффа"); return; }
await withRowAction("hero-admin-buff", async () => {
await api(`heroes/${state.selectedHeroId}/apply-buff`, { method: "POST", body: JSON.stringify({ buffType }) });
await loadHero(state.selectedHeroId);
}, "Бафф применён");
}
async function applyHeroDebuffAdmin() {
if (!state.selectedHeroId) { setMessage("Сначала выберите героя"); return; }
const debuffType = document.getElementById("hero-admin-debuff-type")?.value;
if (!debuffType) { setMessage("Выберите тип дебаффа"); return; }
await withRowAction("hero-admin-debuff", async () => {
await api(`heroes/${state.selectedHeroId}/apply-debuff`, { method: "POST", body: JSON.stringify({ debuffType }) });
await loadHero(state.selectedHeroId);
}, "Дебафф применён");
}
function login() {
state.auth.username = document.getElementById("login-user").value.trim();
state.auth.password = document.getElementById("login-pass").value.trim();
@ -1412,6 +1435,33 @@
<div><button type="button" class="btn" onclick="withAction(teleportHeroToTown)">Teleport to town</button></div>
</div>
<p class="muted" style="margin-top:6px">Towns come from the loaded road graph (<kbd>GET /admin/towns</kbd>). Hero must be alive and not in combat.</p>
<div style="margin-top:14px;padding-top:12px;border-top:1px solid #2a3551">
<h4 style="margin:0 0 6px">Баффы / дебаффы (вручную)</h4>
<p class="muted" style="margin:0 0 8px">Эффект из серверного каталога, без списания бесплатных зарядов. Только вне боя (как и прочие правки героя).</p>
<div class="row" style="align-items:end">
<div>
<label class="muted">Бафф</label>
<select id="hero-admin-buff-type">
<option value=""></option>
${ADMIN_BUFF_TYPES.map(t => `<option value="${e(t)}">${e(t)}</option>`).join("")}
</select>
</div>
<div>
<label class="muted">Дебафф</label>
<select id="hero-admin-debuff-type">
<option value=""></option>
${ADMIN_DEBUFF_TYPES.map(t => `<option value="${e(t)}">${e(t)}</option>`).join("")}
</select>
</div>
<div></div>
</div>
<div style="margin-top:8px">
<button type="button" class="btn" onclick="withAction(applyHeroBuffAdmin)">Наложить бафф</button>
<span class="${state.rowStatus["hero-admin-buff"]?.ok ? "status-ok" : "status-err"}">${e(state.rowStatus["hero-admin-buff"]?.message || "")}</span>
<button type="button" class="btn" onclick="withAction(applyHeroDebuffAdmin)" style="margin-left:8px">Наложить дебафф</button>
<span class="${state.rowStatus["hero-admin-debuff"]?.ok ? "status-ok" : "status-err"}">${e(state.rowStatus["hero-admin-debuff"]?.message || "")}</span>
</div>
</div>
` : `<div class="muted">Select hero from list</div>`}
</div>
</div>

@ -272,15 +272,15 @@ func tryApplyDebuff(hero *model.Hero, enemy *model.Enemy, now time.Time) string
continue
}
applyDebuff(hero, rule.debuff, now)
ApplyDebuff(hero, rule.debuff, now)
return string(rule.debuff)
}
return ""
}
// applyDebuff adds a debuff to the hero. If the same debuff type is already active, it refreshes.
func applyDebuff(hero *model.Hero, debuffType model.DebuffType, now time.Time) {
// ApplyDebuff adds a debuff to the hero. If the same debuff type is already active, it refreshes.
func ApplyDebuff(hero *model.Hero, debuffType model.DebuffType, now time.Time) {
def, ok := model.DebuffDefinition(debuffType)
if !ok {
return

@ -1312,6 +1312,149 @@ func (h *AdminHandler) ResetBuffCharges(w http.ResponseWriter, r *http.Request)
writeJSON(w, http.StatusOK, hero)
}
type applyBuffAdminRequest struct {
BuffType string `json:"buffType"`
}
// ApplyHeroBuff applies a buff from the catalog without consuming free-charge quota (admin/testing).
// POST /admin/heroes/{heroId}/apply-buff
func (h *AdminHandler) ApplyHeroBuff(w http.ResponseWriter, r *http.Request) {
heroID, err := parseHeroID(r)
if err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{
"error": "invalid heroId: " + err.Error(),
})
return
}
if h.isHeroInCombat(w, heroID) {
return
}
var req applyBuffAdminRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{
"error": "invalid request body: " + err.Error(),
})
return
}
bt, ok := model.ValidBuffType(req.BuffType)
if !ok {
writeJSON(w, http.StatusBadRequest, map[string]string{
"error": "invalid buffType: " + req.BuffType,
})
return
}
hero, err := h.store.GetByID(r.Context(), heroID)
if err != nil {
h.logger.Error("admin: get hero for apply-buff", "hero_id", heroID, "error", err)
writeJSON(w, http.StatusInternalServerError, map[string]string{
"error": "failed to load hero",
})
return
}
if hero == nil {
writeJSON(w, http.StatusNotFound, map[string]string{
"error": "hero not found",
})
return
}
now := time.Now()
if game.ApplyBuff(hero, bt, now) == nil {
writeJSON(w, http.StatusBadRequest, map[string]string{
"error": "buff could not be applied (unknown catalog entry)",
})
return
}
if err := h.store.Save(r.Context(), hero); err != nil {
h.logger.Error("admin: save hero after apply-buff", "hero_id", heroID, "error", err)
writeJSON(w, http.StatusInternalServerError, map[string]string{
"error": "failed to save hero",
})
return
}
h.logger.Info("admin: buff applied", "hero_id", heroID, "buff_type", bt)
hero.EnsureGearMap()
hero.RefreshDerivedCombatStats(now)
h.engine.ApplyAdminHeroSnapshot(hero)
writeJSON(w, http.StatusOK, hero)
}
type applyDebuffAdminRequest struct {
DebuffType string `json:"debuffType"`
}
// ApplyHeroDebuff applies a debuff from the catalog (admin/testing).
// POST /admin/heroes/{heroId}/apply-debuff
func (h *AdminHandler) ApplyHeroDebuff(w http.ResponseWriter, r *http.Request) {
heroID, err := parseHeroID(r)
if err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{
"error": "invalid heroId: " + err.Error(),
})
return
}
if h.isHeroInCombat(w, heroID) {
return
}
var req applyDebuffAdminRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{
"error": "invalid request body: " + err.Error(),
})
return
}
dt, ok := model.ValidDebuffType(req.DebuffType)
if !ok {
writeJSON(w, http.StatusBadRequest, map[string]string{
"error": "invalid debuffType: " + req.DebuffType,
})
return
}
hero, err := h.store.GetByID(r.Context(), heroID)
if err != nil {
h.logger.Error("admin: get hero for apply-debuff", "hero_id", heroID, "error", err)
writeJSON(w, http.StatusInternalServerError, map[string]string{
"error": "failed to load hero",
})
return
}
if hero == nil {
writeJSON(w, http.StatusNotFound, map[string]string{
"error": "hero not found",
})
return
}
now := time.Now()
if _, defOk := model.DebuffDefinition(dt); !defOk {
writeJSON(w, http.StatusBadRequest, map[string]string{
"error": "debuff not in catalog: " + req.DebuffType,
})
return
}
game.ApplyDebuff(hero, dt, now)
if err := h.store.Save(r.Context(), hero); err != nil {
h.logger.Error("admin: save hero after apply-debuff", "hero_id", heroID, "error", err)
writeJSON(w, http.StatusInternalServerError, map[string]string{
"error": "failed to save hero",
})
return
}
h.logger.Info("admin: debuff applied", "hero_id", heroID, "debuff_type", dt)
hero.EnsureGearMap()
hero.RefreshDerivedCombatStats(now)
h.engine.ApplyAdminHeroSnapshot(hero)
writeJSON(w, http.StatusOK, hero)
}
// DeleteHero permanently removes a hero from the database.
// DELETE /admin/heroes/{heroId}
func (h *AdminHandler) DeleteHero(w http.ResponseWriter, r *http.Request) {

@ -84,6 +84,8 @@ func New(deps Deps) *chi.Mux {
r.Post("/heroes/{heroId}/revive", adminH.ReviveHero)
r.Post("/heroes/{heroId}/reset", adminH.ResetHero)
r.Post("/heroes/{heroId}/reset-buff-charges", adminH.ResetBuffCharges)
r.Post("/heroes/{heroId}/apply-buff", adminH.ApplyHeroBuff)
r.Post("/heroes/{heroId}/apply-debuff", adminH.ApplyHeroDebuff)
r.Post("/heroes/{heroId}/teleport-town", adminH.TeleportHeroTown)
r.Post("/heroes/{heroId}/start-rest", adminH.StartHeroRest)
r.Post("/heroes/{heroId}/start-roadside-rest", adminH.StartHeroRoadsideRest)

Loading…
Cancel
Save