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 --- 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 +- 5 files changed, 74 insertions(+), 335 deletions(-) (limited to 'internal') 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 += '
'; html += '
'; html += 'Flag Type'; - html += '
'; - html += ''; - html += ''; + html += '
'; + html += ''; + html += ''; html += '
'; html += '
'; - html += '
'; + html += '
'; html += 'Flag Name'; html += ''; html += '
'; - html += '
'; - html += 'Value (optional)'; - html += ''; - html += '
'; - html += '
'; - html += ''; - html += 'Lock Player (atomic sequence)'; html += '
'; html += '
'; html += '
'; - html += '
'; - - html += '
'; - html += '
'; - html += 'Entry Condition'; - html += '
'; - html += '
'; - html += '
' + ie_renderCond(data.condition || null, 'condition', 0, '', 'entry') + '
'; - html += '
'; - html += '
'; - var steps = Array.isArray(data.steps) ? data.steps : []; - html += '
'; + html += '
'; html += '
'; - html += 'Steps (' + steps.length + ')'; + html += 'Action Block'; html += '
'; html += '
'; - html += renderSteps(steps); - html += ''; + html += ie_renderArraySection({ + sec: TRIG_SEC, + data: window._editorData, + visMap: window._interVis || {}, + showField: function() { return 'void(0)'; }, + hideField: function() { return 'void(0)'; }, + renderField: function() { return ''; }, + showRemove: function() { return 'void(0)'; }, + onAdd: function() { return 'void(0)'; } + }); html += '
'; html += '
'; @@ -236,207 +177,7 @@ function renderTriggerEditor(id, data) { ced_setupSearchFields(); } -function renderSteps(steps) { - var h = ''; - (steps || []).forEach(function(step, si) { - h += '
'; - h += '
'; - h += 'Step ' + (si + 1) + ''; - h += 'Wait:'; - h += ''; - if (si > 0) h += ''; - if (si < steps.length - 1) h += ''; - h += ''; - h += ''; - h += '
'; - - h += '
'; - h += '
'; - h += renderStepEffects(step, si); - h += '
'; - h += '
'; - - h += '
'; - IE_STEP_KINDS.forEach(function(sk) { - h += ''; - }); - h += '
'; - h += '
'; - }); - return h; -} - -function renderStepEffects(step, si) { - var h = ''; - - var msgs = Array.isArray(step.messages) ? step.messages : []; - msgs.forEach(function(msg, mi) { - h += '
'; - h += 'Message'; - h += ''; - h += '
'; - }); - - if (step.broadcast !== undefined) { - h += '
Broadcast'; - h += '
'; - } - if (step.broadcast_global !== undefined) { - h += '
Broadcast Global'; - h += '
'; - } - if (step.teleport !== undefined) { - h += '
Teleport'; - h += '
'; - } - if (step.heal !== undefined) { - h += '
Heal'; - h += '
'; - } - if (step.credits !== undefined) { - h += '
Credits'; - h += '
'; - } - if (step.spawn_mob && step.spawn_mob.id) { - h += '
Spawn Mob'; - h += '
'; - } - if (step.despawn_mob !== undefined) { - h += '
Despawn Mob'; - h += '
'; - } - if (step.give_item !== undefined) { - h += '
Give Item'; - h += '
'; - } - if (step.take_item !== undefined) { - h += '
Take Item'; - h += '
'; - } - if (step.aps_node) { - h += '
APS Node'; - h += '
'; - } - - function renderKVSection(label, key, obj) { - var h2 = '
'; - h2 += '
' + label + '
'; - h2 += '
'; - Object.keys(obj).forEach(function(k) { - h2 += '
'; - h2 += ''; - h2 += '='; - h2 += ''; - h2 += '
'; - }); - h2 += '
'; - return h2; - } - - if (step.set_global_flags && typeof step.set_global_flags === 'object' && Object.keys(step.set_global_flags).length > 0) { - h += renderKVSection('Set Global Flags', 'set_global_flags', step.set_global_flags); - } - if (step.set_player_flags && typeof step.set_player_flags === 'object' && Object.keys(step.set_player_flags).length > 0) { - h += renderKVSection('Set Player Flags', 'set_player_flags', step.set_player_flags); - } - - if (Array.isArray(step.drop_table) || step.drop_table !== undefined) { - h += '
Drop Table
'; - h += ie_renderDropTableBlock(step.drop_table, 'trigAddDropRow('+si+',\'item\')', 'trigAddDropRow('+si+',\'table\')', 'trigRemoveDropRow('+si+',__RI__)'); - h += '
'; - } - - return h; -} - -function trigAddDropRow(si, kind) { - if (!triggerData || !Array.isArray(triggerData.steps) || !triggerData.steps[si]) return; - syncAllStepEffects(); - var step = triggerData.steps[si]; - if (!Array.isArray(step.drop_table)) step.drop_table = []; - if (kind === 'table') { - step.drop_table.push({table: '', weight: 10, quantity: 1, _kind: 'table'}); - } else { - step.drop_table.push({item_id: '', weight: 10, quantity: 1, _kind: 'item'}); - } - renderCurrent(); -} - -function trigRemoveDropRow(si, ri) { - if (!triggerData || !Array.isArray(triggerData.steps) || !triggerData.steps[si]) return; - syncAllStepEffects(); - var step = triggerData.steps[si]; - if (!Array.isArray(step.drop_table)) return; - step.drop_table.splice(ri, 1); - renderCurrent(); -} - -function addStep() { - if (!triggerData) return; - if (!Array.isArray(triggerData.steps)) triggerData.steps = []; - triggerData.steps.push({wait: 0}); - renderCurrent(); -} - -function removeStep(si) { - if (!triggerData || !Array.isArray(triggerData.steps)) return; - triggerData.steps.splice(si, 1); - renderCurrent(); -} - -function moveStep(from, to) { - if (!triggerData || !Array.isArray(triggerData.steps)) return; - var s = triggerData.steps[from]; - triggerData.steps.splice(from, 1); - triggerData.steps.splice(to, 0, s); - renderCurrent(); -} - -function addStepEffect(si, kind) { - if (!triggerData) return; - syncCondDOM(triggerData); - syncAllStepEffects(); - if (!Array.isArray(triggerData.steps)) triggerData.steps = []; - if (!triggerData.steps[si]) triggerData.steps[si] = {}; - var step = triggerData.steps[si]; - - if (kind === 'messages') { - if (!Array.isArray(step.messages)) step.messages = []; - step.messages.push(''); - } else if (kind === 'wait') { - step.wait = (step.wait || 0) + 1; - } else { - var at = IE_ACT_TYPES.find(function(a) { return a.key === kind; }); - if (!at) return; - if (at.kind === 'kv') step[kind] = {}; - else if (at.kind === 'checkbox') step[kind] = true; - else if (at.kind === 'droptable') step[kind] = []; - else if (at.kind === 'number') step[kind] = 0; - else if (at.kind === 'search') step[kind] = ''; - else step[kind] = ''; - } - renderCurrent(); -} - -function syncAllStepEffects() { - 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); - }); -} - function saveTrigger() { - syncCondDOM(triggerData); - syncAllStepEffects(); - if (!triggerData || !triggerID) return; var flagTypeEl = document.querySelector('input[name="trigFlagType"]:checked'); if (flagTypeEl) { delete triggerData.on_player_flag; @@ -445,14 +186,8 @@ function saveTrigger() { if (flagTypeEl.value === 'player') triggerData.on_player_flag = n ? n.value.trim() : ''; else 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; } + window._ieSyncAll(window._editorData); + mergeTriggerFromEditor(); var out = JSON.parse(JSON.stringify(triggerData)); delete out.id; diff --git a/internal/behavior/behavior.go b/internal/behavior/behavior.go index 9152f8c..7dab514 100644 --- a/internal/behavior/behavior.go +++ b/internal/behavior/behavior.go @@ -89,9 +89,10 @@ func (s Step) HasEffects() bool { // its Steps run as a scripted sequence. // // For verb blocks only ItemID/Condition/Steps/Lock are meaningful. For -// on_flag_change/on_global_flag_change blocks, OnPlayerFlag/OnGlobalFlag (and -// optional Value) declare the flag subscription; Condition provides an -// additional gate (use the Room condition to scope a flag trigger to a room). +// on_flag_change/on_global_flag_change blocks, OnPlayerFlag/OnGlobalFlag +// declare the flag subscription; Condition provides an additional gate +// (use a player_flag/global_flag + value condition to match a specific +// flag value, or Room to scope a flag trigger to a room). // // Lock, when true, makes the sequence atomic: the player is blocked from any // verb (except quit) until it completes, and the sequence is saved and resumed @@ -104,7 +105,6 @@ type Trigger struct { Steps []Step `yaml:"steps"` OnPlayerFlag string `yaml:"on_player_flag,omitempty"` OnGlobalFlag string `yaml:"on_global_flag,omitempty"` - Value any `yaml:"value,omitempty"` DedupKey string `yaml:"-"` // runtime only — disambiguates flag-trigger dedup keys } diff --git a/internal/game/sys_triggers.go b/internal/game/sys_triggers.go index 683b3e0..cae774e 100644 --- a/internal/game/sys_triggers.go +++ b/internal/game/sys_triggers.go @@ -575,8 +575,8 @@ func (g *Game) loadAllFlagTriggers() { } // firePlayerFlagTriggers starts sequences for every on_flag_change trigger -// subscribed to flagName whose Value (if any) matches and whose Condition -// passes for the player. Called from setPlayerFlag after an actual change. +// subscribed to flagName whose Condition passes for the player. +// Called from setPlayerFlag after an actual change. func (g *Game) firePlayerFlagTriggers(p *player.Player, flagName string, flagValue any) { g.charsMu.Lock() sess := g.loggedInChars[p.Name] @@ -589,9 +589,6 @@ func (g *Game) firePlayerFlagTriggers(p *player.Player, flagName string, flagVal g.flagIndex.mu.Unlock() for i := range triggers { t := triggers[i] - if t.Value != nil && !engine.ValuesEqual(t.Value, flagValue) { - continue - } if t.Condition != nil && !g.checkCondition(sess, t.Condition) { continue } @@ -600,17 +597,14 @@ func (g *Game) firePlayerFlagTriggers(p *player.Player, flagName string, flagVal } // fireGlobalFlagTriggers starts sequences for every on_global_flag_change -// trigger subscribed to flagName whose Value (if any) matches. Global-flag -// triggers have no originating player and run at scopeGlobal. +// trigger subscribed to flagName. Global-flag triggers have no originating +// player and run at scopeGlobal. func (g *Game) fireGlobalFlagTriggers(flagName string, flagValue any) { g.flagIndex.mu.Lock() triggers := append([]*behavior.Trigger(nil), g.flagIndex.globalFlag[flagName]...) g.flagIndex.mu.Unlock() for i := range triggers { t := triggers[i] - if t.Value != nil && !engine.ValuesEqual(t.Value, flagValue) { - continue - } if t.Condition != nil && !g.checkConditionGlobal(t.Condition) { continue } -- cgit v1.2.3