diff options
Diffstat (limited to 'internal/admin')
| -rw-r--r-- | internal/admin/api_courses.go | 4 | ||||
| -rw-r--r-- | internal/admin/api_items.go | 4 | ||||
| -rw-r--r-- | internal/admin/api_map.go | 10 | ||||
| -rw-r--r-- | internal/admin/api_mobs.go | 4 | ||||
| -rw-r--r-- | internal/admin/api_modules.go | 4 | ||||
| -rw-r--r-- | internal/admin/api_objects.go | 4 | ||||
| -rw-r--r-- | internal/admin/api_rooms.go | 16 | ||||
| -rw-r--r-- | internal/admin/api_techs.go | 4 | ||||
| -rw-r--r-- | internal/admin/static/mobeditor.js | 81 | ||||
| -rw-r--r-- | internal/admin/undo.go | 12 |
10 files changed, 104 insertions, 39 deletions
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 = '<div class="obj-inline-group">'; + h += '<span class="obj-inline-label">Atk Type</span>'; + h += '<div class="obj-card-grid" style="margin-top:4px">'; + ['stab','slash','crush'].forEach(function(m) { + h += '<label class="obj-field obj-check obj-narrow"><input type="radio" name="mob_atktype_melee" value="' + m + '"' + (melee === m ? ' checked' : '') + '> <span>' + m + '</span></label>'; + }); + h += '</div>'; + h += '<div class="obj-card-grid" style="margin-top:4px">'; + h += '<label class="obj-field obj-check obj-narrow"><input type="checkbox" id="mob_atktype_ranged"' + (hasRanged ? ' checked' : '') + '> <span>ranged</span></label>'; + h += '<label class="obj-field obj-check obj-narrow"><input type="checkbox" id="mob_atktype_science"' + (hasScience ? ' checked' : '') + '> <span>science</span></label>'; + h += '</div>'; + h += '</div>'; + return h; +} + +// collectMobAttackType reads the Atk Type widget into an array: the selected +// melee type first, then any optional ranged/science types. +function collectMobAttackType() { + var melee = 'crush'; + var r = document.querySelector('input[name="mob_atktype_melee"]:checked'); + if (r) melee = r.value; + var out = [melee]; + var rng = document.getElementById('mob_atktype_ranged'); + var sci = document.getElementById('mob_atktype_science'); + if (rng && rng.checked) out.push('ranged'); + if (sci && sci.checked) out.push('science'); + return out; +} + function renderMobCombatCard(data) { var sec = MOB_SECTIONS.find(function(s) { return s.id === 'combat'; }); if (!sec) return ''; @@ -278,8 +327,12 @@ function renderMobCombatCard(data) { sec.fields.forEach(function(f) { byKey[f[0]] = f; }); var h = '<div class="obj-card-grid" style="margin-top:6px">'; h += renderMobField(sec, byKey['kind'], data, -1); + h += renderMobField(sec, byKey['aggressive'], data, -1); + h += renderMobField(sec, byKey['respawn_ticks'], data, -1); h += '</div>'; + h += renderMobAttackTypeWidget(data); + h += '<div class="obj-inline-group">'; h += '<span class="obj-inline-label">Stats</span>'; h += '<div class="obj-card-grid" style="margin-top:4px">'; @@ -287,8 +340,11 @@ function renderMobCombatCard(data) { statKeys.forEach(function(k) { h += renderMobField(sec, byKey[k], data, -1); }); h += '</div>'; h += '<div class="obj-card-grid" style="margin-top:4px">'; - var statKeys2 = ['stats.attack_type','stats.speed','stats.aggressive','stats.respawn_ticks']; - statKeys2.forEach(function(k) { h += renderMobField(sec, byKey[k], data, -1); }); + h += renderMobField(sec, byKey['stats.speed'], data, -1); + h += '</div>'; + h += '<div class="obj-card-grid" style="margin-top:4px">'; + var statKeys3 = ['stats.max_melee_hit','stats.max_ranged_hit','stats.max_science_hit']; + statKeys3.forEach(function(k) { h += renderMobField(sec, byKey[k], data, -1); }); h += '</div>'; h += '</div>'; @@ -310,6 +366,7 @@ function renderMobCombatCard(data) { h += '</div>'; h += '<div class="obj-card-grid" style="margin-top:4px">'; h += renderMobField(sec, byKey['stats.defenses.weakness'], data, -1); + h += renderMobField(sec, byKey['stats.defenses.weakness_percent'], data, -1); h += '</div>'; h += '</div>'; @@ -523,12 +580,15 @@ function mobAddSection() { if (id === 'combat') { mobData[sec.path] = { kind: 'combat', + aggressive: false, + attack_types: ['crush'], + respawn_ticks: 30, stats: { hp: 1, attack: 1, strength: 1, defense: 1, - attack_type: 'crush', speed: 5, - aggressive: false, respawn_ticks: 30, + speed: 5, + max_melee_hit: 0, max_ranged_hit: 0, max_science_hit: 0, bonuses: {attack_bonus:0, strength_bonus:0, science_bonus:0, science_percent_bonus:0, ranged_bonus:0, ranged_strength_bonus:0}, - defenses: {stab_defense:0, slash_defense:0, crush_defense:0, science_defense:0, ranged_defense:0} + defenses: {stab_defense:0, slash_defense:0, crush_defense:0, science_defense:0, ranged_defense:0, weakness_percent:0} }, size: 'small' }; @@ -610,6 +670,11 @@ function saveMob() { hasValues = ced_saveSectionFields(sec, sectionObj, mobCardPath, mobFieldID) || hasValues; } + if (sec.id === 'combat' && document.querySelector('input[name="mob_atktype_melee"]')) { + sectionObj['attack_types'] = collectMobAttackType(); + hasValues = true; + } + if (sec.subtables) { sec.subtables.forEach(function(st) { if (ced_saveSubtable(sec, st, sectionObj, mobCardPath, mobFieldIDSub)) hasValues = true; diff --git a/internal/admin/undo.go b/internal/admin/undo.go index 8f75d30..fb91a15 100644 --- a/internal/admin/undo.go +++ b/internal/admin/undo.go @@ -42,12 +42,12 @@ func (c ChangeDesc) ShortDesc() string { } type UndoInfo struct { - CanUndo bool `json:"can_undo"` - CanRedo bool `json:"can_redo"` - UndoDesc string `json:"undo_desc"` - RedoDesc string `json:"redo_desc"` - StackSize int `json:"stack_size"` - RedoSize int `json:"redo_size"` + CanUndo bool `json:"can_undo"` + CanRedo bool `json:"can_redo"` + UndoDesc string `json:"undo_desc"` + RedoDesc string `json:"redo_desc"` + StackSize int `json:"stack_size"` + RedoSize int `json:"redo_size"` } type UndoStack struct { |
