From 7b5ed92e003d8a4bf9bdc69d43b7354eb2260dfa Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Sun, 5 Jul 2026 19:18:50 -0400 Subject: feat: duplicate items/obj/mobs from sidebar. item inputs fields changed to search fields --- data/.admin_history.json | 2 +- data/items/000_test_item.yaml | 40 ++++++++++++++++ data/objects/000_test_object.yaml | 4 ++ internal/admin/static/dropeditor.js | 55 ++++++++++++++++++--- internal/admin/static/editor.js | 45 ++++++++++++++++++ internal/admin/static/hazardeditor.js | 63 ++++++++++++++++++++---- internal/admin/static/itemeditor.js | 90 +++++++++++++++++++++++++++-------- internal/admin/static/mobeditor.js | 81 ++++++++++++++++++------------- internal/admin/static/moduleeditor.js | 55 ++++++++++++++++++--- internal/admin/static/objecteditor.js | 64 +++++++++++++++---------- internal/admin/static/techeditor.js | 71 +++++++++++++++++++++------ 11 files changed, 454 insertions(+), 116 deletions(-) create mode 100644 data/items/000_test_item.yaml create mode 100644 data/objects/000_test_object.yaml diff --git a/data/.admin_history.json b/data/.admin_history.json index 65e86d6..aef303b 100644 --- a/data/.admin_history.json +++ b/data/.admin_history.json @@ -1 +1 @@ -{"history":[{"time":"2026-07-05T18:39:14-04:00","description":"move objects adamantite_rock3.yaml to root","file_path":"data/objects/test4/adamantite_rock3.yaml","new_file_path":"data/objects/adamantite_rock3.yaml","old_content":"color: \"78\"\ndescription: \"\"\ngather:\n bait: \"\"\n deplete_timer: \"\"\n depleted_message: \"\"\n drops:\n - depletes: true\n item_id: adamantite_ore\n level: 70\n message: \"\"\n quantity: 0\n table: \"\"\n weight: 100\n xp: 95\n exhausted_message: \"\"\n fail_message: You chip away but get nothing useful.\n gather_message: You swing your pickaxe at the adamantite rock...\n nest_chance: \"\"\n respawn_broadcast: A green glint of adamantite catches your eye from some {name}.\n respawn_timer: \"\"\n skill: mining\n success:\n base: 0.15\n cap: 0.85\n per_level: 0.01\n tools:\n - pickaxe\nhidden: false\nid: adamantite_rock_copy\nname: adamantite rock\n","new_content":"color: \"78\"\ndescription: \"\"\ngather:\n bait: \"\"\n deplete_timer: \"\"\n depleted_message: \"\"\n drops:\n - depletes: true\n item_id: adamantite_ore\n level: 70\n message: \"\"\n quantity: 0\n table: \"\"\n weight: 100\n xp: 95\n exhausted_message: \"\"\n fail_message: You chip away but get nothing useful.\n gather_message: You swing your pickaxe at the adamantite rock...\n nest_chance: \"\"\n respawn_broadcast: A green glint of adamantite catches your eye from some {name}.\n respawn_timer: \"\"\n skill: mining\n success:\n base: 0.15\n cap: 0.85\n per_level: 0.01\n tools:\n - pickaxe\nhidden: false\nid: adamantite_rock_copy\nname: adamantite rock\n","is_delete":false,"is_create":false},{"time":"2026-07-05T18:39:14-04:00","description":"delete directory objects/test4","file_path":"data/objects/test4","old_content":"","new_content":"","is_delete":true,"is_create":false},{"time":"2026-07-05T18:39:15-04:00","description":"move objects adamantite_rock3.yaml to root","file_path":"data/objects/test3/adamantite_rock3.yaml","new_file_path":"data/objects/adamantite_rock3.yaml","old_content":"color: \"78\"\ndescription: \"\"\ngather:\n bait: \"\"\n deplete_timer: \"\"\n depleted_message: \"\"\n drops:\n - depletes: true\n item_id: adamantite_ore\n level: 70\n message: \"\"\n quantity: 0\n table: \"\"\n weight: 100\n xp: 95\n exhausted_message: \"\"\n fail_message: You chip away but get nothing useful.\n gather_message: You swing your pickaxe at the adamantite rock...\n nest_chance: \"\"\n respawn_broadcast: A green glint of adamantite catches your eye from some {name}.\n respawn_timer: \"\"\n skill: mining\n success:\n base: 0.15\n cap: 0.85\n per_level: 0.01\n tools:\n - pickaxe\nhidden: false\nid: adamantite_rock_copy\nname: adamantite rock\n","new_content":"color: \"78\"\ndescription: \"\"\ngather:\n bait: \"\"\n deplete_timer: \"\"\n depleted_message: \"\"\n drops:\n - depletes: true\n item_id: adamantite_ore\n level: 70\n message: \"\"\n quantity: 0\n table: \"\"\n weight: 100\n xp: 95\n exhausted_message: \"\"\n fail_message: You chip away but get nothing useful.\n gather_message: You swing your pickaxe at the adamantite rock...\n nest_chance: \"\"\n respawn_broadcast: A green glint of adamantite catches your eye from some {name}.\n respawn_timer: \"\"\n skill: mining\n success:\n base: 0.15\n cap: 0.85\n per_level: 0.01\n tools:\n - pickaxe\nhidden: false\nid: adamantite_rock_copy\nname: adamantite rock\n","is_delete":false,"is_create":false},{"time":"2026-07-05T18:39:15-04:00","description":"delete directory objects/test3","file_path":"data/objects/test3","old_content":"","new_content":"","is_delete":true,"is_create":false},{"time":"2026-07-05T18:40:55-04:00","description":"rename objects adamantite_rock3 to adamantite_rock4","file_path":"data/objects/adamantite_rock3.yaml","new_file_path":"data/objects/adamantite_rock4.yaml","old_content":"color: \"78\"\ndescription: \"\"\ngather:\n bait: \"\"\n deplete_timer: \"\"\n depleted_message: \"\"\n drops:\n - depletes: true\n item_id: adamantite_ore\n level: 70\n message: \"\"\n quantity: 0\n table: \"\"\n weight: 100\n xp: 95\n exhausted_message: \"\"\n fail_message: You chip away but get nothing useful.\n gather_message: You swing your pickaxe at the adamantite rock...\n nest_chance: \"\"\n respawn_broadcast: A green glint of adamantite catches your eye from some {name}.\n respawn_timer: \"\"\n skill: mining\n success:\n base: 0.15\n cap: 0.85\n per_level: 0.01\n tools:\n - pickaxe\nhidden: false\nid: adamantite_rock_copy\nname: adamantite rock\n","new_content":"color: \"78\"\ndescription: \"\"\ngather:\n bait: \"\"\n deplete_timer: \"\"\n depleted_message: \"\"\n drops:\n - depletes: true\n item_id: adamantite_ore\n level: 70\n message: \"\"\n quantity: 0\n table: \"\"\n weight: 100\n xp: 95\n exhausted_message: \"\"\n fail_message: You chip away but get nothing useful.\n gather_message: You swing your pickaxe at the adamantite rock...\n nest_chance: \"\"\n respawn_broadcast: A green glint of adamantite catches your eye from some {name}.\n respawn_timer: \"\"\n skill: mining\n success:\n base: 0.15\n cap: 0.85\n per_level: 0.01\n tools:\n - pickaxe\nhidden: false\nid: adamantite_rock_copy\nname: adamantite rock\n","is_delete":false,"is_create":false},{"time":"2026-07-05T18:41:13-04:00","description":"move objects adamantite_rock4 to test2/","file_path":"data/objects/adamantite_rock4.yaml","new_file_path":"data/objects/test2/adamantite_rock4.yaml","old_content":"color: \"78\"\ndescription: \"\"\ngather:\n bait: \"\"\n deplete_timer: \"\"\n depleted_message: \"\"\n drops:\n - depletes: true\n item_id: adamantite_ore\n level: 70\n message: \"\"\n quantity: 0\n table: \"\"\n weight: 100\n xp: 95\n exhausted_message: \"\"\n fail_message: You chip away but get nothing useful.\n gather_message: You swing your pickaxe at the adamantite rock...\n nest_chance: \"\"\n respawn_broadcast: A green glint of adamantite catches your eye from some {name}.\n respawn_timer: \"\"\n skill: mining\n success:\n base: 0.15\n cap: 0.85\n per_level: 0.01\n tools:\n - pickaxe\nhidden: false\nid: adamantite_rock_copy\nname: adamantite rock\n","new_content":"color: \"78\"\ndescription: \"\"\ngather:\n bait: \"\"\n deplete_timer: \"\"\n depleted_message: \"\"\n drops:\n - depletes: true\n item_id: adamantite_ore\n level: 70\n message: \"\"\n quantity: 0\n table: \"\"\n weight: 100\n xp: 95\n exhausted_message: \"\"\n fail_message: You chip away but get nothing useful.\n gather_message: You swing your pickaxe at the adamantite rock...\n nest_chance: \"\"\n respawn_broadcast: A green glint of adamantite catches your eye from some {name}.\n respawn_timer: \"\"\n skill: mining\n success:\n base: 0.15\n cap: 0.85\n per_level: 0.01\n tools:\n - pickaxe\nhidden: false\nid: adamantite_rock_copy\nname: adamantite rock\n","is_delete":false,"is_create":false},{"time":"2026-07-05T18:41:15-04:00","description":"move objects adamantite_rock4 to test3/","file_path":"data/objects/test2/adamantite_rock4.yaml","new_file_path":"data/objects/test3/adamantite_rock4.yaml","old_content":"color: \"78\"\ndescription: \"\"\ngather:\n bait: \"\"\n deplete_timer: \"\"\n depleted_message: \"\"\n drops:\n - depletes: true\n item_id: adamantite_ore\n level: 70\n message: \"\"\n quantity: 0\n table: \"\"\n weight: 100\n xp: 95\n exhausted_message: \"\"\n fail_message: You chip away but get nothing useful.\n gather_message: You swing your pickaxe at the adamantite rock...\n nest_chance: \"\"\n respawn_broadcast: A green glint of adamantite catches your eye from some {name}.\n respawn_timer: \"\"\n skill: mining\n success:\n base: 0.15\n cap: 0.85\n per_level: 0.01\n tools:\n - pickaxe\nhidden: false\nid: adamantite_rock_copy\nname: adamantite rock\n","new_content":"color: \"78\"\ndescription: \"\"\ngather:\n bait: \"\"\n deplete_timer: \"\"\n depleted_message: \"\"\n drops:\n - depletes: true\n item_id: adamantite_ore\n level: 70\n message: \"\"\n quantity: 0\n table: \"\"\n weight: 100\n xp: 95\n exhausted_message: \"\"\n fail_message: You chip away but get nothing useful.\n gather_message: You swing your pickaxe at the adamantite rock...\n nest_chance: \"\"\n respawn_broadcast: A green glint of adamantite catches your eye from some {name}.\n respawn_timer: \"\"\n skill: mining\n success:\n base: 0.15\n cap: 0.85\n per_level: 0.01\n tools:\n - pickaxe\nhidden: false\nid: adamantite_rock_copy\nname: adamantite rock\n","is_delete":false,"is_create":false},{"time":"2026-07-05T18:41:36-04:00","description":"move objects adamantite_rock4.yaml to root","file_path":"data/objects/test3/adamantite_rock4.yaml","new_file_path":"data/objects/adamantite_rock4.yaml","old_content":"color: \"78\"\ndescription: \"\"\ngather:\n bait: \"\"\n deplete_timer: \"\"\n depleted_message: \"\"\n drops:\n - depletes: true\n item_id: adamantite_ore\n level: 70\n message: \"\"\n quantity: 0\n table: \"\"\n weight: 100\n xp: 95\n exhausted_message: \"\"\n fail_message: You chip away but get nothing useful.\n gather_message: You swing your pickaxe at the adamantite rock...\n nest_chance: \"\"\n respawn_broadcast: A green glint of adamantite catches your eye from some {name}.\n respawn_timer: \"\"\n skill: mining\n success:\n base: 0.15\n cap: 0.85\n per_level: 0.01\n tools:\n - pickaxe\nhidden: false\nid: adamantite_rock_copy\nname: adamantite rock\n","new_content":"color: \"78\"\ndescription: \"\"\ngather:\n bait: \"\"\n deplete_timer: \"\"\n depleted_message: \"\"\n drops:\n - depletes: true\n item_id: adamantite_ore\n level: 70\n message: \"\"\n quantity: 0\n table: \"\"\n weight: 100\n xp: 95\n exhausted_message: \"\"\n fail_message: You chip away but get nothing useful.\n gather_message: You swing your pickaxe at the adamantite rock...\n nest_chance: \"\"\n respawn_broadcast: A green glint of adamantite catches your eye from some {name}.\n respawn_timer: \"\"\n skill: mining\n success:\n base: 0.15\n cap: 0.85\n per_level: 0.01\n tools:\n - pickaxe\nhidden: false\nid: adamantite_rock_copy\nname: adamantite rock\n","is_delete":false,"is_create":false},{"time":"2026-07-05T18:41:36-04:00","description":"delete directory objects/test3","file_path":"data/objects/test3","old_content":"","new_content":"","is_delete":true,"is_create":false},{"time":"2026-07-05T18:41:37-04:00","description":"delete directory objects/test2","file_path":"data/objects/test2","old_content":"","new_content":"","is_delete":true,"is_create":false},{"time":"2026-07-05T18:42:04-04:00","description":"Create object adamantite_rock4_copy","file_path":"data/objects/adamantite_rock4_copy.yaml","old_content":"","new_content":"id: adamantite_rock4_copy\n","is_delete":false,"is_create":true},{"time":"2026-07-05T18:42:04-04:00","description":"Update object adamantite_rock4_copy","file_path":"data/objects/adamantite_rock4_copy.yaml","old_content":"id: adamantite_rock4_copy\n","new_content":"color: \"78\"\ndescription: \"\"\ngather:\n bait: \"\"\n deplete_timer: \"\"\n depleted_message: \"\"\n drops:\n - depletes: true\n item_id: adamantite_ore\n level: 70\n message: \"\"\n quantity: 0\n table: \"\"\n weight: 100\n xp: 95\n exhausted_message: \"\"\n fail_message: You chip away but get nothing useful.\n gather_message: You swing your pickaxe at the adamantite rock...\n nest_chance: \"\"\n respawn_broadcast: A green glint of adamantite catches your eye from some {name}.\n respawn_timer: \"\"\n skill: mining\n success:\n base: 0.15\n cap: 0.85\n per_level: 0.01\n tools:\n - pickaxe\nhidden: false\nid: adamantite_rock4_copy\nname: adamantite rock\n","is_delete":false,"is_create":false},{"time":"2026-07-05T18:42:31-04:00","description":"Delete object adamantite_rock4","file_path":"data/objects/adamantite_rock4.yaml","old_content":"color: \"78\"\ndescription: \"\"\ngather:\n bait: \"\"\n deplete_timer: \"\"\n depleted_message: \"\"\n drops:\n - depletes: true\n item_id: adamantite_ore\n level: 70\n message: \"\"\n quantity: 0\n table: \"\"\n weight: 100\n xp: 95\n exhausted_message: \"\"\n fail_message: You chip away but get nothing useful.\n gather_message: You swing your pickaxe at the adamantite rock...\n nest_chance: \"\"\n respawn_broadcast: A green glint of adamantite catches your eye from some {name}.\n respawn_timer: \"\"\n skill: mining\n success:\n base: 0.15\n cap: 0.85\n per_level: 0.01\n tools:\n - pickaxe\nhidden: false\nid: adamantite_rock_copy\nname: adamantite rock\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-07-05T18:42:33-04:00","description":"Delete object adamantite_rock4_copy","file_path":"data/objects/adamantite_rock4_copy.yaml","old_content":"color: \"78\"\ndescription: \"\"\ngather:\n bait: \"\"\n deplete_timer: \"\"\n depleted_message: \"\"\n drops:\n - depletes: true\n item_id: adamantite_ore\n level: 70\n message: \"\"\n quantity: 0\n table: \"\"\n weight: 100\n xp: 95\n exhausted_message: \"\"\n fail_message: You chip away but get nothing useful.\n gather_message: You swing your pickaxe at the adamantite rock...\n nest_chance: \"\"\n respawn_broadcast: A green glint of adamantite catches your eye from some {name}.\n respawn_timer: \"\"\n skill: mining\n success:\n base: 0.15\n cap: 0.85\n per_level: 0.01\n tools:\n - pickaxe\nhidden: false\nid: adamantite_rock4_copy\nname: adamantite rock\n","new_content":"","is_delete":true,"is_create":false}],"redo":null} \ No newline at end of file +{"history":[{"time":"2026-07-05T19:16:10-04:00","description":"move items 000_test_item to root","file_path":"data/items/ammo/000_test_item.yaml","new_file_path":"data/items/000_test_item.yaml","old_content":"color: \"78\"\ncraft:\n - end_message: \"\"\n fail: \"\"\n ingredients:\n - items:\n - headless_arrow\n quantity: 15\n - items:\n - adamant_arrowtips\n quantity: 15\n level: 60\n output_qty: 15\n start_message: \"\"\n station: []\n subtype: \"\"\n success_message: \"\"\n ticks_per_cycle: 2\n tool: \"\"\n type: fletching\n xp: 10\ndescription: An adamant-tipped arrow.\nequipment:\n other:\n ranged_strength: 31\n slot: ammo\n type: ammo\nfarm_harvest_xp: 0\nfarm_level: 0\nfarm_max_yield: 0\nfarm_min_yield: 0\nfarm_patch_type: herb\nfarm_plant_xp: 0\nfarm_stages: 0\nid: 000_test_item\nmax_quality: 0\nname: adamant arrow\nrecoverable: true\nstackable: true\nvalue: 20\n","new_content":"color: \"78\"\ncraft:\n - end_message: \"\"\n fail: \"\"\n ingredients:\n - items:\n - headless_arrow\n quantity: 15\n - items:\n - adamant_arrowtips\n quantity: 15\n level: 60\n output_qty: 15\n start_message: \"\"\n station: []\n subtype: \"\"\n success_message: \"\"\n ticks_per_cycle: 2\n tool: \"\"\n type: fletching\n xp: 10\ndescription: An adamant-tipped arrow.\nequipment:\n other:\n ranged_strength: 31\n slot: ammo\n type: ammo\nfarm_harvest_xp: 0\nfarm_level: 0\nfarm_max_yield: 0\nfarm_min_yield: 0\nfarm_patch_type: herb\nfarm_plant_xp: 0\nfarm_stages: 0\nid: 000_test_item\nmax_quality: 0\nname: adamant arrow\nrecoverable: true\nstackable: true\nvalue: 20\n","is_delete":false,"is_create":false},{"time":"2026-07-05T19:17:20-04:00","description":"Create object bio_altar_copy","file_path":"data/objects/bio_altar_copy.yaml","old_content":"","new_content":"id: bio_altar_copy\n","is_delete":false,"is_create":true},{"time":"2026-07-05T19:17:20-04:00","description":"Update object bio_altar_copy","file_path":"data/objects/bio_altar_copy.yaml","old_content":"id: bio_altar_copy\n","new_content":"color: C4\ndescription: A sinister altar of red biological interfaces. Place scrap here with a bio identifier to produce biojunk. Type 'id' to begin.\nid: bio_altar_copy\nname: bio altar\n","is_delete":false,"is_create":false},{"time":"2026-07-05T19:17:25-04:00","description":"rename objects bio_altar_copy to 000_test_object","file_path":"data/objects/bio_altar_copy.yaml","new_file_path":"data/objects/000_test_object.yaml","old_content":"color: C4\ndescription: A sinister altar of red biological interfaces. Place scrap here with a bio identifier to produce biojunk. Type 'id' to begin.\nid: bio_altar_copy\nname: bio altar\n","new_content":"color: C4\ndescription: A sinister altar of red biological interfaces. Place scrap here with a bio identifier to produce biojunk. Type 'id' to begin.\nid: bio_altar_copy\nname: bio altar\n","is_delete":false,"is_create":false}],"redo":null} \ No newline at end of file diff --git a/data/items/000_test_item.yaml b/data/items/000_test_item.yaml new file mode 100644 index 0000000..e6609d8 --- /dev/null +++ b/data/items/000_test_item.yaml @@ -0,0 +1,40 @@ +color: "78" +craft: + - end_message: "" + fail: "" + ingredients: + - items: + - headless_arrow + quantity: 15 + - items: + - adamant_arrowtips + quantity: 15 + level: 60 + output_qty: 15 + start_message: "" + station: [] + subtype: "" + success_message: "" + ticks_per_cycle: 2 + tool: "" + type: fletching + xp: 10 +description: An adamant-tipped arrow. +equipment: + other: + ranged_strength: 31 + slot: ammo + type: ammo +farm_harvest_xp: 0 +farm_level: 0 +farm_max_yield: 0 +farm_min_yield: 0 +farm_patch_type: herb +farm_plant_xp: 0 +farm_stages: 0 +id: 000_test_item +max_quality: 0 +name: adamant arrow +recoverable: true +stackable: true +value: 20 diff --git a/data/objects/000_test_object.yaml b/data/objects/000_test_object.yaml new file mode 100644 index 0000000..afaee11 --- /dev/null +++ b/data/objects/000_test_object.yaml @@ -0,0 +1,4 @@ +color: C4 +description: A sinister altar of red biological interfaces. Place scrap here with a bio identifier to produce biojunk. Type 'id' to begin. +id: bio_altar_copy +name: bio altar diff --git a/internal/admin/static/dropeditor.js b/internal/admin/static/dropeditor.js index c5da314..a6537e9 100644 --- a/internal/admin/static/dropeditor.js +++ b/internal/admin/static/dropeditor.js @@ -7,14 +7,14 @@ var DROP_SECTIONS = [ { id: 'drops', label: 'Drops', always: true, path: 'drops', isArray: true, fields: [ - ['item_id', 'Item ID', 'search', '', '', null, 'items'], - ['table', 'Table', 'search', '', '', null, 'drops'], - ['weight', 'Weight', 'number', '', 'narrow'], + ['item_id', 'Item ID', 'search', 'e.g. copper_ore', '', null, 'items'], + ['table', 'Table', 'search', 'e.g. gem_table', '', null, 'drops'], + ['weight', 'Weight', 'number', '10', 'narrow'], ['quantity', 'Quantity', 'number', '1', 'narrow'], - ['level', 'Level', 'number', '', 'narrow'], - ['xp', 'XP', 'number', '', 'narrow'], + ['level', 'Level', 'number', '1', 'narrow'], + ['xp', 'XP', 'number', '5', 'narrow'], ['depletes', 'Depletes', 'checkbox'], - ['success_message', 'Success Message', 'text', '', 'wide'], + ['success_message', 'Success Message', 'text', 'You get some %n.', 'wide'], ] } ]; @@ -75,6 +75,7 @@ function renderDropEditor(id, data) { html += '
'; html += ''; + html += ''; html += ''; html += ''; html += '
'; @@ -226,3 +227,45 @@ function deleteDrop() { loadList(); }).catch(function(e) { notify('Delete failed: ' + e.message, 'error'); }); } + +function duplicateDrop() { + var baseID = dropID + '_copy'; + var newID = baseID; + var counter = 2; + while (true) { + var exists = false; + for (var i = 0; i < allIDs.length; i++) { + if (allIDs[i] === newID) { exists = true; break; } + } + if (!exists) break; + newID = baseID + '_' + counter; + counter++; + } + + var copy = JSON.parse(JSON.stringify(dropData)); + delete copy._raw; + delete copy._path; + copy.id = newID; + + var dir = ''; + if (dropData._path) { + var parts = dropData._path.split('/'); + var parentDir = parts[parts.length - 2]; + if (parentDir !== 'drops') dir = parentDir; + } + + API.post('/api/drops', {id: newID}).then(function() { + var putOp = API.put('/api/drops/' + encodeURIComponent(newID), copy); + if (dir) { + return putOp.then(function() { + return API.post('/api/drops/move', {id: newID, dir: dir}); + }); + } + return putOp; + }).then(function() { + notify('Duplicated as ' + newID, 'success'); + updateUndoBar(); + loadList(); + loadDropEditor(newID); + }).catch(function(e) { notify('Duplicate failed: ' + e.message, 'error'); }); +} diff --git a/internal/admin/static/editor.js b/internal/admin/static/editor.js index a9c1e1c..50d86f1 100644 --- a/internal/admin/static/editor.js +++ b/internal/admin/static/editor.js @@ -340,12 +340,57 @@ function showItemContextMenu(e, id) { } addBtn('Rename', '', function() { startInlineItemRename(id); }); + addBtn('Duplicate', '', function() { duplicateFromMenu(id); }); addBtn('Delete', 'danger', function() { deleteItem(id); }); document.body.appendChild(overlay); document.body.appendChild(menu); } +function duplicateFromMenu(id) { + API.get('/api/' + editorType + '/' + encodeURIComponent(id)).then(function(data) { + var dir = ''; + if (data._path) { + var parts = data._path.split('/'); + var parentDir = parts[parts.length - 2]; + if (parentDir !== editorType) dir = parentDir; + } + var clean = JSON.parse(JSON.stringify(data)); + delete clean._raw; + delete clean._path; + clean.id = ''; + + var baseID = id + '_copy'; + var newID = baseID; + var counter = 2; + while (true) { + var exists = false; + for (var i = 0; i < allIDs.length; i++) { + if (allIDs[i] === newID) { exists = true; break; } + } + if (!exists) break; + newID = baseID + '_' + counter; + counter++; + } + + clean.id = newID; + API.post('/api/' + editorType, {id: newID}).then(function() { + var putOp = API.put('/api/' + editorType + '/' + encodeURIComponent(newID), clean); + if (dir) { + return putOp.then(function() { + return API.post('/api/' + editorType + '/move', {id: newID, dir: dir}); + }); + } + return putOp; + }).then(function() { + notify('Duplicated as ' + newID, 'success'); + updateUndoBar(); + loadList(); + loadItem(newID); + }).catch(function(e) { notify('Duplicate failed: ' + e.message, 'error'); }); + }).catch(function(e) { notify('Duplicate failed: ' + e.message, 'error'); }); +} + function closeContextMenu() { var o = document.getElementById('_ctxMenuOverlay'); var m = document.getElementById('_ctxMenu'); diff --git a/internal/admin/static/hazardeditor.js b/internal/admin/static/hazardeditor.js index ff2857d..c4eb0b5 100644 --- a/internal/admin/static/hazardeditor.js +++ b/internal/admin/static/hazardeditor.js @@ -8,13 +8,13 @@ var HAZARD_SECTIONS = [ { id: 'core', label: 'Core', always: true, fields: [ - ['name', 'Name', 'text'], - ['description', 'Description', 'textarea', '', 'wide'], - ['warn_message', 'Warn Message', 'text', '', 'wide'], - ['hit_message', 'Hit Message', 'text'], - ['miss_message', 'Miss Message', 'text'], - ['required_item', 'Required Item', 'text'], - ['speed', 'Speed', 'number', '', 'narrow'], + ['name', 'Name', 'text', 'toxic gas'], + ['description', 'Description', 'textarea', 'A cloud of toxic gas fills the room.', 'wide'], + ['warn_message', 'Warn Message', 'text', 'The air grows thick with toxic fumes...', 'wide'], + ['hit_message', 'Hit Message', 'text', 'You cough and choke on the toxic gas!'], + ['miss_message', 'Miss Message', 'text', 'You hold your breath and avoid the fumes.'], + ['required_item', 'Required Item', 'text', 'e.g. gas_mask'], + ['speed', 'Speed', 'number', '10', 'narrow'], ] }, { @@ -22,9 +22,9 @@ var HAZARD_SECTIONS = [ detect: function(d) { return d.attack_type || d.attack || d.attack_bonus || d.max_hit; }, fields: [ ['attack_type', 'Attack Type', 'select', 'stab|slash|crush|ranged|science'], - ['attack', 'Attack', 'number', '', 'narrow'], - ['attack_bonus', 'Attack Bonus', 'number', '', 'narrow'], - ['max_hit', 'Max Hit', 'number', '', 'narrow'], + ['attack', 'Attack', 'number', '5', 'narrow'], + ['attack_bonus', 'Attack Bonus', 'number', '0', 'narrow'], + ['max_hit', 'Max Hit', 'number', '3', 'narrow'], ] } ]; @@ -99,6 +99,7 @@ function renderHazardEditor(id, data) { html += '
'; html += ''; + html += ''; html += ''; html += ''; html += '
'; @@ -228,3 +229,45 @@ function deleteHazard() { loadList(); }).catch(function(e) { notify('Delete failed: ' + e.message, 'error'); }); } + +function duplicateHazard() { + var baseID = hazardID + '_copy'; + var newID = baseID; + var counter = 2; + while (true) { + var exists = false; + for (var i = 0; i < allIDs.length; i++) { + if (allIDs[i] === newID) { exists = true; break; } + } + if (!exists) break; + newID = baseID + '_' + counter; + counter++; + } + + var copy = JSON.parse(JSON.stringify(hazardData)); + delete copy._raw; + delete copy._path; + copy.id = newID; + + var dir = ''; + if (hazardData._path) { + var parts = hazardData._path.split('/'); + var parentDir = parts[parts.length - 2]; + if (parentDir !== 'hazards') dir = parentDir; + } + + API.post('/api/hazards', {id: newID}).then(function() { + var putOp = API.put('/api/hazards/' + encodeURIComponent(newID), copy); + if (dir) { + return putOp.then(function() { + return API.post('/api/hazards/move', {id: newID, dir: dir}); + }); + } + return putOp; + }).then(function() { + notify('Duplicated as ' + newID, 'success'); + updateUndoBar(); + loadList(); + loadHazardEditor(newID); + }).catch(function(e) { notify('Duplicate failed: ' + e.message, 'error'); }); +} diff --git a/internal/admin/static/itemeditor.js b/internal/admin/static/itemeditor.js index ac76f94..ed32267 100644 --- a/internal/admin/static/itemeditor.js +++ b/internal/admin/static/itemeditor.js @@ -10,9 +10,9 @@ var SECTIONS = [ fields: [ ['name', 'Name', 'text', 'Copper Ore'], ['color', 'Color', 'color', 'B2'], - ['value', 'Value', 'number', '0'], + ['value', 'Value', 'number', '100'], ['stackable', 'Stackable', 'checkbox'], - ['max_quality', 'Quality', 'number', ''], + ['max_quality', 'Quality', 'number', '100', 'narrow'], ['aliases', 'Aliases', 'text', 'comma separated'], ['description', 'Description', 'textarea', 'A chunk of copper ore.'], ] @@ -55,7 +55,7 @@ var SECTIONS = [ ['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'], - ['fail', 'Fail Item', 'search', '', '', null, 'items'], + ['fail', 'Fail Item', 'search', 'e.g. burnt_fish', '', null, 'items'], ['success_base', 'Base', 'number', '0.5', 'narrow'], ['success_per_level', 'Per Lvl', 'number', '0.01', 'narrow'], ['success_cap', 'Cap', 'number', '0.95', 'narrow'], @@ -76,8 +76,8 @@ var SECTIONS = [ ['quantity', 'Qty', 'number', '1', 'narrow'], ]}, {label:'Steps', key:'steps', fields:[ - ['delay', 'Delay (ticks)', 'number', '', 'narrow'], - ['message', 'Message', 'text', '', 'wide'], + ['delay', 'Delay (ticks)', 'number', '3', 'narrow'], + ['message', 'Message', 'text', 'You turn the handle...', 'wide'], ]}, ], } @@ -87,8 +87,8 @@ var SECTIONS = [ id: 'search', label: 'Search', detect: function(d) { return d.search_table || d.search_ticks; }, fields: [ - ['search_table', 'Search Table', 'search', '', '', null, 'drops'], - ['search_ticks', 'Search Ticks', 'number', '', 'narrow'], + ['search_table', 'Search Table', 'search', 'e.g. gem_table', '', null, 'drops'], + ['search_ticks', 'Search Ticks', 'number', '8', 'narrow'], ['search_message', 'Search Message', 'text', 'You search...', 'wide'], ] }, @@ -96,7 +96,7 @@ var SECTIONS = [ id: 'food', label: 'Food', detect: function(d) { return d.heal_value || d.eat_message; }, fields: [ - ['heal_value', '+HP', 'number', '', 'narrow'], + ['heal_value', '+HP', 'number', '10', 'narrow'], ['eat_message', 'Eat Message', 'text', 'You eat the...', 'wide'], ] }, @@ -105,22 +105,22 @@ var SECTIONS = [ detect: function(d) { return d.farm_patch_type; }, fields: [ ['farm_patch_type', 'Patch Type', 'select', 'herb|allotment|flower|bush|tree'], - ['farm_level', 'Farm Level', 'number', '', 'narrow'], - ['farm_plant_xp', 'Plant XP', 'number', '', 'narrow'], - ['farm_harvest_xp', 'Harvest XP', 'number', '', 'narrow'], - ['farm_stages', 'Stages', 'number', '', 'narrow'], - ['farm_product', 'Product', 'search', '', '', null, 'items'], - ['farm_min_yield', 'Min Yield', 'number', '', 'narrow'], - ['farm_max_yield', 'Max Yield', 'number', '', 'narrow'], + ['farm_product', 'Product', 'search', 'e.g. tomato', '', null, 'items'], + ['farm_plant_xp', 'Plant XP', 'number', '5', 'narrow'], + ['farm_harvest_xp', 'Harvest XP', 'number', '10', 'narrow'], + ['farm_level', 'Farm Level', 'number', '1', 'narrow'], + ['farm_stages', 'Stages', 'number', '4', 'narrow'], + ['farm_min_yield', 'Min Yield', 'number', '1', 'narrow'], + ['farm_max_yield', 'Max Yield', 'number', '5', 'narrow'], ] }, { id: 'potions', label: 'Potions', detect: function(d) { return d.potion_effect; }, fields: [ - ['potion_effect', 'Effect', 'text', 'strength, accuracy, heal'], - ['potion_bonus', 'Bonus', 'number', '', 'narrow'], - ['potion_duration', 'Duration', 'number', '', 'narrow'], + ['potion_effect', 'Effect', 'select', 'heal|battery|all_combat|accuracy|strength|defense|ranged|science'], + ['potion_bonus', 'Bonus', 'number', '10', 'narrow'], + ['potion_duration', 'Duration', 'number', '50', 'narrow'], ] }, ]; @@ -255,6 +255,10 @@ function renderCard(sec, data) { h += renderCoreCard(data); } else if (sec.id === 'equipment') { h += renderEquipmentCard(data); + } else if (sec.id === 'farming') { + h += renderFarmingCard(data); + } else if (sec.id === 'potions') { + h += renderPotionsCard(data); } else { h += '
'; if (sec.fields) sec.fields.forEach(function(f) { @@ -316,6 +320,41 @@ function renderCoreCard(data) { return h; } +function renderFarmingCard(data) { + var sec = SECTIONS.find(function(s) { return s.id === 'farming'; }); + var byKey = {}; + sec.fields.forEach(function(f) { byKey[f[0]] = f; }); + var h = '
'; + h += renderField(sec, byKey['farm_patch_type'], data, -1); + h += renderField(sec, byKey['farm_product'], data, -1, null, 'flex:1'); + h += '
'; + h += '
'; + h += renderField(sec, byKey['farm_plant_xp'], data, -1); + h += renderField(sec, byKey['farm_harvest_xp'], data, -1); + h += renderField(sec, byKey['farm_level'], data, -1); + h += '
'; + h += '
'; + h += renderField(sec, byKey['farm_stages'], data, -1); + h += renderField(sec, byKey['farm_min_yield'], data, -1); + h += renderField(sec, byKey['farm_max_yield'], data, -1); + h += '
'; + return h; +} + +function renderPotionsCard(data) { + var sec = SECTIONS.find(function(s) { return s.id === 'potions'; }); + var byKey = {}; + sec.fields.forEach(function(f) { byKey[f[0]] = f; }); + var h = '
'; + h += renderField(sec, byKey['potion_effect'], data, -1, null, 'flex:1'); + h += '
'; + h += '
'; + h += renderField(sec, byKey['potion_bonus'], data, -1); + h += renderField(sec, byKey['potion_duration'], data, -1); + h += '
'; + return h; +} + // ========================================================================= // Equipment card (custom render with 3 stat blocks) // ========================================================================= @@ -1551,8 +1590,21 @@ function duplicateItem() { delete copy._path; copy.id = newID; + var dir = ''; + if (itemData._path) { + var parts = itemData._path.split('/'); + var parentDir = parts[parts.length - 2]; + if (parentDir !== 'items') dir = parentDir; + } + API.post('/api/items', {id: newID}).then(function() { - return API.put('/api/items/' + encodeURIComponent(newID), copy); + var putOp = API.put('/api/items/' + encodeURIComponent(newID), copy); + if (dir) { + return putOp.then(function() { + return API.post('/api/items/move', {id: newID, dir: dir}); + }); + } + return putOp; }).then(function() { notify('Duplicated as ' + newID, 'success'); updateUndoBar(); diff --git a/internal/admin/static/mobeditor.js b/internal/admin/static/mobeditor.js index debe7e0..c142fb4 100644 --- a/internal/admin/static/mobeditor.js +++ b/internal/admin/static/mobeditor.js @@ -24,34 +24,34 @@ var MOB_SECTIONS = [ 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.speed', 'Speed', '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'], - ['stats.bonuses.science_percent_bonus', 'Sci % Bonus', 'number', '', 'narrow'], - ['stats.bonuses.ranged_bonus', 'Rng Bonus', 'number', '', 'narrow'], - ['stats.bonuses.ranged_strength_bonus', 'Rng Str Bonus', 'number', '', 'narrow'], - ['stats.defenses.stab_defense', 'Stab Def', 'number', '', 'narrow'], - ['stats.defenses.slash_defense', 'Slash Def', 'number', '', 'narrow'], - ['stats.defenses.crush_defense', 'Crush Def', 'number', '', 'narrow'], - ['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'], - ['size', 'Size', 'text'], + ['respawn_ticks', 'Respawn Ticks', 'number', '30', 'narrow'], + ['stats.hp', 'HP', 'number', '10', 'narrow'], + ['stats.attack', 'Attack', 'number', '5', 'narrow'], + ['stats.strength', 'Strength', 'number', '5', 'narrow'], + ['stats.defense', 'Defense', 'number', '5', 'narrow'], + ['stats.ranged', 'Ranged', 'number', '1', 'narrow'], + ['stats.science', 'Science', 'number', '1', 'narrow'], + ['stats.speed', 'Speed', 'number', '5', 'narrow'], + ['stats.max_melee_hit', 'Max Melee Hit', 'number', '2', 'narrow'], + ['stats.max_ranged_hit', 'Max Ranged Hit', 'number', '0', 'narrow'], + ['stats.max_science_hit', 'Max Science Hit', 'number', '0', 'narrow'], + ['stats.bonuses.attack_bonus', 'Atk Bonus', 'number', '0', 'narrow'], + ['stats.bonuses.strength_bonus', 'Str Bonus', 'number', '0', 'narrow'], + ['stats.bonuses.science_bonus', 'Sci Bonus', 'number', '0', 'narrow'], + ['stats.bonuses.science_percent_bonus', 'Sci % Bonus', 'number', '0', 'narrow'], + ['stats.bonuses.ranged_bonus', 'Rng Bonus', 'number', '0', 'narrow'], + ['stats.bonuses.ranged_strength_bonus', 'Rng Str Bonus', 'number', '0', 'narrow'], + ['stats.defenses.stab_defense', 'Stab Def', 'number', '0', 'narrow'], + ['stats.defenses.slash_defense', 'Slash Def', 'number', '0', 'narrow'], + ['stats.defenses.crush_defense', 'Crush Def', 'number', '0', 'narrow'], + ['stats.defenses.science_defense', 'Sci Def', 'number', '0', 'narrow'], + ['stats.defenses.ranged_defense', 'Rng Def', 'number', '0', 'narrow'], + ['stats.defenses.weakness', 'Weakness', 'text', 'e.g. crush'], + ['stats.defenses.weakness_percent', 'Weakness %', 'number', '50', 'narrow'], + ['assassin_level', 'Assassin Lvl', 'number', '1', 'narrow'], + ['finishing_blow', 'Finishing Blow', 'search', 'e.g. stake', '', null, 'items'], + ['damage_without', 'Damage Without', 'search', 'e.g. holy_symbol', '', null, 'items'], + ['size', 'Size', 'text', 'small'], ], inline: [ {label:'Combat Descriptions', key:'combat_descriptions', type:'tags'}, @@ -61,10 +61,10 @@ var MOB_SECTIONS = [ id: 'steal', label: 'Steal', path: 'steal', detect: function(d) { return d.steal; }, fields: [ - ['table', 'Table ID', 'search', '', '', null, 'drops'], - ['level', 'Level', 'number', '', 'narrow'], - ['xp', 'XP', 'number', '', 'narrow'], - ['speed', 'Speed', 'number', '', 'narrow'], + ['table', 'Table ID', 'search', 'e.g. gem_table', '', null, 'drops'], + ['level', 'Level', 'number', '1', 'narrow'], + ['xp', 'XP', 'number', '5', 'narrow'], + ['speed', 'Speed', 'number', '4', 'narrow'], ] }, { @@ -73,7 +73,7 @@ var MOB_SECTIONS = [ fields: [ ['verb', 'Verb', 'text', 'work'], ['progress_noun', 'Progress Noun', 'text', 'construction'], - ['complete_message', 'Complete Message', 'text'], + ['complete_message', 'Complete Message', 'text', 'The work is finished.'], ] }, { @@ -756,8 +756,21 @@ function duplicateMob() { delete copy._path; copy.id = newID; + var dir = ''; + if (mobData._path) { + var parts = mobData._path.split('/'); + var parentDir = parts[parts.length - 2]; + if (parentDir !== 'mobs') dir = parentDir; + } + API.post('/api/mobs', {id: newID}).then(function() { - return API.put('/api/mobs/' + encodeURIComponent(newID), copy); + var putOp = API.put('/api/mobs/' + encodeURIComponent(newID), copy); + if (dir) { + return putOp.then(function() { + return API.post('/api/mobs/move', {id: newID, dir: dir}); + }); + } + return putOp; }).then(function() { notify('Duplicated as ' + newID, 'success'); updateUndoBar(); diff --git a/internal/admin/static/moduleeditor.js b/internal/admin/static/moduleeditor.js index 5bcb7bc..ac632e8 100644 --- a/internal/admin/static/moduleeditor.js +++ b/internal/admin/static/moduleeditor.js @@ -8,13 +8,13 @@ var MODULE_SECTIONS = [ { id: 'core', label: 'Core', always: true, fields: [ - ['name', 'Name', 'text'], - ['level', 'Level', 'number', '', 'narrow'], - ['max_hit', 'Max Hit', 'number', '', 'narrow'], - ['base_xp', 'Base XP', 'number', '', 'narrow'], + ['name', 'Name', 'text', 'Fire Blast'], + ['level', 'Level', 'number', '20', 'narrow'], + ['max_hit', 'Max Hit', 'number', '10', 'narrow'], + ['base_xp', 'Base XP', 'number', '50', 'narrow'], ['category', 'Category', 'select', 'combat|utility|enchant|processing|transport'], - ['element', 'Element', 'text'], - ['destination', 'Destination', 'number', '', 'narrow'], + ['element', 'Element', 'text', 'e.g. fire'], + ['destination', 'Destination', 'number', '5', 'narrow'], ], inline: [ {label:'Junk Cost', key:'junk_cost', type:'kv', valType:'number'}, @@ -100,6 +100,7 @@ function renderModuleEditor(id, data) { html += '
'; html += ''; + html += ''; html += ''; html += ''; html += '
'; @@ -321,3 +322,45 @@ function deleteModule() { loadList(); }).catch(function(e) { notify('Delete failed: ' + e.message, 'error'); }); } + +function duplicateModule() { + var baseID = moduleID + '_copy'; + var newID = baseID; + var counter = 2; + while (true) { + var exists = false; + for (var i = 0; i < allIDs.length; i++) { + if (allIDs[i] === newID) { exists = true; break; } + } + if (!exists) break; + newID = baseID + '_' + counter; + counter++; + } + + var copy = JSON.parse(JSON.stringify(moduleData)); + delete copy._raw; + delete copy._path; + copy.id = newID; + + var dir = ''; + if (moduleData._path) { + var parts = moduleData._path.split('/'); + var parentDir = parts[parts.length - 2]; + if (parentDir !== 'modules') dir = parentDir; + } + + API.post('/api/modules', {id: newID}).then(function() { + var putOp = API.put('/api/modules/' + encodeURIComponent(newID), copy); + if (dir) { + return putOp.then(function() { + return API.post('/api/modules/move', {id: newID, dir: dir}); + }); + } + return putOp; + }).then(function() { + notify('Duplicated as ' + newID, 'success'); + updateUndoBar(); + loadList(); + loadModuleEditor(newID); + }).catch(function(e) { notify('Duplicate failed: ' + e.message, 'error'); }); +} diff --git a/internal/admin/static/objecteditor.js b/internal/admin/static/objecteditor.js index 5444985..7e2d04b 100644 --- a/internal/admin/static/objecteditor.js +++ b/internal/admin/static/objecteditor.js @@ -21,7 +21,7 @@ var SECTIONS = [ id: 'steal', label: 'Steal', path: 'steal', detect: function(d) { return d.steal !== undefined; }, fields: [ - ['table', 'Table ID', 'search', '', '', null, 'drops'], + ['table', 'Table ID', 'search', 'e.g. gem_table', '', null, 'drops'], ['level', 'Level', 'number', '1', 'narrow'], ['xp', 'XP', 'number', '5', 'narrow'], ['speed', 'Speed', 'number', '4', 'narrow'], @@ -64,16 +64,16 @@ var SECTIONS = [ id: 'use', label: 'Use (Station)', path: 'use', detect: function(d) { return d.use; }, fields: [ - ['message', 'Message', 'text'], - ['wait', 'Wait', 'number', '', 'narrow'], - ['skill', 'Skill', 'text'], - ['level', 'Level', 'number', '', 'narrow'], - ['xp', 'XP', 'number', '', 'narrow'], - ['fail_message', 'Fail Message', 'text'], + ['message', 'Message', 'text', 'You use the station...'], + ['wait', 'Wait', 'number', '4', 'narrow'], + ['skill', 'Skill', 'text', 'e.g. smithing'], + ['level', 'Level', 'number', '1', 'narrow'], + ['xp', 'XP', 'number', '25', 'narrow'], + ['fail_message', 'Fail Message', 'text', 'You fail to use the station.'], ], inline: [ {label:'Success', path:'success', fields:[ - ['base', 'Base', 'number', '', 'narrow'], ['per_level', 'Per Lvl', 'number', '', 'narrow'], ['cap', 'Cap', 'number', '', 'narrow'] + ['base', 'Base', 'number', '0.5', 'narrow'], ['per_level', 'Per Lvl', 'number', '0.01', 'narrow'], ['cap', 'Cap', 'number', '0.95', 'narrow'] ]}, {label:'Consume', key:'consume', type:'kv', valType:'number'}, ], @@ -88,14 +88,13 @@ var SECTIONS = [ id: 'safespot', label: 'Safespot', path: 'safespot', detect: function(d) { return d.safespot; }, fields: [ - ['tier', 'Tier', 'number', '', 'narrow'], + ['tier', 'Tier', 'number', '1', 'narrow'], ['max_block_size', 'Max Block Size', 'select', '|small|medium|large|massive'], - ['max_occupants', 'Max Occupants', 'number', '', 'narrow'], - ['unsafe_chance', 'Unsafe Chance', 'number', '', 'narrow'], - ['decay_ticks', 'Decay Ticks', 'number', '', 'narrow'], - ['decay_chance', 'Decay Chance', 'number', '', 'narrow'], - ['respawn_on_hide', 'Respawn on Hide', 'checkbox'], - ['respawn_ticks', 'Respawn Ticks', 'number', '', 'narrow'], + ['max_occupants', 'Max Occupants', 'number', '1', 'narrow'], + ['unsafe_chance', 'Unsafe Chance', 'number', '0.05', 'narrow'], + ['decay_ticks', 'Decay Ticks', 'number', '100', 'narrow'], + ['decay_chance', 'Decay Chance', 'number', '0.1', 'narrow'], + ['respawn_ticks', 'Respawn Ticks', 'number', '50', 'narrow'], ], subtables: [ {label:'Levels', key:'levels', fields:[ @@ -107,24 +106,24 @@ var SECTIONS = [ id: 'use_interactions', label: 'Use Interactions', labelHint: '(what happens when you use items on this)', path: 'use_interactions', isArray: true, fullWidth: true, detect: function(d) { return d.use_interactions && Array.isArray(d.use_interactions); }, fields: [ - ['item_id', 'Item ID', 'search', '', '', null, 'items'], + ['item_id', 'Item ID', 'search', 'e.g. keycard', '', null, 'items'], ['condition', 'Condition', 'json', 'e.g. {"flag":"my_flag"} or {"player_flag":"x","value":1} or {"all_of":[{"flag":"a"},{"has_item":"key"}]}'], ['action', 'Action', 'json', 'e.g. {"set_flags":{"my_flag":true},"message":"You activate it.","give_item":"gem"}'], - ['message', 'Message', 'text'], + ['message', 'Message', 'text', 'You use the object...'], ] }, { id: 'on_look', label: 'On Look', path: 'on_look', detect: function(d) { return d.on_look; }, fields: [ - ['set_flags', 'Set Flags', 'json'], - ['set_player_flags', 'Set Player Flags', 'json'], - ['give_item', 'Give Item', 'text'], - ['take_item', 'Take Item', 'text'], - ['teleport', 'Teleport', 'number', '', 'narrow'], - ['heal', 'Heal', 'number', '', 'narrow'], - ['credits', 'Credits', 'number', '', 'narrow'], - ['reputation_cost', 'Reputation Cost', 'number', '', 'narrow'], + ['set_flags', 'Set Flags', 'json', '{"flag":"value"}'], + ['set_player_flags', 'Set Player Flags', 'json', '{"pflag":"value"}'], + ['give_item', 'Give Item', 'text', 'e.g. keycard'], + ['take_item', 'Take Item', 'text', 'e.g. bomb'], + ['teleport', 'Teleport', 'number', '5', 'narrow'], + ['heal', 'Heal', 'number', '10', 'narrow'], + ['credits', 'Credits', 'number', '100', 'narrow'], + ['reputation_cost', 'Reputation Cost', 'number', '1', 'narrow'], ['aps_node', 'APS Node', 'checkbox'], ] }, @@ -1106,8 +1105,21 @@ function duplicateObject() { delete copy._path; copy.id = newID; + var dir = ''; + if (objectData._path) { + var parts = objectData._path.split('/'); + var parentDir = parts[parts.length - 2]; + if (parentDir !== 'objects') dir = parentDir; + } + API.post('/api/objects', {id: newID}).then(function() { - return API.put('/api/objects/' + encodeURIComponent(newID), copy); + var putOp = API.put('/api/objects/' + encodeURIComponent(newID), copy); + if (dir) { + return putOp.then(function() { + return API.post('/api/objects/move', {id: newID, dir: dir}); + }); + } + return putOp; }).then(function() { notify('Duplicated as ' + newID, 'success'); updateUndoBar(); diff --git a/internal/admin/static/techeditor.js b/internal/admin/static/techeditor.js index ffb2b7b..8e18a7a 100644 --- a/internal/admin/static/techeditor.js +++ b/internal/admin/static/techeditor.js @@ -7,25 +7,25 @@ var TECH_SECTIONS = [ { id: 'core', label: 'Core', always: true, fields: [ - ['name', 'Name', 'text'], - ['level', 'Level', 'number', '', 'narrow'], - ['drain_rate', 'Drain Rate', 'number', '', 'narrow'], - ['category', 'Category', 'text'], - ['group', 'Group', 'text'], + ['name', 'Name', 'text', 'Protect from Melee'], + ['level', 'Level', 'number', '37', 'narrow'], + ['drain_rate', 'Drain Rate', 'number', '0.5', 'narrow'], + ['category', 'Category', 'text', 'protection'], + ['group', 'Group', 'text', 'melee'], ] }, { id: 'effects', label: 'Effects', always: true, path: 'effects', fields: [ - ['accuracy_percent', 'Accuracy %', 'number', '', 'narrow'], - ['strength_percent', 'Strength %', 'number', '', 'narrow'], - ['defense_percent', 'Defense %', 'number', '', 'narrow'], - ['ranged_percent', 'Ranged %', 'number', '', 'narrow'], - ['science_percent', 'Science %', 'number', '', 'narrow'], - ['damage_reduction', 'Damage Reduction', 'number', '', 'narrow'], - ['hp_regen_multi', 'HP Regen Multi', 'number', '', 'narrow'], - ['preserve_drain', 'Preserve Drain', 'number', '', 'narrow'], - ['retribution_pct', 'Retribution %', 'number', '', 'narrow'], + ['accuracy_percent', 'Accuracy %', 'number', '5', 'narrow'], + ['strength_percent', 'Strength %', 'number', '5', 'narrow'], + ['defense_percent', 'Defense %', 'number', '5', 'narrow'], + ['ranged_percent', 'Ranged %', 'number', '5', 'narrow'], + ['science_percent', 'Science %', 'number', '5', 'narrow'], + ['damage_reduction', 'Damage Reduction', 'number', '40', 'narrow'], + ['hp_regen_multi', 'HP Regen Multi', 'number', '2', 'narrow'], + ['preserve_drain', 'Preserve Drain', 'number', '1', 'narrow'], + ['retribution_pct', 'Retribution %', 'number', '25', 'narrow'], ['protect_melee', 'Protect Melee', 'checkbox'], ['protect_ranged', 'Protect Ranged', 'checkbox'], ['protect_science', 'Protect Science', 'checkbox'], @@ -89,6 +89,7 @@ function renderTechEditor(id, data) { html += '
'; html += ''; + html += ''; html += ''; html += ''; html += '
'; @@ -192,3 +193,45 @@ function deleteTech() { loadList(); }).catch(function(e) { notify('Delete failed: ' + e.message, 'error'); }); } + +function duplicateTech() { + var baseID = techID + '_copy'; + var newID = baseID; + var counter = 2; + while (true) { + var exists = false; + for (var i = 0; i < allIDs.length; i++) { + if (allIDs[i] === newID) { exists = true; break; } + } + if (!exists) break; + newID = baseID + '_' + counter; + counter++; + } + + var copy = JSON.parse(JSON.stringify(techData)); + delete copy._raw; + delete copy._path; + copy.id = newID; + + var dir = ''; + if (techData._path) { + var parts = techData._path.split('/'); + var parentDir = parts[parts.length - 2]; + if (parentDir !== 'techs') dir = parentDir; + } + + API.post('/api/techs', {id: newID}).then(function() { + var putOp = API.put('/api/techs/' + encodeURIComponent(newID), copy); + if (dir) { + return putOp.then(function() { + return API.post('/api/techs/move', {id: newID, dir: dir}); + }); + } + return putOp; + }).then(function() { + notify('Duplicated as ' + newID, 'success'); + updateUndoBar(); + loadList(); + loadTechEditor(newID); + }).catch(function(e) { notify('Duplicate failed: ' + e.message, 'error'); }); +} -- cgit v1.2.3