feat(webui): quests support (#1411)
Some checks failed
Build Docker image / docker (push) Waiting to run
Build / build (22) (push) Has been cancelled
Build / build (20) (push) Has been cancelled
Build / build (18) (push) Has been cancelled

Reviewed-on: #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>
This commit is contained in:
AMelonInsideLemon 2025-04-03 10:40:22 -07:00 committed by Sainan
parent 5cc991baca
commit 710470ca2d
12 changed files with 460 additions and 183 deletions

View File

@ -10,8 +10,6 @@ ENV APP_SKIP_TUTORIAL=true
ENV APP_SKIP_ALL_DIALOGUE=true
ENV APP_UNLOCK_ALL_SCANS=true
ENV APP_UNLOCK_ALL_MISSIONS=true
ENV APP_UNLOCK_ALL_QUESTS=true
ENV APP_COMPLETE_ALL_QUESTS=true
ENV APP_INFINITE_RESOURCES=true
ENV APP_UNLOCK_ALL_SHIP_FEATURES=true
ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=true

View File

@ -5,6 +5,7 @@ import {
ExportAvionics,
ExportDrones,
ExportGear,
ExportKeys,
ExportMisc,
ExportRailjackWeapons,
ExportRecipes,
@ -26,6 +27,7 @@ interface ListedItem {
exalted?: string[];
badReason?: "starter" | "frivolous" | "notraw";
partType?: string;
chainLength?: number;
}
const relicQualitySuffixes: Record<TRelicQuality, string> = {
@ -52,6 +54,7 @@ const getItemListsController: RequestHandler = (req, response) => {
res.miscitems = [];
res.Syndicates = [];
res.OperatorAmps = [];
res.QuestKeys = [];
for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
res[item.productCategory].push({
uniqueName,
@ -208,6 +211,15 @@ const getItemListsController: RequestHandler = (req, response) => {
name: getString(syndicate.name, lang)
});
}
for (const [uniqueName, key] of Object.entries(ExportKeys)) {
if (key.chainStages) {
res.QuestKeys.push({
uniqueName,
name: getString(key.name || "", lang),
chainLength: key.chainStages.length
});
}
}
response.json({
archonCrystalUpgrades,

View File

@ -1,7 +1,11 @@
import { addString } from "@/src/controllers/api/inventoryController";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { addQuestKey, completeQuest, IUpdateQuestRequest, updateQuestKey } from "@/src/services/questService";
import {
addQuestKey,
completeQuest,
giveKeyChainMissionReward,
giveKeyChainStageTriggered
} from "@/src/services/questService";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express";
import { ExportKeys } from "warframe-public-export-plus";
@ -9,13 +13,17 @@ import { ExportKeys } from "warframe-public-export-plus";
export const manageQuestsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const operation = req.query.operation as
| "unlockAll"
| "completeAll"
| "ResetAll"
| "completeAllUnlocked"
| "updateKey"
| "giveAll";
const questKeyUpdate = req.body as IUpdateQuestRequest["QuestKeys"];
| "resetAll"
| "giveAll"
| "completeKey"
| "deleteKey"
| "resetKey"
| "prevStage"
| "nextStage"
| "setInactive";
const questItemType = req.query.itemType as string;
const allQuestKeys: string[] = [];
for (const [k, v] of Object.entries(ExportKeys)) {
@ -26,47 +34,15 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
const inventory = await getInventory(accountId);
switch (operation) {
case "updateKey": {
//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.
await updateQuestKey(inventory, questKeyUpdate);
break;
}
case "unlockAll": {
for (const questKey of allQuestKeys) {
addQuestKey(inventory, { ItemType: questKey, Completed: false, unlock: true, Progress: [] });
}
break;
}
case "completeAll": {
logger.info("completing all quests..");
for (const questKey of allQuestKeys) {
try {
await completeQuest(inventory, questKey);
} catch (error) {
if (error instanceof Error) {
logger.error(
`Something went wrong completing quest ${questKey}, probably could not add some item`
);
logger.error(error.message);
}
}
//Skip "Watch The Maker"
if (questKey === "/Lotus/Types/Keys/NewWarIntroQuest/NewWarIntroKeyChain") {
addString(
inventory.NodeIntrosCompleted,
"/Lotus/Levels/Cinematics/NewWarIntro/NewWarStageTwo.level"
);
}
if (questKey === "/Lotus/Types/Keys/ArchwingQuest/ArchwingQuestKeyChain") {
inventory.ArchwingEnabled = true;
if (allQuestKeys.includes(questItemType)) {
for (const questKey of inventory.QuestKeys) {
await completeQuest(inventory, questKey.ItemType);
}
}
break;
}
case "ResetAll": {
logger.info("resetting all quests..");
case "resetAll": {
for (const questKey of inventory.QuestKeys) {
questKey.Completed = false;
questKey.Progress = [];
@ -75,40 +51,113 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
inventory.ActiveQuest = "";
break;
}
case "completeAllUnlocked": {
logger.info("completing all unlocked quests..");
for (const questKey of inventory.QuestKeys) {
try {
case "giveAll": {
allQuestKeys.forEach(questKey => addQuestKey(inventory, { ItemType: questKey }));
break;
}
case "deleteKey": {
if (allQuestKeys.includes(questItemType)) {
const questKey = inventory.QuestKeys.find(key => key.ItemType === questItemType);
if (!questKey) {
logger.error(`Quest key not found in inventory: ${questItemType}`);
break;
}
inventory.QuestKeys.pull({ ItemType: questItemType });
}
break;
}
case "completeKey": {
if (allQuestKeys.includes(questItemType)) {
const questKey = inventory.QuestKeys.find(key => key.ItemType === questItemType);
if (!questKey) {
logger.error(`Quest key not found in inventory: ${questItemType}`);
break;
}
await completeQuest(inventory, questItemType);
}
break;
}
case "resetKey": {
if (allQuestKeys.includes(questItemType)) {
const questKey = inventory.QuestKeys.find(key => key.ItemType === questItemType);
if (!questKey) {
logger.error(`Quest key not found in inventory: ${questItemType}`);
break;
}
questKey.Completed = false;
questKey.Progress = [];
questKey.CompletionDate = undefined;
}
break;
}
case "prevStage": {
if (allQuestKeys.includes(questItemType)) {
const questKey = inventory.QuestKeys.find(key => key.ItemType === questItemType);
if (!questKey) {
logger.error(`Quest key not found in inventory: ${questItemType}`);
break;
}
if (!questKey.Progress) break;
if (questKey.Completed) {
questKey.Completed = false;
questKey.CompletionDate = undefined;
}
questKey.Progress.pop();
const stage = questKey.Progress.length - 1;
if (stage > 0) {
await giveKeyChainStageTriggered(inventory, {
KeyChain: questKey.ItemType,
ChainStage: stage
});
}
}
break;
}
case "nextStage": {
if (allQuestKeys.includes(questItemType)) {
const questKey = inventory.QuestKeys.find(key => key.ItemType === questItemType);
const questManifest = ExportKeys[questItemType];
if (!questKey) {
logger.error(`Quest key not found in inventory: ${questItemType}`);
break;
}
if (!questKey.Progress) break;
const currentStage = questKey.Progress.length;
if (currentStage + 1 == questManifest.chainStages?.length) {
logger.debug(`Trying to complete last stage with nextStage, calling completeQuest instead`);
await completeQuest(inventory, questKey.ItemType);
} catch (error) {
if (error instanceof Error) {
logger.error(
`Something went wrong completing quest ${questKey.ItemType}, probably could not add some item`
);
logger.error(error.message);
} else {
const progress = {
c: questManifest.chainStages![currentStage].key ? -1 : 0,
i: false,
m: false,
b: []
};
questKey.Progress.push(progress);
await giveKeyChainStageTriggered(inventory, {
KeyChain: questKey.ItemType,
ChainStage: currentStage
});
if (currentStage > 0) {
await giveKeyChainMissionReward(inventory, {
KeyChain: questKey.ItemType,
ChainStage: currentStage - 1
});
}
}
//Skip "Watch The Maker"
if (questKey.ItemType === "/Lotus/Types/Keys/NewWarIntroQuest/NewWarIntroKeyChain") {
addString(
inventory.NodeIntrosCompleted,
"/Lotus/Levels/Cinematics/NewWarIntro/NewWarStageTwo.level"
);
}
if (questKey.ItemType === "/Lotus/Types/Keys/ArchwingQuest/ArchwingQuestKeyChain") {
inventory.ArchwingEnabled = true;
}
}
break;
}
case "giveAll": {
for (const questKey of allQuestKeys) {
addQuestKey(inventory, { ItemType: questKey });
}
case "setInactive":
inventory.ActiveQuest = "";
break;
}
}
await inventory.save();

View File

@ -30,6 +30,9 @@ webuiRouter.get("/webui/mods", (_req, res) => {
webuiRouter.get("/webui/settings", (_req, res) => {
res.sendFile(path.join(rootDir, "static/webui/index.html"));
});
webuiRouter.get("/webui/quests", (_req, res) => {
res.sendFile(path.join(rootDir, "static/webui/index.html"));
});
webuiRouter.get("/webui/cheats", (_req, res) => {
res.sendFile(path.join(rootDir, "static/webui/index.html"));
});

View File

@ -130,73 +130,56 @@ export const completeQuest = async (inventory: TInventoryDatabaseDocument, quest
throw new Error(`Quest ${questKey} does not contain chain stages`);
}
const chainStageTotal = ExportKeys[questKey].chainStages?.length ?? 0;
const chainStageTotal = chainStages.length;
const existingQuestKey = inventory.QuestKeys.find(qk => qk.ItemType === questKey);
const startingStage = Math.max((existingQuestKey?.Progress?.length ?? 0) - 1, 0);
if (existingQuestKey?.Completed) {
return;
}
const Progress = Array(chainStageTotal).fill({
c: 0,
i: false,
m: false,
b: []
} satisfies IQuestStage);
const completedQuestKey: IQuestKeyDatabase = {
ItemType: questKey,
Completed: true,
unlock: true,
Progress: Progress,
CompletionDate: new Date()
};
//overwrite current quest progress, might lead to multiple quest item rewards
if (existingQuestKey) {
existingQuestKey.overwrite(completedQuestKey);
//Object.assign(existingQuestKey, completedQuestKey);
existingQuestKey.Progress = existingQuestKey.Progress ?? [];
const existingProgressLength = existingQuestKey.Progress.length;
if (existingProgressLength < chainStageTotal) {
const missingProgress: IQuestStage[] = Array.from(
{ length: chainStageTotal - existingProgressLength },
() =>
({
c: 0,
i: false,
m: false,
b: []
}) as IQuestStage
);
existingQuestKey.Progress.push(...missingProgress);
existingQuestKey.CompletionDate = new Date();
existingQuestKey.Completed = true;
}
} else {
const completedQuestKey: IQuestKeyDatabase = {
ItemType: questKey,
Completed: true,
unlock: true,
Progress: Array(chainStageTotal).fill({
c: 0,
i: false,
m: false,
b: []
} satisfies IQuestStage),
CompletionDate: new Date()
};
addQuestKey(inventory, completedQuestKey);
}
for (let i = 0; i < chainStageTotal; i++) {
if (chainStages[i].itemsToGiveWhenTriggered.length > 0) {
await giveKeyChainItem(inventory, { KeyChain: questKey, ChainStage: i });
}
for (let i = startingStage; i < chainStageTotal; i++) {
await giveKeyChainStageTriggered(inventory, { KeyChain: questKey, ChainStage: i });
if (chainStages[i].messageToSendWhenTriggered) {
await giveKeyChainMessage(inventory, inventory.accountOwnerId, {
KeyChain: questKey,
ChainStage: i
});
}
const missionName = chainStages[i].key;
if (missionName) {
const fixedLevelRewards = getLevelKeyRewards(missionName);
//logger.debug(`fixedLevelRewards`, fixedLevelRewards);
if (fixedLevelRewards.levelKeyRewards) {
const missionRewards: { StoreItem: string; ItemCount: number }[] = [];
addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, inventory, missionRewards);
for (const reward of missionRewards) {
await addItem(inventory, fromStoreItem(reward.StoreItem), reward.ItemCount);
}
} else if (fixedLevelRewards.levelKeyRewards2) {
for (const reward of fixedLevelRewards.levelKeyRewards2) {
if (reward.rewardType == "RT_CREDITS") {
inventory.RegularCredits += reward.amount;
continue;
}
if (reward.rewardType == "RT_RESOURCE") {
await addItem(inventory, fromStoreItem(reward.itemType), reward.amount);
} else {
await addItem(inventory, fromStoreItem(reward.itemType));
}
}
}
}
await giveKeyChainMissionReward(inventory, { KeyChain: questKey, ChainStage: i });
}
const questCompletionItems = getQuestCompletionItems(questKey);
@ -205,7 +188,7 @@ export const completeQuest = async (inventory: TInventoryDatabaseDocument, quest
await addItems(inventory, questCompletionItems);
}
inventory.ActiveQuest = "";
if (inventory.ActiveQuest == questKey) inventory.ActiveQuest = "";
if (questKey == "/Lotus/Types/Keys/NewWarQuest/NewWarQuestKeyChain") {
setupKahlSyndicate(inventory);
@ -247,3 +230,60 @@ export const giveKeyChainMessage = async (
updateQuestStage(inventory, keyChainInfo, { m: true });
};
export const giveKeyChainMissionReward = async (
inventory: TInventoryDatabaseDocument,
keyChainInfo: IKeyChainRequest
): Promise<void> => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const chainStages = ExportKeys[keyChainInfo.KeyChain]?.chainStages;
if (chainStages) {
const missionName = chainStages[keyChainInfo.ChainStage].key;
if (missionName) {
const fixedLevelRewards = getLevelKeyRewards(missionName);
if (fixedLevelRewards.levelKeyRewards) {
const missionRewards: { StoreItem: string; ItemCount: number }[] = [];
addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, inventory, missionRewards);
for (const reward of missionRewards) {
await addItem(inventory, fromStoreItem(reward.StoreItem), reward.ItemCount);
}
updateQuestStage(inventory, keyChainInfo, { c: 0 });
} else if (fixedLevelRewards.levelKeyRewards2) {
for (const reward of fixedLevelRewards.levelKeyRewards2) {
if (reward.rewardType == "RT_CREDITS") {
inventory.RegularCredits += reward.amount;
continue;
}
if (reward.rewardType == "RT_RESOURCE") {
await addItem(inventory, fromStoreItem(reward.itemType), reward.amount);
} else {
await addItem(inventory, fromStoreItem(reward.itemType));
}
}
updateQuestStage(inventory, keyChainInfo, { c: 0 });
}
}
}
};
export const giveKeyChainStageTriggered = async (
inventory: TInventoryDatabaseDocument,
keyChainInfo: IKeyChainRequest
): Promise<void> => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const chainStages = ExportKeys[keyChainInfo.KeyChain]?.chainStages;
if (chainStages) {
if (chainStages[keyChainInfo.ChainStage].itemsToGiveWhenTriggered.length > 0) {
await giveKeyChainItem(inventory, keyChainInfo);
}
if (chainStages[keyChainInfo.ChainStage].messageToSendWhenTriggered) {
await giveKeyChainMessage(inventory, inventory.accountOwnerId, keyChainInfo);
}
}
};

View File

@ -61,6 +61,9 @@
<li class="nav-item">
<a class="nav-link" href="/webui/mods" data-bs-dismiss="offcanvas" data-bs-target="#sidebar" data-loc="navbar_mods"></a>
</li>
<li class="nav-item">
<a class="nav-link" href="/webui/quests" data-bs-dismiss="offcanvas" data-bs-target="#sidebar" data-loc="navbar_quests"></a>
</li>
<li class="nav-item">
<a class="nav-link" href="/webui/cheats" data-bs-dismiss="offcanvas" data-bs-target="#sidebar" data-loc="navbar_cheats"></a>
</li>
@ -470,22 +473,31 @@
</div>
</div>
<div data-route="/webui/quests" data-title="Quests | OpenWF WebUI">
<div class="card mb-3">
<h5 class="card-header" data-loc="quests_list"></h5>
<div class="card-body">
<table class="table table-hover w-100">
<tbody id="active-quests"></tbody>
</table>
<div class="row g-3">
<div class="col-md-6">
<div class="card mb-3">
<h5 class="card-header" data-loc="quests_list"></h5>
<div class="card-body">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('QuestKeys');return false;">
<input class="form-control" id="acquire-type-QuestKeys" list="datalist-QuestKeys" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<table class="table table-hover w-100">
<tbody id="QuestKeys-list"></tbody>
</table>
</div>
</div>
</div>
</div>
<div class="card mb-3">
<h5 class="card-header" data-loc="quests_Actions"></h5>
<div class="card-body">
<div class="mb-2 d-flex flex-wrap gap-2">
<button class="btn btn-primary" onclick="doQuestUpdate('unlockAll');" data-loc="quests_UnlockAll"></button>
<button class="btn btn-primary" onclick="doQuestUpdate('completeAll');" data-loc="quests_CompleteAll"></button>
<button class="btn btn-primary" onclick="doQuestUpdate('completeAllUnlocked');" data-loc="quests_CompleteAllUnlocked"></button>
<button class="btn btn-primary" onclick="doQuestUpdate('ResetAll');" data-loc="quests_ResetAll"></button>
<div class="col-md-6">
<div class="card mb-3">
<h5 class="card-header" data-loc="general_bulkActions"></h5>
<div class="card-body">
<div class="d-flex flex-wrap gap-2">
<button class="btn btn-primary" onclick="doBulkQuestUpdate('giveAll');" data-loc="quests_giveAll"></button>
<button class="btn btn-primary" onclick="doBulkQuestUpdate('completeAll');" data-loc="quests_completeAll"></button>
<button class="btn btn-primary" onclick="doBulkQuestUpdate('resetAll');" data-loc="quests_resetAll"></button>
</div>
</div>
</div>
</div>
</div>
@ -633,14 +645,6 @@
<button class="btn btn-primary" type="submit" data-loc="cheats_changeButton"></button>
</div>
</form>
<h5 class="mt-3" data-loc="cheats_quests"></h6>
<div class="mb-2 d-flex flex-wrap gap-2">
<button class="btn btn-primary" onclick="doQuestUpdate('unlockAll');" data-loc="cheats_quests_unlockAll"></button>
<button class="btn btn-primary" onclick="doQuestUpdate('completeAll');" data-loc="cheats_quests_completeAll"></button>
<button class="btn btn-primary" onclick="doQuestUpdate('completeAllUnlocked');" data-loc="cheats_quests_completeAllUnlocked"></button>
<button class="btn btn-primary" onclick="doQuestUpdate('ResetAll');" data-loc="cheats_quests_resetAll"></button>
<button class="btn btn-primary" onclick="doQuestUpdate('giveAll');" data-loc="cheats_quests_giveAll"></button>
</div>
</div>
</div>
</div>
@ -666,6 +670,7 @@
<datalist id="datalist-MechSuits"></datalist>
<datalist id="datalist-Syndicates"></datalist>
<datalist id="datalist-MoaPets"></datalist>
<datalist id="datalist-QuestKeys"></datalist>
<datalist id="datalist-miscitems"></datalist>
<datalist id="datalist-mods">
<option data-key="/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser" value="Legendary Core"></option>

View File

@ -489,6 +489,132 @@ function updateInventory() {
});
});
// Populate quests route
document.getElementById("QuestKeys-list").innerHTML = "";
data.QuestKeys.forEach(item => {
const tr = document.createElement("tr");
tr.setAttribute("data-item-type", item.ItemType);
const stage = item.Progress?.length ?? 0;
const datalist = document.getElementById("datalist-QuestKeys");
const optionToRemove = datalist.querySelector(`option[data-key="${item.ItemType}"]`);
if (optionToRemove) {
datalist.removeChild(optionToRemove);
}
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
if (!item.Completed) {
td.textContent +=
" | " + loc("code_stage") + ": [" + stage + "/" + itemMap[item.ItemType].chainLength + "]";
} else {
td.textContent += " | " + loc("code_completed");
}
if (data.ActiveQuest == item.ItemType) td.textContent += " | " + loc("code_active");
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
if (data.ActiveQuest == item.ItemType && !item.Completed) {
console.log(data.ActiveQuest);
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
doQuestUpdate("setInactive", item.ItemType);
};
a.title = loc("code_setInactive");
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>`;
td.appendChild(a);
}
if (stage > 0) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
doQuestUpdate("resetKey", item.ItemType);
};
a.title = loc("code_reset");
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>`;
td.appendChild(a);
}
if (itemMap[item.ItemType].chainLength > stage && !item.Completed) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
doQuestUpdate("completeKey", item.ItemType);
};
a.title = loc("code_complete");
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>`;
td.appendChild(a);
}
if (stage > 0 && itemMap[item.ItemType].chainLength > 1) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
doQuestUpdate("prevStage", item.ItemType);
};
a.title = loc("code_prevStage");
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>`;
td.appendChild(a);
}
if (
itemMap[item.ItemType].chainLength > stage &&
!item.Completed &&
itemMap[item.ItemType].chainLength > 1
) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
doQuestUpdate("nextStage", item.ItemType);
};
a.title = loc("code_nextStage");
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>`;
td.appendChild(a);
}
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
const option = document.createElement("option");
option.setAttribute("data-key", item.ItemType);
option.value = itemMap[item.ItemType]?.name ?? item.ItemType;
document.getElementById("datalist-QuestKeys").appendChild(option);
doQuestUpdate("deleteKey", item.ItemType);
};
a.title = loc("code_remove");
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>`;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("QuestKeys-list").appendChild(tr);
});
const datalistQuestKeys = document.querySelectorAll("#datalist-QuestKeys option");
const form = document.querySelector("form[onsubmit*=\"doAcquireEquipment('QuestKeys')\"]");
const giveAllQuestButton = document.querySelector("button[onclick*=\"doBulkQuestUpdate('giveAll')\"]");
if (datalistQuestKeys.length === 0) {
form.classList.add("disabled");
form.querySelector("input").disabled = true;
form.querySelector("button").disabled = true;
giveAllQuestButton.disabled = true;
} else {
form.classList.remove("disabled");
form.querySelector("input").disabled = false;
form.querySelector("button").disabled = false;
giveAllQuestButton.disabled = false;
}
// Populate mods route
document.getElementById("riven-list").innerHTML = "";
document.getElementById("mods-list").innerHTML = "";
@ -1397,7 +1523,16 @@ function doAddCurrency(currency) {
});
}
function doQuestUpdate(operation) {
function doQuestUpdate(operation, itemType) {
$.post({
url: "/custom/manageQuests?" + window.authz + "&operation=" + operation + "&itemType=" + itemType,
contentType: "application/json"
}).then(function () {
updateInventory();
});
}
function doBulkQuestUpdate(operation) {
$.post({
url: "/custom/manageQuests?" + window.authz + "&operation=" + operation,
contentType: "application/json"

View File

@ -45,6 +45,14 @@ dict = {
code_zanukaA: `Jagdhund: Dorma`,
code_zanukaB: `Jagdhund: Bhaira`,
code_zanukaC: `Jagdhund: Hec`,
code_stage: `[UNTRANSLATED] Stage`,
code_complete: `[UNTRANSLATED] Complete`,
code_nextStage: `[UNTRANSLATED] Next stage`,
code_prevStage: `[UNTRANSLATED] Previous stage`,
code_reset: `[UNTRANSLATED] Reset`,
code_setInactive: `[UNTRANSLATED] Make the quest inactive`,
code_completed: `[UNTRANSLATED] Completed`,
code_active: `[UNTRANSLATED] Active`,
login_description: `Melde dich mit deinem OpenWF-Account an (denselben Angaben wie im Spiel, wenn du dich mit diesem Server verbindest).`,
login_emailLabel: `E-Mail-Adresse`,
login_passwordLabel: `Passwort`,
@ -84,6 +92,11 @@ dict = {
inventory_bulkRankUpSentinels: `Alle Wächter auf Max. Rang`,
inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`,
quests_list: `Quests`,
quests_completeAll: `Alle Quests abschließen`,
quests_resetAll: `Alle Quests zurücksetzen`,
quests_giveAll: `Alle Quests erhalten`,
currency_RegularCredits: `Credits`,
currency_PremiumCredits: `Platinum`,
currency_FusionPoints: `Endo`,
@ -135,12 +148,6 @@ dict = {
cheats_changeSupportedSyndicate: `Unterstütztes Syndikat`,
cheats_changeButton: `Ändern`,
cheats_none: `Keines`,
cheats_quests: `Quests`,
cheats_quests_unlockAll: `Alle Quests freischalten`,
cheats_quests_completeAll: `Alle Quests abschließen`,
cheats_quests_completeAllUnlocked: `Alle freigeschalteten Quests abschließen`,
cheats_quests_resetAll: `Alle Quests zurücksetzen`,
cheats_quests_giveAll: `Alle Quests erhalten`,
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>.`,
import_submit: `Absenden`,
prettier_sucks_ass: ``

View File

@ -44,6 +44,14 @@ dict = {
code_zanukaA: `Dorma Hound`,
code_zanukaB: `Bhaira Hound`,
code_zanukaC: `Hec Hound`,
code_stage: `Stage`,
code_complete: `Complete`,
code_nextStage: `Next stage`,
code_prevStage: `Previous stage`,
code_reset: `Reset`,
code_setInactive: `Make the quest inactive`,
code_completed: `Completed`,
code_active: `Active`,
login_description: `Login using your OpenWF account credentials (same as in-game when connecting to this server).`,
login_emailLabel: `Email address`,
login_passwordLabel: `Password`,
@ -83,6 +91,11 @@ dict = {
inventory_bulkRankUpSentinels: `Max Rank All Sentinels`,
inventory_bulkRankUpSentinelWeapons: `Max Rank All Sentinel Weapons`,
quests_list: `Quests`,
quests_completeAll: `Complete All Quests`,
quests_resetAll: `Reset All Quests`,
quests_giveAll: `Give All Quests`,
currency_RegularCredits: `Credits`,
currency_PremiumCredits: `Platinum`,
currency_FusionPoints: `Endo`,
@ -134,12 +147,6 @@ dict = {
cheats_changeSupportedSyndicate: `Supported syndicate`,
cheats_changeButton: `Change`,
cheats_none: `None`,
cheats_quests: `Quests`,
cheats_quests_unlockAll: `Unlock All Quests`,
cheats_quests_completeAll: `Complete All Quests`,
cheats_quests_completeAllUnlocked: `Complete All Unlocked Quests`,
cheats_quests_resetAll: `Reset All Quests`,
cheats_quests_giveAll: `Give All Quests`,
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.`,
import_submit: `Submit`,
prettier_sucks_ass: ``

View File

@ -45,6 +45,14 @@ dict = {
code_zanukaA: `Molosse Dorma`,
code_zanukaB: `Molosse Bhaira`,
code_zanukaC: `Molosse Hec`,
code_stage: `[UNTRANSLATED] Stage`,
code_complete: `[UNTRANSLATED] Complete`,
code_nextStage: `[UNTRANSLATED] Next stage`,
code_prevStage: `[UNTRANSLATED] Previous stage`,
code_reset: `[UNTRANSLATED] Reset`,
code_setInactive: `[UNTRANSLATED] Make the quest inactive`,
code_completed: `[UNTRANSLATED] Completed`,
code_active: `[UNTRANSLATED] Active`,
login_description: `Connexion avec les informations de connexion OpenWF.`,
login_emailLabel: `Email`,
login_passwordLabel: `Mot de passe`,
@ -84,6 +92,11 @@ dict = {
inventory_bulkRankUpSentinels: `Toutes les Sentinelles rang max`,
inventory_bulkRankUpSentinelWeapons: `Toutes les armes de Sentinelles rang max`,
quests_list: `Quêtes`,
quests_completeAll: `Compléter toutes les quêtes`,
quests_resetAll: `Réinitialiser toutes les quêtes`,
quests_giveAll: `Obtenir toutes les quêtes`,
currency_RegularCredits: `Crédits`,
currency_PremiumCredits: `Platinum`,
currency_FusionPoints: `Endo`,
@ -135,12 +148,6 @@ dict = {
cheats_changeSupportedSyndicate: `Allégeance`,
cheats_changeButton: `Changer`,
cheats_none: `Aucun`,
cheats_quests: `Quêtes`,
cheats_quests_unlockAll: `Débloquer toutes les quêtes`,
cheats_quests_completeAll: `Compléter toutes les quêtes`,
cheats_quests_completeAllUnlocked: `Compléter toutes les quêtes déverrouillées`,
cheats_quests_resetAll: `Réinitialiser toutes les quêtes`,
cheats_quests_giveAll: `Obtenir toutes les quêtes`,
import_importNote: `Import manuel. Toutes les modifcations supportées par l'inventaire <b>écraseront celles présentes dans la base de données</b>.`,
import_submit: `Soumettre`,
prettier_sucks_ass: ``

View File

@ -45,6 +45,14 @@ dict = {
code_zanukaA: `Гончая: Дорма`,
code_zanukaB: `Гончая: Бхайра`,
code_zanukaC: `Гончая: Хек`,
code_stage: `Этап`,
code_complete: `Завершить`,
code_nextStage: `едующий этап`,
code_prevStage: `Предыдущий этап`,
code_reset: `Сбросить`,
code_setInactive: `Сделать квест неактивным`,
code_completed: `Завершено`,
code_active: `Активный`,
login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`,
login_emailLabel: `Адрес электронной почты`,
login_passwordLabel: `Пароль`,
@ -84,6 +92,11 @@ dict = {
inventory_bulkRankUpSentinels: `Максимальный ранг всех стражей`,
inventory_bulkRankUpSentinelWeapons: `Максимальный ранг всего оружия стражей`,
quests_list: `Квесты`,
quests_completeAll: `Завершить все квесты`,
quests_resetAll: `Сбросить прогресс всех квестов`,
quests_giveAll: `Выдать все квесты`,
currency_RegularCredits: `Кредиты`,
currency_PremiumCredits: `Платина`,
currency_FusionPoints: `Эндо`,
@ -135,12 +148,6 @@ dict = {
cheats_changeSupportedSyndicate: `Поддерживаемый синдикат`,
cheats_changeButton: `Изменить`,
cheats_none: `Отсутствует`,
cheats_quests: `Квесты`,
cheats_quests_unlockAll: `Разблокировать все квесты`,
cheats_quests_completeAll: `Завершить все квесты`,
cheats_quests_completeAllUnlocked: `Завершить все разблокированые квесты`,
cheats_quests_resetAll: `Сбросить прогресс всех квестов`,
cheats_quests_giveAll: `Выдать все квесты`,
import_importNote: `Вы можете загрузить полный или частичный ответ инвентаря (клиентское представление) здесь. Все поддерживаемые поля <b>будут перезаписаны</b> в вашем аккаунте.`,
import_submit: `Отправить`,
prettier_sucks_ass: ``

View File

@ -45,6 +45,14 @@ dict = {
code_zanukaA: `铎玛猎犬`,
code_zanukaB: `拜拉猎犬`,
code_zanukaC: `骸克猎犬`,
code_stage: `[UNTRANSLATED] Stage`,
code_complete: `[UNTRANSLATED] Complete`,
code_nextStage: `[UNTRANSLATED] Next stage`,
code_prevStage: `[UNTRANSLATED] Previous stage`,
code_reset: `[UNTRANSLATED] Reset`,
code_setInactive: `[UNTRANSLATED] Make the quest inactive`,
code_completed: `[UNTRANSLATED] Completed`,
code_active: `[UNTRANSLATED] Active`,
login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)。`,
login_emailLabel: `电子邮箱`,
login_passwordLabel: `密码`,
@ -84,6 +92,11 @@ dict = {
inventory_bulkRankUpSentinels: `所有守护升满级`,
inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`,
quests_list: `任务`,
quests_completeAll: `完成所有任务`,
quests_resetAll: `重置所有任务`,
quests_giveAll: `授予所有任务`,
currency_RegularCredits: `现金`,
currency_PremiumCredits: `白金`,
currency_FusionPoints: `内融核心`,
@ -135,12 +148,6 @@ dict = {
cheats_changeSupportedSyndicate: `支持的集团`,
cheats_changeButton: `更改`,
cheats_none: ``,
cheats_quests: `任务`,
cheats_quests_unlockAll: `解锁所有任务`,
cheats_quests_completeAll: `完成所有任务`,
cheats_quests_completeAllUnlocked: `完成所有已解锁任务`,
cheats_quests_resetAll: `重置所有任务`,
cheats_quests_giveAll: `授予所有任务`,
import_importNote: `您可以在此处提供完整或部分库存响应(客户端表示)。支持的所有字段<b>将被覆盖</b>到您的账户中。`,
import_submit: `提交`,
prettier_sucks_ass: ``