diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/admin/api_tools.go | 31 | ||||
| -rw-r--r-- | internal/admin/server.go | 1 | ||||
| -rw-r--r-- | internal/admin/static/itemeditor.js | 29 | ||||
| -rw-r--r-- | internal/admin/static/objecteditor.js | 44 | ||||
| -rw-r--r-- | internal/game/core_utils.go | 2 | ||||
| -rw-r--r-- | internal/item/item.go | 5 |
6 files changed, 95 insertions, 17 deletions
diff --git a/internal/admin/api_tools.go b/internal/admin/api_tools.go new file mode 100644 index 0000000..9ed7ec3 --- /dev/null +++ b/internal/admin/api_tools.go @@ -0,0 +1,31 @@ +package admin + +import ( + "net/http" +) + +func (s *AdminServer) handleTools(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + + items, err := s.itemStore.LoadAll() + if err != nil { + http.Error(w, "failed to load items", http.StatusInternalServerError) + return + } + + seen := map[string]bool{} + var tools []string + for _, def := range items { + if def.Tool != nil && def.Tool.Type != "" { + if !seen[def.Tool.Type] { + seen[def.Tool.Type] = true + tools = append(tools, def.Tool.Type) + } + } + } + + writeJSON(w, tools) +} diff --git a/internal/admin/server.go b/internal/admin/server.go index 04e68a6..53dd5c8 100644 --- a/internal/admin/server.go +++ b/internal/admin/server.go @@ -148,6 +148,7 @@ func NewServer(cfg *config.Config, useTLS bool, accountStore *player.AccountStor apiMux.HandleFunc("/api/players", s.handlePlayers) apiMux.HandleFunc("/api/dashboard", s.handleDashboard) apiMux.HandleFunc("/api/flags", s.handleFlags) + apiMux.HandleFunc("/api/tools", s.handleTools) apiMux.HandleFunc("/api/search", s.handleSearch) apiMux.HandleFunc("/api/undo/state", s.handleUndoState) apiMux.HandleFunc("/api/undo/undo", s.doUndo) diff --git a/internal/admin/static/itemeditor.js b/internal/admin/static/itemeditor.js index 84090a1..f1450ab 100644 --- a/internal/admin/static/itemeditor.js +++ b/internal/admin/static/itemeditor.js @@ -12,8 +12,7 @@ var SECTIONS = [ ['color', 'Color', 'color', 'B2'], ['value', 'Value', 'number', '0'], ['stackable', 'Stackable', 'checkbox'], - ['quality', 'Quality', 'number', ''], - ['max_quality', 'Max Quality', 'number', ''], + ['max_quality', 'Quality', 'number', ''], ['aliases', 'Aliases', 'text', 'comma separated'], ['description', 'Description', 'textarea', 'A chunk of copper ore.'], ] @@ -57,9 +56,9 @@ var SECTIONS = [ ['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'], ['fail', 'Fail Item', 'search', '', '', null, 'items'], - ['success_base', 'Base', 'number', '', 'narrow'], - ['success_per_level', 'Per Lvl', 'number', '', 'narrow'], - ['success_cap', 'Cap', 'number', '', 'narrow'], + ['success_base', 'Base', 'number', '0.5', 'narrow'], + ['success_per_level', 'Per Lvl', 'number', '0.01', 'narrow'], + ['success_cap', 'Cap', 'number', '0.95', 'narrow'], ['start_message', 'Start Message', 'text', 'e.g. You begin smithing %i1.', 'wide'], ['success_message', 'Success Message', 'text', 'e.g. You smith a %n.', 'wide'], ['fail_message', 'Fail Message', 'text', 'e.g. You accidentally burn the %i1.', 'wide', function(item) { return item.type === 'cooking' || item.subtype === 'smelt'; }], @@ -297,7 +296,7 @@ function renderCoreCard(data) { var h = '<div class="obj-core-row">'; h += renderField(sec, sec.fields[0], data, -1, null, 'flex:1'); h += renderField(sec, sec.fields[1], data, -1); - h += renderField(sec, sec.fields[6], data, -1, null, 'flex:1'); + h += renderField(sec, sec.fields[5], data, -1, null, 'flex:1'); h += '</div>'; h += '<div class="obj-core-row" style="align-items:center">'; h += renderField(sec, sec.fields[2], data, -1); @@ -306,7 +305,6 @@ function renderCoreCard(data) { h += '</div>'; h += '<div style="display:flex;gap:8px;margin-left:auto">'; h += renderField(sec, sec.fields[4], data, -1); - h += renderField(sec, sec.fields[5], data, -1); h += '</div>'; h += '</div>'; var descFid = fieldID(sec, 'description', -1); @@ -337,8 +335,8 @@ var EQUIP_STAT_FIELDS = { ], other: [ ['strength', 'Strength', 'number', '', 'narrow'], - ['ranged', 'Ranged', 'number', '', 'narrow'], - ['science', 'Science', 'number', '', 'narrow'], + ['ranged_strength', 'Rng Str.', 'number', '', 'narrow'], + ['science', 'Sci +%', 'number', '', 'narrow'], ['technology', 'Tech', 'number', '', 'narrow'], ], }; @@ -487,7 +485,8 @@ function renderField(sec, f, data, idx, subPath, optStyle) { var hiddenStyle = ''; if (showIf) { - var sectionData = itemData[cardPath(sec)] || {}; + var p = cardPath(sec); + var sectionData = p ? (itemData[p] || {}) : itemData; if (!showIf(sectionData)) hiddenStyle = ' style="display:none"'; } @@ -616,6 +615,10 @@ function renderSubtable(sec, st, data) { if (!f) return; if (f[5] && !f[5](item)) return; var val = item[f[0]]; + if (val === undefined && item.success && (f[0] === 'success_base' || f[0] === 'success_per_level' || f[0] === 'success_cap')) { + var nestedKey = f[0] === 'success_base' ? 'base' : (f[0] === 'success_per_level' ? 'per_level' : 'cap'); + val = item.success[nestedKey]; + } h += renderSubFieldHTML(sec, st.key, idx, f, val); }); } @@ -626,12 +629,12 @@ function renderSubtable(sec, st, data) { h += '<div class="obj-field-note output-qty-note" style="margin:2px 0;padding:2px 8px;font-size:11px;color:#888;font-style:italic;line-height:1.3">' + esc(group.note) + '</div>'; } else if (group.label) { if (group.label === 'Success') { - var hasSuccess = !!(item.fail || (item.success && (item.success.base || item.success.per_level || item.success.cap)) || item.success_base || item.success_per_level || item.success_cap); + var hasSuccess = !!(item.success_base || item.success_per_level || item.success_cap || (item.success && (item.success.base || item.success.per_level || item.success.cap))); var gid = 'sg_' + sec.id + '_' + st.key + '_' + idx; h += '<div class="obj-inline-group success-group" id="' + gid + '" style="margin:2px 0;padding:4px 8px' + (hasSuccess ? '' : ';display:none') + '">'; h += '<div style="display:flex;align-items:center;margin-bottom:2px">'; h += '<span class="obj-inline-label" style="margin-bottom:0;flex:1">Success</span>'; - h += '<button class="btn btn-sm btn-danger" style="padding:2px 6px;font-size:10px;min-width:auto" onclick="var g=document.getElementById(\'' + gid + '\');g.style.display=\'none\';var b=document.getElementById(\'' + gid + '_btn\');b.style.display=\'\'">X</button>'; + h += '<button class="btn btn-sm btn-danger" style="padding:2px 6px;font-size:10px;min-width:auto" onclick="var g=document.getElementById(\'' + gid + '\');g.style.display=\'none\';var b=document.getElementById(\'' + gid + '_btn\');b.style.display=\'\';g.querySelectorAll(\'input\').forEach(function(inp){inp.value=\'\'})">X</button>'; h += '</div>'; h += '<div class="obj-sub-row-line">'; renderGroup(group.fields); @@ -1353,6 +1356,8 @@ function saveItem() { delete item.success_cap; if (sBase || sPer || sCap) { item.success = {base: sBase, per_level: sPer, cap: sCap}; + } else { + delete item.success; } } diff --git a/internal/admin/static/objecteditor.js b/internal/admin/static/objecteditor.js index 3f2a875..5153458 100644 --- a/internal/admin/static/objecteditor.js +++ b/internal/admin/static/objecteditor.js @@ -2,6 +2,7 @@ var objectData = null; var objectID = null; var expandedCards = {}; var cardWidths = {}; +var toolTypes = null; var SECTIONS = [ { @@ -48,7 +49,7 @@ var SECTIONS = [ ['deplete_timer', 'Deplete', 'number', '45', 'narrow', function(d) { return d.skill === 'woodcutting'; }], ['nest_chance', 'Nest 1/n', 'number', '256', 'narrow', function(d) { return d.skill === 'woodcutting'; }], ]}, - {label:'Tools', key:'tools', type:'tags'}, + {label:'Tools', key:'tools', type:'tools_checkbox'}, ], subtables: [ {label:'Drops', key:'drops', fields:[ @@ -133,6 +134,7 @@ function initObjectEditor() { editorType = 'objects'; editorFields = []; loadList(); + fetchToolTypes(); if (window.location.hash) loadObject(window.location.hash.substring(1)); } @@ -266,6 +268,10 @@ function renderCard(sec, data) { h += '<div class="obj-inline-group" style="flex:1">'; h += renderTags(sec, il, data); h += '</div>'; + } else if (il.type === 'tools_checkbox') { + h += '<div class="obj-inline-group" style="flex:1">'; + h += renderToolsCheckbox(sec, il, data); + h += '</div>'; } else if (il.type === 'kv') { h += '<div class="obj-inline-group" style="flex:1">'; h += renderKV(sec, il, data); @@ -286,6 +292,8 @@ function renderCard(sec, data) { noRow.forEach(function(il) { if (il.type === 'tags') { h += renderTags(sec, il, data); + } else if (il.type === 'tools_checkbox') { + h += renderToolsCheckbox(sec, il, data); } else if (il.type === 'kv') { h += renderKV(sec, il, data); } else { @@ -518,6 +526,35 @@ function renderKV(sec, il, data) { return h; } +function renderToolsCheckbox(sec, il, data) { + var sectionRoot = data[cardPath(sec)] || {}; + var arr = sectionRoot[il.key] || []; + var choices = toolTypes || []; + var h = '<div class="obj-inline-group">'; + h += '<span class="obj-inline-label">' + esc(il.label) + '</span>'; + h += '<div class="obj-tools-checkboxes" style="display:flex;flex-wrap:wrap;gap:4px 12px;margin-top:4px">'; + choices.forEach(function(t) { + var checked = arr.indexOf(t) >= 0 ? ' checked' : ''; + h += '<label class="obj-field obj-check" style="margin:0;padding:2px 0"><input type="checkbox" class="obj-tool-chk" data-tool="' + escAttr(t) + '"' + checked + '> <span style="font-size:12px">' + esc(t) + '</span></label>'; + }); + h += '</div>'; + if (choices.length === 0) { + h += '<span style="color:#888;font-size:11px">Loading tool types...</span>'; + } + h += '</div>'; + return h; +} + +function fetchToolTypes() { + API.get('/api/tools').then(function(tools) { + toolTypes = tools || []; + renderCurrent(); + }).catch(function() { + toolTypes = []; + renderCurrent(); + }); +} + function toggleCard(id) { expandedCards[id] = expandedCards[id] === false ? true : false; renderCurrent(); @@ -742,6 +779,11 @@ function saveObject() { sectionObj[il.key] = el.value.split(',').map(function(s) { return s.trim(); }).filter(function(s) { return s; }); hasValues = true; } + } else if (il.type === 'tools_checkbox') { + var chks = document.querySelectorAll('.obj-tool-chk:checked'); + var tools = []; + chks.forEach(function(c) { tools.push(c.getAttribute('data-tool')); }); + if (tools.length > 0) { sectionObj[il.key] = tools; hasValues = true; } } else if (il.type === 'kv') { var kvEl = document.querySelector('.obj-kv-list[data-sec="' + sec.id + '"][data-key="' + il.key + '"]'); if (kvEl) { diff --git a/internal/game/core_utils.go b/internal/game/core_utils.go index 71d0acb..a07e789 100644 --- a/internal/game/core_utils.go +++ b/internal/game/core_utils.go @@ -144,7 +144,7 @@ func (g *Game) broadcastAction(sess *net.Session, format string, args ...interfa func (g *Game) newInventorySlot(itemID string, qty int) *player.InventorySlot { slot := &player.InventorySlot{ItemID: itemID, Quantity: qty} if def, err := g.ItemStore.Load(itemID); err == nil && def.MaxQuality > 0 { - slot.Quality = def.Quality + slot.Quality = def.MaxQuality slot.MaxQuality = def.MaxQuality } return slot diff --git a/internal/item/item.go b/internal/item/item.go index 77f462a..93f736b 100644 --- a/internal/item/item.go +++ b/internal/item/item.go @@ -57,7 +57,7 @@ type DefenseBonuses struct { type OtherBonuses struct { Strength int `yaml:"strength,omitempty"` - Ranged int `yaml:"ranged,omitempty"` + RangedStrength int `yaml:"ranged_strength,omitempty"` Science int `yaml:"science,omitempty"` Technology int `yaml:"technology,omitempty"` } @@ -91,7 +91,6 @@ type ItemDef struct { Tool *ToolDef `yaml:"tool,omitempty"` Requirements map[string]int `yaml:"requirements,omitempty"` - Quality int `yaml:"quality"` MaxQuality int `yaml:"max_quality"` Craft CraftList `yaml:"craft,omitempty"` @@ -207,7 +206,7 @@ func (d *ItemDef) Stats() ItemStats { } if d.Equipment.Other != nil { s.StrengthBonus = d.Equipment.Other.Strength - s.RangedStrength = d.Equipment.Other.Ranged + s.RangedStrength = d.Equipment.Other.RangedStrength s.ScienceDamage = d.Equipment.Other.Science s.TechnologyBonus = d.Equipment.Other.Technology } |
