XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

SpaceNinjaServer

A simple server for a small space ninja game

公开
关注 0 Fork 1 Star 0
返回提交历史

XFEstudio/SpaceNinjaServer

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
AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
提交于

代码差异

12 个文件 +452 -175
Modified Dockerfile +0 -2
@@ -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
Modified src/controllers/custom/getItemListsController.ts +12 -0
@@ -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,
Modified src/controllers/custom/manageQuestsController.ts +112 -63
@@ -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();
Modified src/routes/webui.ts +3 -0
@@ -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 });
Modified src/services/questService.ts +96 -56
@@ -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 };
Modified static/webui/index.html +28 -23
@@ -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>
Modified static/webui/script.js +136 -1
@@ -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"
Modified static/webui/translations/de.js +13 -6
@@ -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: ``
Modified static/webui/translations/en.js +13 -6
@@ -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: ``
Modified static/webui/translations/fr.js +13 -6
@@ -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: ``
Modified static/webui/translations/ru.js +13 -6
@@ -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: ``
Modified static/webui/translations/zh.js +13 -6
@@ -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: ``