feat(webui): quests support (#1411)
Reviewed-on: OpenWF/SpaceNinjaServer#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:
		
							parent
							
								
									5cc991baca
								
							
						
					
					
						commit
						710470ca2d
					
				@ -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
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
 | 
			
		||||
@ -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"));
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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"
 | 
			
		||||
 | 
			
		||||
@ -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: ``
 | 
			
		||||
 | 
			
		||||
@ -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: ``
 | 
			
		||||
 | 
			
		||||
@ -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: ``
 | 
			
		||||
 | 
			
		||||
@ -45,6 +45,14 @@ dict = {
 | 
			
		||||
    code_zanukaA: `Гончая: Дорма`,
 | 
			
		||||
    code_zanukaB: `Гончая: Бхайра`,
 | 
			
		||||
    code_zanukaC: `Гончая: Хек`,
 | 
			
		||||
    code_stage: `Этап`,
 | 
			
		||||
    code_complete: `Завершить`,
 | 
			
		||||
    code_nextStage: `Cледующий этап`,
 | 
			
		||||
    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: ``
 | 
			
		||||
 | 
			
		||||
@ -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: ``
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user