From bf7c27c2cbf3eb56a0a4c5141e39b152c51614b5 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Wed, 1 Jul 2026 04:53:56 -0400 Subject: feat: admin gui map, item, and objects mostly complete --- internal/admin/api_rooms.go | 12 ++--- internal/admin/static/admin.css | 4 +- internal/admin/static/colorpicker.js | 7 +++ internal/admin/static/itemeditor.js | 84 ++++++++++++++++++++++++++++++----- internal/admin/static/map.js | 40 ++++++++++++++++- internal/admin/static/objecteditor.js | 47 +++++++++++++++++--- internal/admin/undo.go | 2 +- internal/behavior/behavior.go | 2 +- internal/behavior/types.go | 10 ++--- internal/game/act.go | 2 +- internal/game/act_clean.go | 2 +- internal/game/act_combine.go | 4 +- internal/game/act_fletch.go | 12 ++--- internal/game/act_use.go | 4 +- internal/game/cmd_clean.go | 4 +- internal/game/cmd_craft.go | 2 +- internal/game/cmd_smelt.go | 2 +- internal/game/cmd_trigger_utility.go | 2 +- internal/game/cmd_use.go | 6 +-- internal/game/core_production.go | 2 +- internal/game/production_engine.go | 16 +++---- internal/item/item.go | 2 +- 22 files changed, 205 insertions(+), 63 deletions(-) (limited to 'internal') diff --git a/internal/admin/api_rooms.go b/internal/admin/api_rooms.go index f615d9d..76bb11c 100644 --- a/internal/admin/api_rooms.go +++ b/internal/admin/api_rooms.go @@ -78,6 +78,8 @@ func (s *AdminServer) handleRooms(w http.ResponseWriter, r *http.Request) { m["exits"] = exits } + var extraFiles []ExtraFile + if linkFrom != nil && linkDir != nil && *linkDir != "" { dir := world.ExitDir(strings.ToLower(*linkDir)) if opp, ok := world.OppositeExit[dir]; ok { @@ -99,11 +101,10 @@ func (s *AdminServer) handleRooms(w http.ResponseWriter, r *http.Request) { writeJSON(w, map[string]any{"error": fmt.Sprintf("write source room: %v", writeErr)}) return } - s.undoStack.Push(ChangeDesc{ - Description: fmt.Sprintf("update room %d (add exit %s to %d)", *linkFrom, *linkDir, id), - FilePath: srcPath, - OldContent: oldContent, - NewContent: newContent, + extraFiles = append(extraFiles, ExtraFile{ + FilePath: srcPath, + OldContent: oldContent, + NewContent: newContent, }) } @@ -124,6 +125,7 @@ func (s *AdminServer) handleRooms(w http.ResponseWriter, r *http.Request) { FilePath: path, NewContent: newContent, IsCreate: true, + ExtraFiles: extraFiles, }) writeJSON(w, map[string]any{"room": m}) diff --git a/internal/admin/static/admin.css b/internal/admin/static/admin.css index bdf9b60..93c1924 100644 --- a/internal/admin/static/admin.css +++ b/internal/admin/static/admin.css @@ -148,7 +148,7 @@ g.ud-hover:hover text{font-weight:bold} .panel-tab-content .search-input{width:100%;padding:4px 6px;font-size:11px;background:var(--input-bg);color:var(--text);border:1px solid var(--input-border);border-radius:3px;font-family:monospace} .panel-tab-content .search-results{position:absolute;top:100%;left:0;right:0;max-height:160px;overflow-y:auto;background:var(--panel);border:1px solid var(--border);border-radius:3px;z-index:50;box-shadow:0 4px 12px rgba(0,0,0,.4)} .panel-tab-content .search-results .sr-item{padding:4px 8px;font-size:11px;cursor:pointer;color:var(--text);border-bottom:1px solid rgba(255,255,255,.03)} -.panel-tab-content .search-results .sr-item:hover{background:var(--accent)} +.panel-tab-content .search-results .sr-item:hover,.panel-tab-content .search-results .sr-item.active{background:var(--accent)} .panel-tab-content .section-add-btn{display:block;width:100%;margin-top:4px;background:var(--accent);color:var(--text);border:1px solid #1a5a8e;padding:4px 8px;font-size:11px;border-radius:3px;cursor:pointer;font-family:monospace} .panel-tab-content .section-add-btn:hover{background:var(--hover)} .panel-tab-content .mini-label{font-size:9px;color:#888;display:block;margin-bottom:1px;margin-top:3px} @@ -201,7 +201,7 @@ g.ud-hover:hover text{font-weight:bold} .obj-search{position:relative!important} .obj-search .search-results{position:absolute;top:100%;left:0;right:0;max-height:160px;overflow-y:auto;background:var(--panel);border:1px solid var(--border);border-radius:3px;z-index:50;box-shadow:0 4px 12px rgba(0,0,0,.4);margin-top:2px} .obj-search .search-results .sr-item{padding:4px 8px;font-size:11px;cursor:pointer;color:var(--text);border-bottom:1px solid rgba(255,255,255,.03)} -.obj-search .search-results .sr-item:hover{background:var(--accent)} +.obj-search .search-results .sr-item:hover,.obj-search .search-results .sr-item.active{background:var(--accent)} .obj-search input.search-input{padding-right:24px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.3-4.3'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 6px center} .obj-search input.search-input::placeholder{color:#666} .obj-narrow.obj-search input{width:80px} diff --git a/internal/admin/static/colorpicker.js b/internal/admin/static/colorpicker.js index 9431e83..bba438e 100644 --- a/internal/admin/static/colorpicker.js +++ b/internal/admin/static/colorpicker.js @@ -62,6 +62,7 @@ window.cssToXtermIdx = function(css) { (function() { var pickerEl = null, boundInput = null, boundSwatch = null; + window._colorPickerActive = false; function ensurePicker() { if (pickerEl) return; @@ -114,6 +115,7 @@ window.cssToXtermIdx = function(css) { idx = parseInt(raw, 16) || 0; } var hex = xtermIdxToHex(idx); + window._colorPickerActive = true; inputEl.value = hex; if (swatchEl) swatchEl.style.background = xtermToCss(idx); @@ -153,7 +155,12 @@ window.cssToXtermIdx = function(css) { window.hideColorPicker = hidePicker; function hidePicker() { if (pickerEl) pickerEl.style.display = 'none'; + var input = boundInput; boundInput = null; boundSwatch = null; + window._colorPickerActive = false; + if (input && input.isConnected) { + input.dispatchEvent(new Event('input', {bubbles:true})); + } } document.addEventListener('click', function(e) { diff --git a/internal/admin/static/itemeditor.js b/internal/admin/static/itemeditor.js index d02945a..7c7c676 100644 --- a/internal/admin/static/itemeditor.js +++ b/internal/admin/static/itemeditor.js @@ -48,10 +48,10 @@ var SECTIONS = [ label: 'Recipes to Craft This Item', key: 'craft', fields: [ ['type', 'Type', 'select', 'smithing|cooking|crafting|fletching|combine|pharmacy|construction'], - ['subtype', 'Subtype', 'select', '|smelt|clean'], + ['subtype', 'Subtype', 'select', '|smelting|cleaning'], ['level', 'Level', 'number', '', 'narrow'], ['xp', 'XP', 'number', '', 'narrow'], - ['wait', 'Wait', 'number', '', 'narrow'], + ['ticks_per_cycle', 'Ticks/Cycle', 'number', '', 'narrow'], ['output_qty', 'Output Qty', 'number', '', 'narrow'], ['station', 'Station', 'select', '|anvil|cooking_range|fire|furnace|pottery_oven|pottery_wheel|spinning_wheel|workbench'], ['tool', 'Tool', 'select', '|pickaxe|axe|fire|chisel|hammer|knife|needle|rake|saw|shears|spade|watering_can|fishing_rod|fly_rod|harpoon|small_net|big_net|lobster_pot'], @@ -65,8 +65,9 @@ var SECTIONS = [ ['end_message', 'End Message', 'text', 'e.g. You\'ve finished smithing.', 'wide'], ], fieldGroups: [ - ['type', 'subtype', 'level', 'xp', 'wait', 'output_qty'], - {fields: ['station', 'tool'], right: true}, + ['type', 'subtype', 'level', 'xp', 'ticks_per_cycle', 'output_qty'], + {note: 'Output Qty is the quantity produced per cycle, only relevant for stackable items.'}, + ['station', 'tool'], {label: 'Success', fields: ['success_base', 'success_per_level', 'success_cap', 'fail']}, ['start_message', 'success_message', 'fail_message', 'end_message'], ], @@ -213,6 +214,7 @@ function renderItemEditor(id, data) { $('#editorMain').innerHTML = html; setupSearchFields(); setupCraftTypeChange(); + updateOutputQtyVisibility(); setTimeout(function() { document.querySelectorAll('.color-field').forEach(function(el) { bindColorField(el); }); }, 50); @@ -229,7 +231,7 @@ function renderCurrent() { function renderCard(sec, data) { var collapsed = expandedCards[sec.id] === false; - var h = '