From 451fdf82ce1385c16d0d88cd5b2a878ba4cba22c Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Fri, 10 Jul 2026 17:39:15 -0400 Subject: feat(admin): better toggle on trigger page, removed irrelevant value field for clarity --- building_guide/triggers.md | 40 ++- data/.admin_history.json | 1 + .../player_flag/example_player_welcome.yaml | 4 +- internal/admin/api_triggers.go | 6 +- internal/admin/static/admin.css | 16 +- internal/admin/static/triggerseditor.js | 365 +++------------------ internal/behavior/behavior.go | 8 +- internal/game/sys_triggers.go | 14 +- 8 files changed, 104 insertions(+), 350 deletions(-) create mode 100644 data/.admin_history.json diff --git a/building_guide/triggers.md b/building_guide/triggers.md index 7ef9b4c..60b1c82 100644 --- a/building_guide/triggers.md +++ b/building_guide/triggers.md @@ -53,7 +53,6 @@ its steps run as a scripted sequence. aps_node: false on_player_flag: "" # on_flag_change only on_global_flag: "" # on_global_flag_change only - value: null # value-match filter for flag triggers ``` Field meanings: @@ -66,7 +65,6 @@ Field meanings: | `steps` | all | Ordered list of Step entries run as a scripted sequence. | | `on_player_flag` | on_flag_change only | Player flag this trigger watches. | | `on_global_flag` | on_global_flag_change only | Global flag this trigger watches. | -| `value` | flag triggers | Optional value-match filter; trigger only fires when the flag changes to this value. | ### Step vocabulary @@ -261,7 +259,8 @@ triggers watching that flag. `on_flag_change` and `on_global_flag_change` replace the old room `triggers:` block. Each entry carries the flag it watches (`on_player_flag` or `on_global_flag`), an optional -`value` filter, an optional `condition`, and a `steps` list. +`condition` (use `player_flag`/`global_flag` + `value` to match a specific flag value), and +a `steps` list. Key behaviors: @@ -293,21 +292,28 @@ To migrate the old room `triggers:` block, replace `triggers:` with `on_flag_cha ### Value-matching flag triggers -By default a flag trigger fires when the watched flag becomes truthy. Add `value:` to -require a specific value — enabling multi-stage quests off one numeric flag: +By default a flag trigger fires when the watched flag becomes truthy. Use a `condition` with +a `player_flag`/`global_flag` + `value` to require a specific value — enabling multi-stage +quests off one numeric flag: ```yaml on_flag_change: - on_player_flag: quest_stage - value: 1 + condition: + player_flag: quest_stage + value: 1 steps: - messages: ["Quest started — find the crystal shard."] - on_player_flag: quest_stage - value: 2 + condition: + player_flag: quest_stage + value: 2 steps: - messages: ["You found the shard — return to the elder."] - on_player_flag: quest_stage - value: 3 + condition: + player_flag: quest_stage + value: 3 steps: - messages: ["The ritual begins..."] - wait: 10 @@ -608,9 +614,11 @@ on_look: # data/rooms/city/5002_alley.yaml on_flag_change: - on_player_flag: investigation - value: 1 condition: - room: 5002 + all_of: + - player_flag: investigation + value: 1 + - room: 5002 steps: - wait: 3 messages: ["You notice a trail of blood leading east..."] @@ -624,9 +632,11 @@ on_flag_change: # data/rooms/city/5003_warehouse.yaml on_flag_change: - on_player_flag: investigation - value: 2 condition: - room: 5003 + all_of: + - player_flag: investigation + value: 2 + - room: 5003 steps: - wait: 3 messages: ["A glint of metal catches your eye under a crate."] @@ -639,9 +649,11 @@ spawns boss # data/rooms/city/5004_docks.yaml on_flag_change: - on_player_flag: investigation - value: 3 condition: - room: 5004 + all_of: + - player_flag: investigation + value: 3 + - room: 5004 steps: - wait: 5 broadcast: "A shadow detaches itself from the warehouse wall..." diff --git a/data/.admin_history.json b/data/.admin_history.json new file mode 100644 index 0000000..faf3bf7 --- /dev/null +++ b/data/.admin_history.json @@ -0,0 +1 @@ +{"history":[{"time":"2026-07-10T17:28:35-04:00","description":"Update trigger example_player_welcome","file_path":"data/triggers/player_flag/example_player_welcome.yaml","old_content":"steps:\n - messages:\n - You feel a warm welcome, {0B italic}%p{/}.\n - wait: 5\n - broadcast: '%p has been welcomed!'\n - {}\non_player_flag: example_player_welcome\nvalue: true\n","new_content":"condition:\n player_flag: example_player_welcome\n value: true\nsteps:\n - messages:\n - You feel a warm welcome, {0B italic}%p{/}.\n - wait: 5\n - broadcast: '%p has been welcomed!'\n - {}\non_player_flag: example_player_welcome\nvalue: true\n","is_delete":false,"is_create":false},{"time":"2026-07-10T17:28:46-04:00","description":"Update trigger example_player_welcome","file_path":"data/triggers/player_flag/example_player_welcome.yaml","old_content":"condition:\n player_flag: example_player_welcome\n value: true\nsteps:\n - messages:\n - You feel a warm welcome, {0B italic}%p{/}.\n - wait: 5\n - broadcast: '%p has been welcomed!'\n - {}\non_player_flag: example_player_welcome\nvalue: true\n","new_content":"condition:\n player_flag: example_player_welcome\n value: true\nsteps:\n - messages:\n - You feel a warm welcome, {0B italic}%p{/}.\n - wait: 5\n - broadcast: '%p has been welcomed!'\n - {}\non_player_flag: example_player_welcome\n","is_delete":false,"is_create":false}],"redo":null} \ No newline at end of file diff --git a/data/triggers/player_flag/example_player_welcome.yaml b/data/triggers/player_flag/example_player_welcome.yaml index 0ecb02b..58236a1 100644 --- a/data/triggers/player_flag/example_player_welcome.yaml +++ b/data/triggers/player_flag/example_player_welcome.yaml @@ -1,3 +1,6 @@ +condition: + player_flag: example_player_welcome + value: true steps: - messages: - You feel a warm welcome, {0B italic}%p{/}. @@ -5,4 +8,3 @@ steps: - broadcast: '%p has been welcomed!' - {} on_player_flag: example_player_welcome -value: true diff --git a/internal/admin/api_triggers.go b/internal/admin/api_triggers.go index b2fecbc..ccfd52d 100644 --- a/internal/admin/api_triggers.go +++ b/internal/admin/api_triggers.go @@ -267,7 +267,7 @@ func findTriggerFile(dataDir, id string) (path string, dir string, err error) { } // triggerMapToYAML serializes a flat map into a Trigger YAML document. -// The map contains keys like on_player_flag, on_global_flag, value, +// The map contains keys like on_player_flag, on_global_flag, // lock, condition, steps, etc. func triggerMapToYAML(m map[string]any) ([]byte, error) { var t behavior.Trigger @@ -319,10 +319,6 @@ func triggerMapToYAML(m map[string]any) ([]byte, error) { } delete(m, "lock") } - if v, ok := m["value"]; ok { - t.Value = v - delete(m, "value") - } // Serialize to YAML. yamlBytes, err := yaml.Marshal(t) diff --git a/internal/admin/static/admin.css b/internal/admin/static/admin.css index 496a031..74e70f2 100644 --- a/internal/admin/static/admin.css +++ b/internal/admin/static/admin.css @@ -298,6 +298,13 @@ g.ud-hover:hover text{font-weight:bold} .obj-field.obj-check{flex-direction:row;align-items:center;gap:4px;padding-bottom:3px} .obj-field.obj-check input{width:auto} .obj-field.obj-check span{font-size:11px;color:var(--text)} +.seg-toggle{display:inline-flex;border:1px solid var(--border);border-radius:6px;overflow:hidden} +.seg-toggle label{display:inline-flex;align-items:center;padding:5px 12px;font-size:11px;cursor:pointer;color:#888;background:transparent;border-right:1px solid var(--border);user-select:none;transition:background .15s,color .15s} +.seg-toggle label:last-child{border-right:none} +.seg-toggle label:hover{color:var(--text);background:rgba(255,255,255,.04)} +.seg-toggle input:checked+span{color:#3a8fd4} +.seg-toggle label:has(input:checked){color:var(--text);background:var(--accent)} +.seg-toggle input{position:absolute;opacity:0;pointer-events:none;width:0;height:0} .obj-color-row{display:flex;align-items:center;gap:4px} .obj-color-row .color-swatch{flex-shrink:0} .obj-color-row input{width:38px!important;text-align:center;padding:4px 2px} @@ -380,8 +387,9 @@ g.ud-hover:hover text{font-weight:bold} .ie-cond-rm{flex-shrink:0} .ie-cond-grp-rm{margin-left:auto} .ie-cond-addbar{display:grid;grid-template-columns:repeat(auto-fit,minmax(max(100%/5,110px),1fr));gap:4px;margin-top:4px} -.ie-add-btn{font-size:10px!important;padding:2px 6px!important;background:var(--accent)!important;color:var(--text)!important;border:1px solid #1a5a8e!important;font-family:monospace!important;border-radius:3px!important;cursor:pointer;white-space:nowrap;width:100%;min-width:0} +.ie-add-btn{font-size:10px!important;padding:2px 6px!important;background:var(--accent)!important;color:var(--text)!important;border:1px solid #1a5a8e!important;font-family:monospace!important;border-radius:3px!important;cursor:pointer;white-space:nowrap} .ie-add-btn:hover{background:#1a5a8e!important;color:#fff!important} +.ie-cond-addbar .ie-add-btn,.ie-step-addbar .ie-add-btn,.ie-step-addbar-bottom .ie-add-btn{width:100%;min-width:0} /* Action editor */ .ie-act-root{background:rgba(100,160,255,.04);border:1px solid rgba(100,160,255,.12);border-radius:5px;padding:8px;display:flex;flex-direction:column;gap:6px} @@ -448,3 +456,9 @@ g.ud-hover:hover text{font-weight:bold} /* ── Empty condition group placeholder ── */ .ie-cond-empty{margin-bottom:2px} .ie-cond-empty-bar{display:flex;align-items:center;gap:6px;flex-wrap:wrap;padding-bottom:2px} + +/* ── Trigger editor: single-row action block hides add/remove buttons ── */ +.obj-card[data-card="trigger_action"] .ie-inter-remove, +.obj-card[data-card="trigger_action"]>.obj-card-body>.obj-sub-add{display:none} +.obj-card[data-card="trigger_action"] .ie-inter-header .ie-trig-up, +.obj-card[data-card="trigger_action"] .ie-inter-header .ie-trig-dn{display:none} diff --git a/internal/admin/static/triggerseditor.js b/internal/admin/static/triggerseditor.js index dbc1e41..28d97a5 100644 --- a/internal/admin/static/triggerseditor.js +++ b/internal/admin/static/triggerseditor.js @@ -2,6 +2,15 @@ var triggerData = null; var triggerID = null; var triggerKind = ''; +var TRIG_SEC = { + id: 'trigger_action', + path: '_trig', + isArray: true, + interactionStyle: true, + itemIDAllowed: false, + label: 'Action Block' +}; + function initTriggerEditor() { editorType = 'triggers'; editorFields = []; @@ -50,29 +59,39 @@ function loadTrigger(id) { triggerData = data; triggerKind = data._kind || ''; if (!Array.isArray(triggerData.steps)) triggerData.steps = []; - window._editorData = triggerData; + window._editorData = { + _trig: [{ + lock: !!triggerData.lock, + condition: triggerData.condition || null, + item_id: '', + steps: triggerData.steps + }] + }; + window._interVis = {}; window._ieRenderCurrent = renderCurrent; - window._ieRenderOnly = function() { if (triggerID && triggerData) renderTriggerEditor(triggerID, triggerData); }; - window._ieSyncAll = function(ed) { if (ed) syncCondDOM(ed); }; + window._ieRenderOnly = function() { mergeTriggerFromEditor(); renderTriggerEditor(triggerID, triggerData); }; + window._ieSyncAll = function(ed) { if (ed) ie_syncAllInteractions(ed, [TRIG_SEC]); }; renderTriggerEditor(id, data); }).catch(function(e) { $('#editorMain').innerHTML = '
Failed to load: ' + esc(e.message) + '
'; }); } -function syncCondDOM(ed) { - if (!ed) return; - var condRoot = document.querySelector('.obj-card[data-card="condition"] .ie-cond-root'); - if (condRoot) { - var cond = ie_collectCond(condRoot); - if (cond) ed.condition = cond; - else delete ed.condition; +function mergeTriggerFromEditor() { + var ab = window._editorData._trig[0] || {}; + if (ab.lock) triggerData.lock = true; else delete triggerData.lock; + if (ab.condition) { + var cc = ie_cleanCondition(ab.condition); + if (cc) triggerData.condition = cc; else delete triggerData.condition; + } else { + delete triggerData.condition; } + triggerData.steps = ab.steps || []; + delete triggerData.item_id; } function renderCurrent() { if (!triggerData || !triggerID) return; - syncCondDOM(triggerData); var flagTypeEl = document.querySelector('input[name="trigFlagType"]:checked'); if (flagTypeEl) { delete triggerData.on_player_flag; @@ -83,79 +102,11 @@ function renderCurrent() { var n = $('#trigFlagName'); triggerData.on_global_flag = n ? n.value.trim() : ''; } } - var valEl = $('#trigValue'); - if (valEl) { - var v = valEl.value.trim(); - if (v === '') delete triggerData.value; - else { try { triggerData.value = JSON.parse(v); } catch(e) { triggerData.value = v; } } - } - var lockEl = $('#trigLock'); - if (lockEl) { if (lockEl.checked) triggerData.lock = true; else delete triggerData.lock; } - var stepRows = document.querySelectorAll('.obj-card[data-card="steps"] .ie-step-row'); - if (!Array.isArray(triggerData.steps)) triggerData.steps = []; - stepRows.forEach(function(stepRow) { - var si = parseInt(stepRow.getAttribute('data-step'), 10); - if (isNaN(si)) return; - if (!triggerData.steps[si]) triggerData.steps[si] = {}; - var waitEl = stepRow.querySelector('.wait-input'); - if (waitEl) triggerData.steps[si].wait = parseInt(waitEl.value) || 0; - syncStepEffects(stepRow, si); - }); - if (triggerData.steps.length > stepRows.length) triggerData.steps.length = stepRows.length; + window._ieSyncAll(window._editorData); + mergeTriggerFromEditor(); renderTriggerEditor(triggerID, triggerData); } -function syncStepEffects(row, si) { - var effectsEl = row.querySelector('.ie-act-root'); - if (!effectsEl) return; - var step = triggerData.steps[si] || {}; - ['messages','broadcast','broadcast_global','teleport','heal','credits', - 'give_item','take_item','despawn_mob','aps_node','drop_table'].forEach(function(k) { delete step[k]; }); - delete step.set_global_flags; delete step.set_player_flags; delete step.spawn_mob; - - var msgInputs = effectsEl.querySelectorAll('.trig-msg-input'); - var msgs = []; - msgInputs.forEach(function(inp) { if (inp.value !== '') msgs.push(inp.value); }); - if (msgs.length > 0) step.messages = msgs; - - effectsEl.querySelectorAll('.trig-effect-input').forEach(function(inp) { - var key = inp.getAttribute('data-key'); - var v = inp.value.trim(); - if (v !== '') { if (key === 'teleport' || key === 'heal' || key === 'credits') step[key] = parseInt(v)||0; else step[key] = v; } - }); - - var cb = effectsEl.querySelector('.trig-aps-cb'); - if (cb && cb.checked) step.aps_node = true; - - effectsEl.querySelectorAll('.trig-kv-section').forEach(function(kvSec) { - var kvKey = kvSec.getAttribute('data-kv'); - var kvObj = {}; - kvSec.querySelectorAll('.trig-kv-row').forEach(function(kvRow) { - var kEl = kvRow.querySelector('.trig-kv-key'); - var vEl = kvRow.querySelector('.trig-kv-val'); - if (kEl && kEl.value) { - var v = vEl ? vEl.value : ''; - try { kvObj[kEl.value] = JSON.parse(v); } catch(e) { kvObj[kEl.value] = v; } - } - }); - if (Object.keys(kvObj).length > 0) step[kvKey] = kvObj; - }); - - var smInput = effectsEl.querySelector('.trig-spawn-mob'); - if (smInput && smInput.value.trim()) { - if (!step.spawn_mob) step.spawn_mob = {}; - step.spawn_mob.id = smInput.value.trim(); - } - - var dtBlock = effectsEl.querySelector('.ie-drop-table'); - if (dtBlock) { - var dt = ie_collectDropTableBlock(dtBlock); - if (dt) step.drop_table = dt; - } - - triggerData.steps[si] = step; -} - function renderTriggerEditor(id, data) { var html = renderRenameableHeader('Trigger', id); @@ -180,44 +131,34 @@ function renderTriggerEditor(id, data) { html += '