From c14c2e403c75a913e1a6d962fb6fe64f013adae5 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Wed, 1 Jul 2026 22:30:31 -0400 Subject: fix: combat formulas and mob attack types (mobs switch attack styles if safespotting) --- internal/admin/api_courses.go | 4 +- internal/admin/api_items.go | 4 +- internal/admin/api_map.go | 10 ++--- internal/admin/api_mobs.go | 4 +- internal/admin/api_modules.go | 4 +- internal/admin/api_objects.go | 4 +- internal/admin/api_rooms.go | 16 ++++---- internal/admin/api_techs.go | 4 +- internal/admin/static/mobeditor.js | 81 ++++++++++++++++++++++++++++++++++---- internal/admin/undo.go | 12 +++--- 10 files changed, 104 insertions(+), 39 deletions(-) (limited to 'internal/admin') diff --git a/internal/admin/api_courses.go b/internal/admin/api_courses.go index facb8f2..64218cb 100644 --- a/internal/admin/api_courses.go +++ b/internal/admin/api_courses.go @@ -25,8 +25,8 @@ func (s *AdminServer) handleCourses(w http.ResponseWriter, r *http.Request) { writeJSON(w, map[string]any{"error": "invalid json"}) return } - id, _ := m["id"].(string) - if strings.TrimSpace(id) == "" { + id, ok := m["id"].(string) + if !ok || strings.TrimSpace(id) == "" { writeJSON(w, map[string]any{"error": "missing id"}) return } diff --git a/internal/admin/api_items.go b/internal/admin/api_items.go index 721a6f8..617908c 100644 --- a/internal/admin/api_items.go +++ b/internal/admin/api_items.go @@ -137,8 +137,8 @@ func (s *AdminServer) createItem(w http.ResponseWriter, r *http.Request) { http.Error(w, `{"error":"invalid json body"}`, http.StatusBadRequest) return } - id, _ := m["id"].(string) - if strings.TrimSpace(id) == "" { + id, ok := m["id"].(string) + if !ok || strings.TrimSpace(id) == "" { http.Error(w, `{"error":"missing item id"}`, http.StatusBadRequest) return } diff --git a/internal/admin/api_map.go b/internal/admin/api_map.go index 561c1e8..e546a39 100644 --- a/internal/admin/api_map.go +++ b/internal/admin/api_map.go @@ -41,11 +41,11 @@ func (s *AdminServer) handleMap(w http.ResponseWriter, r *http.Request) { }, nil, nil) type RoomEntry struct { - ID int `json:"id"` - X int `json:"x"` - Y int `json:"y"` - Name string `json:"name"` - Color string `json:"color"` + ID int `json:"id"` + X int `json:"x"` + Y int `json:"y"` + Name string `json:"name"` + Color string `json:"color"` } type LinkEntry struct { diff --git a/internal/admin/api_mobs.go b/internal/admin/api_mobs.go index a575076..0646753 100644 --- a/internal/admin/api_mobs.go +++ b/internal/admin/api_mobs.go @@ -138,8 +138,8 @@ func (s *AdminServer) createMob(w http.ResponseWriter, r *http.Request) { http.Error(w, `{"error":"invalid json body"}`, http.StatusBadRequest) return } - id, _ := m["id"].(string) - if strings.TrimSpace(id) == "" { + id, ok := m["id"].(string) + if !ok || strings.TrimSpace(id) == "" { http.Error(w, `{"error":"missing mob id"}`, http.StatusBadRequest) return } diff --git a/internal/admin/api_modules.go b/internal/admin/api_modules.go index 9fedccd..e468c79 100644 --- a/internal/admin/api_modules.go +++ b/internal/admin/api_modules.go @@ -25,8 +25,8 @@ func (s *AdminServer) handleModules(w http.ResponseWriter, r *http.Request) { writeJSON(w, map[string]any{"error": "invalid json"}) return } - id, _ := m["id"].(string) - if strings.TrimSpace(id) == "" { + id, ok := m["id"].(string) + if !ok || strings.TrimSpace(id) == "" { writeJSON(w, map[string]any{"error": "missing id"}) return } diff --git a/internal/admin/api_objects.go b/internal/admin/api_objects.go index 7bcda1c..340c87c 100644 --- a/internal/admin/api_objects.go +++ b/internal/admin/api_objects.go @@ -137,8 +137,8 @@ func (s *AdminServer) createObject(w http.ResponseWriter, r *http.Request) { http.Error(w, `{"error":"invalid json body"}`, http.StatusBadRequest) return } - id, _ := m["id"].(string) - if strings.TrimSpace(id) == "" { + id, ok := m["id"].(string) + if !ok || strings.TrimSpace(id) == "" { http.Error(w, `{"error":"missing object id"}`, http.StatusBadRequest) return } diff --git a/internal/admin/api_rooms.go b/internal/admin/api_rooms.go index 76bb11c..cf23761 100644 --- a/internal/admin/api_rooms.go +++ b/internal/admin/api_rooms.go @@ -374,14 +374,14 @@ func (s *AdminServer) handleRoomLink(w http.ResponseWriter, r *http.Request) { cA, okA := grid.Coord[body.From] cB, okB := grid.Coord[body.To] - if !okA || !okB { - writeJSON(w, map[string]any{"error": "one or both rooms are not reachable from the seed room"}) - return - } - if cA[2] != cB[2] { - writeJSON(w, map[string]any{"error": "rooms must be on same z-level"}) - return - } + if !okA || !okB { + writeJSON(w, map[string]any{"error": "one or both rooms are not reachable from the seed room"}) + return + } + if cA[2] != cB[2] { + writeJSON(w, map[string]any{"error": "rooms must be on same z-level"}) + return + } dx, dy := cB[0]-cA[0], cB[1]-cA[1] for d, delta := range world.DirectionDeltas3D { if delta[0] == dx && delta[1] == dy && delta[2] == 0 { diff --git a/internal/admin/api_techs.go b/internal/admin/api_techs.go index bdb8295..0d3e201 100644 --- a/internal/admin/api_techs.go +++ b/internal/admin/api_techs.go @@ -25,8 +25,8 @@ func (s *AdminServer) handleTechs(w http.ResponseWriter, r *http.Request) { writeJSON(w, map[string]any{"error": "invalid json"}) return } - id, _ := m["id"].(string) - if strings.TrimSpace(id) == "" { + id, ok := m["id"].(string) + if !ok || strings.TrimSpace(id) == "" { writeJSON(w, map[string]any{"error": "missing id"}) return } diff --git a/internal/admin/static/mobeditor.js b/internal/admin/static/mobeditor.js index 3d6f4d4..4895c6f 100644 --- a/internal/admin/static/mobeditor.js +++ b/internal/admin/static/mobeditor.js @@ -22,16 +22,18 @@ var MOB_SECTIONS = [ detect: function(d) { return d.combat; }, fields: [ ['kind', 'Kind', 'select', 'combat|task'], + ['aggressive', 'Aggressive', 'checkbox'], + ['respawn_ticks', 'Respawn Ticks', 'number', '', 'narrow'], ['stats.hp', 'HP', 'number', '', 'narrow'], ['stats.attack', 'Attack', 'number', '', 'narrow'], ['stats.strength', 'Strength', 'number', '', 'narrow'], ['stats.defense', 'Defense', 'number', '', 'narrow'], ['stats.ranged', 'Ranged', 'number', '', 'narrow'], ['stats.science', 'Science', 'number', '', 'narrow'], - ['stats.attack_type', 'Atk Type', 'select', 'stab|slash|crush|ranged|science'], ['stats.speed', 'Speed', 'number', '', 'narrow'], - ['stats.aggressive', 'Aggressive', 'checkbox'], - ['stats.respawn_ticks', 'Respawn Ticks', 'number', '', 'narrow'], + ['stats.max_melee_hit', 'Max Melee Hit', 'number', '', 'narrow'], + ['stats.max_ranged_hit', 'Max Ranged Hit', 'number', '', 'narrow'], + ['stats.max_science_hit', 'Max Science Hit', 'number', '', 'narrow'], ['stats.bonuses.attack_bonus', 'Atk Bonus', 'number', '', 'narrow'], ['stats.bonuses.strength_bonus', 'Str Bonus', 'number', '', 'narrow'], ['stats.bonuses.science_bonus', 'Sci Bonus', 'number', '', 'narrow'], @@ -44,6 +46,7 @@ var MOB_SECTIONS = [ ['stats.defenses.science_defense', 'Sci Def', 'number', '', 'narrow'], ['stats.defenses.ranged_defense', 'Rng Def', 'number', '', 'narrow'], ['stats.defenses.weakness', 'Weakness', 'text'], + ['stats.defenses.weakness_percent', 'Weakness %', 'number', '', 'narrow'], ['assassin_level', 'Assassin Lvl', 'number', '', 'narrow'], ['finishing_blow', 'Finishing Blow', 'search', '', '', null, 'items'], ['damage_without', 'Damage Without', 'search', '', '', null, 'items'], @@ -271,6 +274,52 @@ function renderMobCoreCard(data) { return h; } +// normalizeAttackTypes coerces a mob attack_types value (array) into an array of +// strings, tolerating a legacy scalar string for display only. +function normalizeAttackTypes(v) { + if (Array.isArray(v)) return v.slice(); + if (typeof v === 'string' && v) return [v]; + return []; +} + +// renderMobAttackTypeWidget renders the Atk Type selector: one required melee +// type (radio: stab/slash/crush) plus optional ranged/science (checkboxes). +function renderMobAttackTypeWidget(data) { + var combat = data.combat || {}; + var types = normalizeAttackTypes(combat.attack_types); + var melee = types.filter(function(t) { return t === 'stab' || t === 'slash' || t === 'crush'; })[0] || 'crush'; + var hasRanged = types.indexOf('ranged') >= 0; + var hasScience = types.indexOf('science') >= 0; + + var h = '