返回提交历史
Modified
Dockerfile
+0
-2
Modified
src/controllers/custom/getItemListsController.ts
+12
-0
Modified
src/controllers/custom/manageQuestsController.ts
+112
-63
Modified
src/routes/webui.ts
+3
-0
Modified
src/services/questService.ts
+96
-56
Modified
static/webui/index.html
+28
-23
Modified
static/webui/script.js
+136
-1
Modified
static/webui/translations/de.js
+13
-6
Modified
static/webui/translations/en.js
+13
-6
Modified
static/webui/translations/fr.js
+13
-6
Modified
static/webui/translations/ru.js
+13
-6
Modified
static/webui/translations/zh.js
+13
-6
XFEstudio/XFESpaceNinjaServer
feat(webui): quests support (#1411)
Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/1411 Reviewed-by: Sainan <sainan@calamity.inc> Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
710470ca
代码差异
12 个文件
+452
-175
@@ -10,8 +10,6 @@ ENV APP_SKIP_TUTORIAL=true
10
10
ENV APP_SKIP_ALL_DIALOGUE=true
11
11
ENV APP_UNLOCK_ALL_SCANS=true
12
12
ENV APP_UNLOCK_ALL_MISSIONS=true
13
ENV APP_UNLOCK_ALL_QUESTS=true
14
ENV APP_COMPLETE_ALL_QUESTS=true
15
13
ENV APP_INFINITE_RESOURCES=true
16
14
ENV APP_UNLOCK_ALL_SHIP_FEATURES=true
17
15
ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=true
@@ -5,6 +5,7 @@ import {
5
5
ExportAvionics,
6
6
ExportDrones,
7
7
ExportGear,
8
ExportKeys,
8
9
ExportMisc,
9
10
ExportRailjackWeapons,
10
11
ExportRecipes,
@@ -26,6 +27,7 @@ interface ListedItem {
26
27
exalted?: string[];
27
28
badReason?: "starter" | "frivolous" | "notraw";
28
29
partType?: string;
30
chainLength?: number;
29
31
}
30
32
31
33
const relicQualitySuffixes: Record<TRelicQuality, string> = {
@@ -52,6 +54,7 @@ const getItemListsController: RequestHandler = (req, response) => {
52
54
res.miscitems = [];
53
55
res.Syndicates = [];
54
56
res.OperatorAmps = [];
57
res.QuestKeys = [];
55
58
for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
56
59
res[item.productCategory].push({
57
60
uniqueName,
@@ -208,6 +211,15 @@ const getItemListsController: RequestHandler = (req, response) => {
208
211
name: getString(syndicate.name, lang)
209
212
});
210
213
}
214
for (const [uniqueName, key] of Object.entries(ExportKeys)) {
215
if (key.chainStages) {
216
res.QuestKeys.push({
217
uniqueName,
218
name: getString(key.name || "", lang),
219
chainLength: key.chainStages.length
220
});
221
}
222
}
211
223
212
224
response.json({
213
225
archonCrystalUpgrades,
@@ -1,7 +1,11 @@
1
import { addString } from "@/src/controllers/api/inventoryController";
2
1
import { getInventory } from "@/src/services/inventoryService";
3
2
import { getAccountIdForRequest } from "@/src/services/loginService";
4
import { addQuestKey, completeQuest, IUpdateQuestRequest, updateQuestKey } from "@/src/services/questService";
3
import {
4
addQuestKey,
5
completeQuest,
6
giveKeyChainMissionReward,
7
giveKeyChainStageTriggered
8
} from "@/src/services/questService";
5
9
import { logger } from "@/src/utils/logger";
6
10
import { RequestHandler } from "express";
7
11
import { ExportKeys } from "warframe-public-export-plus";
@@ -9,13 +13,17 @@ import { ExportKeys } from "warframe-public-export-plus";
9
13
export const manageQuestsController: RequestHandler = async (req, res) => {
10
14
const accountId = await getAccountIdForRequest(req);
11
15
const operation = req.query.operation as
12
| "unlockAll"
13
16
| "completeAll"
14
| "ResetAll"
15
| "completeAllUnlocked"
16
| "updateKey"
17
| "giveAll";
18
const questKeyUpdate = req.body as IUpdateQuestRequest["QuestKeys"];
17
| "resetAll"
18
| "giveAll"
19
| "completeKey"
20
| "deleteKey"
21
| "resetKey"
22
| "prevStage"
23
| "nextStage"
24
| "setInactive";
25
26
const questItemType = req.query.itemType as string;
19
27
20
28
const allQuestKeys: string[] = [];
21
29
for (const [k, v] of Object.entries(ExportKeys)) {
@@ -26,89 +34,130 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
26
34
const inventory = await getInventory(accountId);
27
35
28
36
switch (operation) {
29
case "updateKey": {
30
//TODO: if this is intended to be used, one needs to add a updateQuestKeyMultiple, the game does never intend to do it, so it errors for multiple keys.
31
await updateQuestKey(inventory, questKeyUpdate);
37
case "completeAll": {
38
if (allQuestKeys.includes(questItemType)) {
39
for (const questKey of inventory.QuestKeys) {
40
await completeQuest(inventory, questKey.ItemType);
41
}
42
}
32
43
break;
33
44
}
34
case "unlockAll": {
35
for (const questKey of allQuestKeys) {
36
addQuestKey(inventory, { ItemType: questKey, Completed: false, unlock: true, Progress: [] });
45
case "resetAll": {
46
for (const questKey of inventory.QuestKeys) {
47
questKey.Completed = false;
48
questKey.Progress = [];
49
questKey.CompletionDate = undefined;
37
50
}
51
inventory.ActiveQuest = "";
38
52
break;
39
53
}
40
case "completeAll": {
41
logger.info("completing all quests..");
42
for (const questKey of allQuestKeys) {
43
try {
44
await completeQuest(inventory, questKey);
45
} catch (error) {
46
if (error instanceof Error) {
47
logger.error(
48
`Something went wrong completing quest ${questKey}, probably could not add some item`
49
);
50
logger.error(error.message);
51
}
54
case "giveAll": {
55
allQuestKeys.forEach(questKey => addQuestKey(inventory, { ItemType: questKey }));
56
break;
57
}
58
case "deleteKey": {
59
if (allQuestKeys.includes(questItemType)) {
60
const questKey = inventory.QuestKeys.find(key => key.ItemType === questItemType);
61
if (!questKey) {
62
logger.error(`Quest key not found in inventory: ${questItemType}`);
63
break;
52
64
}
53
65
54
//Skip "Watch The Maker"
55
if (questKey === "/Lotus/Types/Keys/NewWarIntroQuest/NewWarIntroKeyChain") {
56
addString(
57
inventory.NodeIntrosCompleted,
58
"/Lotus/Levels/Cinematics/NewWarIntro/NewWarStageTwo.level"
59
);
66
inventory.QuestKeys.pull({ ItemType: questItemType });
67
}
68
break;
69
}
70
case "completeKey": {
71
if (allQuestKeys.includes(questItemType)) {
72
const questKey = inventory.QuestKeys.find(key => key.ItemType === questItemType);
73
if (!questKey) {
74
logger.error(`Quest key not found in inventory: ${questItemType}`);
75
break;
60
76
}
61
77
62
if (questKey === "/Lotus/Types/Keys/ArchwingQuest/ArchwingQuestKeyChain") {
63
inventory.ArchwingEnabled = true;
64
}
78
await completeQuest(inventory, questItemType);
65
79
}
66
80
break;
67
81
}
68
case "ResetAll": {
69
logger.info("resetting all quests..");
70
for (const questKey of inventory.QuestKeys) {
82
case "resetKey": {
83
if (allQuestKeys.includes(questItemType)) {
84
const questKey = inventory.QuestKeys.find(key => key.ItemType === questItemType);
85
if (!questKey) {
86
logger.error(`Quest key not found in inventory: ${questItemType}`);
87
break;
88
}
89
71
90
questKey.Completed = false;
72
91
questKey.Progress = [];
73
92
questKey.CompletionDate = undefined;
74
93
}
75
inventory.ActiveQuest = "";
76
94
break;
77
95
}
78
case "completeAllUnlocked": {
79
logger.info("completing all unlocked quests..");
80
for (const questKey of inventory.QuestKeys) {
81
try {
82
await completeQuest(inventory, questKey.ItemType);
83
} catch (error) {
84
if (error instanceof Error) {
85
logger.error(
86
`Something went wrong completing quest ${questKey.ItemType}, probably could not add some item`
87
);
88
logger.error(error.message);
89
}
96
case "prevStage": {
97
if (allQuestKeys.includes(questItemType)) {
98
const questKey = inventory.QuestKeys.find(key => key.ItemType === questItemType);
99
if (!questKey) {
100
logger.error(`Quest key not found in inventory: ${questItemType}`);
101
break;
90
102
}
103
if (!questKey.Progress) break;
91
104
92
//Skip "Watch The Maker"
93
if (questKey.ItemType === "/Lotus/Types/Keys/NewWarIntroQuest/NewWarIntroKeyChain") {
94
addString(
95
inventory.NodeIntrosCompleted,
96
"/Lotus/Levels/Cinematics/NewWarIntro/NewWarStageTwo.level"
97
);
105
if (questKey.Completed) {
106
questKey.Completed = false;
107
questKey.CompletionDate = undefined;
98
108
}
99
100
if (questKey.ItemType === "/Lotus/Types/Keys/ArchwingQuest/ArchwingQuestKeyChain") {
101
inventory.ArchwingEnabled = true;
109
questKey.Progress.pop();
110
const stage = questKey.Progress.length - 1;
111
if (stage > 0) {
112
await giveKeyChainStageTriggered(inventory, {
113
KeyChain: questKey.ItemType,
114
ChainStage: stage
115
});
102
116
}
103
117
}
104
118
break;
105
119
}
106
case "giveAll": {
107
for (const questKey of allQuestKeys) {
108
addQuestKey(inventory, { ItemType: questKey });
120
case "nextStage": {
121
if (allQuestKeys.includes(questItemType)) {
122
const questKey = inventory.QuestKeys.find(key => key.ItemType === questItemType);
123
const questManifest = ExportKeys[questItemType];
124
if (!questKey) {
125
logger.error(`Quest key not found in inventory: ${questItemType}`);
126
break;
127
}
128
if (!questKey.Progress) break;
129
130
const currentStage = questKey.Progress.length;
131
if (currentStage + 1 == questManifest.chainStages?.length) {
132
logger.debug(`Trying to complete last stage with nextStage, calling completeQuest instead`);
133
await completeQuest(inventory, questKey.ItemType);
134
} else {
135
const progress = {
136
c: questManifest.chainStages![currentStage].key ? -1 : 0,
137
i: false,
138
m: false,
139
b: []
140
};
141
questKey.Progress.push(progress);
142
143
await giveKeyChainStageTriggered(inventory, {
144
KeyChain: questKey.ItemType,
145
ChainStage: currentStage
146
});
147
148
if (currentStage > 0) {
149
await giveKeyChainMissionReward(inventory, {
150
KeyChain: questKey.ItemType,
151
ChainStage: currentStage - 1
152
});
153
}
154
}
109
155
}
110
156
break;
111
157
}
158
case "setInactive":
159
inventory.ActiveQuest = "";
160
break;
112
161
}
113
162
114
163
await inventory.save();
@@ -30,6 +30,9 @@ webuiRouter.get("/webui/mods", (_req, res) => {
30
30
webuiRouter.get("/webui/settings", (_req, res) => {
31
31
res.sendFile(path.join(rootDir, "static/webui/index.html"));
32
32
});
33
webuiRouter.get("/webui/quests", (_req, res) => {
34
res.sendFile(path.join(rootDir, "static/webui/index.html"));
35
});
33
36
webuiRouter.get("/webui/cheats", (_req, res) => {
34
37
res.sendFile(path.join(rootDir, "static/webui/index.html"));
35
38
});
@@ -130,73 +130,56 @@ export const completeQuest = async (inventory: TInventoryDatabaseDocument, quest
130
130
throw new Error(`Quest ${questKey} does not contain chain stages`);
131
131
}
132
132
133
const chainStageTotal = ExportKeys[questKey].chainStages?.length ?? 0;
133
const chainStageTotal = chainStages.length;
134
134
135
135
const existingQuestKey = inventory.QuestKeys.find(qk => qk.ItemType === questKey);
136
136
137
const startingStage = Math.max((existingQuestKey?.Progress?.length ?? 0) - 1, 0);
138
137
139
if (existingQuestKey?.Completed) {
138
140
return;
139
141
}
140
const Progress = Array(chainStageTotal).fill({
141
c: 0,
142
i: false,
143
m: false,
144
b: []
145
} satisfies IQuestStage);
146
147
const completedQuestKey: IQuestKeyDatabase = {
148
ItemType: questKey,
149
Completed: true,
150
unlock: true,
151
Progress: Progress,
152
CompletionDate: new Date()
153
};
154
155
//overwrite current quest progress, might lead to multiple quest item rewards
156
142
if (existingQuestKey) {
157
existingQuestKey.overwrite(completedQuestKey);
158
//Object.assign(existingQuestKey, completedQuestKey);
143
existingQuestKey.Progress = existingQuestKey.Progress ?? [];
144
145
const existingProgressLength = existingQuestKey.Progress.length;
146
147
if (existingProgressLength < chainStageTotal) {
148
const missingProgress: IQuestStage[] = Array.from(
149
{ length: chainStageTotal - existingProgressLength },
150
() =>
151
({
152
c: 0,
153
i: false,
154
m: false,
155
b: []
156
}) as IQuestStage
157
);
158
159
existingQuestKey.Progress.push(...missingProgress);
160
existingQuestKey.CompletionDate = new Date();
161
existingQuestKey.Completed = true;
162
}
159
163
} else {
164
const completedQuestKey: IQuestKeyDatabase = {
165
ItemType: questKey,
166
Completed: true,
167
unlock: true,
168
Progress: Array(chainStageTotal).fill({
169
c: 0,
170
i: false,
171
m: false,
172
b: []
173
} satisfies IQuestStage),
174
CompletionDate: new Date()
175
};
160
176
addQuestKey(inventory, completedQuestKey);
161
177
}
162
178
163
for (let i = 0; i < chainStageTotal; i++) {
164
if (chainStages[i].itemsToGiveWhenTriggered.length > 0) {
165
await giveKeyChainItem(inventory, { KeyChain: questKey, ChainStage: i });
166
}
167
168
if (chainStages[i].messageToSendWhenTriggered) {
169
await giveKeyChainMessage(inventory, inventory.accountOwnerId, {
170
KeyChain: questKey,
171
ChainStage: i
172
});
173
}
174
175
const missionName = chainStages[i].key;
176
if (missionName) {
177
const fixedLevelRewards = getLevelKeyRewards(missionName);
178
//logger.debug(`fixedLevelRewards`, fixedLevelRewards);
179
if (fixedLevelRewards.levelKeyRewards) {
180
const missionRewards: { StoreItem: string; ItemCount: number }[] = [];
181
addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, inventory, missionRewards);
179
for (let i = startingStage; i < chainStageTotal; i++) {
180
await giveKeyChainStageTriggered(inventory, { KeyChain: questKey, ChainStage: i });
182
181
183
for (const reward of missionRewards) {
184
await addItem(inventory, fromStoreItem(reward.StoreItem), reward.ItemCount);
185
}
186
} else if (fixedLevelRewards.levelKeyRewards2) {
187
for (const reward of fixedLevelRewards.levelKeyRewards2) {
188
if (reward.rewardType == "RT_CREDITS") {
189
inventory.RegularCredits += reward.amount;
190
continue;
191
}
192
if (reward.rewardType == "RT_RESOURCE") {
193
await addItem(inventory, fromStoreItem(reward.itemType), reward.amount);
194
} else {
195
await addItem(inventory, fromStoreItem(reward.itemType));
196
}
197
}
198
}
199
}
182
await giveKeyChainMissionReward(inventory, { KeyChain: questKey, ChainStage: i });
200
183
}
201
184
202
185
const questCompletionItems = getQuestCompletionItems(questKey);
@@ -205,7 +188,7 @@ export const completeQuest = async (inventory: TInventoryDatabaseDocument, quest
205
188
await addItems(inventory, questCompletionItems);
206
189
}
207
190
208
inventory.ActiveQuest = "";
191
if (inventory.ActiveQuest == questKey) inventory.ActiveQuest = "";
209
192
210
193
if (questKey == "/Lotus/Types/Keys/NewWarQuest/NewWarQuestKeyChain") {
211
194
setupKahlSyndicate(inventory);
@@ -247,3 +230,60 @@ export const giveKeyChainMessage = async (
247
230
248
231
updateQuestStage(inventory, keyChainInfo, { m: true });
249
232
};
233
234
export const giveKeyChainMissionReward = async (
235
inventory: TInventoryDatabaseDocument,
236
keyChainInfo: IKeyChainRequest
237
): Promise<void> => {
238
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
239
const chainStages = ExportKeys[keyChainInfo.KeyChain]?.chainStages;
240
241
if (chainStages) {
242
const missionName = chainStages[keyChainInfo.ChainStage].key;
243
if (missionName) {
244
const fixedLevelRewards = getLevelKeyRewards(missionName);
245
if (fixedLevelRewards.levelKeyRewards) {
246
const missionRewards: { StoreItem: string; ItemCount: number }[] = [];
247
addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, inventory, missionRewards);
248
249
for (const reward of missionRewards) {
250
await addItem(inventory, fromStoreItem(reward.StoreItem), reward.ItemCount);
251
}
252
253
updateQuestStage(inventory, keyChainInfo, { c: 0 });
254
} else if (fixedLevelRewards.levelKeyRewards2) {
255
for (const reward of fixedLevelRewards.levelKeyRewards2) {
256
if (reward.rewardType == "RT_CREDITS") {
257
inventory.RegularCredits += reward.amount;
258
continue;
259
}
260
if (reward.rewardType == "RT_RESOURCE") {
261
await addItem(inventory, fromStoreItem(reward.itemType), reward.amount);
262
} else {
263
await addItem(inventory, fromStoreItem(reward.itemType));
264
}
265
}
266
267
updateQuestStage(inventory, keyChainInfo, { c: 0 });
268
}
269
}
270
}
271
};
272
273
export const giveKeyChainStageTriggered = async (
274
inventory: TInventoryDatabaseDocument,
275
keyChainInfo: IKeyChainRequest
276
): Promise<void> => {
277
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
278
const chainStages = ExportKeys[keyChainInfo.KeyChain]?.chainStages;
279
280
if (chainStages) {
281
if (chainStages[keyChainInfo.ChainStage].itemsToGiveWhenTriggered.length > 0) {
282
await giveKeyChainItem(inventory, keyChainInfo);
283
}
284
285
if (chainStages[keyChainInfo.ChainStage].messageToSendWhenTriggered) {
286
await giveKeyChainMessage(inventory, inventory.accountOwnerId, keyChainInfo);
287
}
288
}
289
};
@@ -61,6 +61,9 @@
61
61
<li class="nav-item">
62
62
<a class="nav-link" href="/webui/mods" data-bs-dismiss="offcanvas" data-bs-target="#sidebar" data-loc="navbar_mods"></a>
63
63
</li>
64
<li class="nav-item">
65
<a class="nav-link" href="/webui/quests" data-bs-dismiss="offcanvas" data-bs-target="#sidebar" data-loc="navbar_quests"></a>
66
</li>
64
67
<li class="nav-item">
65
68
<a class="nav-link" href="/webui/cheats" data-bs-dismiss="offcanvas" data-bs-target="#sidebar" data-loc="navbar_cheats"></a>
66
69
</li>
@@ -470,22 +473,31 @@
470
473
</div>
471
474
</div>
472
475
<div data-route="/webui/quests" data-title="Quests | OpenWF WebUI">
473
<div class="card mb-3">
474
<h5 class="card-header" data-loc="quests_list"></h5>
475
<div class="card-body">
476
<table class="table table-hover w-100">
477
<tbody id="active-quests"></tbody>
478
</table>
476
<div class="row g-3">
477
<div class="col-md-6">
478
<div class="card mb-3">
479
<h5 class="card-header" data-loc="quests_list"></h5>
480
<div class="card-body">
481
<form class="input-group mb-3" onsubmit="doAcquireEquipment('QuestKeys');return false;">
482
<input class="form-control" id="acquire-type-QuestKeys" list="datalist-QuestKeys" />
483
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
484
</form>
485
<table class="table table-hover w-100">
486
<tbody id="QuestKeys-list"></tbody>
487
</table>
488
</div>
489
</div>
479
490
</div>
480
</div>
481
<div class="card mb-3">
482
<h5 class="card-header" data-loc="quests_Actions"></h5>
483
<div class="card-body">
484
<div class="mb-2 d-flex flex-wrap gap-2">
485
<button class="btn btn-primary" onclick="doQuestUpdate('unlockAll');" data-loc="quests_UnlockAll"></button>
486
<button class="btn btn-primary" onclick="doQuestUpdate('completeAll');" data-loc="quests_CompleteAll"></button>
487
<button class="btn btn-primary" onclick="doQuestUpdate('completeAllUnlocked');" data-loc="quests_CompleteAllUnlocked"></button>
488
<button class="btn btn-primary" onclick="doQuestUpdate('ResetAll');" data-loc="quests_ResetAll"></button>
491
<div class="col-md-6">
492
<div class="card mb-3">
493
<h5 class="card-header" data-loc="general_bulkActions"></h5>
494
<div class="card-body">
495
<div class="d-flex flex-wrap gap-2">
496
<button class="btn btn-primary" onclick="doBulkQuestUpdate('giveAll');" data-loc="quests_giveAll"></button>
497
<button class="btn btn-primary" onclick="doBulkQuestUpdate('completeAll');" data-loc="quests_completeAll"></button>
498
<button class="btn btn-primary" onclick="doBulkQuestUpdate('resetAll');" data-loc="quests_resetAll"></button>
499
</div>
500
</div>
489
501
</div>
490
502
</div>
491
503
</div>
@@ -633,14 +645,6 @@
633
645
<button class="btn btn-primary" type="submit" data-loc="cheats_changeButton"></button>
634
646
</div>
635
647
</form>
636
<h5 class="mt-3" data-loc="cheats_quests"></h6>
637
<div class="mb-2 d-flex flex-wrap gap-2">
638
<button class="btn btn-primary" onclick="doQuestUpdate('unlockAll');" data-loc="cheats_quests_unlockAll"></button>
639
<button class="btn btn-primary" onclick="doQuestUpdate('completeAll');" data-loc="cheats_quests_completeAll"></button>
640
<button class="btn btn-primary" onclick="doQuestUpdate('completeAllUnlocked');" data-loc="cheats_quests_completeAllUnlocked"></button>
641
<button class="btn btn-primary" onclick="doQuestUpdate('ResetAll');" data-loc="cheats_quests_resetAll"></button>
642
<button class="btn btn-primary" onclick="doQuestUpdate('giveAll');" data-loc="cheats_quests_giveAll"></button>
643
</div>
644
648
</div>
645
649
</div>
646
650
</div>
@@ -666,6 +670,7 @@
666
670
<datalist id="datalist-MechSuits"></datalist>
667
671
<datalist id="datalist-Syndicates"></datalist>
668
672
<datalist id="datalist-MoaPets"></datalist>
673
<datalist id="datalist-QuestKeys"></datalist>
669
674
<datalist id="datalist-miscitems"></datalist>
670
675
<datalist id="datalist-mods">
671
676
<option data-key="/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser" value="Legendary Core"></option>
@@ -489,6 +489,132 @@ function updateInventory() {
489
489
});
490
490
});
491
491
492
// Populate quests route
493
document.getElementById("QuestKeys-list").innerHTML = "";
494
data.QuestKeys.forEach(item => {
495
const tr = document.createElement("tr");
496
tr.setAttribute("data-item-type", item.ItemType);
497
const stage = item.Progress?.length ?? 0;
498
499
const datalist = document.getElementById("datalist-QuestKeys");
500
const optionToRemove = datalist.querySelector(`option[data-key="${item.ItemType}"]`);
501
if (optionToRemove) {
502
datalist.removeChild(optionToRemove);
503
}
504
505
{
506
const td = document.createElement("td");
507
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
508
if (!item.Completed) {
509
td.textContent +=
510
" | " + loc("code_stage") + ": [" + stage + "/" + itemMap[item.ItemType].chainLength + "]";
511
} else {
512
td.textContent += " | " + loc("code_completed");
513
}
514
515
if (data.ActiveQuest == item.ItemType) td.textContent += " | " + loc("code_active");
516
tr.appendChild(td);
517
}
518
{
519
const td = document.createElement("td");
520
td.classList = "text-end text-nowrap";
521
if (data.ActiveQuest == item.ItemType && !item.Completed) {
522
console.log(data.ActiveQuest);
523
524
const a = document.createElement("a");
525
a.href = "#";
526
a.onclick = function (event) {
527
event.preventDefault();
528
doQuestUpdate("setInactive", item.ItemType);
529
};
530
a.title = loc("code_setInactive");
531
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zm192-96l128 0c17.7 0 32 14.3 32 32l0 128c0 17.7-14.3 32-32 32l-128 0c-17.7 0-32-14.3-32-32l0-128c0-17.7 14.3-32 32-32z"/></svg>`;
532
td.appendChild(a);
533
}
534
if (stage > 0) {
535
const a = document.createElement("a");
536
a.href = "#";
537
a.onclick = function (event) {
538
event.preventDefault();
539
doQuestUpdate("resetKey", item.ItemType);
540
};
541
a.title = loc("code_reset");
542
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M463.5 224l8.5 0c13.3 0 24-10.7 24-24l0-128c0-9.7-5.8-18.5-14.8-22.2s-19.3-1.7-26.2 5.2L413.4 96.6c-87.6-86.5-228.7-86.2-315.8 1c-87.5 87.5-87.5 229.3 0 316.8s229.3 87.5 316.8 0c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0c-62.5 62.5-163.8 62.5-226.3 0s-62.5-163.8 0-226.3c62.2-62.2 162.7-62.5 225.3-1L327 183c-6.9 6.9-8.9 17.2-5.2 26.2s12.5 14.8 22.2 14.8l119.5 0z"/></svg>`;
543
td.appendChild(a);
544
}
545
if (itemMap[item.ItemType].chainLength > stage && !item.Completed) {
546
const a = document.createElement("a");
547
a.href = "#";
548
a.onclick = function (event) {
549
event.preventDefault();
550
doQuestUpdate("completeKey", item.ItemType);
551
};
552
a.title = loc("code_complete");
553
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"/></svg>`;
554
td.appendChild(a);
555
}
556
if (stage > 0 && itemMap[item.ItemType].chainLength > 1) {
557
const a = document.createElement("a");
558
a.href = "#";
559
a.onclick = function (event) {
560
event.preventDefault();
561
doQuestUpdate("prevStage", item.ItemType);
562
};
563
a.title = loc("code_prevStage");
564
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg>`;
565
td.appendChild(a);
566
}
567
if (
568
itemMap[item.ItemType].chainLength > stage &&
569
!item.Completed &&
570
itemMap[item.ItemType].chainLength > 1
571
) {
572
const a = document.createElement("a");
573
a.href = "#";
574
a.onclick = function (event) {
575
event.preventDefault();
576
doQuestUpdate("nextStage", item.ItemType);
577
};
578
a.title = loc("code_nextStage");
579
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M278.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-160 160c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L210.7 256 73.4 118.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l160 160z"/></svg>`;
580
td.appendChild(a);
581
}
582
{
583
const a = document.createElement("a");
584
a.href = "#";
585
a.onclick = function (event) {
586
event.preventDefault();
587
const option = document.createElement("option");
588
option.setAttribute("data-key", item.ItemType);
589
option.value = itemMap[item.ItemType]?.name ?? item.ItemType;
590
document.getElementById("datalist-QuestKeys").appendChild(option);
591
doQuestUpdate("deleteKey", item.ItemType);
592
};
593
a.title = loc("code_remove");
594
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/></svg>`;
595
td.appendChild(a);
596
}
597
tr.appendChild(td);
598
}
599
document.getElementById("QuestKeys-list").appendChild(tr);
600
});
601
602
const datalistQuestKeys = document.querySelectorAll("#datalist-QuestKeys option");
603
const form = document.querySelector("form[onsubmit*=\"doAcquireEquipment('QuestKeys')\"]");
604
const giveAllQuestButton = document.querySelector("button[onclick*=\"doBulkQuestUpdate('giveAll')\"]");
605
606
if (datalistQuestKeys.length === 0) {
607
form.classList.add("disabled");
608
form.querySelector("input").disabled = true;
609
form.querySelector("button").disabled = true;
610
giveAllQuestButton.disabled = true;
611
} else {
612
form.classList.remove("disabled");
613
form.querySelector("input").disabled = false;
614
form.querySelector("button").disabled = false;
615
giveAllQuestButton.disabled = false;
616
}
617
492
618
// Populate mods route
493
619
document.getElementById("riven-list").innerHTML = "";
494
620
document.getElementById("mods-list").innerHTML = "";
@@ -1397,7 +1523,16 @@ function doAddCurrency(currency) {
1397
1523
});
1398
1524
}
1399
1525
1400
function doQuestUpdate(operation) {
1526
function doQuestUpdate(operation, itemType) {
1527
$.post({
1528
url: "/custom/manageQuests?" + window.authz + "&operation=" + operation + "&itemType=" + itemType,
1529
contentType: "application/json"
1530
}).then(function () {
1531
updateInventory();
1532
});
1533
}
1534
1535
function doBulkQuestUpdate(operation) {
1401
1536
$.post({
1402
1537
url: "/custom/manageQuests?" + window.authz + "&operation=" + operation,
1403
1538
contentType: "application/json"
@@ -45,6 +45,14 @@ dict = {
45
45
code_zanukaA: `Jagdhund: Dorma`,
46
46
code_zanukaB: `Jagdhund: Bhaira`,
47
47
code_zanukaC: `Jagdhund: Hec`,
48
code_stage: `[UNTRANSLATED] Stage`,
49
code_complete: `[UNTRANSLATED] Complete`,
50
code_nextStage: `[UNTRANSLATED] Next stage`,
51
code_prevStage: `[UNTRANSLATED] Previous stage`,
52
code_reset: `[UNTRANSLATED] Reset`,
53
code_setInactive: `[UNTRANSLATED] Make the quest inactive`,
54
code_completed: `[UNTRANSLATED] Completed`,
55
code_active: `[UNTRANSLATED] Active`,
48
56
login_description: `Melde dich mit deinem OpenWF-Account an (denselben Angaben wie im Spiel, wenn du dich mit diesem Server verbindest).`,
49
57
login_emailLabel: `E-Mail-Adresse`,
50
58
login_passwordLabel: `Passwort`,
@@ -84,6 +92,11 @@ dict = {
84
92
inventory_bulkRankUpSentinels: `Alle Wächter auf Max. Rang`,
85
93
inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`,
86
94
95
quests_list: `Quests`,
96
quests_completeAll: `Alle Quests abschließen`,
97
quests_resetAll: `Alle Quests zurücksetzen`,
98
quests_giveAll: `Alle Quests erhalten`,
99
87
100
currency_RegularCredits: `Credits`,
88
101
currency_PremiumCredits: `Platinum`,
89
102
currency_FusionPoints: `Endo`,
@@ -135,12 +148,6 @@ dict = {
135
148
cheats_changeSupportedSyndicate: `Unterstütztes Syndikat`,
136
149
cheats_changeButton: `Ändern`,
137
150
cheats_none: `Keines`,
138
cheats_quests: `Quests`,
139
cheats_quests_unlockAll: `Alle Quests freischalten`,
140
cheats_quests_completeAll: `Alle Quests abschließen`,
141
cheats_quests_completeAllUnlocked: `Alle freigeschalteten Quests abschließen`,
142
cheats_quests_resetAll: `Alle Quests zurücksetzen`,
143
cheats_quests_giveAll: `Alle Quests erhalten`,
144
151
import_importNote: `Du kannst hier eine vollständige oder teilweise Inventarantwort (Client-Darstellung) einfügen. Alle Felder, die vom Importer unterstützt werden, <b>werden in deinem Account überschrieben</b>.`,
145
152
import_submit: `Absenden`,
146
153
prettier_sucks_ass: ``
@@ -44,6 +44,14 @@ dict = {
44
44
code_zanukaA: `Dorma Hound`,
45
45
code_zanukaB: `Bhaira Hound`,
46
46
code_zanukaC: `Hec Hound`,
47
code_stage: `Stage`,
48
code_complete: `Complete`,
49
code_nextStage: `Next stage`,
50
code_prevStage: `Previous stage`,
51
code_reset: `Reset`,
52
code_setInactive: `Make the quest inactive`,
53
code_completed: `Completed`,
54
code_active: `Active`,
47
55
login_description: `Login using your OpenWF account credentials (same as in-game when connecting to this server).`,
48
56
login_emailLabel: `Email address`,
49
57
login_passwordLabel: `Password`,
@@ -83,6 +91,11 @@ dict = {
83
91
inventory_bulkRankUpSentinels: `Max Rank All Sentinels`,
84
92
inventory_bulkRankUpSentinelWeapons: `Max Rank All Sentinel Weapons`,
85
93
94
quests_list: `Quests`,
95
quests_completeAll: `Complete All Quests`,
96
quests_resetAll: `Reset All Quests`,
97
quests_giveAll: `Give All Quests`,
98
86
99
currency_RegularCredits: `Credits`,
87
100
currency_PremiumCredits: `Platinum`,
88
101
currency_FusionPoints: `Endo`,
@@ -134,12 +147,6 @@ dict = {
134
147
cheats_changeSupportedSyndicate: `Supported syndicate`,
135
148
cheats_changeButton: `Change`,
136
149
cheats_none: `None`,
137
cheats_quests: `Quests`,
138
cheats_quests_unlockAll: `Unlock All Quests`,
139
cheats_quests_completeAll: `Complete All Quests`,
140
cheats_quests_completeAllUnlocked: `Complete All Unlocked Quests`,
141
cheats_quests_resetAll: `Reset All Quests`,
142
cheats_quests_giveAll: `Give All Quests`,
143
150
import_importNote: `You can provide a full or partial inventory response (client respresentation) here. All fields that are supported by the importer <b>will be overwritten</b> in your account.`,
144
151
import_submit: `Submit`,
145
152
prettier_sucks_ass: ``
@@ -45,6 +45,14 @@ dict = {
45
45
code_zanukaA: `Molosse Dorma`,
46
46
code_zanukaB: `Molosse Bhaira`,
47
47
code_zanukaC: `Molosse Hec`,
48
code_stage: `[UNTRANSLATED] Stage`,
49
code_complete: `[UNTRANSLATED] Complete`,
50
code_nextStage: `[UNTRANSLATED] Next stage`,
51
code_prevStage: `[UNTRANSLATED] Previous stage`,
52
code_reset: `[UNTRANSLATED] Reset`,
53
code_setInactive: `[UNTRANSLATED] Make the quest inactive`,
54
code_completed: `[UNTRANSLATED] Completed`,
55
code_active: `[UNTRANSLATED] Active`,
48
56
login_description: `Connexion avec les informations de connexion OpenWF.`,
49
57
login_emailLabel: `Email`,
50
58
login_passwordLabel: `Mot de passe`,
@@ -84,6 +92,11 @@ dict = {
84
92
inventory_bulkRankUpSentinels: `Toutes les Sentinelles rang max`,
85
93
inventory_bulkRankUpSentinelWeapons: `Toutes les armes de Sentinelles rang max`,
86
94
95
quests_list: `Quêtes`,
96
quests_completeAll: `Compléter toutes les quêtes`,
97
quests_resetAll: `Réinitialiser toutes les quêtes`,
98
quests_giveAll: `Obtenir toutes les quêtes`,
99
87
100
currency_RegularCredits: `Crédits`,
88
101
currency_PremiumCredits: `Platinum`,
89
102
currency_FusionPoints: `Endo`,
@@ -135,12 +148,6 @@ dict = {
135
148
cheats_changeSupportedSyndicate: `Allégeance`,
136
149
cheats_changeButton: `Changer`,
137
150
cheats_none: `Aucun`,
138
cheats_quests: `Quêtes`,
139
cheats_quests_unlockAll: `Débloquer toutes les quêtes`,
140
cheats_quests_completeAll: `Compléter toutes les quêtes`,
141
cheats_quests_completeAllUnlocked: `Compléter toutes les quêtes déverrouillées`,
142
cheats_quests_resetAll: `Réinitialiser toutes les quêtes`,
143
cheats_quests_giveAll: `Obtenir toutes les quêtes`,
144
151
import_importNote: `Import manuel. Toutes les modifcations supportées par l'inventaire <b>écraseront celles présentes dans la base de données</b>.`,
145
152
import_submit: `Soumettre`,
146
153
prettier_sucks_ass: ``
@@ -45,6 +45,14 @@ dict = {
45
45
code_zanukaA: `Гончая: Дорма`,
46
46
code_zanukaB: `Гончая: Бхайра`,
47
47
code_zanukaC: `Гончая: Хек`,
48
code_stage: `Этап`,
49
code_complete: `Завершить`,
50
code_nextStage: `Cледующий этап`,
51
code_prevStage: `Предыдущий этап`,
52
code_reset: `Сбросить`,
53
code_setInactive: `Сделать квест неактивным`,
54
code_completed: `Завершено`,
55
code_active: `Активный`,
48
56
login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`,
49
57
login_emailLabel: `Адрес электронной почты`,
50
58
login_passwordLabel: `Пароль`,
@@ -84,6 +92,11 @@ dict = {
84
92
inventory_bulkRankUpSentinels: `Максимальный ранг всех стражей`,
85
93
inventory_bulkRankUpSentinelWeapons: `Максимальный ранг всего оружия стражей`,
86
94
95
quests_list: `Квесты`,
96
quests_completeAll: `Завершить все квесты`,
97
quests_resetAll: `Сбросить прогресс всех квестов`,
98
quests_giveAll: `Выдать все квесты`,
99
87
100
currency_RegularCredits: `Кредиты`,
88
101
currency_PremiumCredits: `Платина`,
89
102
currency_FusionPoints: `Эндо`,
@@ -135,12 +148,6 @@ dict = {
135
148
cheats_changeSupportedSyndicate: `Поддерживаемый синдикат`,
136
149
cheats_changeButton: `Изменить`,
137
150
cheats_none: `Отсутствует`,
138
cheats_quests: `Квесты`,
139
cheats_quests_unlockAll: `Разблокировать все квесты`,
140
cheats_quests_completeAll: `Завершить все квесты`,
141
cheats_quests_completeAllUnlocked: `Завершить все разблокированые квесты`,
142
cheats_quests_resetAll: `Сбросить прогресс всех квестов`,
143
cheats_quests_giveAll: `Выдать все квесты`,
144
151
import_importNote: `Вы можете загрузить полный или частичный ответ инвентаря (клиентское представление) здесь. Все поддерживаемые поля <b>будут перезаписаны</b> в вашем аккаунте.`,
145
152
import_submit: `Отправить`,
146
153
prettier_sucks_ass: ``
@@ -45,6 +45,14 @@ dict = {
45
45
code_zanukaA: `铎玛猎犬`,
46
46
code_zanukaB: `拜拉猎犬`,
47
47
code_zanukaC: `骸克猎犬`,
48
code_stage: `[UNTRANSLATED] Stage`,
49
code_complete: `[UNTRANSLATED] Complete`,
50
code_nextStage: `[UNTRANSLATED] Next stage`,
51
code_prevStage: `[UNTRANSLATED] Previous stage`,
52
code_reset: `[UNTRANSLATED] Reset`,
53
code_setInactive: `[UNTRANSLATED] Make the quest inactive`,
54
code_completed: `[UNTRANSLATED] Completed`,
55
code_active: `[UNTRANSLATED] Active`,
48
56
login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)。`,
49
57
login_emailLabel: `电子邮箱`,
50
58
login_passwordLabel: `密码`,
@@ -84,6 +92,11 @@ dict = {
84
92
inventory_bulkRankUpSentinels: `所有守护升满级`,
85
93
inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`,
86
94
95
quests_list: `任务`,
96
quests_completeAll: `完成所有任务`,
97
quests_resetAll: `重置所有任务`,
98
quests_giveAll: `授予所有任务`,
99
87
100
currency_RegularCredits: `现金`,
88
101
currency_PremiumCredits: `白金`,
89
102
currency_FusionPoints: `内融核心`,
@@ -135,12 +148,6 @@ dict = {
135
148
cheats_changeSupportedSyndicate: `支持的集团`,
136
149
cheats_changeButton: `更改`,
137
150
cheats_none: `无`,
138
cheats_quests: `任务`,
139
cheats_quests_unlockAll: `解锁所有任务`,
140
cheats_quests_completeAll: `完成所有任务`,
141
cheats_quests_completeAllUnlocked: `完成所有已解锁任务`,
142
cheats_quests_resetAll: `重置所有任务`,
143
cheats_quests_giveAll: `授予所有任务`,
144
151
import_importNote: `您可以在此处提供完整或部分库存响应(客户端表示)。支持的所有字段<b>将被覆盖</b>到您的账户中。`,
145
152
import_submit: `提交`,
146
153
prettier_sucks_ass: ``