cyberia 3.2.22 → 3.2.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +127 -68
- package/.github/workflows/cyberia-client.cd.yml +40 -0
- package/.github/workflows/cyberia-server.cd.yml +40 -0
- package/.github/workflows/docker-image.cyberia-client.ci.yml +49 -0
- package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +48 -0
- package/.github/workflows/docker-image.cyberia-server.ci.yml +69 -0
- package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +69 -0
- package/.github/workflows/docker-image.engine-cyberia.ci.yml +52 -0
- package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +52 -0
- package/.github/workflows/engine-cyberia.cd.yml +33 -24
- package/.github/workflows/engine-cyberia.ci.yml +13 -3
- package/.github/workflows/ghpkg.ci.yml +88 -1
- package/.github/workflows/gitlab.ci.yml +1 -1
- package/.github/workflows/hardhat.ci.yml +1 -1
- package/.github/workflows/npmpkg.ci.yml +10 -7
- package/.github/workflows/publish.ci.yml +2 -2
- package/.github/workflows/publish.cyberia.ci.yml +5 -16
- package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -8
- package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
- package/CHANGELOG.md +301 -1
- package/CLI-HELP.md +71 -6
- package/Dockerfile +141 -43
- package/Dockerfile.dev +143 -0
- package/Dockerfile.test +165 -0
- package/README.md +1 -1
- package/baremetal/commission-workflows.json +1 -0
- package/bin/build.js +31 -0
- package/bin/cyberia.js +1080 -184
- package/bin/deploy.js +2 -2
- package/bin/index.js +1080 -184
- package/bump.config.js +1 -0
- package/compose.env +131 -0
- package/conf.js +18 -1
- package/deployment.yaml +2 -2
- package/docker-compose.yml +316 -0
- package/hardhat/hardhat.config.js +2 -2
- package/hardhat/package-lock.json +620 -2041
- package/hardhat/package.json +7 -5
- package/hardhat/scripts/deployObjectLayerToken.js +18 -18
- package/hardhat/test/ObjectLayerToken.js +378 -274
- package/ipfs/configure-ipfs.sh +13 -0
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-cyberia-development/grpc-service.yaml +17 -0
- package/manifests/deployment/dd-cyberia-development/pv-pvc.yaml +32 -0
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/mongodb/entrypoint.sh +76 -0
- package/nginx.conf +87 -0
- package/package.json +31 -19
- package/pv-pvc.yaml +32 -0
- package/scripts/disk-clean.sh +85 -60
- package/scripts/kubeadm-node-setup.sh +317 -0
- package/scripts/rocky-kickstart.sh +877 -185
- package/scripts/rpmfusion-ffmpeg-setup.sh +26 -50
- package/scripts/test-monitor.sh +3 -5
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +43 -7
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +17 -25
- package/src/api/cyberia-action/cyberia-action.controller.js +19 -0
- package/src/api/cyberia-action/cyberia-action.model.js +21 -29
- package/src/api/cyberia-action/cyberia-action.router.js +42 -7
- package/src/api/cyberia-action/cyberia-action.service.js +20 -4
- package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +58 -57
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +37 -6
- package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +8 -2
- package/src/api/cyberia-entity/cyberia-entity.router.js +39 -7
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +74 -0
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +67 -0
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +63 -0
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +46 -0
- package/src/api/cyberia-instance/cyberia-fallback-world.js +62 -10
- package/src/api/cyberia-instance/cyberia-instance.model.js +32 -2
- package/src/api/cyberia-instance/cyberia-instance.router.js +6 -6
- package/src/api/cyberia-instance/cyberia-world-generator.js +116 -3
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +3 -0
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +39 -7
- package/src/api/cyberia-map/cyberia-map.router.js +21 -6
- package/src/api/cyberia-quest/cyberia-quest.controller.js +38 -0
- package/src/api/cyberia-quest/cyberia-quest.router.js +47 -7
- package/src/api/cyberia-quest/cyberia-quest.service.js +59 -4
- package/src/api/cyberia-saga/cyberia-saga.controller.js +74 -0
- package/src/api/cyberia-saga/cyberia-saga.model.js +59 -0
- package/src/api/cyberia-saga/cyberia-saga.router.js +63 -0
- package/src/api/cyberia-saga/cyberia-saga.service.js +42 -0
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +551 -129
- package/src/api/cyberia-skill/cyberia-skill.controller.js +74 -0
- package/src/api/cyberia-skill/cyberia-skill.model.js +50 -0
- package/src/api/cyberia-skill/cyberia-skill.router.js +63 -0
- package/src/api/cyberia-skill/cyberia-skill.service.js +42 -0
- package/src/api/ipfs/ipfs.service.js +28 -15
- package/src/api/object-layer/object-layer.router.js +25 -15
- package/src/api/object-layer/object-layer.service.js +15 -20
- package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +39 -7
- package/src/cli/baremetal.js +717 -16
- package/src/cli/cluster.js +80 -5
- package/src/cli/deploy.js +127 -11
- package/src/cli/docker-compose.js +648 -0
- package/src/cli/env.js +11 -2
- package/src/cli/fs.js +75 -30
- package/src/cli/image.js +129 -51
- package/src/cli/index.js +110 -6
- package/src/cli/kickstart.js +142 -20
- package/src/cli/release.js +46 -4
- package/src/cli/repository.js +238 -33
- package/src/cli/run.js +520 -114
- package/src/cli/secrets.js +82 -48
- package/src/cli/ssh.js +234 -0
- package/src/cli/static.js +2 -2
- package/src/client/components/cyberia/ActionEngineCyberia.js +1867 -0
- package/src/client/components/cyberia/EntityEngineCyberia.js +585 -0
- package/src/client/components/cyberia/InstanceEngineCyberia.js +219 -13
- package/src/client/components/cyberia/MapEngineCyberia.js +154 -71
- package/src/client/components/cyberia/ObjectLayerEngineModal.js +40 -63
- package/src/client/components/cyberia/ObjectLayerEngineViewer.js +34 -20
- package/src/client/components/cyberia/SharedDefaultsCyberia.js +195 -4
- package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +66 -0
- package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +8 -0
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +8 -0
- package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +45 -27
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +3 -3
- package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +39 -22
- package/src/client/public/cyberia-docs/CYBERIA-LORE.md +88 -0
- package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +394 -0
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +8 -1
- package/src/client/public/cyberia-docs/CYBERIA.md +1 -1
- package/src/client/public/cyberia-docs/QUEST-SYSTEM.md +5 -5
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
- package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +99 -0
- package/src/client/services/cyberia-instance/cyberia-instance.management.js +2 -2
- package/src/client/services/cyberia-map/cyberia-map.management.js +2 -2
- package/src/client/services/cyberia-saga/cyberia-saga.service.js +99 -0
- package/src/client/services/cyberia-skill/cyberia-skill.service.js +99 -0
- package/src/client/services/object-layer/object-layer.management.js +6 -6
- package/src/{server → client-builder}/client-build-docs.js +15 -5
- package/src/{server → client-builder}/client-build-live.js +3 -3
- package/src/{server → client-builder}/client-build.js +25 -22
- package/src/{server → client-builder}/client-dev-server.js +3 -3
- package/src/{server → client-builder}/client-icons.js +2 -2
- package/src/{server → client-builder}/ssr.js +5 -5
- package/src/client.build.js +1 -3
- package/src/client.dev.js +1 -1
- package/src/db/mongo/MongoBootstrap.js +12 -12
- package/src/grpc/cyberia/grpc-server.js +255 -70
- package/src/index.js +12 -1
- package/src/mailer/EmailRender.js +1 -1
- package/src/{server → projects/cyberia}/atlas-sprite-sheet-generator.js +2 -2
- package/src/{server → projects/cyberia}/besu-genesis-generator.js +3 -3
- package/src/projects/cyberia/catalog-cyberia.js +81 -0
- package/src/projects/cyberia/gemini-client.js +175 -0
- package/src/projects/cyberia/generate-saga.js +2107 -0
- package/src/{server → projects/cyberia}/ipfs-client.js +2 -2
- package/src/{server → projects/cyberia}/object-layer.js +12 -108
- package/src/{server → projects/cyberia}/semantic-layer-generator-floor.js +1 -1
- package/src/{server → projects/cyberia}/semantic-layer-generator-resource.js +1 -1
- package/src/{server → projects/cyberia}/semantic-layer-generator-skin.js +1 -1
- package/src/{server → projects/cyberia}/semantic-layer-generator.js +2 -2
- package/src/{server → projects/cyberia}/shape-generator.js +2 -2
- package/src/{server → projects/underpost}/catalog-underpost.js +1 -2
- package/src/runtime/cyberia-client/Dockerfile +27 -61
- package/src/runtime/cyberia-client/Dockerfile.dev +20 -12
- package/src/runtime/cyberia-server/Dockerfile +21 -20
- package/src/runtime/cyberia-server/Dockerfile.dev +17 -8
- package/src/runtime/engine-cyberia/Dockerfile +143 -0
- package/src/runtime/engine-cyberia/Dockerfile.dev +143 -0
- package/src/runtime/engine-cyberia/Dockerfile.test +165 -0
- package/src/runtime/engine-cyberia/compose.env +131 -0
- package/src/runtime/engine-cyberia/docker-compose.yml +316 -0
- package/src/runtime/engine-cyberia/ipfs/configure-ipfs.sh +13 -0
- package/src/runtime/engine-cyberia/mongodb/entrypoint.sh +76 -0
- package/src/runtime/engine-cyberia/nginx.conf +87 -0
- package/src/runtime/express/Express.js +2 -2
- package/src/runtime/nginx/Nginx.js +250 -0
- package/src/server/catalog.js +9 -14
- package/src/server/conf.js +3 -6
- package/src/server/runtime-status.js +18 -1
- package/src/server/start.js +12 -2
- package/src/server.js +6 -2
- package/test/cyberia-instance-conf-defaults.test.js +140 -0
- package/test/deploy-monitor.test.js +26 -10
- package/test/shape-generator.test.js +7 -1
- package/typedoc.dd-cyberia.json +3 -1
- package/typedoc.json +3 -1
- /package/src/client/ssr/{Render.js → RootDocument.js} +0 -0
- /package/src/{server → client-builder}/client-formatted.js +0 -0
|
@@ -0,0 +1,1867 @@
|
|
|
1
|
+
import { BtnIcon } from '../core/BtnIcon.js';
|
|
2
|
+
import { Input } from '../core/Input.js';
|
|
3
|
+
import { commonModeratorGuard } from '../core/CommonJs.js';
|
|
4
|
+
import { htmls, s } from '../core/VanillaJs.js';
|
|
5
|
+
import { NotificationManager } from '../core/NotificationManager.js';
|
|
6
|
+
import { Translate } from '../core/Translate.js';
|
|
7
|
+
import { darkTheme, dynamicCol, ThemeEvents } from '../core/Css.js';
|
|
8
|
+
import { DropDown } from '../core/DropDown.js';
|
|
9
|
+
import { getProxyPath } from '../core/Router.js';
|
|
10
|
+
import { ObjectLayerService } from '../../services/object-layer/object-layer.service.js';
|
|
11
|
+
import { CyberiaMapService } from '../../services/cyberia-map/cyberia-map.service.js';
|
|
12
|
+
import { CyberiaQuestService } from '../../services/cyberia-quest/cyberia-quest.service.js';
|
|
13
|
+
import { CyberiaActionService } from '../../services/cyberia-action/cyberia-action.service.js';
|
|
14
|
+
import { CyberiaDialogueService } from '../../services/cyberia-dialogue/cyberia-dialogue.service.js';
|
|
15
|
+
import { CyberiaSkillService } from '../../services/cyberia-skill/cyberia-skill.service.js';
|
|
16
|
+
import { QUEST_STEPS_TYPES, getDefaultCyberiaItemById, SKILL_LOGIC_IDS } from './SharedDefaultsCyberia.js';
|
|
17
|
+
|
|
18
|
+
const textFilter = (filter) => ({ filterType: 'text', type: 'equals', filter });
|
|
19
|
+
// DropDown invokes optionData.onClick on selection, so every option (including
|
|
20
|
+
// serviceProvider results) must carry one.
|
|
21
|
+
const dropdownOption = (value) => ({ value, display: value, data: value, onClick: () => {} });
|
|
22
|
+
const groupBorder = () => (darkTheme ? '#3a3a3a' : '#d4d4d4');
|
|
23
|
+
const subtleBorder = () => (darkTheme ? '#444' : '#e0e0e0');
|
|
24
|
+
|
|
25
|
+
// ActionEngineCyberia — a map-driven engine that unifies quest, action and
|
|
26
|
+
// dialogue authoring around a shared, visual map surface. It is intentionally
|
|
27
|
+
// decoupled from MapEngineCyberia: the only integration points are the public
|
|
28
|
+
// data services (maps, quests, actions, dialogues, object-layer item ids) and
|
|
29
|
+
// the same object-layer texture-loading convention. The engine reads a map
|
|
30
|
+
// purely as a placement backdrop and never mutates map documents. CRUD is
|
|
31
|
+
// surfaced as per-entity card lists (mirroring the map-engine entity list),
|
|
32
|
+
// not management grids.
|
|
33
|
+
class ActionEngineCyberia {
|
|
34
|
+
static mode = 'quest';
|
|
35
|
+
static mapCode = '';
|
|
36
|
+
static mapDoc = null;
|
|
37
|
+
static imageCache = {};
|
|
38
|
+
static assignments = { quests: [], actions: [] };
|
|
39
|
+
|
|
40
|
+
static questListCache = [];
|
|
41
|
+
static actionListCache = [];
|
|
42
|
+
static dialogueListCache = [];
|
|
43
|
+
static skillListCache = [];
|
|
44
|
+
|
|
45
|
+
static currentQuestId = null;
|
|
46
|
+
static currentActionId = null;
|
|
47
|
+
static currentDialogueId = null;
|
|
48
|
+
static currentSkillId = null;
|
|
49
|
+
|
|
50
|
+
static questSteps = [];
|
|
51
|
+
static questRewards = [];
|
|
52
|
+
static actionQuestDialogues = [];
|
|
53
|
+
static loadedActionPayloadExtras = {};
|
|
54
|
+
// Editable `skills[]` for the loaded CyberiaSkill (logicEventIds are derived
|
|
55
|
+
// from these on save, mirroring DefaultSkillConfig).
|
|
56
|
+
static skillDefs = [];
|
|
57
|
+
|
|
58
|
+
static ids = {
|
|
59
|
+
mapDropdown: 'action-engine-map-dropdown',
|
|
60
|
+
objectiveItemPicker: 'action-engine-objective-item-picker',
|
|
61
|
+
rewardItemPicker: 'action-engine-reward-item-picker',
|
|
62
|
+
objectiveType: 'action-engine-objective-type',
|
|
63
|
+
actionDialogPicker: 'action-engine-action-dialog-picker',
|
|
64
|
+
actionQuestDialogPicker: 'action-engine-action-quest-dialog-picker',
|
|
65
|
+
skillTriggerItemPicker: 'action-engine-skill-trigger-item-picker',
|
|
66
|
+
skillSummonedItemPicker: 'action-engine-skill-summoned-item-picker',
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// ── Searchable dropdown helpers ─────────────────────────────────────────
|
|
70
|
+
static getSingleDropdownValue(id) {
|
|
71
|
+
const value = DropDown.Tokens[id]?.value;
|
|
72
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static setSingleDropdownValue(id, value) {
|
|
76
|
+
if (!DropDown.Tokens[id]) return;
|
|
77
|
+
const v = value || '';
|
|
78
|
+
DropDown.Tokens[id].value = v;
|
|
79
|
+
if (s(`.${id}`)) s(`.${id}`).value = v;
|
|
80
|
+
htmls(`.dropdown-current-${id}`, v || '');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// itemId references the item/object-layer collection, so the picker is a fast
|
|
84
|
+
// server-backed autocomplete instead of a free-text input.
|
|
85
|
+
static async buildItemIdDropdown(id, label) {
|
|
86
|
+
return await DropDown.instance({
|
|
87
|
+
id,
|
|
88
|
+
label,
|
|
89
|
+
data: [],
|
|
90
|
+
containerClass: 'inl',
|
|
91
|
+
serviceProvider: async (q) => {
|
|
92
|
+
const result = await ObjectLayerService.searchItemIds({ q });
|
|
93
|
+
if (result.status === 'success' && result.data?.itemIds) {
|
|
94
|
+
return result.data.itemIds.map((itemId) => dropdownOption(itemId));
|
|
95
|
+
}
|
|
96
|
+
return [];
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
static async buildMapDropdown(id) {
|
|
102
|
+
return await DropDown.instance({
|
|
103
|
+
id,
|
|
104
|
+
label: html`Map`,
|
|
105
|
+
data: [],
|
|
106
|
+
containerClass: 'inl',
|
|
107
|
+
serviceProvider: async (q) => {
|
|
108
|
+
const result = await CyberiaMapService.searchCodes({ q });
|
|
109
|
+
if (result.status === 'success' && result.data?.codes) {
|
|
110
|
+
return result.data.codes.map((code) => dropdownOption(code));
|
|
111
|
+
}
|
|
112
|
+
return [];
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Dialogue groups are keyed by `code`; the picker derives distinct codes from
|
|
118
|
+
// the dialogue collection so authors can reuse existing dialogue groups.
|
|
119
|
+
static async buildDialogCodeDropdown(id, label) {
|
|
120
|
+
return await DropDown.instance({
|
|
121
|
+
id,
|
|
122
|
+
label,
|
|
123
|
+
data: [],
|
|
124
|
+
containerClass: 'inl',
|
|
125
|
+
serviceProvider: async (q) => {
|
|
126
|
+
const result = await CyberiaDialogueService.get({
|
|
127
|
+
limit: 100,
|
|
128
|
+
filterModel: { code: { filterType: 'text', type: 'contains', filter: q } },
|
|
129
|
+
});
|
|
130
|
+
const rows = result?.data?.data || [];
|
|
131
|
+
const codes = [...new Set(rows.map((r) => r.code).filter(Boolean))];
|
|
132
|
+
return codes.map((code) => dropdownOption(code));
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ── Object-layer textures (same loading convention as MapEngineCyberia) ──
|
|
138
|
+
static loadObjectLayerImage(itemId, onLoad) {
|
|
139
|
+
if (ActionEngineCyberia.imageCache[itemId]) return;
|
|
140
|
+
ActionEngineCyberia.imageCache[itemId] = { img: null, loaded: false, error: false };
|
|
141
|
+
|
|
142
|
+
const loadImage = (type, id) => {
|
|
143
|
+
const img = new Image();
|
|
144
|
+
img.onload = () => {
|
|
145
|
+
ActionEngineCyberia.imageCache[itemId].img = img;
|
|
146
|
+
ActionEngineCyberia.imageCache[itemId].loaded = true;
|
|
147
|
+
if (onLoad) onLoad();
|
|
148
|
+
};
|
|
149
|
+
img.onerror = () => {
|
|
150
|
+
ActionEngineCyberia.imageCache[itemId].error = true;
|
|
151
|
+
};
|
|
152
|
+
img.src = `${getProxyPath()}assets/${type}/${id}/08/0.png`;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const sharedItem = getDefaultCyberiaItemById(itemId)?.item;
|
|
156
|
+
if (sharedItem?.type && sharedItem?.id) {
|
|
157
|
+
loadImage(sharedItem.type, sharedItem.id);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
ObjectLayerService.get({
|
|
162
|
+
limit: 1,
|
|
163
|
+
filterModel: { 'data.item.id': { filterType: 'text', type: 'equals', filter: itemId } },
|
|
164
|
+
})
|
|
165
|
+
.then((res) => {
|
|
166
|
+
const doc = res?.data?.data?.[0];
|
|
167
|
+
if (!doc || !doc.data?.item?.type || !doc.data?.item?.id) {
|
|
168
|
+
ActionEngineCyberia.imageCache[itemId].error = true;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
loadImage(doc.data.item.type, doc.data.item.id);
|
|
172
|
+
})
|
|
173
|
+
.catch(() => {
|
|
174
|
+
ActionEngineCyberia.imageCache[itemId].error = true;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
static preloadMapObjectLayers(onLoad) {
|
|
179
|
+
for (const entity of ActionEngineCyberia.mapDoc?.entities || [])
|
|
180
|
+
for (const itemId of entity.objectLayerItemIds || []) ActionEngineCyberia.loadObjectLayerImage(itemId, onLoad);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ── Map canvas ──────────────────────────────────────────────────────────
|
|
184
|
+
static getMapGeometry() {
|
|
185
|
+
const map = ActionEngineCyberia.mapDoc;
|
|
186
|
+
return {
|
|
187
|
+
cols: map?.gridX || 16,
|
|
188
|
+
rows: map?.gridY || 16,
|
|
189
|
+
cellW: Math.max(8, Math.min(28, map?.cellWidth || 18)),
|
|
190
|
+
cellH: Math.max(8, Math.min(28, map?.cellHeight || 18)),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
static getSelectedCell() {
|
|
195
|
+
return {
|
|
196
|
+
x: parseInt(s('.action-engine-cell-x')?.value),
|
|
197
|
+
y: parseInt(s('.action-engine-cell-y')?.value),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
static renderMapCanvas() {
|
|
202
|
+
const canvas = s('.action-engine-canvas');
|
|
203
|
+
if (!canvas) return;
|
|
204
|
+
const { cols, rows, cellW, cellH } = ActionEngineCyberia.getMapGeometry();
|
|
205
|
+
canvas.width = cols * cellW;
|
|
206
|
+
canvas.height = rows * cellH;
|
|
207
|
+
const ctx = canvas.getContext('2d');
|
|
208
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
209
|
+
|
|
210
|
+
for (const entity of ActionEngineCyberia.mapDoc?.entities || []) {
|
|
211
|
+
const x = entity.initCellX * cellW;
|
|
212
|
+
const y = entity.initCellY * cellH;
|
|
213
|
+
const w = entity.dimX * cellW;
|
|
214
|
+
const h = entity.dimY * cellH;
|
|
215
|
+
let drew = false;
|
|
216
|
+
if (entity.objectLayerItemIds?.length) {
|
|
217
|
+
for (const itemId of entity.objectLayerItemIds) {
|
|
218
|
+
const cached = ActionEngineCyberia.imageCache[itemId];
|
|
219
|
+
if (cached?.loaded && cached.img) {
|
|
220
|
+
ctx.drawImage(cached.img, x, y, w, h);
|
|
221
|
+
drew = true;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (!drew) {
|
|
226
|
+
ctx.fillStyle = entity.color || 'rgba(80,80,80,0.6)';
|
|
227
|
+
ctx.fillRect(x, y, w, h);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
ctx.strokeStyle = darkTheme ? '#3a3a3a' : '#cccccc';
|
|
232
|
+
ctx.lineWidth = 1;
|
|
233
|
+
for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) ctx.strokeRect(c * cellW, r * cellH, cellW, cellH);
|
|
234
|
+
|
|
235
|
+
const drawMarker = (x, y, color, glyph) => {
|
|
236
|
+
const cx = x * cellW + cellW / 2;
|
|
237
|
+
const cy = y * cellH + cellH / 2;
|
|
238
|
+
ctx.beginPath();
|
|
239
|
+
ctx.arc(cx, cy, Math.min(cellW, cellH) / 2.4, 0, Math.PI * 2);
|
|
240
|
+
ctx.fillStyle = color;
|
|
241
|
+
ctx.fill();
|
|
242
|
+
ctx.fillStyle = '#fff';
|
|
243
|
+
ctx.font = `bold ${Math.floor(Math.min(cellW, cellH) * 0.7)}px monospace`;
|
|
244
|
+
ctx.textAlign = 'center';
|
|
245
|
+
ctx.textBaseline = 'middle';
|
|
246
|
+
ctx.fillText(glyph, cx, cy);
|
|
247
|
+
};
|
|
248
|
+
for (const q of ActionEngineCyberia.assignments.quests)
|
|
249
|
+
if (q.sourceCellX != null) drawMarker(q.sourceCellX, q.sourceCellY, 'rgba(40,120,220,0.92)', 'Q');
|
|
250
|
+
for (const a of ActionEngineCyberia.assignments.actions)
|
|
251
|
+
if (a.sourceCellX != null) drawMarker(a.sourceCellX, a.sourceCellY, 'rgba(220,120,40,0.92)', 'A');
|
|
252
|
+
|
|
253
|
+
const sel = ActionEngineCyberia.getSelectedCell();
|
|
254
|
+
if (Number.isFinite(sel.x) && Number.isFinite(sel.y)) {
|
|
255
|
+
ctx.strokeStyle = '#1fd11f';
|
|
256
|
+
ctx.lineWidth = 3;
|
|
257
|
+
ctx.strokeRect(sel.x * cellW + 1, sel.y * cellH + 1, cellW - 2, cellH - 2);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
static async loadMap(code) {
|
|
262
|
+
if (!code) {
|
|
263
|
+
NotificationManager.Push({ html: 'Select a map first.', status: 'warning' });
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const result = await CyberiaMapService.get({ limit: 1, filterModel: { code: textFilter(code) } });
|
|
267
|
+
const doc = result?.data?.data?.[0];
|
|
268
|
+
if (!doc) {
|
|
269
|
+
NotificationManager.Push({ html: `Map "${code}" not found`, status: 'error' });
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
ActionEngineCyberia.mapCode = code;
|
|
273
|
+
ActionEngineCyberia.mapDoc = doc;
|
|
274
|
+
htmls('.action-engine-map-meta', `${doc.code} · ${doc.gridX || 16}×${doc.gridY || 16} cells`);
|
|
275
|
+
ActionEngineCyberia.preloadMapObjectLayers(() => ActionEngineCyberia.renderMapCanvas());
|
|
276
|
+
await ActionEngineCyberia.refreshAssignments();
|
|
277
|
+
ActionEngineCyberia.renderMapCanvas();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
static async refreshAssignments() {
|
|
281
|
+
const code = ActionEngineCyberia.mapCode;
|
|
282
|
+
if (!code) {
|
|
283
|
+
ActionEngineCyberia.assignments = { quests: [], actions: [] };
|
|
284
|
+
ActionEngineCyberia.renderAssignmentReview();
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const [questRes, actionRes] = await Promise.all([
|
|
288
|
+
CyberiaQuestService.get({ limit: 500, filterModel: { sourceMapCode: textFilter(code) } }),
|
|
289
|
+
CyberiaActionService.get({ limit: 500, filterModel: { sourceMapCode: textFilter(code) } }),
|
|
290
|
+
]);
|
|
291
|
+
ActionEngineCyberia.assignments = {
|
|
292
|
+
quests: questRes?.data?.data || [],
|
|
293
|
+
actions: actionRes?.data?.data || [],
|
|
294
|
+
};
|
|
295
|
+
ActionEngineCyberia.renderMapCanvas();
|
|
296
|
+
ActionEngineCyberia.renderAssignmentReview();
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
static renderAssignmentReview() {
|
|
300
|
+
const container = s('.action-engine-assignment-review');
|
|
301
|
+
if (!container) return;
|
|
302
|
+
const { quests, actions } = ActionEngineCyberia.assignments;
|
|
303
|
+
const row = (kind, color, doc) =>
|
|
304
|
+
html`<div
|
|
305
|
+
class="fl action-engine-assignment-row"
|
|
306
|
+
style="border-bottom:1px solid ${subtleBorder()};padding:4px 0;align-items:center;cursor:pointer;"
|
|
307
|
+
data-kind="${kind}"
|
|
308
|
+
data-id="${doc._id}"
|
|
309
|
+
>
|
|
310
|
+
<div
|
|
311
|
+
class="in fll"
|
|
312
|
+
style="width:18px;height:18px;border-radius:50%;background:${color};color:#fff;text-align:center;font-size:11px;line-height:18px;margin-right:6px;"
|
|
313
|
+
>
|
|
314
|
+
${kind === 'quest' ? 'Q' : 'A'}
|
|
315
|
+
</div>
|
|
316
|
+
<div class="in fll" style="flex:1;font-size:12px;font-family:monospace;">
|
|
317
|
+
${doc.code}
|
|
318
|
+
${doc.sourceCellX != null
|
|
319
|
+
? `(${doc.sourceCellX}, ${doc.sourceCellY})`
|
|
320
|
+
: '<span style="color:#999;">unplaced</span>'}
|
|
321
|
+
</div>
|
|
322
|
+
</div>`;
|
|
323
|
+
let out = '';
|
|
324
|
+
for (const q of quests) out += row('quest', 'rgba(40,120,220,0.92)', q);
|
|
325
|
+
for (const a of actions) out += row('action', 'rgba(220,120,40,0.92)', a);
|
|
326
|
+
if (!out)
|
|
327
|
+
out = `<div style="color:#888;font-size:13px;">${ActionEngineCyberia.mapCode ? 'No assignments on this map yet.' : 'Load a map to review assignments.'}</div>`;
|
|
328
|
+
htmls('.action-engine-assignment-review', out);
|
|
329
|
+
|
|
330
|
+
container.querySelectorAll('.action-engine-assignment-row').forEach((rowEl) => {
|
|
331
|
+
rowEl.onclick = async () => {
|
|
332
|
+
const { kind, id } = rowEl.dataset;
|
|
333
|
+
if (kind === 'quest') {
|
|
334
|
+
ActionEngineCyberia.setMode('quest');
|
|
335
|
+
const res = await CyberiaQuestService.get({ id });
|
|
336
|
+
if (res.status === 'success' && res.data) ActionEngineCyberia.loadQuest(res.data);
|
|
337
|
+
} else {
|
|
338
|
+
ActionEngineCyberia.setMode('action');
|
|
339
|
+
const res = await CyberiaActionService.get({ id });
|
|
340
|
+
if (res.status === 'success' && res.data) ActionEngineCyberia.loadAction(res.data);
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ── Mode switching ──────────────────────────────────────────────────────
|
|
347
|
+
static setMode(mode) {
|
|
348
|
+
ActionEngineCyberia.mode = mode;
|
|
349
|
+
for (const m of ['quest', 'action', 'dialogue', 'skill']) {
|
|
350
|
+
const panel = s(`.action-engine-panel-${m}`);
|
|
351
|
+
if (panel) panel.classList[m === mode ? 'remove' : 'add']('hide');
|
|
352
|
+
const tab = s(`.action-engine-tab-${m}`);
|
|
353
|
+
if (tab) {
|
|
354
|
+
tab.style.opacity = m === mode ? '1' : '0.5';
|
|
355
|
+
tab.style.fontWeight = m === mode ? 'bold' : 'normal';
|
|
356
|
+
tab.style.borderBottomColor = m === mode ? '#1fd11f' : 'transparent';
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Keeps the shared map surface in sync with the loaded document: reflects the
|
|
362
|
+
// map dropdown, loads the matching map (with object-layer textures) and
|
|
363
|
+
// highlights the assigned cell so the editor is reactive on load.
|
|
364
|
+
static async syncMapForLoadedDoc(doc) {
|
|
365
|
+
if (doc.sourceMapCode) {
|
|
366
|
+
ActionEngineCyberia.setSingleDropdownValue(ActionEngineCyberia.ids.mapDropdown, doc.sourceMapCode);
|
|
367
|
+
if (doc.sourceMapCode !== ActionEngineCyberia.mapCode) {
|
|
368
|
+
await ActionEngineCyberia.loadMap(doc.sourceMapCode);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
ActionEngineCyberia.renderMapCanvas();
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// ── Multi-step quest editor ─────────────────────────────────────────────
|
|
376
|
+
static syncStepDescriptions() {
|
|
377
|
+
ActionEngineCyberia.questSteps.forEach((step, i) => {
|
|
378
|
+
const el = s(`.action-engine-step-desc-${i}`);
|
|
379
|
+
if (el) step.description = el.value;
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
static async renderStepList() {
|
|
384
|
+
const container = s('.action-engine-step-list');
|
|
385
|
+
if (!container) return;
|
|
386
|
+
const steps = ActionEngineCyberia.questSteps;
|
|
387
|
+
let out = '';
|
|
388
|
+
for (let i = 0; i < steps.length; i++) {
|
|
389
|
+
const step = steps[i];
|
|
390
|
+
let objOut = '';
|
|
391
|
+
for (let j = 0; j < step.objectives.length; j++) {
|
|
392
|
+
const o = step.objectives[j];
|
|
393
|
+
objOut += html`<div
|
|
394
|
+
class="fl"
|
|
395
|
+
style="border-bottom:1px solid ${subtleBorder()};padding:3px 0;align-items:center;"
|
|
396
|
+
>
|
|
397
|
+
<div class="in fll" style="flex:1;font-family:monospace;font-size:12px;">
|
|
398
|
+
<span style="color:${darkTheme ? '#8cf' : '#246'};">${o.type}</span> · ${o.itemId} ×${o.quantity}
|
|
399
|
+
</div>
|
|
400
|
+
${await BtnIcon.instance({
|
|
401
|
+
class: `btn-aeq-rmobj-${i}-${j}`,
|
|
402
|
+
label: html`<i class="fa-solid fa-xmark"></i>`,
|
|
403
|
+
style: 'min-width:30px;padding:2px 8px;',
|
|
404
|
+
})}
|
|
405
|
+
</div>`;
|
|
406
|
+
}
|
|
407
|
+
if (!objOut) objOut = '<div style="color:#888;font-size:11px;padding:2px 0;">No objectives in this step.</div>';
|
|
408
|
+
out += html`<div
|
|
409
|
+
class="in"
|
|
410
|
+
style="border:1px solid ${subtleBorder()};border-radius:6px;padding:8px;margin-bottom:8px;"
|
|
411
|
+
>
|
|
412
|
+
<div class="fl" style="align-items:center;margin-bottom:6px;">
|
|
413
|
+
<div class="in fll" style="flex:1;font-weight:bold;font-size:12px;">
|
|
414
|
+
<i class="fa-solid fa-layer-group"></i> Step ${i + 1}
|
|
415
|
+
</div>
|
|
416
|
+
${await BtnIcon.instance({
|
|
417
|
+
class: `btn-aeq-rmstep-${i}`,
|
|
418
|
+
label: html`<i class="fa-solid fa-trash"></i>`,
|
|
419
|
+
style: 'min-width:30px;padding:2px 8px;',
|
|
420
|
+
})}
|
|
421
|
+
</div>
|
|
422
|
+
${await Input.instance({
|
|
423
|
+
id: `action-engine-step-desc-${i}`,
|
|
424
|
+
label: html`Step description`,
|
|
425
|
+
containerClass: 'inl',
|
|
426
|
+
type: 'text',
|
|
427
|
+
value: step.description || '',
|
|
428
|
+
})}
|
|
429
|
+
<div class="in" style="margin-top:6px;">${objOut}</div>
|
|
430
|
+
</div>`;
|
|
431
|
+
}
|
|
432
|
+
if (!out) out = '<div style="color:#888;font-size:12px;">No steps yet. Add a step to begin.</div>';
|
|
433
|
+
htmls('.action-engine-step-list', out);
|
|
434
|
+
|
|
435
|
+
for (let i = 0; i < steps.length; i++) {
|
|
436
|
+
if (s(`.btn-aeq-rmstep-${i}`))
|
|
437
|
+
s(`.btn-aeq-rmstep-${i}`).onclick = () => {
|
|
438
|
+
ActionEngineCyberia.syncStepDescriptions();
|
|
439
|
+
ActionEngineCyberia.questSteps.splice(i, 1);
|
|
440
|
+
ActionEngineCyberia.renderStepList();
|
|
441
|
+
};
|
|
442
|
+
for (let j = 0; j < steps[i].objectives.length; j++) {
|
|
443
|
+
if (s(`.btn-aeq-rmobj-${i}-${j}`))
|
|
444
|
+
s(`.btn-aeq-rmobj-${i}-${j}`).onclick = () => {
|
|
445
|
+
ActionEngineCyberia.syncStepDescriptions();
|
|
446
|
+
ActionEngineCyberia.questSteps[i].objectives.splice(j, 1);
|
|
447
|
+
ActionEngineCyberia.renderStepList();
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
static addStep() {
|
|
454
|
+
ActionEngineCyberia.syncStepDescriptions();
|
|
455
|
+
const n = ActionEngineCyberia.questSteps.length + 1;
|
|
456
|
+
ActionEngineCyberia.questSteps.push({ id: `step-${n}`, description: '', objectives: [] });
|
|
457
|
+
ActionEngineCyberia.renderStepList();
|
|
458
|
+
if (s('.action-engine-objective-step')) s('.action-engine-objective-step').value = n;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
static addObjective() {
|
|
462
|
+
const itemId = ActionEngineCyberia.getSingleDropdownValue(ActionEngineCyberia.ids.objectiveItemPicker);
|
|
463
|
+
const type = DropDown.Tokens[ActionEngineCyberia.ids.objectiveType]?.value || QUEST_STEPS_TYPES[0];
|
|
464
|
+
const quantity = Math.max(1, parseInt(s('.action-engine-objective-qty')?.value) || 1);
|
|
465
|
+
if (!itemId) {
|
|
466
|
+
NotificationManager.Push({ html: 'Select an itemId for the objective.', status: 'error' });
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
ActionEngineCyberia.syncStepDescriptions();
|
|
470
|
+
let stepIdx = (parseInt(s('.action-engine-objective-step')?.value) || 1) - 1;
|
|
471
|
+
if (ActionEngineCyberia.questSteps.length === 0) {
|
|
472
|
+
ActionEngineCyberia.questSteps.push({ id: 'step-1', description: '', objectives: [] });
|
|
473
|
+
stepIdx = 0;
|
|
474
|
+
if (s('.action-engine-objective-step')) s('.action-engine-objective-step').value = 1;
|
|
475
|
+
}
|
|
476
|
+
if (stepIdx < 0 || stepIdx >= ActionEngineCyberia.questSteps.length) {
|
|
477
|
+
NotificationManager.Push({ html: `Step #${stepIdx + 1} does not exist.`, status: 'error' });
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const duplicate = ActionEngineCyberia.questSteps[stepIdx].objectives.some(
|
|
481
|
+
(o) => o.type === type && o.itemId === itemId,
|
|
482
|
+
);
|
|
483
|
+
if (duplicate) {
|
|
484
|
+
NotificationManager.Push({
|
|
485
|
+
html: `Step ${stepIdx + 1} already has a "${type}" objective for "${itemId}".`,
|
|
486
|
+
status: 'error',
|
|
487
|
+
});
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
ActionEngineCyberia.questSteps[stepIdx].objectives.push({ type, itemId, quantity });
|
|
491
|
+
ActionEngineCyberia.renderStepList();
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
static async renderRewardList() {
|
|
495
|
+
const container = s('.action-engine-reward-list');
|
|
496
|
+
if (!container) return;
|
|
497
|
+
let out = '';
|
|
498
|
+
for (let i = 0; i < ActionEngineCyberia.questRewards.length; i++) {
|
|
499
|
+
const r = ActionEngineCyberia.questRewards[i];
|
|
500
|
+
out += html`<div
|
|
501
|
+
class="fl"
|
|
502
|
+
style="border-bottom:1px solid ${subtleBorder()};padding:3px 0;align-items:center;font-size:12px;font-family:monospace;"
|
|
503
|
+
>
|
|
504
|
+
<div class="in fll" style="flex:1;">${r.itemId} ×${r.quantity}</div>
|
|
505
|
+
${await BtnIcon.instance({
|
|
506
|
+
class: `btn-aeq-rmreward-${i}`,
|
|
507
|
+
label: html`<i class="fa-solid fa-xmark"></i>`,
|
|
508
|
+
style: 'min-width:30px;padding:2px 8px;',
|
|
509
|
+
})}
|
|
510
|
+
</div>`;
|
|
511
|
+
}
|
|
512
|
+
if (!out) out = '<div style="color:#888;font-size:12px;">No rewards.</div>';
|
|
513
|
+
htmls('.action-engine-reward-list', out);
|
|
514
|
+
for (let i = 0; i < ActionEngineCyberia.questRewards.length; i++) {
|
|
515
|
+
if (s(`.btn-aeq-rmreward-${i}`))
|
|
516
|
+
s(`.btn-aeq-rmreward-${i}`).onclick = () => {
|
|
517
|
+
ActionEngineCyberia.questRewards.splice(i, 1);
|
|
518
|
+
ActionEngineCyberia.renderRewardList();
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
static addReward() {
|
|
524
|
+
const itemId = ActionEngineCyberia.getSingleDropdownValue(ActionEngineCyberia.ids.rewardItemPicker);
|
|
525
|
+
const quantity = Math.max(1, parseInt(s('.action-engine-reward-qty')?.value) || 1);
|
|
526
|
+
if (!itemId) {
|
|
527
|
+
NotificationManager.Push({ html: 'Select an itemId for the reward.', status: 'error' });
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
ActionEngineCyberia.questRewards.push({ itemId, quantity });
|
|
531
|
+
ActionEngineCyberia.renderRewardList();
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
static async renderQuestDialogueList() {
|
|
535
|
+
const container = s('.action-engine-quest-dialogue-list');
|
|
536
|
+
if (!container) return;
|
|
537
|
+
let out = '';
|
|
538
|
+
for (let i = 0; i < ActionEngineCyberia.actionQuestDialogues.length; i++) {
|
|
539
|
+
const d = ActionEngineCyberia.actionQuestDialogues[i];
|
|
540
|
+
out += html`<div
|
|
541
|
+
class="fl"
|
|
542
|
+
style="border-bottom:1px solid ${subtleBorder()};padding:3px 0;align-items:center;font-size:12px;font-family:monospace;"
|
|
543
|
+
>
|
|
544
|
+
<div class="in fll" style="flex:1;">
|
|
545
|
+
<span style="color:${darkTheme ? '#8cf' : '#246'};">${d.questCode}</span>
|
|
546
|
+
<span style="margin:0 4px;">→</span>${d.dialogCode}
|
|
547
|
+
</div>
|
|
548
|
+
${await BtnIcon.instance({
|
|
549
|
+
class: `btn-aea-rmqd-${i}`,
|
|
550
|
+
label: html`<i class="fa-solid fa-xmark"></i>`,
|
|
551
|
+
style: 'min-width:30px;padding:2px 8px;',
|
|
552
|
+
})}
|
|
553
|
+
</div>`;
|
|
554
|
+
}
|
|
555
|
+
if (!out) out = '<div style="color:#888;font-size:12px;">No quest dialogues mapped.</div>';
|
|
556
|
+
htmls('.action-engine-quest-dialogue-list', out);
|
|
557
|
+
for (let i = 0; i < ActionEngineCyberia.actionQuestDialogues.length; i++) {
|
|
558
|
+
if (s(`.btn-aea-rmqd-${i}`))
|
|
559
|
+
s(`.btn-aea-rmqd-${i}`).onclick = () => {
|
|
560
|
+
ActionEngineCyberia.actionQuestDialogues.splice(i, 1);
|
|
561
|
+
ActionEngineCyberia.renderQuestDialogueList();
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// ── Card lists (CRUD surface, mirrors the map-engine entity list) ────────
|
|
567
|
+
static cardActionsHtml(loadCls, delCls) {
|
|
568
|
+
return Promise.all([
|
|
569
|
+
BtnIcon.instance({
|
|
570
|
+
class: loadCls,
|
|
571
|
+
label: html`<i class="fa-solid fa-upload"></i>`,
|
|
572
|
+
style: 'min-width:34px;padding:3px 9px;',
|
|
573
|
+
}),
|
|
574
|
+
ActionEngineCyberia.canMutate
|
|
575
|
+
? BtnIcon.instance({
|
|
576
|
+
class: delCls,
|
|
577
|
+
label: html`<i class="fa-solid fa-trash"></i>`,
|
|
578
|
+
style: 'min-width:34px;padding:3px 9px;',
|
|
579
|
+
})
|
|
580
|
+
: '',
|
|
581
|
+
]);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
static cardWrap(inner) {
|
|
585
|
+
return html`<div
|
|
586
|
+
class="in"
|
|
587
|
+
style="border:1px solid ${subtleBorder()};border-radius:6px;padding:8px;margin-bottom:6px;"
|
|
588
|
+
>
|
|
589
|
+
${inner}
|
|
590
|
+
</div>`;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
static async renderQuestCards() {
|
|
594
|
+
const container = s('.action-engine-quest-cards');
|
|
595
|
+
if (!container) return;
|
|
596
|
+
const filter = (s('.action-engine-quest-search')?.value || '').toLowerCase();
|
|
597
|
+
const items = ActionEngineCyberia.questListCache.filter(
|
|
598
|
+
(d) => !filter || (d.code || '').toLowerCase().includes(filter) || (d.title || '').toLowerCase().includes(filter),
|
|
599
|
+
);
|
|
600
|
+
let out = '';
|
|
601
|
+
for (const doc of items) {
|
|
602
|
+
const stepCount = (doc.steps || []).length;
|
|
603
|
+
const objCount = (doc.steps || []).reduce((a, st) => a + (st.objectives || []).length, 0);
|
|
604
|
+
const [loadBtn, delBtn] = await ActionEngineCyberia.cardActionsHtml(
|
|
605
|
+
`btn-aeq-load-${doc._id}`,
|
|
606
|
+
`btn-aeq-del-${doc._id}`,
|
|
607
|
+
);
|
|
608
|
+
out += ActionEngineCyberia.cardWrap(
|
|
609
|
+
html`<div class="fl" style="align-items:center;">
|
|
610
|
+
<div class="in fll" style="flex:1;">
|
|
611
|
+
<div style="font-weight:bold;font-family:monospace;font-size:12px;">${doc.code}</div>
|
|
612
|
+
<div style="font-size:11px;color:#888;">${doc.title || ''}</div>
|
|
613
|
+
<div style="font-size:11px;color:#888;">
|
|
614
|
+
${doc.sourceMapCode ? `${doc.sourceMapCode} (${doc.sourceCellX}, ${doc.sourceCellY})` : 'unplaced'} ·
|
|
615
|
+
${stepCount} step(s) · ${objCount} objective(s)
|
|
616
|
+
</div>
|
|
617
|
+
</div>
|
|
618
|
+
<div class="in fll" style="display:flex;gap:4px;">${loadBtn}${delBtn}</div>
|
|
619
|
+
</div>`,
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
if (!out) out = '<div style="color:#888;font-size:12px;">No quests found.</div>';
|
|
623
|
+
htmls('.action-engine-quest-cards', out);
|
|
624
|
+
for (const doc of items) {
|
|
625
|
+
if (s(`.btn-aeq-load-${doc._id}`))
|
|
626
|
+
s(`.btn-aeq-load-${doc._id}`).onclick = async () => {
|
|
627
|
+
const res = await CyberiaQuestService.get({ id: doc._id });
|
|
628
|
+
if (res.status === 'success' && res.data) ActionEngineCyberia.loadQuest(res.data);
|
|
629
|
+
};
|
|
630
|
+
if (s(`.btn-aeq-del-${doc._id}`))
|
|
631
|
+
s(`.btn-aeq-del-${doc._id}`).onclick = () => ActionEngineCyberia.deleteQuest(doc._id);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
static async renderActionCards() {
|
|
636
|
+
const container = s('.action-engine-action-cards');
|
|
637
|
+
if (!container) return;
|
|
638
|
+
const filter = (s('.action-engine-action-search')?.value || '').toLowerCase();
|
|
639
|
+
const items = ActionEngineCyberia.actionListCache.filter(
|
|
640
|
+
(d) => !filter || (d.code || '').toLowerCase().includes(filter) || (d.label || '').toLowerCase().includes(filter),
|
|
641
|
+
);
|
|
642
|
+
let out = '';
|
|
643
|
+
for (const doc of items) {
|
|
644
|
+
const [loadBtn, delBtn] = await ActionEngineCyberia.cardActionsHtml(
|
|
645
|
+
`btn-aea-load-${doc._id}`,
|
|
646
|
+
`btn-aea-del-${doc._id}`,
|
|
647
|
+
);
|
|
648
|
+
out += ActionEngineCyberia.cardWrap(
|
|
649
|
+
html`<div class="fl" style="align-items:center;">
|
|
650
|
+
<div class="in fll" style="flex:1;">
|
|
651
|
+
<div style="font-weight:bold;font-family:monospace;font-size:12px;">${doc.code}</div>
|
|
652
|
+
<div style="font-size:11px;color:#888;">${doc.label || ''}</div>
|
|
653
|
+
<div style="font-size:11px;color:#888;">
|
|
654
|
+
${doc.sourceMapCode ? `${doc.sourceMapCode} (${doc.sourceCellX}, ${doc.sourceCellY})` : 'unplaced'} ·
|
|
655
|
+
dialog: ${doc.dialogCode || '—'} · ${(doc.questDialogueCodes || []).length} quest-dialog(s)
|
|
656
|
+
</div>
|
|
657
|
+
</div>
|
|
658
|
+
<div class="in fll" style="display:flex;gap:4px;">${loadBtn}${delBtn}</div>
|
|
659
|
+
</div>`,
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
if (!out) out = '<div style="color:#888;font-size:12px;">No actions found.</div>';
|
|
663
|
+
htmls('.action-engine-action-cards', out);
|
|
664
|
+
for (const doc of items) {
|
|
665
|
+
if (s(`.btn-aea-load-${doc._id}`))
|
|
666
|
+
s(`.btn-aea-load-${doc._id}`).onclick = async () => {
|
|
667
|
+
const res = await CyberiaActionService.get({ id: doc._id });
|
|
668
|
+
if (res.status === 'success' && res.data) ActionEngineCyberia.loadAction(res.data);
|
|
669
|
+
};
|
|
670
|
+
if (s(`.btn-aea-del-${doc._id}`))
|
|
671
|
+
s(`.btn-aea-del-${doc._id}`).onclick = () => ActionEngineCyberia.deleteAction(doc._id);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
static async renderDialogueCards() {
|
|
676
|
+
const container = s('.action-engine-dialogue-cards');
|
|
677
|
+
if (!container) return;
|
|
678
|
+
const filter = (s('.action-engine-dialogue-search')?.value || '').toLowerCase();
|
|
679
|
+
const items = ActionEngineCyberia.dialogueListCache.filter(
|
|
680
|
+
(d) =>
|
|
681
|
+
!filter ||
|
|
682
|
+
(d.code || '').toLowerCase().includes(filter) ||
|
|
683
|
+
(d.speaker || '').toLowerCase().includes(filter) ||
|
|
684
|
+
(d.text || '').toLowerCase().includes(filter),
|
|
685
|
+
);
|
|
686
|
+
let out = '';
|
|
687
|
+
for (const doc of items) {
|
|
688
|
+
const [loadBtn, delBtn] = await ActionEngineCyberia.cardActionsHtml(
|
|
689
|
+
`btn-aed-load-${doc._id}`,
|
|
690
|
+
`btn-aed-del-${doc._id}`,
|
|
691
|
+
);
|
|
692
|
+
const snippet = (doc.text || '').length > 60 ? `${doc.text.slice(0, 60)}…` : doc.text || '';
|
|
693
|
+
out += ActionEngineCyberia.cardWrap(
|
|
694
|
+
html`<div class="fl" style="align-items:center;">
|
|
695
|
+
<div class="in fll" style="flex:1;">
|
|
696
|
+
<div style="font-weight:bold;font-family:monospace;font-size:12px;">${doc.code} #${doc.order ?? 0}</div>
|
|
697
|
+
<div style="font-size:11px;color:#888;">${doc.speaker || '—'} · ${doc.mood || 'neutral'}</div>
|
|
698
|
+
<div style="font-size:11px;color:#888;">${snippet}</div>
|
|
699
|
+
</div>
|
|
700
|
+
<div class="in fll" style="display:flex;gap:4px;">${loadBtn}${delBtn}</div>
|
|
701
|
+
</div>`,
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
if (!out) out = '<div style="color:#888;font-size:12px;">No dialogue lines found.</div>';
|
|
705
|
+
htmls('.action-engine-dialogue-cards', out);
|
|
706
|
+
for (const doc of items) {
|
|
707
|
+
if (s(`.btn-aed-load-${doc._id}`))
|
|
708
|
+
s(`.btn-aed-load-${doc._id}`).onclick = async () => {
|
|
709
|
+
const res = await CyberiaDialogueService.get({ id: doc._id });
|
|
710
|
+
if (res.status === 'success' && res.data) ActionEngineCyberia.loadDialogue(res.data);
|
|
711
|
+
};
|
|
712
|
+
if (s(`.btn-aed-del-${doc._id}`))
|
|
713
|
+
s(`.btn-aed-del-${doc._id}`).onclick = () => ActionEngineCyberia.deleteDialogue(doc._id);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
static async refreshQuestList() {
|
|
718
|
+
const res = await CyberiaQuestService.get({ limit: 500 });
|
|
719
|
+
ActionEngineCyberia.questListCache = res?.data?.data || [];
|
|
720
|
+
await ActionEngineCyberia.renderQuestCards();
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
static async refreshActionList() {
|
|
724
|
+
const res = await CyberiaActionService.get({ limit: 500 });
|
|
725
|
+
ActionEngineCyberia.actionListCache = res?.data?.data || [];
|
|
726
|
+
await ActionEngineCyberia.renderActionCards();
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
static async refreshDialogueList() {
|
|
730
|
+
const res = await CyberiaDialogueService.get({ limit: 500 });
|
|
731
|
+
ActionEngineCyberia.dialogueListCache = res?.data?.data || [];
|
|
732
|
+
await ActionEngineCyberia.renderDialogueCards();
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
static notifyResult(result, isUpdate) {
|
|
736
|
+
NotificationManager.Push({
|
|
737
|
+
html:
|
|
738
|
+
result.status === 'error'
|
|
739
|
+
? result.message
|
|
740
|
+
: isUpdate
|
|
741
|
+
? Translate.instance('success-update-item')
|
|
742
|
+
: Translate.instance('success-create-item'),
|
|
743
|
+
status: result.status,
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// ── Quest payload / load / persistence ──────────────────────────────────
|
|
748
|
+
static getQuestPayload() {
|
|
749
|
+
ActionEngineCyberia.syncStepDescriptions();
|
|
750
|
+
const csv = (v) =>
|
|
751
|
+
(v || '')
|
|
752
|
+
.split(',')
|
|
753
|
+
.map((t) => t.trim())
|
|
754
|
+
.filter(Boolean);
|
|
755
|
+
const payload = {
|
|
756
|
+
code: s('.action-engine-quest-code')?.value?.trim() || '',
|
|
757
|
+
title: s('.action-engine-quest-title')?.value?.trim() || '',
|
|
758
|
+
description: s('.action-engine-quest-description')?.value || '',
|
|
759
|
+
prerequisiteCodes: csv(s('.action-engine-quest-prerequisites')?.value),
|
|
760
|
+
unlocksQuestCodes: csv(s('.action-engine-quest-unlocks')?.value),
|
|
761
|
+
steps: ActionEngineCyberia.questSteps.map((st, i) => ({
|
|
762
|
+
id: st.id || `step-${i + 1}`,
|
|
763
|
+
description: st.description || '',
|
|
764
|
+
objectives: st.objectives.map((o) => ({ ...o })),
|
|
765
|
+
})),
|
|
766
|
+
rewards: ActionEngineCyberia.questRewards.map((r) => ({ ...r })),
|
|
767
|
+
};
|
|
768
|
+
const mapCode = s('.action-engine-quest-map-code')?.value?.trim();
|
|
769
|
+
const cellX = parseInt(s('.action-engine-cell-x')?.value);
|
|
770
|
+
const cellY = parseInt(s('.action-engine-cell-y')?.value);
|
|
771
|
+
if (mapCode) payload.sourceMapCode = mapCode;
|
|
772
|
+
if (Number.isFinite(cellX)) payload.sourceCellX = cellX;
|
|
773
|
+
if (Number.isFinite(cellY)) payload.sourceCellY = cellY;
|
|
774
|
+
return payload;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
static async loadQuest(doc) {
|
|
778
|
+
ActionEngineCyberia.setMode('quest');
|
|
779
|
+
ActionEngineCyberia.currentQuestId = doc._id || null;
|
|
780
|
+
if (s('.action-engine-quest-code')) s('.action-engine-quest-code').value = doc.code || '';
|
|
781
|
+
if (s('.action-engine-quest-title')) s('.action-engine-quest-title').value = doc.title || '';
|
|
782
|
+
if (s('.action-engine-quest-description')) s('.action-engine-quest-description').value = doc.description || '';
|
|
783
|
+
if (s('.action-engine-quest-prerequisites'))
|
|
784
|
+
s('.action-engine-quest-prerequisites').value = (doc.prerequisiteCodes || []).join(', ');
|
|
785
|
+
if (s('.action-engine-quest-unlocks'))
|
|
786
|
+
s('.action-engine-quest-unlocks').value = (doc.unlocksQuestCodes || []).join(', ');
|
|
787
|
+
if (s('.action-engine-quest-map-code')) s('.action-engine-quest-map-code').value = doc.sourceMapCode || '';
|
|
788
|
+
if (s('.action-engine-cell-x')) s('.action-engine-cell-x').value = doc.sourceCellX ?? '';
|
|
789
|
+
if (s('.action-engine-cell-y')) s('.action-engine-cell-y').value = doc.sourceCellY ?? '';
|
|
790
|
+
ActionEngineCyberia.questSteps = (doc.steps || []).map((st, i) => ({
|
|
791
|
+
id: st.id || `step-${i + 1}`,
|
|
792
|
+
description: st.description || '',
|
|
793
|
+
objectives: (st.objectives || []).map((o) => ({ type: o.type, itemId: o.itemId, quantity: o.quantity || 1 })),
|
|
794
|
+
}));
|
|
795
|
+
ActionEngineCyberia.questRewards = (doc.rewards || []).map((r) => ({
|
|
796
|
+
itemId: r.itemId,
|
|
797
|
+
quantity: r.quantity || 1,
|
|
798
|
+
}));
|
|
799
|
+
ActionEngineCyberia.renderStepList();
|
|
800
|
+
ActionEngineCyberia.renderRewardList();
|
|
801
|
+
await ActionEngineCyberia.syncMapForLoadedDoc(doc);
|
|
802
|
+
NotificationManager.Push({ html: `Quest "${doc.code}" loaded`, status: 'success' });
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
static resetQuest() {
|
|
806
|
+
ActionEngineCyberia.currentQuestId = null;
|
|
807
|
+
ActionEngineCyberia.questSteps = [];
|
|
808
|
+
ActionEngineCyberia.questRewards = [];
|
|
809
|
+
for (const cls of [
|
|
810
|
+
'action-engine-quest-code',
|
|
811
|
+
'action-engine-quest-title',
|
|
812
|
+
'action-engine-quest-description',
|
|
813
|
+
'action-engine-quest-prerequisites',
|
|
814
|
+
'action-engine-quest-unlocks',
|
|
815
|
+
])
|
|
816
|
+
if (s(`.${cls}`)) s(`.${cls}`).value = '';
|
|
817
|
+
ActionEngineCyberia.renderStepList();
|
|
818
|
+
ActionEngineCyberia.renderRewardList();
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
static async saveQuest() {
|
|
822
|
+
const body = ActionEngineCyberia.getQuestPayload();
|
|
823
|
+
if (!body.code || !body.title) {
|
|
824
|
+
NotificationManager.Push({ html: 'Quest code and title are required.', status: 'error' });
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
const isUpdate = !!ActionEngineCyberia.currentQuestId;
|
|
828
|
+
const result = isUpdate
|
|
829
|
+
? await CyberiaQuestService.put({ id: ActionEngineCyberia.currentQuestId, body })
|
|
830
|
+
: await CyberiaQuestService.post({ body });
|
|
831
|
+
ActionEngineCyberia.notifyResult(result, isUpdate);
|
|
832
|
+
if (result.status === 'success') {
|
|
833
|
+
if (result.data?._id) ActionEngineCyberia.currentQuestId = result.data._id;
|
|
834
|
+
await ActionEngineCyberia.refreshQuestList();
|
|
835
|
+
await ActionEngineCyberia.refreshAssignments();
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
static async cloneQuest() {
|
|
840
|
+
if (!ActionEngineCyberia.currentQuestId) {
|
|
841
|
+
NotificationManager.Push({ html: 'Load a quest to clone first.', status: 'warning' });
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
const body = ActionEngineCyberia.getQuestPayload();
|
|
845
|
+
body.code = `${body.code}-clone`;
|
|
846
|
+
const result = await CyberiaQuestService.post({ body });
|
|
847
|
+
ActionEngineCyberia.notifyResult(result, false);
|
|
848
|
+
if (result.status === 'success') {
|
|
849
|
+
if (result.data?._id) ActionEngineCyberia.currentQuestId = result.data._id;
|
|
850
|
+
if (s('.action-engine-quest-code')) s('.action-engine-quest-code').value = body.code;
|
|
851
|
+
await ActionEngineCyberia.refreshQuestList();
|
|
852
|
+
await ActionEngineCyberia.refreshAssignments();
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
static async deleteQuest(id) {
|
|
857
|
+
const targetId = id || ActionEngineCyberia.currentQuestId;
|
|
858
|
+
if (!targetId) return;
|
|
859
|
+
const result = await CyberiaQuestService.delete({ id: targetId });
|
|
860
|
+
NotificationManager.Push({
|
|
861
|
+
html: result.status === 'error' ? result.message : Translate.instance('item-success-delete'),
|
|
862
|
+
status: result.status,
|
|
863
|
+
});
|
|
864
|
+
if (result.status === 'success') {
|
|
865
|
+
if (targetId === ActionEngineCyberia.currentQuestId) ActionEngineCyberia.resetQuest();
|
|
866
|
+
await ActionEngineCyberia.refreshQuestList();
|
|
867
|
+
await ActionEngineCyberia.refreshAssignments();
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// ── Action payload / load / persistence ─────────────────────────────────
|
|
872
|
+
static getActionPayload() {
|
|
873
|
+
const payload = {
|
|
874
|
+
...ActionEngineCyberia.loadedActionPayloadExtras,
|
|
875
|
+
code: s('.action-engine-action-code')?.value?.trim() || '',
|
|
876
|
+
label: s('.action-engine-action-label')?.value?.trim() || '',
|
|
877
|
+
dialogCode: ActionEngineCyberia.getSingleDropdownValue(ActionEngineCyberia.ids.actionDialogPicker),
|
|
878
|
+
questDialogueCodes: ActionEngineCyberia.actionQuestDialogues.map((d) => ({ ...d })),
|
|
879
|
+
};
|
|
880
|
+
const mapCode = s('.action-engine-action-map-code')?.value?.trim();
|
|
881
|
+
const cellX = parseInt(s('.action-engine-cell-x')?.value);
|
|
882
|
+
const cellY = parseInt(s('.action-engine-cell-y')?.value);
|
|
883
|
+
if (mapCode) payload.sourceMapCode = mapCode;
|
|
884
|
+
if (Number.isFinite(cellX)) payload.sourceCellX = cellX;
|
|
885
|
+
if (Number.isFinite(cellY)) payload.sourceCellY = cellY;
|
|
886
|
+
return payload;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
static async loadAction(doc) {
|
|
890
|
+
ActionEngineCyberia.setMode('action');
|
|
891
|
+
ActionEngineCyberia.currentActionId = doc._id || null;
|
|
892
|
+
// Out-of-scope payloads (shop/craft/storage) are preserved verbatim so
|
|
893
|
+
// editing an action here never drops data the engine doesn't manage yet.
|
|
894
|
+
const {
|
|
895
|
+
_id,
|
|
896
|
+
code,
|
|
897
|
+
label,
|
|
898
|
+
dialogCode,
|
|
899
|
+
questDialogueCodes,
|
|
900
|
+
sourceMapCode,
|
|
901
|
+
sourceCellX,
|
|
902
|
+
sourceCellY,
|
|
903
|
+
createdAt,
|
|
904
|
+
updatedAt,
|
|
905
|
+
__v,
|
|
906
|
+
...extras
|
|
907
|
+
} = doc;
|
|
908
|
+
ActionEngineCyberia.loadedActionPayloadExtras = extras;
|
|
909
|
+
if (s('.action-engine-action-code')) s('.action-engine-action-code').value = doc.code || '';
|
|
910
|
+
if (s('.action-engine-action-label')) s('.action-engine-action-label').value = doc.label || '';
|
|
911
|
+
if (s('.action-engine-action-map-code')) s('.action-engine-action-map-code').value = doc.sourceMapCode || '';
|
|
912
|
+
if (s('.action-engine-cell-x')) s('.action-engine-cell-x').value = doc.sourceCellX ?? '';
|
|
913
|
+
if (s('.action-engine-cell-y')) s('.action-engine-cell-y').value = doc.sourceCellY ?? '';
|
|
914
|
+
ActionEngineCyberia.setSingleDropdownValue(ActionEngineCyberia.ids.actionDialogPicker, doc.dialogCode || '');
|
|
915
|
+
ActionEngineCyberia.actionQuestDialogues = (doc.questDialogueCodes || []).map((d) => ({
|
|
916
|
+
questCode: d.questCode,
|
|
917
|
+
dialogCode: d.dialogCode,
|
|
918
|
+
}));
|
|
919
|
+
ActionEngineCyberia.renderQuestDialogueList();
|
|
920
|
+
await ActionEngineCyberia.syncMapForLoadedDoc(doc);
|
|
921
|
+
NotificationManager.Push({ html: `Action "${doc.code}" loaded`, status: 'success' });
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
static resetAction() {
|
|
925
|
+
ActionEngineCyberia.currentActionId = null;
|
|
926
|
+
ActionEngineCyberia.actionQuestDialogues = [];
|
|
927
|
+
ActionEngineCyberia.loadedActionPayloadExtras = {};
|
|
928
|
+
for (const cls of ['action-engine-action-code', 'action-engine-action-label'])
|
|
929
|
+
if (s(`.${cls}`)) s(`.${cls}`).value = '';
|
|
930
|
+
ActionEngineCyberia.setSingleDropdownValue(ActionEngineCyberia.ids.actionDialogPicker, '');
|
|
931
|
+
ActionEngineCyberia.renderQuestDialogueList();
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
static async saveAction() {
|
|
935
|
+
const body = ActionEngineCyberia.getActionPayload();
|
|
936
|
+
if (!body.code) {
|
|
937
|
+
NotificationManager.Push({ html: 'Action code is required.', status: 'error' });
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
const isUpdate = !!ActionEngineCyberia.currentActionId;
|
|
941
|
+
const result = isUpdate
|
|
942
|
+
? await CyberiaActionService.put({ id: ActionEngineCyberia.currentActionId, body })
|
|
943
|
+
: await CyberiaActionService.post({ body });
|
|
944
|
+
ActionEngineCyberia.notifyResult(result, isUpdate);
|
|
945
|
+
if (result.status === 'success') {
|
|
946
|
+
if (result.data?._id) ActionEngineCyberia.currentActionId = result.data._id;
|
|
947
|
+
await ActionEngineCyberia.refreshActionList();
|
|
948
|
+
await ActionEngineCyberia.refreshAssignments();
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
static async cloneAction() {
|
|
953
|
+
if (!ActionEngineCyberia.currentActionId) {
|
|
954
|
+
NotificationManager.Push({ html: 'Load an action to clone first.', status: 'warning' });
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
const body = ActionEngineCyberia.getActionPayload();
|
|
958
|
+
body.code = `${body.code}-clone`;
|
|
959
|
+
const result = await CyberiaActionService.post({ body });
|
|
960
|
+
ActionEngineCyberia.notifyResult(result, false);
|
|
961
|
+
if (result.status === 'success') {
|
|
962
|
+
if (result.data?._id) ActionEngineCyberia.currentActionId = result.data._id;
|
|
963
|
+
if (s('.action-engine-action-code')) s('.action-engine-action-code').value = body.code;
|
|
964
|
+
await ActionEngineCyberia.refreshActionList();
|
|
965
|
+
await ActionEngineCyberia.refreshAssignments();
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
static async deleteAction(id) {
|
|
970
|
+
const targetId = id || ActionEngineCyberia.currentActionId;
|
|
971
|
+
if (!targetId) return;
|
|
972
|
+
const result = await CyberiaActionService.delete({ id: targetId });
|
|
973
|
+
NotificationManager.Push({
|
|
974
|
+
html: result.status === 'error' ? result.message : Translate.instance('item-success-delete'),
|
|
975
|
+
status: result.status,
|
|
976
|
+
});
|
|
977
|
+
if (result.status === 'success') {
|
|
978
|
+
if (targetId === ActionEngineCyberia.currentActionId) ActionEngineCyberia.resetAction();
|
|
979
|
+
await ActionEngineCyberia.refreshActionList();
|
|
980
|
+
await ActionEngineCyberia.refreshAssignments();
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// ── Dialogue line persistence ───────────────────────────────────────────
|
|
985
|
+
static getDialoguePayload() {
|
|
986
|
+
return {
|
|
987
|
+
code: s('.action-engine-dialogue-code')?.value?.trim() || '',
|
|
988
|
+
order: parseInt(s('.action-engine-dialogue-order')?.value) || 0,
|
|
989
|
+
speaker: s('.action-engine-dialogue-speaker')?.value || '',
|
|
990
|
+
text: s('.action-engine-dialogue-text')?.value || '',
|
|
991
|
+
mood: s('.action-engine-dialogue-mood')?.value?.trim() || 'neutral',
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
static loadDialogue(doc) {
|
|
996
|
+
ActionEngineCyberia.setMode('dialogue');
|
|
997
|
+
ActionEngineCyberia.currentDialogueId = doc._id || null;
|
|
998
|
+
if (s('.action-engine-dialogue-code')) s('.action-engine-dialogue-code').value = doc.code || '';
|
|
999
|
+
if (s('.action-engine-dialogue-order')) s('.action-engine-dialogue-order').value = doc.order ?? 0;
|
|
1000
|
+
if (s('.action-engine-dialogue-speaker')) s('.action-engine-dialogue-speaker').value = doc.speaker || '';
|
|
1001
|
+
if (s('.action-engine-dialogue-text')) s('.action-engine-dialogue-text').value = doc.text || '';
|
|
1002
|
+
if (s('.action-engine-dialogue-mood')) s('.action-engine-dialogue-mood').value = doc.mood || 'neutral';
|
|
1003
|
+
NotificationManager.Push({ html: `Dialogue line "${doc.code}" loaded`, status: 'success' });
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
static resetDialogue() {
|
|
1007
|
+
ActionEngineCyberia.currentDialogueId = null;
|
|
1008
|
+
if (s('.action-engine-dialogue-code')) s('.action-engine-dialogue-code').value = '';
|
|
1009
|
+
if (s('.action-engine-dialogue-order')) s('.action-engine-dialogue-order').value = 0;
|
|
1010
|
+
if (s('.action-engine-dialogue-speaker')) s('.action-engine-dialogue-speaker').value = '';
|
|
1011
|
+
if (s('.action-engine-dialogue-text')) s('.action-engine-dialogue-text').value = '';
|
|
1012
|
+
if (s('.action-engine-dialogue-mood')) s('.action-engine-dialogue-mood').value = 'neutral';
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
static async saveDialogue() {
|
|
1016
|
+
const body = ActionEngineCyberia.getDialoguePayload();
|
|
1017
|
+
if (!body.code || !body.text) {
|
|
1018
|
+
NotificationManager.Push({ html: 'Dialogue code and text are required.', status: 'error' });
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
const isUpdate = !!ActionEngineCyberia.currentDialogueId;
|
|
1022
|
+
const result = isUpdate
|
|
1023
|
+
? await CyberiaDialogueService.put({ id: ActionEngineCyberia.currentDialogueId, body })
|
|
1024
|
+
: await CyberiaDialogueService.post({ body });
|
|
1025
|
+
ActionEngineCyberia.notifyResult(result, isUpdate);
|
|
1026
|
+
if (result.status === 'success') {
|
|
1027
|
+
if (result.data?._id) ActionEngineCyberia.currentDialogueId = result.data._id;
|
|
1028
|
+
await ActionEngineCyberia.refreshDialogueList();
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
static async deleteDialogue(id) {
|
|
1033
|
+
const targetId = id || ActionEngineCyberia.currentDialogueId;
|
|
1034
|
+
if (!targetId) return;
|
|
1035
|
+
const result = await CyberiaDialogueService.delete({ id: targetId });
|
|
1036
|
+
NotificationManager.Push({
|
|
1037
|
+
html: result.status === 'error' ? result.message : Translate.instance('item-success-delete'),
|
|
1038
|
+
status: result.status,
|
|
1039
|
+
});
|
|
1040
|
+
if (result.status === 'success') {
|
|
1041
|
+
if (targetId === ActionEngineCyberia.currentDialogueId) ActionEngineCyberia.resetDialogue();
|
|
1042
|
+
await ActionEngineCyberia.refreshDialogueList();
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// ── Skill list editor + persistence (own model: CyberiaSkill) ────────────
|
|
1047
|
+
static async renderSkillDefList() {
|
|
1048
|
+
const container = s('.action-engine-skill-def-list');
|
|
1049
|
+
if (!container) return;
|
|
1050
|
+
let out = '';
|
|
1051
|
+
for (let i = 0; i < ActionEngineCyberia.skillDefs.length; i++) {
|
|
1052
|
+
const sk = ActionEngineCyberia.skillDefs[i];
|
|
1053
|
+
out += html`<div
|
|
1054
|
+
class="fl"
|
|
1055
|
+
style="border-bottom:1px solid ${subtleBorder()};padding:3px 0;align-items:center;font-size:12px;font-family:monospace;"
|
|
1056
|
+
>
|
|
1057
|
+
<div class="in fll" style="flex:1;">
|
|
1058
|
+
<span style="color:${darkTheme ? '#8cf' : '#246'};">${sk.logicEventId}</span>${sk.name
|
|
1059
|
+
? ` · ${sk.name}`
|
|
1060
|
+
: ''}${sk.summonedEntityItemId
|
|
1061
|
+
? html`<span style="margin:0 4px;">→</span>${sk.summonedEntityItemId}`
|
|
1062
|
+
: ''}
|
|
1063
|
+
</div>
|
|
1064
|
+
${await BtnIcon.instance({
|
|
1065
|
+
class: `btn-aes-rmskill-${i}`,
|
|
1066
|
+
label: html`<i class="fa-solid fa-xmark"></i>`,
|
|
1067
|
+
style: 'min-width:30px;padding:2px 8px;',
|
|
1068
|
+
})}
|
|
1069
|
+
</div>`;
|
|
1070
|
+
}
|
|
1071
|
+
if (!out) out = '<div style="color:#888;font-size:12px;">No skills. Add at least one logic event.</div>';
|
|
1072
|
+
htmls('.action-engine-skill-def-list', out);
|
|
1073
|
+
for (let i = 0; i < ActionEngineCyberia.skillDefs.length; i++) {
|
|
1074
|
+
if (s(`.btn-aes-rmskill-${i}`))
|
|
1075
|
+
s(`.btn-aes-rmskill-${i}`).onclick = () => {
|
|
1076
|
+
ActionEngineCyberia.skillDefs.splice(i, 1);
|
|
1077
|
+
ActionEngineCyberia.renderSkillDefList();
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
static addSkillDef() {
|
|
1083
|
+
const logicEventId = ActionEngineCyberia.getSingleDropdownValue('action-engine-skill-logic-event');
|
|
1084
|
+
const name = s('.action-engine-skill-name')?.value?.trim() || '';
|
|
1085
|
+
const description = s('.action-engine-skill-description')?.value || '';
|
|
1086
|
+
const summonedEntityItemId = ActionEngineCyberia.getSingleDropdownValue(
|
|
1087
|
+
ActionEngineCyberia.ids.skillSummonedItemPicker,
|
|
1088
|
+
);
|
|
1089
|
+
if (!logicEventId) {
|
|
1090
|
+
NotificationManager.Push({ html: 'Select a canonical logic event id first.', status: 'error' });
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
if (ActionEngineCyberia.skillDefs.some((sk) => sk.logicEventId === logicEventId)) {
|
|
1094
|
+
NotificationManager.Push({ html: `Logic event "${logicEventId}" already added.`, status: 'error' });
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
ActionEngineCyberia.skillDefs.push({ logicEventId, name, description, summonedEntityItemId });
|
|
1098
|
+
ActionEngineCyberia.setSingleDropdownValue('action-engine-skill-logic-event', '');
|
|
1099
|
+
for (const cls of ['action-engine-skill-name', 'action-engine-skill-description'])
|
|
1100
|
+
if (s(`.${cls}`)) s(`.${cls}`).value = '';
|
|
1101
|
+
ActionEngineCyberia.setSingleDropdownValue(ActionEngineCyberia.ids.skillSummonedItemPicker, '');
|
|
1102
|
+
ActionEngineCyberia.renderSkillDefList();
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
static async renderSkillCards() {
|
|
1106
|
+
const container = s('.action-engine-skill-cards');
|
|
1107
|
+
if (!container) return;
|
|
1108
|
+
const filter = (s('.action-engine-skill-search')?.value || '').toLowerCase();
|
|
1109
|
+
const items = ActionEngineCyberia.skillListCache.filter(
|
|
1110
|
+
(d) =>
|
|
1111
|
+
!filter ||
|
|
1112
|
+
(d.triggerItemId || '').toLowerCase().includes(filter) ||
|
|
1113
|
+
(d.logicEventIds || []).some((l) => (l || '').toLowerCase().includes(filter)),
|
|
1114
|
+
);
|
|
1115
|
+
let out = '';
|
|
1116
|
+
for (const doc of items) {
|
|
1117
|
+
const [loadBtn, delBtn] = await ActionEngineCyberia.cardActionsHtml(
|
|
1118
|
+
`btn-aes-load-${doc._id}`,
|
|
1119
|
+
`btn-aes-del-${doc._id}`,
|
|
1120
|
+
);
|
|
1121
|
+
out += ActionEngineCyberia.cardWrap(
|
|
1122
|
+
html`<div class="fl" style="align-items:center;">
|
|
1123
|
+
<div class="in fll" style="flex:1;">
|
|
1124
|
+
<div style="font-weight:bold;font-family:monospace;font-size:12px;">${doc.triggerItemId}</div>
|
|
1125
|
+
<div style="font-size:11px;color:#888;">${(doc.logicEventIds || []).join(', ') || '—'}</div>
|
|
1126
|
+
<div style="font-size:11px;color:#888;">${(doc.skills || []).length} skill(s)</div>
|
|
1127
|
+
</div>
|
|
1128
|
+
<div class="in fll" style="display:flex;gap:4px;">${loadBtn}${delBtn}</div>
|
|
1129
|
+
</div>`,
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
if (!out) out = '<div style="color:#888;font-size:12px;">No skills found.</div>';
|
|
1133
|
+
htmls('.action-engine-skill-cards', out);
|
|
1134
|
+
for (const doc of items) {
|
|
1135
|
+
if (s(`.btn-aes-load-${doc._id}`))
|
|
1136
|
+
s(`.btn-aes-load-${doc._id}`).onclick = async () => {
|
|
1137
|
+
const res = await CyberiaSkillService.get({ id: doc._id });
|
|
1138
|
+
if (res.status === 'success' && res.data) ActionEngineCyberia.loadSkill(res.data);
|
|
1139
|
+
};
|
|
1140
|
+
if (s(`.btn-aes-del-${doc._id}`))
|
|
1141
|
+
s(`.btn-aes-del-${doc._id}`).onclick = () => ActionEngineCyberia.deleteSkill(doc._id);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
static async refreshSkillList() {
|
|
1146
|
+
const res = await CyberiaSkillService.get({ limit: 500 });
|
|
1147
|
+
ActionEngineCyberia.skillListCache = res?.data?.data || [];
|
|
1148
|
+
await ActionEngineCyberia.renderSkillCards();
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
static getSkillPayload() {
|
|
1152
|
+
const triggerItemId = ActionEngineCyberia.getSingleDropdownValue(ActionEngineCyberia.ids.skillTriggerItemPicker);
|
|
1153
|
+
const skills = ActionEngineCyberia.skillDefs.map((sk) => ({
|
|
1154
|
+
logicEventId: sk.logicEventId,
|
|
1155
|
+
name: sk.name || '',
|
|
1156
|
+
description: sk.description || '',
|
|
1157
|
+
summonedEntityItemId: sk.summonedEntityItemId || '',
|
|
1158
|
+
}));
|
|
1159
|
+
// logicEventIds is the compact discriminator list — derived from the skills
|
|
1160
|
+
// so the two can never drift (matches DefaultSkillConfig).
|
|
1161
|
+
const logicEventIds = [...new Set(skills.map((sk) => sk.logicEventId).filter(Boolean))];
|
|
1162
|
+
return { triggerItemId, logicEventIds, skills };
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
static loadSkill(doc) {
|
|
1166
|
+
ActionEngineCyberia.setMode('skill');
|
|
1167
|
+
ActionEngineCyberia.currentSkillId = doc._id || null;
|
|
1168
|
+
ActionEngineCyberia.setSingleDropdownValue(ActionEngineCyberia.ids.skillTriggerItemPicker, doc.triggerItemId || '');
|
|
1169
|
+
ActionEngineCyberia.skillDefs = (doc.skills || []).map((sk) => ({
|
|
1170
|
+
logicEventId: sk.logicEventId,
|
|
1171
|
+
name: sk.name || '',
|
|
1172
|
+
description: sk.description || '',
|
|
1173
|
+
summonedEntityItemId: sk.summonedEntityItemId || '',
|
|
1174
|
+
}));
|
|
1175
|
+
ActionEngineCyberia.renderSkillDefList();
|
|
1176
|
+
NotificationManager.Push({ html: `Skill "${doc.triggerItemId}" loaded`, status: 'success' });
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
static resetSkill() {
|
|
1180
|
+
ActionEngineCyberia.currentSkillId = null;
|
|
1181
|
+
ActionEngineCyberia.skillDefs = [];
|
|
1182
|
+
ActionEngineCyberia.setSingleDropdownValue(ActionEngineCyberia.ids.skillTriggerItemPicker, '');
|
|
1183
|
+
ActionEngineCyberia.setSingleDropdownValue(ActionEngineCyberia.ids.skillSummonedItemPicker, '');
|
|
1184
|
+
ActionEngineCyberia.setSingleDropdownValue('action-engine-skill-logic-event', '');
|
|
1185
|
+
for (const cls of ['action-engine-skill-name', 'action-engine-skill-description'])
|
|
1186
|
+
if (s(`.${cls}`)) s(`.${cls}`).value = '';
|
|
1187
|
+
ActionEngineCyberia.renderSkillDefList();
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
static async saveSkill() {
|
|
1191
|
+
const body = ActionEngineCyberia.getSkillPayload();
|
|
1192
|
+
if (!body.triggerItemId) {
|
|
1193
|
+
NotificationManager.Push({ html: 'Trigger item id is required.', status: 'error' });
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
if (body.skills.length === 0) {
|
|
1197
|
+
NotificationManager.Push({ html: 'Add at least one skill (logic event).', status: 'error' });
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
const isUpdate = !!ActionEngineCyberia.currentSkillId;
|
|
1201
|
+
const result = isUpdate
|
|
1202
|
+
? await CyberiaSkillService.put({ id: ActionEngineCyberia.currentSkillId, body })
|
|
1203
|
+
: await CyberiaSkillService.post({ body });
|
|
1204
|
+
ActionEngineCyberia.notifyResult(result, isUpdate);
|
|
1205
|
+
if (result.status === 'success') {
|
|
1206
|
+
if (result.data?._id) ActionEngineCyberia.currentSkillId = result.data._id;
|
|
1207
|
+
await ActionEngineCyberia.refreshSkillList();
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
static async deleteSkill(id) {
|
|
1212
|
+
const targetId = id || ActionEngineCyberia.currentSkillId;
|
|
1213
|
+
if (!targetId) return;
|
|
1214
|
+
const result = await CyberiaSkillService.delete({ id: targetId });
|
|
1215
|
+
NotificationManager.Push({
|
|
1216
|
+
html: result.status === 'error' ? result.message : Translate.instance('item-success-delete'),
|
|
1217
|
+
status: result.status,
|
|
1218
|
+
});
|
|
1219
|
+
if (result.status === 'success') {
|
|
1220
|
+
if (targetId === ActionEngineCyberia.currentSkillId) ActionEngineCyberia.resetSkill();
|
|
1221
|
+
await ActionEngineCyberia.refreshSkillList();
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// ── Render ──────────────────────────────────────────────────────────────
|
|
1226
|
+
static async render(options = {}) {
|
|
1227
|
+
const { appStore } = options;
|
|
1228
|
+
const role = appStore?.Data?.user?.main?.model?.user?.role || 'guest';
|
|
1229
|
+
ActionEngineCyberia.canMutate = commonModeratorGuard(role);
|
|
1230
|
+
ActionEngineCyberia.mode = 'quest';
|
|
1231
|
+
ActionEngineCyberia.mapCode = '';
|
|
1232
|
+
ActionEngineCyberia.mapDoc = null;
|
|
1233
|
+
ActionEngineCyberia.imageCache = {};
|
|
1234
|
+
ActionEngineCyberia.assignments = { quests: [], actions: [] };
|
|
1235
|
+
ActionEngineCyberia.questListCache = [];
|
|
1236
|
+
ActionEngineCyberia.actionListCache = [];
|
|
1237
|
+
ActionEngineCyberia.dialogueListCache = [];
|
|
1238
|
+
ActionEngineCyberia.skillListCache = [];
|
|
1239
|
+
ActionEngineCyberia.currentQuestId = null;
|
|
1240
|
+
ActionEngineCyberia.currentActionId = null;
|
|
1241
|
+
ActionEngineCyberia.currentDialogueId = null;
|
|
1242
|
+
ActionEngineCyberia.currentSkillId = null;
|
|
1243
|
+
ActionEngineCyberia.questSteps = [];
|
|
1244
|
+
ActionEngineCyberia.questRewards = [];
|
|
1245
|
+
ActionEngineCyberia.actionQuestDialogues = [];
|
|
1246
|
+
ActionEngineCyberia.loadedActionPayloadExtras = {};
|
|
1247
|
+
ActionEngineCyberia.skillDefs = [];
|
|
1248
|
+
|
|
1249
|
+
const ids = ActionEngineCyberia.ids;
|
|
1250
|
+
const objectiveTypeOptions = QUEST_STEPS_TYPES.map((t) => ({ value: t, display: t, data: t, onClick: () => {} }));
|
|
1251
|
+
|
|
1252
|
+
// Responsive column groups: dynamicCol collapses these to full width on
|
|
1253
|
+
// narrow / mobile widths (observing .action-engine-container).
|
|
1254
|
+
const cont = 'action-engine-container';
|
|
1255
|
+
const dc = {
|
|
1256
|
+
cell: 'action-engine-dc-cell',
|
|
1257
|
+
questCt: 'action-engine-dc-quest-ct',
|
|
1258
|
+
questChain: 'action-engine-dc-quest-chain',
|
|
1259
|
+
objective: 'action-engine-dc-objective',
|
|
1260
|
+
reward: 'action-engine-dc-reward',
|
|
1261
|
+
actionCl: 'action-engine-dc-action-cl',
|
|
1262
|
+
actionQd: 'action-engine-dc-action-qd',
|
|
1263
|
+
dialogue: 'action-engine-dc-dialogue',
|
|
1264
|
+
skillDef: 'action-engine-dc-skill-def',
|
|
1265
|
+
};
|
|
1266
|
+
|
|
1267
|
+
const group = (title, icon, inner) =>
|
|
1268
|
+
html`<div class="in" style="border:1px solid ${groupBorder()};border-radius:8px;padding:12px;margin-bottom:14px;">
|
|
1269
|
+
<div
|
|
1270
|
+
class="in"
|
|
1271
|
+
style="font-size:12px;font-weight:bold;text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px;opacity:.85;"
|
|
1272
|
+
>
|
|
1273
|
+
<i class="${icon}"></i> ${title}
|
|
1274
|
+
</div>
|
|
1275
|
+
${inner}
|
|
1276
|
+
</div>`;
|
|
1277
|
+
|
|
1278
|
+
// Buttons grow equally and wrap to their own line once they drop below the
|
|
1279
|
+
// flex-basis, which keeps them tappable on narrow / mobile widths.
|
|
1280
|
+
const crudButtons = async (prefix, includeClone) =>
|
|
1281
|
+
html`<div class="fl" style="margin-top:4px;flex-wrap:wrap;">
|
|
1282
|
+
${ActionEngineCyberia.canMutate
|
|
1283
|
+
? html`<div class="in fll" style="flex:1 1 120px;padding:3px;">
|
|
1284
|
+
${await BtnIcon.instance({
|
|
1285
|
+
class: `wfa btn-action-engine-${prefix}-save`,
|
|
1286
|
+
label: html`<i class="fa-solid fa-floppy-disk"></i> Save`,
|
|
1287
|
+
})}
|
|
1288
|
+
</div>
|
|
1289
|
+
${includeClone
|
|
1290
|
+
? html`<div class="in fll" style="flex:1 1 120px;padding:3px;">
|
|
1291
|
+
${await BtnIcon.instance({
|
|
1292
|
+
class: `wfa btn-action-engine-${prefix}-clone`,
|
|
1293
|
+
label: html`<i class="fa-solid fa-clone"></i> Clone`,
|
|
1294
|
+
})}
|
|
1295
|
+
</div>`
|
|
1296
|
+
: ''}
|
|
1297
|
+
<div class="in fll" style="flex:1 1 120px;padding:3px;">
|
|
1298
|
+
${await BtnIcon.instance({
|
|
1299
|
+
class: `wfa btn-action-engine-${prefix}-delete`,
|
|
1300
|
+
label: html`<i class="fa-solid fa-trash"></i> Delete`,
|
|
1301
|
+
})}
|
|
1302
|
+
</div>`
|
|
1303
|
+
: ''}
|
|
1304
|
+
<div class="in fll" style="flex:1 1 120px;padding:3px;">
|
|
1305
|
+
${await BtnIcon.instance({
|
|
1306
|
+
class: `wfa btn-action-engine-${prefix}-new`,
|
|
1307
|
+
label: html`<i class="fa-solid fa-file"></i> New`,
|
|
1308
|
+
})}
|
|
1309
|
+
</div>
|
|
1310
|
+
</div>`;
|
|
1311
|
+
|
|
1312
|
+
const listToolbar = async (prefix) =>
|
|
1313
|
+
html`<div class="fl" style="align-items:flex-end;margin-bottom:8px;flex-wrap:wrap;">
|
|
1314
|
+
<div class="in fll" style="flex:1 1 160px;">
|
|
1315
|
+
${await Input.instance({
|
|
1316
|
+
id: `action-engine-${prefix}-search`,
|
|
1317
|
+
label: html`Search`,
|
|
1318
|
+
containerClass: 'inl',
|
|
1319
|
+
type: 'text',
|
|
1320
|
+
placeholder: true,
|
|
1321
|
+
})}
|
|
1322
|
+
</div>
|
|
1323
|
+
<div class="in fll" style="padding-left:6px;">
|
|
1324
|
+
${await BtnIcon.instance({
|
|
1325
|
+
class: `wfa btn-action-engine-${prefix}-refresh`,
|
|
1326
|
+
label: html`<i class="fa-solid fa-rotate"></i> Refresh`,
|
|
1327
|
+
})}
|
|
1328
|
+
</div>
|
|
1329
|
+
</div>`;
|
|
1330
|
+
|
|
1331
|
+
setTimeout(async () => {
|
|
1332
|
+
ThemeEvents['action-engine-theme'] = () => {
|
|
1333
|
+
ActionEngineCyberia.renderMapCanvas();
|
|
1334
|
+
ActionEngineCyberia.renderAssignmentReview();
|
|
1335
|
+
};
|
|
1336
|
+
|
|
1337
|
+
for (const m of ['quest', 'action', 'dialogue', 'skill'])
|
|
1338
|
+
if (s(`.action-engine-tab-${m}`)) s(`.action-engine-tab-${m}`).onclick = () => ActionEngineCyberia.setMode(m);
|
|
1339
|
+
ActionEngineCyberia.setMode('quest');
|
|
1340
|
+
|
|
1341
|
+
if (s('.btn-action-engine-load-map'))
|
|
1342
|
+
s('.btn-action-engine-load-map').onclick = () =>
|
|
1343
|
+
ActionEngineCyberia.loadMap(ActionEngineCyberia.getSingleDropdownValue(ids.mapDropdown));
|
|
1344
|
+
|
|
1345
|
+
const canvas = s('.action-engine-canvas');
|
|
1346
|
+
if (canvas)
|
|
1347
|
+
canvas.onclick = (e) => {
|
|
1348
|
+
if (!ActionEngineCyberia.mapDoc) return;
|
|
1349
|
+
const rect = canvas.getBoundingClientRect();
|
|
1350
|
+
const { cellW, cellH } = ActionEngineCyberia.getMapGeometry();
|
|
1351
|
+
const col = Math.floor(((e.clientX - rect.left) * (canvas.width / rect.width)) / cellW);
|
|
1352
|
+
const row = Math.floor(((e.clientY - rect.top) * (canvas.height / rect.height)) / cellH);
|
|
1353
|
+
if (s('.action-engine-cell-x')) s('.action-engine-cell-x').value = col;
|
|
1354
|
+
if (s('.action-engine-cell-y')) s('.action-engine-cell-y').value = row;
|
|
1355
|
+
const mapField =
|
|
1356
|
+
ActionEngineCyberia.mode === 'action' ? '.action-engine-action-map-code' : '.action-engine-quest-map-code';
|
|
1357
|
+
if (s(mapField)) s(mapField).value = ActionEngineCyberia.mapCode;
|
|
1358
|
+
htmls('.action-engine-cell-coords', `Cell: (${col}, ${row})`);
|
|
1359
|
+
ActionEngineCyberia.renderMapCanvas();
|
|
1360
|
+
};
|
|
1361
|
+
|
|
1362
|
+
// Quest wiring
|
|
1363
|
+
if (s('.btn-action-engine-add-step'))
|
|
1364
|
+
s('.btn-action-engine-add-step').onclick = () => ActionEngineCyberia.addStep();
|
|
1365
|
+
if (s('.btn-action-engine-add-objective'))
|
|
1366
|
+
s('.btn-action-engine-add-objective').onclick = () => ActionEngineCyberia.addObjective();
|
|
1367
|
+
if (s('.btn-action-engine-add-reward'))
|
|
1368
|
+
s('.btn-action-engine-add-reward').onclick = () => ActionEngineCyberia.addReward();
|
|
1369
|
+
if (s('.btn-action-engine-quest-save'))
|
|
1370
|
+
s('.btn-action-engine-quest-save').onclick = () => ActionEngineCyberia.saveQuest();
|
|
1371
|
+
if (s('.btn-action-engine-quest-clone'))
|
|
1372
|
+
s('.btn-action-engine-quest-clone').onclick = () => ActionEngineCyberia.cloneQuest();
|
|
1373
|
+
if (s('.btn-action-engine-quest-delete'))
|
|
1374
|
+
s('.btn-action-engine-quest-delete').onclick = () => ActionEngineCyberia.deleteQuest();
|
|
1375
|
+
if (s('.btn-action-engine-quest-new'))
|
|
1376
|
+
s('.btn-action-engine-quest-new').onclick = () => ActionEngineCyberia.resetQuest();
|
|
1377
|
+
if (s('.btn-action-engine-quest-refresh'))
|
|
1378
|
+
s('.btn-action-engine-quest-refresh').onclick = () => ActionEngineCyberia.refreshQuestList();
|
|
1379
|
+
if (s('.action-engine-quest-search'))
|
|
1380
|
+
s('.action-engine-quest-search').addEventListener('input', () => ActionEngineCyberia.renderQuestCards());
|
|
1381
|
+
|
|
1382
|
+
// Action wiring
|
|
1383
|
+
if (s('.btn-action-engine-add-quest-dialogue'))
|
|
1384
|
+
s('.btn-action-engine-add-quest-dialogue').onclick = () => {
|
|
1385
|
+
const questCode = s('.action-engine-action-quest-code')?.value?.trim();
|
|
1386
|
+
const dialogCode = ActionEngineCyberia.getSingleDropdownValue(ids.actionQuestDialogPicker);
|
|
1387
|
+
if (!questCode || !dialogCode) {
|
|
1388
|
+
NotificationManager.Push({ html: 'Quest code and dialog code are required.', status: 'error' });
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
ActionEngineCyberia.actionQuestDialogues.push({ questCode, dialogCode });
|
|
1392
|
+
ActionEngineCyberia.renderQuestDialogueList();
|
|
1393
|
+
};
|
|
1394
|
+
if (s('.btn-action-engine-action-save'))
|
|
1395
|
+
s('.btn-action-engine-action-save').onclick = () => ActionEngineCyberia.saveAction();
|
|
1396
|
+
if (s('.btn-action-engine-action-clone'))
|
|
1397
|
+
s('.btn-action-engine-action-clone').onclick = () => ActionEngineCyberia.cloneAction();
|
|
1398
|
+
if (s('.btn-action-engine-action-delete'))
|
|
1399
|
+
s('.btn-action-engine-action-delete').onclick = () => ActionEngineCyberia.deleteAction();
|
|
1400
|
+
if (s('.btn-action-engine-action-new'))
|
|
1401
|
+
s('.btn-action-engine-action-new').onclick = () => ActionEngineCyberia.resetAction();
|
|
1402
|
+
if (s('.btn-action-engine-action-refresh'))
|
|
1403
|
+
s('.btn-action-engine-action-refresh').onclick = () => ActionEngineCyberia.refreshActionList();
|
|
1404
|
+
if (s('.action-engine-action-search'))
|
|
1405
|
+
s('.action-engine-action-search').addEventListener('input', () => ActionEngineCyberia.renderActionCards());
|
|
1406
|
+
|
|
1407
|
+
// Dialogue wiring
|
|
1408
|
+
if (s('.btn-action-engine-dialogue-save'))
|
|
1409
|
+
s('.btn-action-engine-dialogue-save').onclick = () => ActionEngineCyberia.saveDialogue();
|
|
1410
|
+
if (s('.btn-action-engine-dialogue-delete'))
|
|
1411
|
+
s('.btn-action-engine-dialogue-delete').onclick = () => ActionEngineCyberia.deleteDialogue();
|
|
1412
|
+
if (s('.btn-action-engine-dialogue-new'))
|
|
1413
|
+
s('.btn-action-engine-dialogue-new').onclick = () => ActionEngineCyberia.resetDialogue();
|
|
1414
|
+
if (s('.btn-action-engine-dialogue-refresh'))
|
|
1415
|
+
s('.btn-action-engine-dialogue-refresh').onclick = () => ActionEngineCyberia.refreshDialogueList();
|
|
1416
|
+
if (s('.action-engine-dialogue-search'))
|
|
1417
|
+
s('.action-engine-dialogue-search').addEventListener('input', () => ActionEngineCyberia.renderDialogueCards());
|
|
1418
|
+
|
|
1419
|
+
// Skill wiring
|
|
1420
|
+
if (s('.btn-action-engine-add-skill-def'))
|
|
1421
|
+
s('.btn-action-engine-add-skill-def').onclick = () => ActionEngineCyberia.addSkillDef();
|
|
1422
|
+
if (s('.btn-action-engine-skill-save'))
|
|
1423
|
+
s('.btn-action-engine-skill-save').onclick = () => ActionEngineCyberia.saveSkill();
|
|
1424
|
+
if (s('.btn-action-engine-skill-delete'))
|
|
1425
|
+
s('.btn-action-engine-skill-delete').onclick = () => ActionEngineCyberia.deleteSkill();
|
|
1426
|
+
if (s('.btn-action-engine-skill-new'))
|
|
1427
|
+
s('.btn-action-engine-skill-new').onclick = () => ActionEngineCyberia.resetSkill();
|
|
1428
|
+
if (s('.btn-action-engine-skill-refresh'))
|
|
1429
|
+
s('.btn-action-engine-skill-refresh').onclick = () => ActionEngineCyberia.refreshSkillList();
|
|
1430
|
+
if (s('.action-engine-skill-search'))
|
|
1431
|
+
s('.action-engine-skill-search').addEventListener('input', () => ActionEngineCyberia.renderSkillCards());
|
|
1432
|
+
|
|
1433
|
+
ActionEngineCyberia.renderStepList();
|
|
1434
|
+
ActionEngineCyberia.renderRewardList();
|
|
1435
|
+
ActionEngineCyberia.renderQuestDialogueList();
|
|
1436
|
+
ActionEngineCyberia.renderSkillDefList();
|
|
1437
|
+
ActionEngineCyberia.renderAssignmentReview();
|
|
1438
|
+
await ActionEngineCyberia.refreshQuestList();
|
|
1439
|
+
await ActionEngineCyberia.refreshActionList();
|
|
1440
|
+
await ActionEngineCyberia.refreshDialogueList();
|
|
1441
|
+
await ActionEngineCyberia.refreshSkillList();
|
|
1442
|
+
});
|
|
1443
|
+
|
|
1444
|
+
const tabBtn = (mode, label) =>
|
|
1445
|
+
html`<div
|
|
1446
|
+
class="in fll action-engine-tab-${mode}"
|
|
1447
|
+
style="cursor:pointer;padding:9px 16px;font-size:13px;border-bottom:3px solid transparent;text-align:center;"
|
|
1448
|
+
>
|
|
1449
|
+
${label}
|
|
1450
|
+
</div>`;
|
|
1451
|
+
|
|
1452
|
+
return html`<div class="in section-mp action-engine-container">
|
|
1453
|
+
${group(
|
|
1454
|
+
'Map Placement',
|
|
1455
|
+
'fa-solid fa-map-location-dot',
|
|
1456
|
+
html`<div class="fl" style="align-items:flex-end;">
|
|
1457
|
+
<div class="in fll" style="flex:1;">${await ActionEngineCyberia.buildMapDropdown(ids.mapDropdown)}</div>
|
|
1458
|
+
<div class="in fll" style="padding-left:6px;">
|
|
1459
|
+
${await BtnIcon.instance({
|
|
1460
|
+
class: 'wfa btn-action-engine-load-map',
|
|
1461
|
+
label: html`<i class="fa-solid fa-download"></i> Load Map`,
|
|
1462
|
+
})}
|
|
1463
|
+
</div>
|
|
1464
|
+
</div>
|
|
1465
|
+
<div
|
|
1466
|
+
class="in action-engine-map-meta"
|
|
1467
|
+
style="font-size:12px;color:#888;font-family:monospace;margin:6px 0 2px;"
|
|
1468
|
+
></div>
|
|
1469
|
+
<div class="in action-engine-cell-coords" style="font-size:12px;font-family:monospace;margin-bottom:6px;">
|
|
1470
|
+
Cell: (—, —)
|
|
1471
|
+
</div>
|
|
1472
|
+
<div class="in" style="overflow:auto;max-width:100%;border:1px solid ${subtleBorder()};border-radius:4px;">
|
|
1473
|
+
<canvas class="action-engine-canvas" style="display:block;cursor:crosshair;"></canvas>
|
|
1474
|
+
</div>
|
|
1475
|
+
${dynamicCol({ containerSelector: cont, id: dc.cell, type: 'a-50-b-50' })}
|
|
1476
|
+
<div class="fl" style="margin-top:8px;">
|
|
1477
|
+
<div class="in fll ${dc.cell}-col-a" style="padding-right:4px;">
|
|
1478
|
+
${await Input.instance({
|
|
1479
|
+
id: 'action-engine-cell-x',
|
|
1480
|
+
label: html`Cell X`,
|
|
1481
|
+
containerClass: 'inl',
|
|
1482
|
+
type: 'number',
|
|
1483
|
+
min: 0,
|
|
1484
|
+
})}
|
|
1485
|
+
</div>
|
|
1486
|
+
<div class="in fll ${dc.cell}-col-b" style="padding-left:4px;">
|
|
1487
|
+
${await Input.instance({
|
|
1488
|
+
id: 'action-engine-cell-y',
|
|
1489
|
+
label: html`Cell Y`,
|
|
1490
|
+
containerClass: 'inl',
|
|
1491
|
+
type: 'number',
|
|
1492
|
+
min: 0,
|
|
1493
|
+
})}
|
|
1494
|
+
</div>
|
|
1495
|
+
</div>
|
|
1496
|
+
<div class="in input-label" style="font-size:12px;margin:10px 0 4px;opacity:.8;">
|
|
1497
|
+
<i class="fa-solid fa-list-check"></i> Assignments on map
|
|
1498
|
+
</div>
|
|
1499
|
+
<div class="in action-engine-assignment-review" style="max-height:140px;overflow-y:auto;"></div>`,
|
|
1500
|
+
)}
|
|
1501
|
+
|
|
1502
|
+
<div class="fl" style="border-bottom:1px solid ${groupBorder()};margin-bottom:14px;">
|
|
1503
|
+
${tabBtn('quest', html`<i class="fa-solid fa-scroll"></i> Quests`)}
|
|
1504
|
+
${tabBtn('action', html`<i class="fa-solid fa-handshake"></i> Actions`)}
|
|
1505
|
+
${tabBtn('dialogue', html`<i class="fa-solid fa-comments"></i> Dialogues`)}
|
|
1506
|
+
${tabBtn('skill', html`<i class="fa-solid fa-wand-sparkles"></i> Skills`)}
|
|
1507
|
+
</div>
|
|
1508
|
+
|
|
1509
|
+
<!-- Quest panel -->
|
|
1510
|
+
<div class="in action-engine-panel-quest">
|
|
1511
|
+
${group(
|
|
1512
|
+
'Quest Details',
|
|
1513
|
+
'fa-solid fa-circle-info',
|
|
1514
|
+
html`${dynamicCol({ containerSelector: cont, id: dc.questCt, type: 'a-50-b-50' })}
|
|
1515
|
+
<div class="fl">
|
|
1516
|
+
<div class="in fll ${dc.questCt}-col-a" style="padding-right:4px;">
|
|
1517
|
+
${await Input.instance({
|
|
1518
|
+
id: 'action-engine-quest-code',
|
|
1519
|
+
label: html`Quest Code`,
|
|
1520
|
+
containerClass: 'inl',
|
|
1521
|
+
type: 'text',
|
|
1522
|
+
})}
|
|
1523
|
+
</div>
|
|
1524
|
+
<div class="in fll ${dc.questCt}-col-b" style="padding-left:4px;">
|
|
1525
|
+
${await Input.instance({
|
|
1526
|
+
id: 'action-engine-quest-title',
|
|
1527
|
+
label: html`Title`,
|
|
1528
|
+
containerClass: 'inl',
|
|
1529
|
+
type: 'text',
|
|
1530
|
+
})}
|
|
1531
|
+
</div>
|
|
1532
|
+
</div>
|
|
1533
|
+
<div class="in" style="margin-top:6px;">
|
|
1534
|
+
${await Input.instance({
|
|
1535
|
+
id: 'action-engine-quest-description',
|
|
1536
|
+
label: html`Description`,
|
|
1537
|
+
containerClass: 'inl',
|
|
1538
|
+
type: 'text',
|
|
1539
|
+
})}
|
|
1540
|
+
</div>
|
|
1541
|
+
<div class="in" style="margin-top:6px;">
|
|
1542
|
+
${await Input.instance({
|
|
1543
|
+
id: 'action-engine-quest-map-code',
|
|
1544
|
+
label: html`Source Map Code (click a cell to assign)`,
|
|
1545
|
+
containerClass: 'inl',
|
|
1546
|
+
type: 'text',
|
|
1547
|
+
})}
|
|
1548
|
+
</div>`,
|
|
1549
|
+
)}
|
|
1550
|
+
${group(
|
|
1551
|
+
'Quest Chain',
|
|
1552
|
+
'fa-solid fa-diagram-project',
|
|
1553
|
+
html`${dynamicCol({ containerSelector: cont, id: dc.questChain, type: 'a-50-b-50' })}
|
|
1554
|
+
<div class="fl">
|
|
1555
|
+
<div class="in fll ${dc.questChain}-col-a" style="padding-right:4px;">
|
|
1556
|
+
${await Input.instance({
|
|
1557
|
+
id: 'action-engine-quest-prerequisites',
|
|
1558
|
+
label: html`Prerequisite Codes (csv)`,
|
|
1559
|
+
containerClass: 'inl',
|
|
1560
|
+
type: 'text',
|
|
1561
|
+
})}
|
|
1562
|
+
</div>
|
|
1563
|
+
<div class="in fll ${dc.questChain}-col-b" style="padding-left:4px;">
|
|
1564
|
+
${await Input.instance({
|
|
1565
|
+
id: 'action-engine-quest-unlocks',
|
|
1566
|
+
label: html`Unlocks Codes (csv)`,
|
|
1567
|
+
containerClass: 'inl',
|
|
1568
|
+
type: 'text',
|
|
1569
|
+
})}
|
|
1570
|
+
</div>
|
|
1571
|
+
</div>`,
|
|
1572
|
+
)}
|
|
1573
|
+
${group(
|
|
1574
|
+
'Steps & Objectives',
|
|
1575
|
+
'fa-solid fa-list-ol',
|
|
1576
|
+
html`<div class="in" style="margin-bottom:8px;">
|
|
1577
|
+
${await BtnIcon.instance({
|
|
1578
|
+
class: 'wfa btn-action-engine-add-step',
|
|
1579
|
+
label: html`<i class="fa-solid fa-plus"></i> Add Step`,
|
|
1580
|
+
})}
|
|
1581
|
+
</div>
|
|
1582
|
+
<div class="in action-engine-step-list" style="margin-bottom:10px;"></div>
|
|
1583
|
+
<div class="in input-label" style="font-size:12px;margin-bottom:6px;opacity:.8;">Add objective to step</div>
|
|
1584
|
+
${dynamicCol({ containerSelector: cont, id: dc.objective, type: 'search-inputs' })}
|
|
1585
|
+
<div class="fl" style="align-items:flex-end;">
|
|
1586
|
+
<div class="in fll ${dc.objective}-col-a" style="padding-right:4px;">
|
|
1587
|
+
${await Input.instance({
|
|
1588
|
+
id: 'action-engine-objective-step',
|
|
1589
|
+
label: html`Step #`,
|
|
1590
|
+
containerClass: 'inl',
|
|
1591
|
+
type: 'number',
|
|
1592
|
+
min: 1,
|
|
1593
|
+
value: 1,
|
|
1594
|
+
})}
|
|
1595
|
+
</div>
|
|
1596
|
+
<div class="in fll ${dc.objective}-col-b" style="padding-right:4px;">
|
|
1597
|
+
${await DropDown.instance({
|
|
1598
|
+
id: ids.objectiveType,
|
|
1599
|
+
label: html`Type`,
|
|
1600
|
+
data: objectiveTypeOptions,
|
|
1601
|
+
value: QUEST_STEPS_TYPES[0],
|
|
1602
|
+
containerClass: 'inl',
|
|
1603
|
+
})}
|
|
1604
|
+
</div>
|
|
1605
|
+
<div class="in fll ${dc.objective}-col-c" style="padding-right:4px;">
|
|
1606
|
+
${await Input.instance({
|
|
1607
|
+
id: 'action-engine-objective-qty',
|
|
1608
|
+
label: html`Qty`,
|
|
1609
|
+
containerClass: 'inl',
|
|
1610
|
+
type: 'number',
|
|
1611
|
+
min: 1,
|
|
1612
|
+
value: 1,
|
|
1613
|
+
})}
|
|
1614
|
+
</div>
|
|
1615
|
+
</div>
|
|
1616
|
+
<div class="in" style="margin-top:6px;">
|
|
1617
|
+
${await ActionEngineCyberia.buildItemIdDropdown(ids.objectiveItemPicker, html`Search itemId`)}
|
|
1618
|
+
</div>
|
|
1619
|
+
<div class="in" style="margin-top:6px;">
|
|
1620
|
+
${await BtnIcon.instance({
|
|
1621
|
+
class: 'wfa btn-action-engine-add-objective',
|
|
1622
|
+
label: html`<i class="fa-solid fa-plus"></i> Add Objective`,
|
|
1623
|
+
})}
|
|
1624
|
+
</div>`,
|
|
1625
|
+
)}
|
|
1626
|
+
${group(
|
|
1627
|
+
'Rewards',
|
|
1628
|
+
'fa-solid fa-gift',
|
|
1629
|
+
html`${dynamicCol({ containerSelector: cont, id: dc.reward, type: 'default' })}
|
|
1630
|
+
<div class="fl" style="align-items:flex-end;">
|
|
1631
|
+
<div class="in fll ${dc.reward}-col-a" style="padding-right:4px;">
|
|
1632
|
+
${await Input.instance({
|
|
1633
|
+
id: 'action-engine-reward-qty',
|
|
1634
|
+
label: html`Qty`,
|
|
1635
|
+
containerClass: 'inl',
|
|
1636
|
+
type: 'number',
|
|
1637
|
+
min: 1,
|
|
1638
|
+
value: 1,
|
|
1639
|
+
})}
|
|
1640
|
+
</div>
|
|
1641
|
+
<div class="in fll ${dc.reward}-col-b">
|
|
1642
|
+
${await ActionEngineCyberia.buildItemIdDropdown(ids.rewardItemPicker, html`Search itemId`)}
|
|
1643
|
+
</div>
|
|
1644
|
+
</div>
|
|
1645
|
+
<div class="in" style="margin-top:6px;">
|
|
1646
|
+
${await BtnIcon.instance({
|
|
1647
|
+
class: 'wfa btn-action-engine-add-reward',
|
|
1648
|
+
label: html`<i class="fa-solid fa-plus"></i> Add Reward`,
|
|
1649
|
+
})}
|
|
1650
|
+
</div>
|
|
1651
|
+
<div class="in action-engine-reward-list" style="margin-top:8px;"></div>`,
|
|
1652
|
+
)}
|
|
1653
|
+
${group('Save Quest', 'fa-solid fa-floppy-disk', await crudButtons('quest', true))}
|
|
1654
|
+
${group(
|
|
1655
|
+
'Existing Quests',
|
|
1656
|
+
'fa-solid fa-scroll',
|
|
1657
|
+
html`${await listToolbar('quest')}
|
|
1658
|
+
<div class="in action-engine-quest-cards" style="max-height:300px;overflow-y:auto;"></div>`,
|
|
1659
|
+
)}
|
|
1660
|
+
</div>
|
|
1661
|
+
|
|
1662
|
+
<!-- Action panel -->
|
|
1663
|
+
<div class="in action-engine-panel-action hide">
|
|
1664
|
+
${group(
|
|
1665
|
+
'Action Details',
|
|
1666
|
+
'fa-solid fa-circle-info',
|
|
1667
|
+
html`${dynamicCol({ containerSelector: cont, id: dc.actionCl, type: 'a-50-b-50' })}
|
|
1668
|
+
<div class="fl">
|
|
1669
|
+
<div class="in fll ${dc.actionCl}-col-a" style="padding-right:4px;">
|
|
1670
|
+
${await Input.instance({
|
|
1671
|
+
id: 'action-engine-action-code',
|
|
1672
|
+
label: html`Action Code`,
|
|
1673
|
+
containerClass: 'inl',
|
|
1674
|
+
type: 'text',
|
|
1675
|
+
})}
|
|
1676
|
+
</div>
|
|
1677
|
+
<div class="in fll ${dc.actionCl}-col-b" style="padding-left:4px;">
|
|
1678
|
+
${await Input.instance({
|
|
1679
|
+
id: 'action-engine-action-label',
|
|
1680
|
+
label: html`Label (nameplate)`,
|
|
1681
|
+
containerClass: 'inl',
|
|
1682
|
+
type: 'text',
|
|
1683
|
+
})}
|
|
1684
|
+
</div>
|
|
1685
|
+
</div>
|
|
1686
|
+
<div class="in" style="margin-top:6px;">
|
|
1687
|
+
${await Input.instance({
|
|
1688
|
+
id: 'action-engine-action-map-code',
|
|
1689
|
+
label: html`Source Map Code (click a cell to assign)`,
|
|
1690
|
+
containerClass: 'inl',
|
|
1691
|
+
type: 'text',
|
|
1692
|
+
})}
|
|
1693
|
+
</div>
|
|
1694
|
+
<div class="in" style="margin-top:8px;">
|
|
1695
|
+
${await ActionEngineCyberia.buildDialogCodeDropdown(
|
|
1696
|
+
ids.actionDialogPicker,
|
|
1697
|
+
html`Default greeting dialog code`,
|
|
1698
|
+
)}
|
|
1699
|
+
</div>`,
|
|
1700
|
+
)}
|
|
1701
|
+
${group(
|
|
1702
|
+
'Per-quest Dialogue Mapping',
|
|
1703
|
+
'fa-solid fa-comments',
|
|
1704
|
+
html`${dynamicCol({ containerSelector: cont, id: dc.actionQd, type: 'a-50-b-50' })}
|
|
1705
|
+
<div class="fl" style="align-items:flex-end;">
|
|
1706
|
+
<div class="in fll ${dc.actionQd}-col-a" style="padding-right:4px;">
|
|
1707
|
+
${await Input.instance({
|
|
1708
|
+
id: 'action-engine-action-quest-code',
|
|
1709
|
+
label: html`Quest Code`,
|
|
1710
|
+
containerClass: 'inl',
|
|
1711
|
+
type: 'text',
|
|
1712
|
+
})}
|
|
1713
|
+
</div>
|
|
1714
|
+
<div class="in fll ${dc.actionQd}-col-b">
|
|
1715
|
+
${await ActionEngineCyberia.buildDialogCodeDropdown(ids.actionQuestDialogPicker, html`Dialog Code`)}
|
|
1716
|
+
</div>
|
|
1717
|
+
</div>
|
|
1718
|
+
<div class="in" style="margin-top:6px;">
|
|
1719
|
+
${await BtnIcon.instance({
|
|
1720
|
+
class: 'wfa btn-action-engine-add-quest-dialogue',
|
|
1721
|
+
label: html`<i class="fa-solid fa-plus"></i> Map Dialogue`,
|
|
1722
|
+
})}
|
|
1723
|
+
</div>
|
|
1724
|
+
<div class="in action-engine-quest-dialogue-list" style="margin-top:8px;"></div>`,
|
|
1725
|
+
)}
|
|
1726
|
+
${group('Save Action', 'fa-solid fa-floppy-disk', await crudButtons('action', true))}
|
|
1727
|
+
${group(
|
|
1728
|
+
'Existing Actions',
|
|
1729
|
+
'fa-solid fa-handshake',
|
|
1730
|
+
html`${await listToolbar('action')}
|
|
1731
|
+
<div class="in action-engine-action-cards" style="max-height:300px;overflow-y:auto;"></div>`,
|
|
1732
|
+
)}
|
|
1733
|
+
</div>
|
|
1734
|
+
|
|
1735
|
+
<!-- Dialogue panel -->
|
|
1736
|
+
<div class="in action-engine-panel-dialogue hide">
|
|
1737
|
+
${group(
|
|
1738
|
+
'Dialogue Line',
|
|
1739
|
+
'fa-solid fa-comment-dots',
|
|
1740
|
+
html`${dynamicCol({ containerSelector: cont, id: dc.dialogue, type: 'search-inputs' })}
|
|
1741
|
+
<div class="fl">
|
|
1742
|
+
<div class="in fll ${dc.dialogue}-col-a" style="padding-right:4px;">
|
|
1743
|
+
${await Input.instance({
|
|
1744
|
+
id: 'action-engine-dialogue-code',
|
|
1745
|
+
label: html`Dialogue Code`,
|
|
1746
|
+
containerClass: 'inl',
|
|
1747
|
+
type: 'text',
|
|
1748
|
+
})}
|
|
1749
|
+
</div>
|
|
1750
|
+
<div class="in fll ${dc.dialogue}-col-b" style="padding-right:4px;">
|
|
1751
|
+
${await Input.instance({
|
|
1752
|
+
id: 'action-engine-dialogue-order',
|
|
1753
|
+
label: html`Order`,
|
|
1754
|
+
containerClass: 'inl',
|
|
1755
|
+
type: 'number',
|
|
1756
|
+
min: 0,
|
|
1757
|
+
value: 0,
|
|
1758
|
+
})}
|
|
1759
|
+
</div>
|
|
1760
|
+
<div class="in fll ${dc.dialogue}-col-c">
|
|
1761
|
+
${await Input.instance({
|
|
1762
|
+
id: 'action-engine-dialogue-mood',
|
|
1763
|
+
label: html`Mood`,
|
|
1764
|
+
containerClass: 'inl',
|
|
1765
|
+
type: 'text',
|
|
1766
|
+
})}
|
|
1767
|
+
</div>
|
|
1768
|
+
</div>
|
|
1769
|
+
<div class="in" style="margin-top:6px;">
|
|
1770
|
+
${await Input.instance({
|
|
1771
|
+
id: 'action-engine-dialogue-speaker',
|
|
1772
|
+
label: html`Speaker`,
|
|
1773
|
+
containerClass: 'inl',
|
|
1774
|
+
type: 'text',
|
|
1775
|
+
})}
|
|
1776
|
+
</div>
|
|
1777
|
+
<div class="in" style="margin-top:6px;">
|
|
1778
|
+
${await Input.instance({
|
|
1779
|
+
id: 'action-engine-dialogue-text',
|
|
1780
|
+
label: html`Text`,
|
|
1781
|
+
containerClass: 'inl',
|
|
1782
|
+
type: 'text',
|
|
1783
|
+
})}
|
|
1784
|
+
</div>`,
|
|
1785
|
+
)}
|
|
1786
|
+
${group('Save Dialogue Line', 'fa-solid fa-floppy-disk', await crudButtons('dialogue', false))}
|
|
1787
|
+
${group(
|
|
1788
|
+
'Existing Dialogue Lines',
|
|
1789
|
+
'fa-solid fa-comments',
|
|
1790
|
+
html`${await listToolbar('dialogue')}
|
|
1791
|
+
<div class="in action-engine-dialogue-cards" style="max-height:300px;overflow-y:auto;"></div>`,
|
|
1792
|
+
)}
|
|
1793
|
+
</div>
|
|
1794
|
+
|
|
1795
|
+
<!-- Skill panel -->
|
|
1796
|
+
<div class="in action-engine-panel-skill hide">
|
|
1797
|
+
${group(
|
|
1798
|
+
'Skill Definition',
|
|
1799
|
+
'fa-solid fa-circle-info',
|
|
1800
|
+
html`<div class="in input-label" style="font-size:12px;margin-bottom:6px;opacity:.8;">
|
|
1801
|
+
Trigger item — the ObjectLayer item whose active layer fires these skills.
|
|
1802
|
+
</div>
|
|
1803
|
+
${await ActionEngineCyberia.buildItemIdDropdown(ids.skillTriggerItemPicker, html`Search trigger itemId`)}`,
|
|
1804
|
+
)}
|
|
1805
|
+
${group(
|
|
1806
|
+
'Logic Skills',
|
|
1807
|
+
'fa-solid fa-bolt',
|
|
1808
|
+
html`<div class="in input-label" style="font-size:12px;margin-bottom:6px;opacity:.8;">Add logic event</div>
|
|
1809
|
+
${dynamicCol({ containerSelector: cont, id: dc.skillDef, type: 'a-50-b-50' })}
|
|
1810
|
+
<div class="fl">
|
|
1811
|
+
<div class="in fll ${dc.skillDef}-col-a" style="padding-right:4px;">
|
|
1812
|
+
${await DropDown.instance({
|
|
1813
|
+
id: 'action-engine-skill-logic-event',
|
|
1814
|
+
label: html`Logic Event Id`,
|
|
1815
|
+
containerClass: 'inl',
|
|
1816
|
+
data: SKILL_LOGIC_IDS.map((l) => ({
|
|
1817
|
+
value: l.id,
|
|
1818
|
+
display: l.name ? `${l.id} — ${l.name}` : l.id,
|
|
1819
|
+
data: l.id,
|
|
1820
|
+
onClick: () => {},
|
|
1821
|
+
})),
|
|
1822
|
+
})}
|
|
1823
|
+
</div>
|
|
1824
|
+
<div class="in fll ${dc.skillDef}-col-b" style="padding-left:4px;">
|
|
1825
|
+
${await Input.instance({
|
|
1826
|
+
id: 'action-engine-skill-name',
|
|
1827
|
+
label: html`Name`,
|
|
1828
|
+
containerClass: 'inl',
|
|
1829
|
+
type: 'text',
|
|
1830
|
+
})}
|
|
1831
|
+
</div>
|
|
1832
|
+
</div>
|
|
1833
|
+
<div class="in" style="margin-top:6px;">
|
|
1834
|
+
${await Input.instance({
|
|
1835
|
+
id: 'action-engine-skill-description',
|
|
1836
|
+
label: html`Description`,
|
|
1837
|
+
containerClass: 'inl',
|
|
1838
|
+
type: 'text',
|
|
1839
|
+
})}
|
|
1840
|
+
</div>
|
|
1841
|
+
<div class="in" style="margin-top:6px;">
|
|
1842
|
+
${await ActionEngineCyberia.buildItemIdDropdown(
|
|
1843
|
+
ids.skillSummonedItemPicker,
|
|
1844
|
+
html`Summoned entity itemId`,
|
|
1845
|
+
)}
|
|
1846
|
+
</div>
|
|
1847
|
+
<div class="in" style="margin-top:6px;">
|
|
1848
|
+
${await BtnIcon.instance({
|
|
1849
|
+
class: 'wfa btn-action-engine-add-skill-def',
|
|
1850
|
+
label: html`<i class="fa-solid fa-plus"></i> Add Skill`,
|
|
1851
|
+
})}
|
|
1852
|
+
</div>
|
|
1853
|
+
<div class="in action-engine-skill-def-list" style="margin-top:8px;"></div>`,
|
|
1854
|
+
)}
|
|
1855
|
+
${group('Save Skill', 'fa-solid fa-floppy-disk', await crudButtons('skill', false))}
|
|
1856
|
+
${group(
|
|
1857
|
+
'Existing Skills',
|
|
1858
|
+
'fa-solid fa-wand-sparkles',
|
|
1859
|
+
html`${await listToolbar('skill')}
|
|
1860
|
+
<div class="in action-engine-skill-cards" style="max-height:300px;overflow-y:auto;"></div>`,
|
|
1861
|
+
)}
|
|
1862
|
+
</div>
|
|
1863
|
+
</div>`;
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
export { ActionEngineCyberia };
|