aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-07-05 19:18:50 -0400
committerhistoria <[not public]>2026-07-05 19:18:50 -0400
commit7b5ed92e003d8a4bf9bdc69d43b7354eb2260dfa (patch)
tree04310b22c25e2d9fe86b2c06e78e12d8f4caa61b /internal
parent6bd5365cc22f84c047c1585e3f54571af46f53e5 (diff)
downloadthehouseoficarus-7b5ed92e003d8a4bf9bdc69d43b7354eb2260dfa.tar.gz
feat: duplicate items/obj/mobs from sidebar. item inputs fields changed to search fields
Diffstat (limited to 'internal')
-rw-r--r--internal/admin/static/dropeditor.js55
-rw-r--r--internal/admin/static/editor.js45
-rw-r--r--internal/admin/static/hazardeditor.js63
-rw-r--r--internal/admin/static/itemeditor.js90
-rw-r--r--internal/admin/static/mobeditor.js81
-rw-r--r--internal/admin/static/moduleeditor.js55
-rw-r--r--internal/admin/static/objecteditor.js64
-rw-r--r--internal/admin/static/techeditor.js71
8 files changed, 409 insertions, 115 deletions
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 += '<div class="btn-row">';
html += '<button class="btn btn-primary" onclick="saveDrop()">Save</button>';
+ html += '<button class="btn" onclick="duplicateDrop()">Duplicate</button>';
html += '<button class="btn btn-danger" onclick="deleteDrop()">Delete</button>';
html += '<button class="btn btn-sm" style="margin-left:auto" onclick="createNewDir()">New Directory</button>';
html += '</div>';
@@ -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 += '<div class="btn-row">';
html += '<button class="btn btn-primary" onclick="saveHazard()">Save</button>';
+ html += '<button class="btn" onclick="duplicateHazard()">Duplicate</button>';
html += '<button class="btn btn-danger" onclick="deleteHazard()">Delete</button>';
html += '<button class="btn btn-sm" style="margin-left:auto" onclick="createNewDir()">New Directory</button>';
html += '</div>';
@@ -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 += '<div class="obj-card-grid">';
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 = '<div class="obj-core-row">';
+ h += renderField(sec, byKey['farm_patch_type'], data, -1);
+ h += renderField(sec, byKey['farm_product'], data, -1, null, 'flex:1');
+ h += '</div>';
+ h += '<div class="obj-core-row">';
+ 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 += '</div>';
+ h += '<div class="obj-core-row">';
+ 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 += '</div>';
+ 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 = '<div class="obj-core-row">';
+ h += renderField(sec, byKey['potion_effect'], data, -1, null, 'flex:1');
+ h += '</div>';
+ h += '<div class="obj-core-row">';
+ h += renderField(sec, byKey['potion_bonus'], data, -1);
+ h += renderField(sec, byKey['potion_duration'], data, -1);
+ h += '</div>';
+ 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 += '<div class="btn-row">';
html += '<button class="btn btn-primary" onclick="saveModule()">Save</button>';
+ html += '<button class="btn" onclick="duplicateModule()">Duplicate</button>';
html += '<button class="btn btn-danger" onclick="deleteModule()">Delete</button>';
html += '<button class="btn btn-sm" style="margin-left:auto" onclick="createNewDir()">New Directory</button>';
html += '</div>';
@@ -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 += '<div class="btn-row">';
html += '<button class="btn btn-primary" onclick="saveTech()">Save</button>';
+ html += '<button class="btn" onclick="duplicateTech()">Duplicate</button>';
html += '<button class="btn btn-danger" onclick="deleteTech()">Delete</button>';
html += '<button class="btn btn-sm" style="margin-left:auto" onclick="createNewDir()">New Directory</button>';
html += '</div>';
@@ -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'); });
+}