forked from OpenWF/SpaceNinjaServer
		
	fix(webui): quest cheats (#965)
Completing Quests via the webui will now also award the quest's items and mails. Also fixes doubly adding key chain items. A few items will not be added, as it is currently impossible to determine the item category by path for these items. This will be fixed soon. Reviewed-on: OpenWF/SpaceNinjaServer#965 Co-authored-by: Ordis <134585663+OrdisPrime@users.noreply.github.com> Co-committed-by: Ordis <134585663+OrdisPrime@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									b551563681
								
							
						
					
					
						commit
						fb8d176fbe
					
				@ -1,34 +1,19 @@
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
import { isEmptyObject, parseString } from "@/src/helpers/general";
 | 
			
		||||
import { parseString } from "@/src/helpers/general";
 | 
			
		||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
			
		||||
import { addKeyChainItems, getInventory } from "@/src/services/inventoryService";
 | 
			
		||||
import { getInventory } from "@/src/services/inventoryService";
 | 
			
		||||
import { IGroup } from "@/src/types/loginTypes";
 | 
			
		||||
import { updateQuestStage } from "@/src/services/questService";
 | 
			
		||||
import { giveKeyChainItem } from "@/src/services/questService";
 | 
			
		||||
 | 
			
		||||
export const giveKeyChainTriggeredItemsController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = parseString(req.query.accountId);
 | 
			
		||||
    const keyChainInfo = getJSONfromString<IKeyChainRequest>((req.body as string).toString());
 | 
			
		||||
 | 
			
		||||
    const inventory = await getInventory(accountId);
 | 
			
		||||
    const inventoryChanges = await addKeyChainItems(inventory, keyChainInfo);
 | 
			
		||||
 | 
			
		||||
    if (isEmptyObject(inventoryChanges)) {
 | 
			
		||||
        throw new Error("inventory changes was empty after getting keychain items: should not happen");
 | 
			
		||||
    }
 | 
			
		||||
    // items were added: update quest stage's i (item was given)
 | 
			
		||||
    updateQuestStage(inventory, keyChainInfo, { i: true });
 | 
			
		||||
 | 
			
		||||
    const inventoryChanges = giveKeyChainItem(inventory, keyChainInfo);
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.send(inventoryChanges);
 | 
			
		||||
 | 
			
		||||
    //TODO: Check whether Wishlist is used to track items which should exist uniquely in the inventory
 | 
			
		||||
    /*
 | 
			
		||||
    some items are added or removed (not sure) to the wishlist, in that case a 
 | 
			
		||||
    WishlistChanges: ["/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem"],
 | 
			
		||||
    is added to the response, need to determine for which items this is the case and what purpose this has.
 | 
			
		||||
    */
 | 
			
		||||
    //{"KeyChain":"/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain","ChainStage":0}
 | 
			
		||||
    //{"WishlistChanges":["/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem"],"MiscItems":[{"ItemType":"/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem","ItemCount":1}]}
 | 
			
		||||
    res.send(inventoryChanges);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export interface IKeyChainRequest {
 | 
			
		||||
 | 
			
		||||
@ -1,34 +1,15 @@
 | 
			
		||||
import { IKeyChainRequest } from "@/src/controllers/api/giveKeyChainTriggeredItemsController";
 | 
			
		||||
import { IMessage } from "@/src/models/inboxModel";
 | 
			
		||||
import { createMessage } from "@/src/services/inboxService";
 | 
			
		||||
import { getInventory } from "@/src/services/inventoryService";
 | 
			
		||||
import { getKeyChainMessage } from "@/src/services/itemDataService";
 | 
			
		||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
			
		||||
import { updateQuestStage } from "@/src/services/questService";
 | 
			
		||||
import { giveKeyChainMessage } from "@/src/services/questService";
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
 | 
			
		||||
export const giveKeyChainTriggeredMessageController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const keyChainInfo = JSON.parse((req.body as Buffer).toString()) as IKeyChainRequest;
 | 
			
		||||
 | 
			
		||||
    const keyChainMessage = getKeyChainMessage(keyChainInfo);
 | 
			
		||||
 | 
			
		||||
    const message = {
 | 
			
		||||
        sndr: keyChainMessage.sender,
 | 
			
		||||
        msg: keyChainMessage.body,
 | 
			
		||||
        sub: keyChainMessage.title,
 | 
			
		||||
        att: keyChainMessage.attachments.length > 0 ? keyChainMessage.attachments : undefined,
 | 
			
		||||
        countedAtt: keyChainMessage.countedAttachments.length > 0 ? keyChainMessage.countedAttachments : undefined,
 | 
			
		||||
        icon: keyChainMessage.icon ?? "",
 | 
			
		||||
        transmission: keyChainMessage.transmission ?? "",
 | 
			
		||||
        highPriority: keyChainMessage.highPriority ?? false,
 | 
			
		||||
        r: false
 | 
			
		||||
    } satisfies IMessage;
 | 
			
		||||
 | 
			
		||||
    await createMessage(accountId, [message]);
 | 
			
		||||
 | 
			
		||||
    const inventory = await getInventory(accountId, "QuestKeys");
 | 
			
		||||
    updateQuestStage(inventory, keyChainInfo, { m: true });
 | 
			
		||||
    await giveKeyChainMessage(inventory, accountId, keyChainInfo);
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
 | 
			
		||||
    res.send(1);
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,7 @@
 | 
			
		||||
import { addString } from "@/src/controllers/api/inventoryController";
 | 
			
		||||
import { getInventory } from "@/src/services/inventoryService";
 | 
			
		||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
			
		||||
import { addQuestKey, IUpdateQuestRequest, updateQuestKey } from "@/src/services/questService";
 | 
			
		||||
import { IQuestStage } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
			
		||||
import { addQuestKey, completeQuest, IUpdateQuestRequest, updateQuestKey } from "@/src/services/questService";
 | 
			
		||||
import { logger } from "@/src/utils/logger";
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
import { ExportKeys } from "warframe-public-export-plus";
 | 
			
		||||
@ -23,7 +22,7 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
 | 
			
		||||
            allQuestKeys.push(k);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    const inventory = await getInventory(accountId, "QuestKeys NodeIntrosCompleted");
 | 
			
		||||
    const inventory = await getInventory(accountId);
 | 
			
		||||
 | 
			
		||||
    switch (operation) {
 | 
			
		||||
        case "updateKey": {
 | 
			
		||||
@ -40,21 +39,31 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
 | 
			
		||||
        case "completeAll": {
 | 
			
		||||
            logger.info("completing all quests..");
 | 
			
		||||
            for (const questKey of allQuestKeys) {
 | 
			
		||||
                const chainStageTotal = ExportKeys[questKey].chainStages?.length ?? 0;
 | 
			
		||||
                const Progress = Array(chainStageTotal).fill({ c: 0, i: true, m: true, b: [] } satisfies IQuestStage);
 | 
			
		||||
                const inventoryQuestKey = inventory.QuestKeys.find(qk => qk.ItemType === questKey);
 | 
			
		||||
                if (inventoryQuestKey) {
 | 
			
		||||
                    inventoryQuestKey.Completed = true;
 | 
			
		||||
                    inventoryQuestKey.Progress = Progress;
 | 
			
		||||
                    continue;
 | 
			
		||||
                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);
 | 
			
		||||
                    }
 | 
			
		||||
                addQuestKey(inventory, { ItemType: questKey, Completed: true, unlock: true, Progress: Progress });
 | 
			
		||||
                }
 | 
			
		||||
            inventory.ArchwingEnabled = true;
 | 
			
		||||
            inventory.ActiveQuest = "";
 | 
			
		||||
 | 
			
		||||
                //Skip "Watch The Maker"
 | 
			
		||||
            addString(inventory.NodeIntrosCompleted, "/Lotus/Levels/Cinematics/NewWarIntro/NewWarStageTwo.level");
 | 
			
		||||
                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;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            inventory.ActiveQuest = "";
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case "ResetAll": {
 | 
			
		||||
@ -63,14 +72,37 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
 | 
			
		||||
                questKey.Completed = false;
 | 
			
		||||
                questKey.Progress = [];
 | 
			
		||||
            }
 | 
			
		||||
            inventory.ActiveQuest = "";
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case "completeAllUnlocked": {
 | 
			
		||||
            logger.info("completing all unlocked quests..");
 | 
			
		||||
            for (const questKey of inventory.QuestKeys) {
 | 
			
		||||
                //if (!questKey.unlock) { continue; }
 | 
			
		||||
                questKey.Completed = true;
 | 
			
		||||
                console.log("size of questkeys", inventory.QuestKeys.length);
 | 
			
		||||
                try {
 | 
			
		||||
                    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);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                //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;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            inventory.ActiveQuest = "";
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1249,6 +1249,7 @@ export type InventoryDocumentProps = {
 | 
			
		||||
    KahlLoadOuts: Types.DocumentArray<IOperatorConfigDatabase>;
 | 
			
		||||
    PendingRecipes: Types.DocumentArray<IPendingRecipeDatabase>;
 | 
			
		||||
    WeaponSkins: Types.DocumentArray<IWeaponSkinDatabase>;
 | 
			
		||||
    QuestKeys: Types.DocumentArray<IQuestKeyDatabase>;
 | 
			
		||||
} & { [K in TEquipmentKey]: Types.DocumentArray<IEquipmentDatabase> };
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/ban-types
 | 
			
		||||
 | 
			
		||||
@ -367,7 +367,7 @@ export const addItem = async (
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    if (typeName in ExportKeys) {
 | 
			
		||||
        // Note: "/Lotus/Types/Keys/" contains some EmailItems and ShipFeatureItems
 | 
			
		||||
        // Note: "/Lotus/Types/Keys/" contains some EmailItems
 | 
			
		||||
        inventory.QuestKeys.push({ ItemType: typeName });
 | 
			
		||||
        return {
 | 
			
		||||
            InventoryChanges: {
 | 
			
		||||
@ -524,9 +524,7 @@ export const addItem = async (
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
    const errorMessage = `unable to add item: ${typeName}`;
 | 
			
		||||
    logger.error(errorMessage);
 | 
			
		||||
    throw new Error(errorMessage);
 | 
			
		||||
    throw new Error(`unable to add item: ${typeName}`);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const addItems = async (
 | 
			
		||||
@ -1183,7 +1181,5 @@ export const addKeyChainItems = async (
 | 
			
		||||
        combineInventoryChanges(inventoryChanges, inventoryChangesDelta.InventoryChanges);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await addItems(inventory, nonStoreItems);
 | 
			
		||||
 | 
			
		||||
    return inventoryChanges;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,14 @@
 | 
			
		||||
import { IKeyChainRequest } from "@/src/controllers/api/giveKeyChainTriggeredItemsController";
 | 
			
		||||
import { isEmptyObject } from "@/src/helpers/general";
 | 
			
		||||
import { IMessage } from "@/src/models/inboxModel";
 | 
			
		||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
			
		||||
import { createMessage } from "@/src/services/inboxService";
 | 
			
		||||
import { addKeyChainItems } from "@/src/services/inventoryService";
 | 
			
		||||
import { getKeyChainMessage } from "@/src/services/itemDataService";
 | 
			
		||||
import { IInventoryDatabase, IQuestKeyDatabase, IQuestStage } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
			
		||||
import { logger } from "@/src/utils/logger";
 | 
			
		||||
import { HydratedDocument } from "mongoose";
 | 
			
		||||
import { ExportKeys } from "warframe-public-export-plus";
 | 
			
		||||
 | 
			
		||||
export interface IUpdateQuestRequest {
 | 
			
		||||
    QuestKeys: Omit<IQuestKeyDatabase, "CompletionDate">[];
 | 
			
		||||
@ -70,3 +76,98 @@ export const addQuestKey = (inventory: TInventoryDatabaseDocument, questKey: IQu
 | 
			
		||||
    }
 | 
			
		||||
    inventory.QuestKeys.push(questKey);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const completeQuest = async (inventory: TInventoryDatabaseDocument, questKey: string) => {
 | 
			
		||||
    const chainStages = ExportKeys[questKey]?.chainStages;
 | 
			
		||||
 | 
			
		||||
    if (!chainStages) {
 | 
			
		||||
        throw new Error(`Quest ${questKey} does not contain chain stages`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const chainStageTotal = ExportKeys[questKey].chainStages?.length ?? 0;
 | 
			
		||||
 | 
			
		||||
    const existingQuestKey = inventory.QuestKeys.find(qk => qk.ItemType === questKey);
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
    } else {
 | 
			
		||||
        addQuestKey(inventory, completedQuestKey);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < chainStageTotal; i++) {
 | 
			
		||||
        if (chainStages[i].itemsToGiveWhenTriggered.length > 0) {
 | 
			
		||||
            await giveKeyChainItem(inventory, { KeyChain: questKey, ChainStage: i });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (chainStages[i].messageToSendWhenTriggered) {
 | 
			
		||||
            await giveKeyChainMessage(inventory, inventory.accountOwnerId.toString(), {
 | 
			
		||||
                KeyChain: questKey,
 | 
			
		||||
                ChainStage: i
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    //TODO: handle quest completions
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const giveKeyChainItem = async (inventory: TInventoryDatabaseDocument, keyChainInfo: IKeyChainRequest) => {
 | 
			
		||||
    const inventoryChanges = await addKeyChainItems(inventory, keyChainInfo);
 | 
			
		||||
 | 
			
		||||
    if (isEmptyObject(inventoryChanges)) {
 | 
			
		||||
        throw new Error("inventory changes was empty after getting keychain items: should not happen");
 | 
			
		||||
    }
 | 
			
		||||
    // items were added: update quest stage's i (item was given)
 | 
			
		||||
    updateQuestStage(inventory, keyChainInfo, { i: true });
 | 
			
		||||
 | 
			
		||||
    //TODO: Check whether Wishlist is used to track items which should exist uniquely in the inventory
 | 
			
		||||
    /*
 | 
			
		||||
    some items are added or removed (not sure) to the wishlist, in that case a 
 | 
			
		||||
    WishlistChanges: ["/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem"],
 | 
			
		||||
    is added to the response, need to determine for which items this is the case and what purpose this has.
 | 
			
		||||
    */
 | 
			
		||||
    //{"KeyChain":"/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain","ChainStage":0}
 | 
			
		||||
    //{"WishlistChanges":["/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem"],"MiscItems":[{"ItemType":"/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem","ItemCount":1}]}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const giveKeyChainMessage = async (
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    accountId: string,
 | 
			
		||||
    keyChainInfo: IKeyChainRequest
 | 
			
		||||
) => {
 | 
			
		||||
    const keyChainMessage = getKeyChainMessage(keyChainInfo);
 | 
			
		||||
 | 
			
		||||
    const message = {
 | 
			
		||||
        sndr: keyChainMessage.sender,
 | 
			
		||||
        msg: keyChainMessage.body,
 | 
			
		||||
        sub: keyChainMessage.title,
 | 
			
		||||
        att: keyChainMessage.attachments.length > 0 ? keyChainMessage.attachments : undefined,
 | 
			
		||||
        countedAtt: keyChainMessage.countedAttachments.length > 0 ? keyChainMessage.countedAttachments : undefined,
 | 
			
		||||
        icon: keyChainMessage.icon ?? "",
 | 
			
		||||
        transmission: keyChainMessage.transmission ?? "",
 | 
			
		||||
        highPriority: keyChainMessage.highPriority ?? false,
 | 
			
		||||
        r: false
 | 
			
		||||
    } satisfies IMessage;
 | 
			
		||||
 | 
			
		||||
    await createMessage(accountId, [message]);
 | 
			
		||||
 | 
			
		||||
    updateQuestStage(inventory, keyChainInfo, { m: true });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user