forked from OpenWF/SpaceNinjaServer
		
	feat: Quests1 (#852)
This commit is contained in:
		
							parent
							
								
									8858b15693
								
							
						
					
					
						commit
						ef2708b510
					
				
							
								
								
									
										1655
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1655
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										56
									
								
								src/controllers/api/giveKeyChainTriggeredItemsController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/controllers/api/giveKeyChainTriggeredItemsController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,56 @@
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
import { isEmptyObject, parseString } from "@/src/helpers/general";
 | 
			
		||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
			
		||||
import { addKeyChainItems, getInventory } from "@/src/services/inventoryService";
 | 
			
		||||
 | 
			
		||||
export const giveKeyChainTriggeredItemsController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = parseString(req.query.accountId);
 | 
			
		||||
    const keyChainTriggeredItemsRequest = getJSONfromString(
 | 
			
		||||
        (req.body as string).toString()
 | 
			
		||||
    ) as IGiveKeyChainTriggeredItemsRequest;
 | 
			
		||||
 | 
			
		||||
    const inventory = await getInventory(accountId);
 | 
			
		||||
    const inventoryChanges = await addKeyChainItems(inventory, keyChainTriggeredItemsRequest);
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
    const quest = inventory.QuestKeys.find(quest => quest.ItemType === keyChainTriggeredItemsRequest.KeyChain);
 | 
			
		||||
 | 
			
		||||
    if (!quest) {
 | 
			
		||||
        throw new Error(`Quest ${keyChainTriggeredItemsRequest.KeyChain} not found in QuestKeys`);
 | 
			
		||||
    }
 | 
			
		||||
    if (!quest.Progress) {
 | 
			
		||||
        throw new Error(`Progress should always exist when giving keychain triggered items`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const questStage = quest.Progress[keyChainTriggeredItemsRequest.ChainStage];
 | 
			
		||||
    if (questStage) {
 | 
			
		||||
        questStage.i = true;
 | 
			
		||||
    } else {
 | 
			
		||||
        const questStageIndex = quest.Progress.push({ i: true }) - 1;
 | 
			
		||||
        if (questStageIndex !== keyChainTriggeredItemsRequest.ChainStage) {
 | 
			
		||||
            throw new Error(
 | 
			
		||||
                `Quest stage index mismatch: ${questStageIndex} !== ${keyChainTriggeredItemsRequest.ChainStage}`
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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}]}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export interface IGiveKeyChainTriggeredItemsRequest {
 | 
			
		||||
    KeyChain: string;
 | 
			
		||||
    ChainStage: number;
 | 
			
		||||
}
 | 
			
		||||
@ -22,8 +22,6 @@ export const inventorySlotsController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    //const body = JSON.parse(req.body as string) as IInventorySlotsRequest;
 | 
			
		||||
 | 
			
		||||
    //console.log(body);
 | 
			
		||||
 | 
			
		||||
    //TODO: check which slot was purchased because pvpBonus is also possible
 | 
			
		||||
 | 
			
		||||
    const inventory = await getInventory(accountId);
 | 
			
		||||
@ -31,7 +29,5 @@ export const inventorySlotsController: RequestHandler = async (req, res) => {
 | 
			
		||||
    updateSlots(inventory, InventorySlot.PVE_LOADOUTS, 1, 1);
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
 | 
			
		||||
    //console.log({ InventoryChanges: currencyChanges }, " added loadout changes:");
 | 
			
		||||
 | 
			
		||||
    res.json({ InventoryChanges: currencyChanges });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,13 @@
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
import { missionInventoryUpdate } from "@/src/services/inventoryService";
 | 
			
		||||
import { combineRewardAndLootInventory, getRewards } from "@/src/services/missionInventoryUpdateService";
 | 
			
		||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
			
		||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
			
		||||
import { IMissionInventoryUpdateRequest } from "@/src/types/requestTypes";
 | 
			
		||||
import { logger } from "@/src/utils/logger";
 | 
			
		||||
import {
 | 
			
		||||
    addMissionInventoryUpdates,
 | 
			
		||||
    addMissionRewards,
 | 
			
		||||
    calculateFinalCredits
 | 
			
		||||
} from "@/src/services/missionInventoryUpdateService";
 | 
			
		||||
import { getInventory } from "@/src/services/inventoryService";
 | 
			
		||||
/*
 | 
			
		||||
**** INPUT ****
 | 
			
		||||
- [ ]  crossPlaySetting
 | 
			
		||||
@ -30,13 +33,13 @@ import { logger } from "@/src/utils/logger";
 | 
			
		||||
- [ ]  hosts
 | 
			
		||||
- [x]  ChallengeProgress
 | 
			
		||||
- [ ]  SeasonChallengeHistory
 | 
			
		||||
- [ ]  PS (Passive anti-cheat data which includes your username, module list, process list, and system name.)
 | 
			
		||||
- [ ]  PS (anticheat data)
 | 
			
		||||
- [ ]  ActiveDojoColorResearch
 | 
			
		||||
- [x]  RewardInfo
 | 
			
		||||
- [ ]  ReceivedCeremonyMsg
 | 
			
		||||
- [ ]  LastCeremonyResetDate
 | 
			
		||||
- [ ]  MissionPTS (Used to validate the mission/alive time above.)
 | 
			
		||||
- [ ]  RepHash (A hash from the replication manager/RepMgr Unknown what it does.)
 | 
			
		||||
- [ ]  RepHash
 | 
			
		||||
- [ ]  EndOfMatchUpload
 | 
			
		||||
- [ ]  ObjectiveReached
 | 
			
		||||
- [ ]  FpsAvg
 | 
			
		||||
@ -45,34 +48,52 @@ import { logger } from "@/src/utils/logger";
 | 
			
		||||
- [ ]  FpsSamples
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
const missionInventoryUpdateController: RequestHandler = async (req, res): Promise<void> => {
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
 | 
			
		||||
export const missionInventoryUpdateController: RequestHandler = async (req, res): Promise<void> => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call
 | 
			
		||||
        const lootInventory = getJSONfromString(req.body.toString()) as IMissionInventoryUpdateRequest;
 | 
			
		||||
    const missionReport = getJSONfromString((req.body as string).toString()) as IMissionInventoryUpdateRequest;
 | 
			
		||||
 | 
			
		||||
        logger.debug("missionInventoryUpdate with lootInventory =", lootInventory);
 | 
			
		||||
 | 
			
		||||
        const { InventoryChanges, MissionRewards } = getRewards(lootInventory);
 | 
			
		||||
 | 
			
		||||
        const { combinedInventoryChanges, TotalCredits, CreditsBonus, MissionCredits, FusionPoints } =
 | 
			
		||||
            combineRewardAndLootInventory(InventoryChanges, lootInventory);
 | 
			
		||||
 | 
			
		||||
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
        const InventoryJson = JSON.stringify(await missionInventoryUpdate(combinedInventoryChanges, accountId));
 | 
			
		||||
        res.json({
 | 
			
		||||
            // InventoryJson, // this part will reset game data and missions will be locked
 | 
			
		||||
            MissionRewards,
 | 
			
		||||
            InventoryChanges,
 | 
			
		||||
            TotalCredits,
 | 
			
		||||
            CreditsBonus,
 | 
			
		||||
            MissionCredits,
 | 
			
		||||
            FusionPoints
 | 
			
		||||
        });
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
        console.error("Error parsing JSON data:", err);
 | 
			
		||||
    if (missionReport.MissionStatus !== "GS_SUCCESS") {
 | 
			
		||||
        console.log(`Mission failed: ${missionReport.RewardInfo?.node}`);
 | 
			
		||||
        //todo: return expected response for failed mission
 | 
			
		||||
        res.json([]);
 | 
			
		||||
        //duvirisadjob does not provide missionStatus
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const inventory = await getInventory(accountId);
 | 
			
		||||
 | 
			
		||||
    const missionRewardsResults = await addMissionRewards(inventory, missionReport);
 | 
			
		||||
 | 
			
		||||
    if (!missionRewardsResults) {
 | 
			
		||||
        console.error("Failed to add mission rewards");
 | 
			
		||||
        res.status(500).json({ error: "Failed to add mission rewards" });
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const { MissionRewards, inventoryChanges, missionCompletionCredits } = missionRewardsResults;
 | 
			
		||||
 | 
			
		||||
    const inventoryUpdates = addMissionInventoryUpdates(inventory, missionReport);
 | 
			
		||||
 | 
			
		||||
    //todo ? can go after not awaiting
 | 
			
		||||
    //creditBonus is not correct for mirage mission 3
 | 
			
		||||
    const credits = calculateFinalCredits(inventory, {
 | 
			
		||||
        missionCompletionCredits,
 | 
			
		||||
        missionDropCredits: missionReport.RegularCredits ?? 0,
 | 
			
		||||
        rngRewardCredits: inventoryChanges.RegularCredits as number
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const InventoryJson = JSON.stringify((await inventory.save()).toJSON());
 | 
			
		||||
 | 
			
		||||
    //TODO: figure out when to send inventory. it is needed for many cases.
 | 
			
		||||
    res.json({
 | 
			
		||||
        InventoryJson,
 | 
			
		||||
        InventoryChanges: inventoryChanges,
 | 
			
		||||
        MissionRewards,
 | 
			
		||||
        ...credits,
 | 
			
		||||
        ...inventoryUpdates,
 | 
			
		||||
        FusionPoints: inventoryChanges.FusionPoints
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
@ -85,5 +106,3 @@ const missionInventoryUpdateController: RequestHandler = async (req, res): Promi
 | 
			
		||||
- [x]  InventoryChanges
 | 
			
		||||
- [x]  FusionPoints
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
export { missionInventoryUpdateController };
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										12
									
								
								src/controllers/api/unlockShipFeatureController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/controllers/api/unlockShipFeatureController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
import { updateShipFeature } from "@/src/services/personalRoomsService";
 | 
			
		||||
import { IUnlockShipFeatureRequest } from "@/src/types/requestTypes";
 | 
			
		||||
import { parseString } from "@/src/helpers/general";
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
 | 
			
		||||
export const unlockShipFeatureController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = parseString(req.query.accountId);
 | 
			
		||||
    const shipFeatureRequest = JSON.parse((req.body as string).toString()) as IUnlockShipFeatureRequest;
 | 
			
		||||
    await updateShipFeature(accountId, shipFeatureRequest.Feature);
 | 
			
		||||
    res.send([]);
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										41
									
								
								src/controllers/api/updateQuestController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/controllers/api/updateQuestController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,41 @@
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
import { parseString } from "@/src/helpers/general";
 | 
			
		||||
import { logger } from "@/src/utils/logger";
 | 
			
		||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
 | 
			
		||||
import { updateQuestKey, IUpdateQuestRequest } from "@/src/services/questService";
 | 
			
		||||
import { getQuestCompletionItems } from "@/src/services/itemDataService";
 | 
			
		||||
import { addItem, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
 | 
			
		||||
export const updateQuestController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = parseString(req.query.accountId);
 | 
			
		||||
    const updateQuestRequest = getJSONfromString((req.body as string).toString()) as IUpdateQuestRequest;
 | 
			
		||||
 | 
			
		||||
    // updates should be made only to one quest key per request
 | 
			
		||||
    if (updateQuestRequest.QuestKeys.length > 1) {
 | 
			
		||||
        throw new Error(`quest keys array should only have 1 item, but has ${updateQuestRequest.QuestKeys.length}`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const inventory = await getInventory(accountId);
 | 
			
		||||
 | 
			
		||||
    updateQuestKey(inventory, updateQuestRequest.QuestKeys);
 | 
			
		||||
 | 
			
		||||
    if (updateQuestRequest.QuestKeys[0].Completed) {
 | 
			
		||||
        logger.debug(`completed quest ${updateQuestRequest.QuestKeys[0].ItemType} `);
 | 
			
		||||
        const questKeyName = updateQuestRequest.QuestKeys[0].ItemType;
 | 
			
		||||
        const questCompletionItems = getQuestCompletionItems(questKeyName);
 | 
			
		||||
 | 
			
		||||
        logger.debug(`quest completion items { ${questCompletionItems.map(item => item.ItemType).join(", ")} }`);
 | 
			
		||||
 | 
			
		||||
        const inventoryChanges = {};
 | 
			
		||||
        for (const item of questCompletionItems) {
 | 
			
		||||
            const inventoryDelta = await addItem(inventory, item.ItemType, item.ItemCount);
 | 
			
		||||
            combineInventoryChanges(inventoryChanges, inventoryDelta.InventoryChanges);
 | 
			
		||||
        }
 | 
			
		||||
        res.json({ MissionRewards: [], inventoryChanges });
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.send({ MissionRewards: [] });
 | 
			
		||||
};
 | 
			
		||||
@ -26,9 +26,9 @@ import {
 | 
			
		||||
    IInfestedFoundryDatabase,
 | 
			
		||||
    IHelminthResource,
 | 
			
		||||
    IConsumedSuit,
 | 
			
		||||
    IQuestProgress,
 | 
			
		||||
    IQuestStage,
 | 
			
		||||
    IQuestKeyDatabase,
 | 
			
		||||
    IQuestKeyResponse,
 | 
			
		||||
    IQuestKeyClient,
 | 
			
		||||
    IFusionTreasure,
 | 
			
		||||
    ISpectreLoadout,
 | 
			
		||||
    IWeaponSkinDatabase,
 | 
			
		||||
@ -518,7 +518,7 @@ infestedFoundrySchema.set("toJSON", {
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const questProgressSchema = new Schema<IQuestProgress>({
 | 
			
		||||
const questProgressSchema = new Schema<IQuestStage>({
 | 
			
		||||
    c: Number,
 | 
			
		||||
    i: Boolean,
 | 
			
		||||
    m: Boolean,
 | 
			
		||||
@ -527,7 +527,7 @@ const questProgressSchema = new Schema<IQuestProgress>({
 | 
			
		||||
 | 
			
		||||
const questKeysSchema = new Schema<IQuestKeyDatabase>(
 | 
			
		||||
    {
 | 
			
		||||
        Progress: [questProgressSchema],
 | 
			
		||||
        Progress: { type: [questProgressSchema], default: undefined },
 | 
			
		||||
        unlock: Boolean,
 | 
			
		||||
        Completed: Boolean,
 | 
			
		||||
        //CustomData: Schema.Types.Mixed,
 | 
			
		||||
@ -544,7 +544,7 @@ questKeysSchema.set("toJSON", {
 | 
			
		||||
        const questKeysDatabase = ret as IQuestKeyDatabase;
 | 
			
		||||
 | 
			
		||||
        if (questKeysDatabase.CompletionDate) {
 | 
			
		||||
            (questKeysDatabase as IQuestKeyResponse).CompletionDate = toMongoDate(questKeysDatabase.CompletionDate);
 | 
			
		||||
            (questKeysDatabase as IQuestKeyClient).CompletionDate = toMongoDate(questKeysDatabase.CompletionDate);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
@ -941,6 +941,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
 | 
			
		||||
        //Complete Mission\Quests
 | 
			
		||||
        Missions: [Schema.Types.Mixed],
 | 
			
		||||
        QuestKeys: [questKeysSchema],
 | 
			
		||||
        ActiveQuest: { type: String, default: "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain" }, //TODO: check after mission starting gear
 | 
			
		||||
        //item like DojoKey or Boss missions key
 | 
			
		||||
        LevelKeys: [Schema.Types.Mixed],
 | 
			
		||||
        //Active quests
 | 
			
		||||
@ -1164,7 +1165,7 @@ inventorySchema.set("toJSON", {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// type overwrites for subdocuments/subdocument arrays
 | 
			
		||||
type InventoryDocumentProps = {
 | 
			
		||||
export type InventoryDocumentProps = {
 | 
			
		||||
    Suits: Types.DocumentArray<IEquipmentDatabase>;
 | 
			
		||||
    LongGuns: Types.DocumentArray<IEquipmentDatabase>;
 | 
			
		||||
    Pistols: Types.DocumentArray<IEquipmentDatabase>;
 | 
			
		||||
 | 
			
		||||
@ -70,7 +70,7 @@ const apartmentSchema = new Schema<IApartment>(
 | 
			
		||||
    {
 | 
			
		||||
        Rooms: [roomSchema],
 | 
			
		||||
        FavouriteLoadouts: [Schema.Types.Mixed],
 | 
			
		||||
        Gardening: gardeningSchema
 | 
			
		||||
        Gardening: gardeningSchema // TODO: ensure this is correct
 | 
			
		||||
    },
 | 
			
		||||
    { _id: false }
 | 
			
		||||
);
 | 
			
		||||
@ -96,7 +96,7 @@ const orbiterSchema = new Schema<IOrbiter>(
 | 
			
		||||
    { _id: false }
 | 
			
		||||
);
 | 
			
		||||
const orbiterDefault: IOrbiter = {
 | 
			
		||||
    Features: [],
 | 
			
		||||
    Features: ["/Lotus/Types/Items/ShipFeatureItems/EarthNavigationFeatureItem"], //TODO: potentially remove after missionstarting gear
 | 
			
		||||
    Rooms: [
 | 
			
		||||
        { Name: "AlchemyRoom", MaxCapacity: 1600 },
 | 
			
		||||
        { Name: "BridgeRoom", MaxCapacity: 1600 },
 | 
			
		||||
 | 
			
		||||
@ -78,6 +78,9 @@ import { updateChallengeProgressController } from "@/src/controllers/api/updateC
 | 
			
		||||
import { updateSessionGetController, updateSessionPostController } from "@/src/controllers/api/updateSessionController";
 | 
			
		||||
import { updateThemeController } from "../controllers/api/updateThemeController";
 | 
			
		||||
import { upgradesController } from "@/src/controllers/api/upgradesController";
 | 
			
		||||
import { updateQuestController } from "@/src/controllers/api/updateQuestController";
 | 
			
		||||
import { giveKeyChainTriggeredItemsController } from "@/src/controllers/api/giveKeyChainTriggeredItemsController";
 | 
			
		||||
import { unlockShipFeatureController } from "@/src/controllers/api/unlockShipFeatureController";
 | 
			
		||||
 | 
			
		||||
const apiRouter = express.Router();
 | 
			
		||||
 | 
			
		||||
@ -132,6 +135,7 @@ apiRouter.post("/genericUpdate.php", genericUpdateController);
 | 
			
		||||
apiRouter.post("/getAlliance.php", getAllianceController);
 | 
			
		||||
apiRouter.post("/getVoidProjectionRewards.php", getVoidProjectionRewardsController);
 | 
			
		||||
apiRouter.post("/gildWeapon.php", gildWeaponController);
 | 
			
		||||
apiRouter.post("/giveKeyChainTriggeredItems.php", giveKeyChainTriggeredItemsController);
 | 
			
		||||
apiRouter.post("/guildTech.php", guildTechController);
 | 
			
		||||
apiRouter.post("/hostSession.php", hostSessionController);
 | 
			
		||||
apiRouter.post("/infestedFoundry.php", infestedFoundryController);
 | 
			
		||||
@ -161,10 +165,12 @@ apiRouter.post("/syndicateSacrifice.php", syndicateSacrificeController);
 | 
			
		||||
apiRouter.post("/syndicateStandingBonus.php", syndicateStandingBonusController);
 | 
			
		||||
apiRouter.post("/tauntHistory.php", tauntHistoryController);
 | 
			
		||||
apiRouter.post("/trainingResult.php", trainingResultController);
 | 
			
		||||
apiRouter.post("/unlockShipFeature.php", unlockShipFeatureController);
 | 
			
		||||
apiRouter.post("/updateChallengeProgress.php", updateChallengeProgressController);
 | 
			
		||||
apiRouter.post("/updateNodeIntros.php", genericUpdateController);
 | 
			
		||||
apiRouter.post("/updateSession.php", updateSessionPostController);
 | 
			
		||||
apiRouter.post("/updateTheme.php", updateThemeController);
 | 
			
		||||
apiRouter.post("/updateQuest.php", updateQuestController);
 | 
			
		||||
apiRouter.post("/upgrades.php", upgradesController);
 | 
			
		||||
 | 
			
		||||
export { apiRouter };
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,10 @@
 | 
			
		||||
import { Inventory, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
			
		||||
import {
 | 
			
		||||
    Inventory,
 | 
			
		||||
    InventoryDocumentProps,
 | 
			
		||||
    TInventoryDatabaseDocument
 | 
			
		||||
} from "@/src/models/inventoryModels/inventoryModel";
 | 
			
		||||
import { config } from "@/src/services/configService";
 | 
			
		||||
import { Types } from "mongoose";
 | 
			
		||||
import { HydratedDocument, Types } from "mongoose";
 | 
			
		||||
import { SlotNames, IInventoryChanges, IBinChanges, ICurrencyChanges } from "@/src/types/purchaseTypes";
 | 
			
		||||
import {
 | 
			
		||||
    IChallengeProgress,
 | 
			
		||||
@ -14,9 +18,9 @@ import {
 | 
			
		||||
    InventorySlot,
 | 
			
		||||
    IWeaponSkinClient,
 | 
			
		||||
    TEquipmentKey,
 | 
			
		||||
    equipmentKeys,
 | 
			
		||||
    IFusionTreasure,
 | 
			
		||||
    IDailyAffiliations
 | 
			
		||||
    IDailyAffiliations,
 | 
			
		||||
    IInventoryDatabase
 | 
			
		||||
} from "@/src/types/inventoryTypes/inventoryTypes";
 | 
			
		||||
import { IGenericUpdate } from "../types/genericUpdate";
 | 
			
		||||
import {
 | 
			
		||||
@ -25,7 +29,7 @@ import {
 | 
			
		||||
    IUpdateChallengeProgressRequest
 | 
			
		||||
} from "../types/requestTypes";
 | 
			
		||||
import { logger } from "@/src/utils/logger";
 | 
			
		||||
import { getWeaponType, getExalted } from "@/src/services/itemDataService";
 | 
			
		||||
import { getWeaponType, getExalted, getKeyChainItems } from "@/src/services/itemDataService";
 | 
			
		||||
import { IEquipmentClient, IItemConfig } from "../types/inventoryTypes/commonInventoryTypes";
 | 
			
		||||
import {
 | 
			
		||||
    ExportArcanes,
 | 
			
		||||
@ -39,6 +43,8 @@ import {
 | 
			
		||||
    TStandingLimitBin
 | 
			
		||||
} from "warframe-public-export-plus";
 | 
			
		||||
import { createShip } from "./shipService";
 | 
			
		||||
import { creditBundles, fusionBundles } from "@/src/services/missionInventoryUpdateService";
 | 
			
		||||
import { IGiveKeyChainTriggeredItemsRequest } from "@/src/controllers/api/giveKeyChainTriggeredItemsController";
 | 
			
		||||
 | 
			
		||||
export const createInventory = async (
 | 
			
		||||
    accountOwnerId: Types.ObjectId,
 | 
			
		||||
@ -64,31 +70,32 @@ export const createInventory = async (
 | 
			
		||||
                { ItemCount: 1, ItemType: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem2" },
 | 
			
		||||
                { ItemCount: 1, ItemType: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem3" },
 | 
			
		||||
                { ItemCount: 1, ItemType: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem4" },
 | 
			
		||||
                { ItemCount: 1, ItemType: "/Lotus/Types/Restoratives/LisetAutoHack" },
 | 
			
		||||
 | 
			
		||||
                // Vor's Prize rewards
 | 
			
		||||
                { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarHealthMaxMod" },
 | 
			
		||||
                { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarShieldMaxMod" },
 | 
			
		||||
                { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarAbilityRangeMod" },
 | 
			
		||||
                { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarAbilityStrengthMod" },
 | 
			
		||||
                { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarAbilityDurationMod" },
 | 
			
		||||
                { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarPickupBonusMod" },
 | 
			
		||||
                { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarPowerMaxMod" },
 | 
			
		||||
                { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarEnemyRadarMod" },
 | 
			
		||||
                { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Melee/WeaponFireRateMod" },
 | 
			
		||||
                { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Melee/WeaponMeleeDamageMod" },
 | 
			
		||||
                { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Rifle/WeaponFactionDamageCorpus" },
 | 
			
		||||
                { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Rifle/WeaponFactionDamageGrineer" },
 | 
			
		||||
                { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Rifle/WeaponDamageAmountMod" },
 | 
			
		||||
                { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Pistol/WeaponFireDamageMod" },
 | 
			
		||||
                { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Pistol/WeaponElectricityDamageMod" },
 | 
			
		||||
                { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Pistol/WeaponDamageAmountMod" },
 | 
			
		||||
                { ItemCount: 1, ItemType: "/Lotus/Types/Recipes/Weapons/BurstonRifleBlueprint" },
 | 
			
		||||
                { ItemCount: 1, ItemType: "/Lotus/Types/Items/MiscItems/Morphic" },
 | 
			
		||||
                { ItemCount: 400, ItemType: "/Lotus/Types/Items/MiscItems/PolymerBundle" },
 | 
			
		||||
                { ItemCount: 150, ItemType: "/Lotus/Types/Items/MiscItems/AlloyPlate" }
 | 
			
		||||
                { ItemCount: 1, ItemType: "/Lotus/Types/Restoratives/LisetAutoHack" }
 | 
			
		||||
            ];
 | 
			
		||||
 | 
			
		||||
            // const vorsPrizeRewards = [
 | 
			
		||||
            //     // Vor's Prize rewards
 | 
			
		||||
            //     { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarHealthMaxMod" },
 | 
			
		||||
            //     { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarShieldMaxMod" },
 | 
			
		||||
            //     { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarAbilityRangeMod" },
 | 
			
		||||
            //     { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarAbilityStrengthMod" },
 | 
			
		||||
            //     { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarAbilityDurationMod" },
 | 
			
		||||
            //     { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarPickupBonusMod" },
 | 
			
		||||
            //     { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarPowerMaxMod" },
 | 
			
		||||
            //     { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Warframe/AvatarEnemyRadarMod" },
 | 
			
		||||
            //     { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Melee/WeaponFireRateMod" },
 | 
			
		||||
            //     { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Melee/WeaponMeleeDamageMod" },
 | 
			
		||||
            //     { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Rifle/WeaponFactionDamageCorpus" },
 | 
			
		||||
            //     { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Rifle/WeaponFactionDamageGrineer" },
 | 
			
		||||
            //     { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Rifle/WeaponDamageAmountMod" },
 | 
			
		||||
            //     { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Pistol/WeaponFireDamageMod" },
 | 
			
		||||
            //     { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Pistol/WeaponElectricityDamageMod" },
 | 
			
		||||
            //     { ItemCount: 1, ItemType: "/Lotus/Upgrades/Mods/Pistol/WeaponDamageAmountMod" },
 | 
			
		||||
            //     { ItemCount: 1, ItemType: "/Lotus/Types/Recipes/Weapons/BurstonRifleBlueprint" },
 | 
			
		||||
            //     { ItemCount: 1, ItemType: "/Lotus/Types/Items/MiscItems/Morphic" },
 | 
			
		||||
            //     { ItemCount: 400, ItemType: "/Lotus/Types/Items/MiscItems/PolymerBundle" },
 | 
			
		||||
            //     { ItemCount: 150, ItemType: "/Lotus/Types/Items/MiscItems/AlloyPlate" }
 | 
			
		||||
            // ];
 | 
			
		||||
            for (const equipment of defaultEquipment) {
 | 
			
		||||
                await addItem(inventory, equipment.ItemType, equipment.ItemCount);
 | 
			
		||||
            }
 | 
			
		||||
@ -109,7 +116,6 @@ export const createInventory = async (
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            inventory.QuestKeys.push({
 | 
			
		||||
                Completed: true,
 | 
			
		||||
                ItemType: "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain"
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
@ -132,13 +138,19 @@ export const createInventory = async (
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Combines two inventory changes objects into one.
 | 
			
		||||
 *
 | 
			
		||||
 * @param InventoryChanges - will hold the combined changes
 | 
			
		||||
 * @param delta - inventory changes to be added
 | 
			
		||||
 */
 | 
			
		||||
export const combineInventoryChanges = (InventoryChanges: IInventoryChanges, delta: IInventoryChanges): void => {
 | 
			
		||||
    for (const key in delta) {
 | 
			
		||||
        if (!(key in InventoryChanges)) {
 | 
			
		||||
            InventoryChanges[key] = delta[key];
 | 
			
		||||
        } else if (Array.isArray(delta[key])) {
 | 
			
		||||
            const left = InventoryChanges[key] as object[];
 | 
			
		||||
            const right: object[] = delta[key];
 | 
			
		||||
            const right: object[] | string[] = delta[key];
 | 
			
		||||
            for (const item of right) {
 | 
			
		||||
                left.push(item);
 | 
			
		||||
            }
 | 
			
		||||
@ -154,8 +166,10 @@ export const combineInventoryChanges = (InventoryChanges: IInventoryChanges, del
 | 
			
		||||
                left.Extra ??= 0;
 | 
			
		||||
                left.Extra += right.Extra;
 | 
			
		||||
            }
 | 
			
		||||
        } else if (typeof delta[key] === "number") {
 | 
			
		||||
            (InventoryChanges[key] as number) += delta[key];
 | 
			
		||||
        } else {
 | 
			
		||||
            logger.warn(`inventory change not merged: ${key}`);
 | 
			
		||||
            throw new Error(`inventory change not merged: unhandled type for inventory key ${key}`);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
@ -274,6 +288,24 @@ export const addItem = async (
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    if (typeName in creditBundles) {
 | 
			
		||||
        const creditsTotal = creditBundles[typeName] * quantity;
 | 
			
		||||
        inventory.RegularCredits += creditsTotal;
 | 
			
		||||
        return {
 | 
			
		||||
            InventoryChanges: {
 | 
			
		||||
                RegularCredits: creditsTotal
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    if (typeName in fusionBundles) {
 | 
			
		||||
        const fusionPointsTotal = fusionBundles[typeName] * quantity;
 | 
			
		||||
        inventory.FusionPoints += fusionPointsTotal;
 | 
			
		||||
        return {
 | 
			
		||||
            InventoryChanges: {
 | 
			
		||||
                FusionPoints: fusionPointsTotal
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Path-based duck typing
 | 
			
		||||
    switch (typeName.substr(1).split("/")[1]) {
 | 
			
		||||
@ -364,7 +396,7 @@ export const addItem = async (
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                case "Game":
 | 
			
		||||
                case "Game": {
 | 
			
		||||
                    if (typeName.substr(1).split("/")[3] == "Projections") {
 | 
			
		||||
                        // Void Relics, e.g. /Lotus/Types/Game/Projections/T2VoidProjectionGaussPrimeDBronze
 | 
			
		||||
                        const miscItemChanges = [
 | 
			
		||||
@ -381,6 +413,40 @@ export const addItem = async (
 | 
			
		||||
                        };
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                case "Keys": {
 | 
			
		||||
                    inventory.QuestKeys.push({ ItemType: typeName });
 | 
			
		||||
                    return {
 | 
			
		||||
                        InventoryChanges: {
 | 
			
		||||
                            QuestKeys: [
 | 
			
		||||
                                {
 | 
			
		||||
                                    ItemType: typeName
 | 
			
		||||
                                }
 | 
			
		||||
                            ]
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
                }
 | 
			
		||||
                case "NeutralCreatures": {
 | 
			
		||||
                    const horseIndex = inventory.Horses.push({ ItemType: typeName });
 | 
			
		||||
                    return {
 | 
			
		||||
                        InventoryChanges: {
 | 
			
		||||
                            Horses: inventory.Horses[horseIndex - 1].toJSON()
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
                }
 | 
			
		||||
                case "Recipes": {
 | 
			
		||||
                    inventory.MiscItems.push({ ItemType: typeName, ItemCount: quantity });
 | 
			
		||||
                    return {
 | 
			
		||||
                        InventoryChanges: {
 | 
			
		||||
                            MiscItems: [
 | 
			
		||||
                                {
 | 
			
		||||
                                    ItemType: typeName,
 | 
			
		||||
                                    ItemCount: quantity
 | 
			
		||||
                                }
 | 
			
		||||
                            ]
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
@ -684,7 +750,8 @@ const addCrewShip = (
 | 
			
		||||
    return inventoryChanges;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const addGearExpByCategory = (
 | 
			
		||||
//TODO: wrong id is not erroring
 | 
			
		||||
export const addGearExpByCategory = (
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    gearArray: IEquipmentClient[] | undefined,
 | 
			
		||||
    categoryName: TEquipmentKey
 | 
			
		||||
@ -870,7 +937,7 @@ export const addChallenges = (
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag, Completes }: IMission): void => {
 | 
			
		||||
export const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag, Completes }: IMission): void => {
 | 
			
		||||
    const { Missions } = inventory;
 | 
			
		||||
    const itemIndex = Missions.findIndex(item => item.Tag === Tag);
 | 
			
		||||
 | 
			
		||||
@ -882,83 +949,6 @@ const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag, Comple
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const missionInventoryUpdate = async (data: IMissionInventoryUpdateRequest, accountId: string) => {
 | 
			
		||||
    const {
 | 
			
		||||
        RawUpgrades,
 | 
			
		||||
        MiscItems,
 | 
			
		||||
        RegularCredits,
 | 
			
		||||
        ChallengeProgress,
 | 
			
		||||
        FusionPoints,
 | 
			
		||||
        Consumables,
 | 
			
		||||
        Recipes,
 | 
			
		||||
        Missions,
 | 
			
		||||
        FusionTreasures
 | 
			
		||||
    } = data;
 | 
			
		||||
    const inventory = await getInventory(accountId);
 | 
			
		||||
 | 
			
		||||
    // credits
 | 
			
		||||
    inventory.RegularCredits += RegularCredits || 0;
 | 
			
		||||
 | 
			
		||||
    // endo
 | 
			
		||||
    inventory.FusionPoints += FusionPoints || 0;
 | 
			
		||||
 | 
			
		||||
    // syndicate
 | 
			
		||||
    data.AffiliationChanges?.forEach(affiliation => {
 | 
			
		||||
        const syndicate = inventory.Affiliations.find(x => x.Tag == affiliation.Tag);
 | 
			
		||||
        if (syndicate !== undefined) {
 | 
			
		||||
            syndicate.Standing =
 | 
			
		||||
                syndicate.Standing === undefined ? affiliation.Standing : syndicate.Standing + affiliation.Standing;
 | 
			
		||||
            syndicate.Title = syndicate.Title === undefined ? affiliation.Title : syndicate.Title + affiliation.Title;
 | 
			
		||||
        } else {
 | 
			
		||||
            inventory.Affiliations.push({
 | 
			
		||||
                Standing: affiliation.Standing,
 | 
			
		||||
                Title: affiliation.Title,
 | 
			
		||||
                Tag: affiliation.Tag,
 | 
			
		||||
                FreeFavorsEarned: [],
 | 
			
		||||
                FreeFavorsUsed: []
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Gear XP
 | 
			
		||||
    equipmentKeys.forEach(key => addGearExpByCategory(inventory, data[key], key));
 | 
			
		||||
 | 
			
		||||
    // Incarnon Challenges
 | 
			
		||||
    if (data.EvolutionProgress) {
 | 
			
		||||
        for (const evoProgress of data.EvolutionProgress) {
 | 
			
		||||
            const entry = inventory.EvolutionProgress
 | 
			
		||||
                ? inventory.EvolutionProgress.find(entry => entry.ItemType == evoProgress.ItemType)
 | 
			
		||||
                : undefined;
 | 
			
		||||
            if (entry) {
 | 
			
		||||
                entry.Progress = evoProgress.Progress;
 | 
			
		||||
                entry.Rank = evoProgress.Rank;
 | 
			
		||||
            } else {
 | 
			
		||||
                inventory.EvolutionProgress ??= [];
 | 
			
		||||
                inventory.EvolutionProgress.push(evoProgress);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // LastRegionPlayed
 | 
			
		||||
    if (data.LastRegionPlayed) {
 | 
			
		||||
        inventory.LastRegionPlayed = data.LastRegionPlayed;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // other
 | 
			
		||||
    addMods(inventory, RawUpgrades);
 | 
			
		||||
    addMiscItems(inventory, MiscItems);
 | 
			
		||||
    addConsumables(inventory, Consumables);
 | 
			
		||||
    addRecipes(inventory, Recipes);
 | 
			
		||||
    addChallenges(inventory, ChallengeProgress);
 | 
			
		||||
    addFusionTreasures(inventory, FusionTreasures);
 | 
			
		||||
    if (Missions) {
 | 
			
		||||
        addMissionComplete(inventory, Missions);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const changedInventory = await inventory.save();
 | 
			
		||||
    return changedInventory.toJSON();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const addBooster = async (ItemType: string, time: number, accountId: string): Promise<void> => {
 | 
			
		||||
    const currentTime = Math.floor(Date.now() / 1000) - 129600; // Value is wrong without 129600. Figure out why, please. :)
 | 
			
		||||
 | 
			
		||||
@ -977,3 +967,52 @@ export const addBooster = async (ItemType: string, time: number, accountId: stri
 | 
			
		||||
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const updateSyndicate = (
 | 
			
		||||
    inventory: HydratedDocument<IInventoryDatabase, InventoryDocumentProps>,
 | 
			
		||||
    syndicateUpdate: IMissionInventoryUpdateRequest["AffiliationChanges"]
 | 
			
		||||
) => {
 | 
			
		||||
    syndicateUpdate?.forEach(affiliation => {
 | 
			
		||||
        const syndicate = inventory.Affiliations.find(x => x.Tag == affiliation.Tag);
 | 
			
		||||
        if (syndicate !== undefined) {
 | 
			
		||||
            syndicate.Standing =
 | 
			
		||||
                syndicate.Standing === undefined ? affiliation.Standing : syndicate.Standing + affiliation.Standing;
 | 
			
		||||
            syndicate.Title = syndicate.Title === undefined ? affiliation.Title : syndicate.Title + affiliation.Title;
 | 
			
		||||
        } else {
 | 
			
		||||
            inventory.Affiliations.push({
 | 
			
		||||
                Standing: affiliation.Standing,
 | 
			
		||||
                Title: affiliation.Title,
 | 
			
		||||
                Tag: affiliation.Tag,
 | 
			
		||||
                FreeFavorsEarned: [],
 | 
			
		||||
                FreeFavorsUsed: []
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    return { AffiliationMods: [] };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @returns object with inventory keys of changes or empty object when no items were added
 | 
			
		||||
 */
 | 
			
		||||
export const addKeyChainItems = async (
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    keyChainData: IGiveKeyChainTriggeredItemsRequest
 | 
			
		||||
): Promise<IInventoryChanges> => {
 | 
			
		||||
    const keyChainItems = getKeyChainItems(keyChainData);
 | 
			
		||||
 | 
			
		||||
    logger.debug(
 | 
			
		||||
        `adding key chain items ${keyChainItems.join()} for ${keyChainData.KeyChain} at stage ${keyChainData.ChainStage}`
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const nonStoreItems = keyChainItems.map(item => item.replace("StoreItems/", ""));
 | 
			
		||||
 | 
			
		||||
    //TODO: inventoryChanges is not typed correctly
 | 
			
		||||
    const inventoryChanges = {};
 | 
			
		||||
 | 
			
		||||
    for (const item of nonStoreItems) {
 | 
			
		||||
        const inventoryChangesDelta = await addItem(inventory, item);
 | 
			
		||||
        combineInventoryChanges(inventoryChanges, inventoryChangesDelta.InventoryChanges);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return inventoryChanges;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,7 @@
 | 
			
		||||
import { IGiveKeyChainTriggeredItemsRequest } from "@/src/controllers/api/giveKeyChainTriggeredItemsController";
 | 
			
		||||
import { getIndexAfter } from "@/src/helpers/stringHelpers";
 | 
			
		||||
import { ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
			
		||||
import { logger } from "@/src/utils/logger";
 | 
			
		||||
import {
 | 
			
		||||
    dict_de,
 | 
			
		||||
    dict_en,
 | 
			
		||||
@ -20,13 +23,16 @@ import {
 | 
			
		||||
    ExportGear,
 | 
			
		||||
    ExportKeys,
 | 
			
		||||
    ExportRecipes,
 | 
			
		||||
    ExportRegions,
 | 
			
		||||
    ExportResources,
 | 
			
		||||
    ExportSentinels,
 | 
			
		||||
    ExportWarframes,
 | 
			
		||||
    ExportWeapons,
 | 
			
		||||
    IPowersuit,
 | 
			
		||||
    IRecipe
 | 
			
		||||
    IRecipe,
 | 
			
		||||
    IRegion
 | 
			
		||||
} from "warframe-public-export-plus";
 | 
			
		||||
import questCompletionItems from "@/static/fixed_responses/questCompletionRewards.json";
 | 
			
		||||
 | 
			
		||||
export type WeaponTypeInternal =
 | 
			
		||||
    | "LongGuns"
 | 
			
		||||
@ -150,3 +156,56 @@ export const getDict = (lang: string): Record<string, string> => {
 | 
			
		||||
export const getString = (key: string, dict: Record<string, string>): string => {
 | 
			
		||||
    return dict[key] ?? key;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getKeyChainItems = ({ KeyChain, ChainStage }: IGiveKeyChainTriggeredItemsRequest): string[] => {
 | 
			
		||||
    const chainStages = ExportKeys[KeyChain].chainStages;
 | 
			
		||||
    if (!chainStages) {
 | 
			
		||||
        throw new Error(`KeyChain ${KeyChain} does not contain chain stages`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const keyChainStage = chainStages[ChainStage];
 | 
			
		||||
    if (!keyChainStage) {
 | 
			
		||||
        throw new Error(`KeyChainStage ${ChainStage} not found`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (keyChainStage.itemsToGiveWhenTriggered.length === 0) {
 | 
			
		||||
        throw new Error(`No items to give for KeyChain ${KeyChain} at stage ${ChainStage}`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return keyChainStage.itemsToGiveWhenTriggered;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getLevelKeyRewards = (levelKey: string) => {
 | 
			
		||||
    const levelKeyData = ExportKeys[levelKey];
 | 
			
		||||
    if (!levelKeyData) {
 | 
			
		||||
        const error = `LevelKey ${levelKey} not found`;
 | 
			
		||||
        logger.error(error);
 | 
			
		||||
        throw new Error(error);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!levelKeyData.rewards) {
 | 
			
		||||
        const error = `LevelKey ${levelKey} does not contain rewards`;
 | 
			
		||||
        logger.error(error);
 | 
			
		||||
        throw new Error(error);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return levelKeyData.rewards;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getNode = (nodeName: string): IRegion => {
 | 
			
		||||
    const node = ExportRegions[nodeName];
 | 
			
		||||
    if (!node) {
 | 
			
		||||
        throw new Error(`Node ${nodeName} not found`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return node;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getQuestCompletionItems = (questKey: string) => {
 | 
			
		||||
    const items = (questCompletionItems as unknown as Record<string, ITypeCount[] | undefined>)[questKey];
 | 
			
		||||
    if (!items) {
 | 
			
		||||
        throw new Error(`Quest ${questKey} not found in questCompletionItems`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return items;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -45,16 +45,16 @@ export const createPersonalRooms = async (accountId: Types.ObjectId, shipId: Typ
 | 
			
		||||
        activeShipId: shipId
 | 
			
		||||
    });
 | 
			
		||||
    if (config.skipTutorial) {
 | 
			
		||||
        // Vor's Prize rewards
 | 
			
		||||
        const defaultFeatures = [
 | 
			
		||||
            "/Lotus/Types/Items/ShipFeatureItems/EarthNavigationFeatureItem",
 | 
			
		||||
            "/Lotus/Types/Items/ShipFeatureItems/MercuryNavigationFeatureItem",
 | 
			
		||||
            "/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem",
 | 
			
		||||
            "/Lotus/Types/Items/ShipFeatureItems/SocialMenuFeatureItem",
 | 
			
		||||
            "/Lotus/Types/Items/ShipFeatureItems/FoundryFeatureItem",
 | 
			
		||||
            "/Lotus/Types/Items/ShipFeatureItems/ModsFeatureItem"
 | 
			
		||||
        ];
 | 
			
		||||
        personalRooms.Ship.Features.push(...defaultFeatures);
 | 
			
		||||
        // // Vor's Prize rewards
 | 
			
		||||
        // const defaultFeatures = [
 | 
			
		||||
        //     "/Lotus/Types/Items/ShipFeatureItems/EarthNavigationFeatureItem",
 | 
			
		||||
        //     "/Lotus/Types/Items/ShipFeatureItems/MercuryNavigationFeatureItem",
 | 
			
		||||
        //     "/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem",
 | 
			
		||||
        //     "/Lotus/Types/Items/ShipFeatureItems/SocialMenuFeatureItem",
 | 
			
		||||
        //     "/Lotus/Types/Items/ShipFeatureItems/FoundryFeatureItem",
 | 
			
		||||
        //     "/Lotus/Types/Items/ShipFeatureItems/ModsFeatureItem"
 | 
			
		||||
        // ];
 | 
			
		||||
        // personalRooms.Ship.Features.push(...defaultFeatures);
 | 
			
		||||
    }
 | 
			
		||||
    await personalRooms.save();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,30 +1,280 @@
 | 
			
		||||
import { IMissionRewardResponse, IInventoryFieldType, inventoryFields } from "@/src/types/missionTypes";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
    ExportRegions,
 | 
			
		||||
    ExportRewards,
 | 
			
		||||
    ExportUpgrades,
 | 
			
		||||
    ExportGear,
 | 
			
		||||
    ExportRecipes,
 | 
			
		||||
    ExportRelics,
 | 
			
		||||
    ExportResources,
 | 
			
		||||
    IReward
 | 
			
		||||
} from "warframe-public-export-plus";
 | 
			
		||||
import { IMissionInventoryUpdateRequest } from "../types/requestTypes";
 | 
			
		||||
import { ExportRegions, ExportRewards, IReward } from "warframe-public-export-plus";
 | 
			
		||||
import { IMissionInventoryUpdateRequest, IRewardInfo } from "../types/requestTypes";
 | 
			
		||||
import { logger } from "@/src/utils/logger";
 | 
			
		||||
import { IRngResult, getRandomReward } from "@/src/services/rngService";
 | 
			
		||||
import { IInventoryDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
			
		||||
import {
 | 
			
		||||
    addChallenges,
 | 
			
		||||
    addConsumables,
 | 
			
		||||
    addFusionTreasures,
 | 
			
		||||
    addGearExpByCategory,
 | 
			
		||||
    addItem,
 | 
			
		||||
    addMiscItems,
 | 
			
		||||
    addMissionComplete,
 | 
			
		||||
    addMods,
 | 
			
		||||
    addRecipes,
 | 
			
		||||
    combineInventoryChanges,
 | 
			
		||||
    updateSyndicate
 | 
			
		||||
} from "@/src/services/inventoryService";
 | 
			
		||||
import { updateQuestKey } from "@/src/services/questService";
 | 
			
		||||
import { HydratedDocument } from "mongoose";
 | 
			
		||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
			
		||||
import { getLevelKeyRewards, getNode } from "@/src/services/itemDataService";
 | 
			
		||||
import { InventoryDocumentProps, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
			
		||||
 | 
			
		||||
// need reverse engineer rewardSeed, otherwise ingame displayed rotation reward will be different than added to db or displayed on mission end
 | 
			
		||||
const getRewards = ({
 | 
			
		||||
    RewardInfo
 | 
			
		||||
}: IMissionInventoryUpdateRequest): {
 | 
			
		||||
    InventoryChanges: IMissionInventoryUpdateRequest;
 | 
			
		||||
    MissionRewards: IMissionRewardResponse[];
 | 
			
		||||
} => {
 | 
			
		||||
    if (!RewardInfo) {
 | 
			
		||||
        return { InventoryChanges: {}, MissionRewards: [] };
 | 
			
		||||
const getRotations = (rotationCount: number): number[] => {
 | 
			
		||||
    if (rotationCount === 0) return [0];
 | 
			
		||||
 | 
			
		||||
    const rotationPattern = [0, 0, 1, 2]; // A, A, B, C
 | 
			
		||||
    const rotatedValues = [];
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < rotationCount; i++) {
 | 
			
		||||
        rotatedValues.push(rotationPattern[i % rotationPattern.length]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return rotatedValues;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getRandomRewardByChance = (pool: IReward[]): IRngResult | undefined => {
 | 
			
		||||
    return getRandomReward(pool as IRngResult[]);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const creditBundles: Record<string, number> = {
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/1500Credits": 1500,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/2000Credits": 2000,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/2500Credits": 2500,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/3000Credits": 3000,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/4000Credits": 4000,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/5000Credits": 5000,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/7500Credits": 7500,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/10000Credits": 10000,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/5000Hollars": 5000,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/7500Hollars": 7500,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/10000Hollars": 10000,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/CorpusArenaCreditRewards/CorpusArenaRewardOneHard": 105000,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/CorpusArenaCreditRewards/CorpusArenaRewardTwoHard": 175000,
 | 
			
		||||
    "/Lotus/Types/PickUps/Credits/CorpusArenaCreditRewards/CorpusArenaRewardThreeHard": 250000,
 | 
			
		||||
    "/Lotus/Types/StoreItems/CreditBundles/Zariman/TableACreditsCommon": 15000,
 | 
			
		||||
    "/Lotus/Types/StoreItems/CreditBundles/Zariman/TableACreditsUncommon": 30000
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const fusionBundles: Record<string, number> = {
 | 
			
		||||
    "/Lotus/Upgrades/Mods/FusionBundles/CommonFusionBundle": 15,
 | 
			
		||||
    "/Lotus/Upgrades/Mods/FusionBundles/UncommonFusionBundle": 50,
 | 
			
		||||
    "/Lotus/Upgrades/Mods/FusionBundles/RareFusionBundle": 80
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type Entries<T, K extends keyof T = keyof T> = (K extends unknown ? [K, T[K]] : never)[];
 | 
			
		||||
 | 
			
		||||
function getEntriesUnsafe<T extends object>(object: T): Entries<T> {
 | 
			
		||||
    return Object.entries(object) as Entries<T>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//type TMissionInventoryUpdateKeys = keyof IMissionInventoryUpdateRequest;
 | 
			
		||||
//const ignoredInventoryUpdateKeys = ["FpsAvg", "FpsMax", "FpsMin", "FpsSamples"] satisfies TMissionInventoryUpdateKeys[]; // for keys with no meaning for this server
 | 
			
		||||
//type TignoredInventoryUpdateKeys = (typeof ignoredInventoryUpdateKeys)[number];
 | 
			
		||||
//const knownUnhandledKeys: readonly string[] = ["test"] as const; // for unimplemented but important keys
 | 
			
		||||
 | 
			
		||||
export const addMissionInventoryUpdates = (
 | 
			
		||||
    inventory: HydratedDocument<IInventoryDatabase, InventoryDocumentProps>,
 | 
			
		||||
    inventoryUpdates: IMissionInventoryUpdateRequest
 | 
			
		||||
) => {
 | 
			
		||||
    //TODO: type this properly
 | 
			
		||||
    const inventoryChanges: Partial<IInventoryDatabase> = {};
 | 
			
		||||
    if (inventoryUpdates.MissionFailed === true) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    for (const [key, value] of getEntriesUnsafe(inventoryUpdates)) {
 | 
			
		||||
        if (value === undefined) {
 | 
			
		||||
            logger.error(`Inventory update key ${key} has no value `);
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        switch (key) {
 | 
			
		||||
            case "RegularCredits":
 | 
			
		||||
                inventory.RegularCredits += value;
 | 
			
		||||
                break;
 | 
			
		||||
            case "QuestKeys":
 | 
			
		||||
                updateQuestKey(inventory, value);
 | 
			
		||||
                break;
 | 
			
		||||
            case "AffiliationChanges":
 | 
			
		||||
                updateSyndicate(inventory, value);
 | 
			
		||||
                break;
 | 
			
		||||
            // Incarnon Challenges
 | 
			
		||||
            case "EvolutionProgress": {
 | 
			
		||||
                for (const evoProgress of value) {
 | 
			
		||||
                    const entry = inventory.EvolutionProgress
 | 
			
		||||
                        ? inventory.EvolutionProgress.find(entry => entry.ItemType == evoProgress.ItemType)
 | 
			
		||||
                        : undefined;
 | 
			
		||||
                    if (entry) {
 | 
			
		||||
                        entry.Progress = evoProgress.Progress;
 | 
			
		||||
                        entry.Rank = evoProgress.Rank;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        inventory.EvolutionProgress ??= [];
 | 
			
		||||
                        inventory.EvolutionProgress.push(evoProgress);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            case "Missions":
 | 
			
		||||
                addMissionComplete(inventory, value);
 | 
			
		||||
                break;
 | 
			
		||||
            case "LastRegionPlayed":
 | 
			
		||||
                inventory.LastRegionPlayed = value;
 | 
			
		||||
                break;
 | 
			
		||||
            case "RawUpgrades":
 | 
			
		||||
                addMods(inventory, value);
 | 
			
		||||
                break;
 | 
			
		||||
            case "MiscItems":
 | 
			
		||||
                addMiscItems(inventory, value);
 | 
			
		||||
                break;
 | 
			
		||||
            case "Consumables":
 | 
			
		||||
                addConsumables(inventory, value);
 | 
			
		||||
                break;
 | 
			
		||||
            case "Recipes":
 | 
			
		||||
                addRecipes(inventory, value);
 | 
			
		||||
                break;
 | 
			
		||||
            case "ChallengeProgress":
 | 
			
		||||
                addChallenges(inventory, value);
 | 
			
		||||
                break;
 | 
			
		||||
            case "FusionTreasures":
 | 
			
		||||
                addFusionTreasures(inventory, value);
 | 
			
		||||
                break;
 | 
			
		||||
            case "FusionBundles": {
 | 
			
		||||
                let fusionPoints = 0;
 | 
			
		||||
                for (const fusionBundle of value) {
 | 
			
		||||
                    const fusionPointsTotal = fusionBundles[fusionBundle.ItemType] * fusionBundle.ItemCount;
 | 
			
		||||
                    inventory.FusionPoints += fusionPointsTotal;
 | 
			
		||||
                    fusionPoints += fusionPointsTotal;
 | 
			
		||||
                }
 | 
			
		||||
                inventoryChanges.FusionPoints = fusionPoints;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            // Equipment XP updates
 | 
			
		||||
            case "Suits":
 | 
			
		||||
            case "LongGuns":
 | 
			
		||||
            case "Pistols":
 | 
			
		||||
            case "Melee":
 | 
			
		||||
            case "SpecialItems":
 | 
			
		||||
            case "Sentinels":
 | 
			
		||||
            case "SentinelWeapons":
 | 
			
		||||
            case "SpaceSuits":
 | 
			
		||||
            case "SpaceGuns":
 | 
			
		||||
            case "SpaceMelee":
 | 
			
		||||
            case "Hoverboards":
 | 
			
		||||
            case "OperatorAmps":
 | 
			
		||||
            case "MoaPets":
 | 
			
		||||
                addGearExpByCategory(inventory, value, key);
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
            // if (
 | 
			
		||||
            //     (ignoredInventoryUpdateKeys as readonly string[]).includes(key) ||
 | 
			
		||||
            //     knownUnhandledKeys.includes(key)
 | 
			
		||||
            // ) {
 | 
			
		||||
            //     continue;
 | 
			
		||||
            // }
 | 
			
		||||
            // logger.error(`Unhandled inventory update key: ${key}`);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return inventoryChanges;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//TODO: return type of partial missioninventoryupdate response
 | 
			
		||||
export const addMissionRewards = async (
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    { RewardInfo: rewardInfo, LevelKeyName: levelKeyName, Missions: missions }: IMissionInventoryUpdateRequest
 | 
			
		||||
) => {
 | 
			
		||||
    if (!rewardInfo) {
 | 
			
		||||
        logger.error("no reward info provided");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //TODO: check double reward merging
 | 
			
		||||
    const MissionRewards = getRandomMissionDrops(rewardInfo).map(drop => {
 | 
			
		||||
        return { StoreItem: drop.type, ItemCount: drop.itemCount };
 | 
			
		||||
    });
 | 
			
		||||
    console.log("random mission drops:", MissionRewards);
 | 
			
		||||
    const inventoryChanges: IInventoryChanges = {};
 | 
			
		||||
 | 
			
		||||
    let missionCompletionCredits = 0;
 | 
			
		||||
    //inventory change is what the client has not rewarded itself, credit updates seem to be taken from totalCredits
 | 
			
		||||
    if (levelKeyName) {
 | 
			
		||||
        const fixedLevelRewards = getLevelKeyRewards(levelKeyName);
 | 
			
		||||
        //logger.debug(`fixedLevelRewards ${fixedLevelRewards}`);
 | 
			
		||||
        for (const reward of fixedLevelRewards) {
 | 
			
		||||
            if (reward.rewardType == "RT_CREDITS") {
 | 
			
		||||
                inventory.RegularCredits += reward.amount;
 | 
			
		||||
                missionCompletionCredits += reward.amount;
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            MissionRewards.push({
 | 
			
		||||
                StoreItem: reward.itemType,
 | 
			
		||||
                ItemCount: reward.rewardType === "RT_RESOURCE" ? reward.amount : 1
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (missions) {
 | 
			
		||||
        const levelCreditReward = getLevelCreditRewards(missions?.Tag);
 | 
			
		||||
        missionCompletionCredits += levelCreditReward;
 | 
			
		||||
        inventory.RegularCredits += levelCreditReward;
 | 
			
		||||
        logger.debug(`levelCreditReward ${levelCreditReward}`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //TODO: resolve issue with creditbundles
 | 
			
		||||
    for (const reward of MissionRewards) {
 | 
			
		||||
        //TODO: additem should take in storeItems
 | 
			
		||||
        const inventoryChange = await addItem(inventory, reward.StoreItem.replace("StoreItems/", ""), reward.ItemCount);
 | 
			
		||||
        //TODO: combineInventoryChanges improve type safety, merging 2 of the same item?
 | 
			
		||||
        //TODO: check for the case when two of the same item are added, combineInventoryChanges should merge them
 | 
			
		||||
        //TODO: some conditional types to rule out binchanges?
 | 
			
		||||
        combineInventoryChanges(inventoryChanges, inventoryChange.InventoryChanges);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return { inventoryChanges, MissionRewards, missionCompletionCredits };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//might not be faithful to original
 | 
			
		||||
//TODO: consider ActiveBoosters
 | 
			
		||||
export const calculateFinalCredits = (
 | 
			
		||||
    inventory: HydratedDocument<IInventoryDatabase>,
 | 
			
		||||
    {
 | 
			
		||||
        missionDropCredits,
 | 
			
		||||
        missionCompletionCredits,
 | 
			
		||||
        rngRewardCredits = 0
 | 
			
		||||
    }: { missionDropCredits: number; missionCompletionCredits: number; rngRewardCredits: number }
 | 
			
		||||
) => {
 | 
			
		||||
    const hasDailyCreditBonus = true;
 | 
			
		||||
    const totalCredits = missionDropCredits + missionCompletionCredits + rngRewardCredits;
 | 
			
		||||
 | 
			
		||||
    const finalCredits = {
 | 
			
		||||
        MissionCredits: [missionDropCredits, missionDropCredits],
 | 
			
		||||
        CreditBonus: [missionCompletionCredits, missionCompletionCredits],
 | 
			
		||||
        TotalCredits: [totalCredits, totalCredits]
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (hasDailyCreditBonus) {
 | 
			
		||||
        inventory.RegularCredits += totalCredits;
 | 
			
		||||
        finalCredits.CreditBonus[1] *= 2;
 | 
			
		||||
        finalCredits.MissionCredits[1] *= 2;
 | 
			
		||||
        finalCredits.TotalCredits[1] *= 2;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!hasDailyCreditBonus) {
 | 
			
		||||
        return finalCredits;
 | 
			
		||||
    }
 | 
			
		||||
    return { ...finalCredits, DailyMissionBonus: true };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function getLevelCreditRewards(nodeName: string): number {
 | 
			
		||||
    const minEnemyLevel = getNode(nodeName).minEnemyLevel;
 | 
			
		||||
 | 
			
		||||
    return 1000 + (minEnemyLevel - 1) * 100;
 | 
			
		||||
 | 
			
		||||
    //TODO: get dark sektor fixed credit rewards and railjack bonus
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getRandomMissionDrops(RewardInfo: IRewardInfo): IRngResult[] {
 | 
			
		||||
    const drops: IRngResult[] = [];
 | 
			
		||||
    if (RewardInfo.node in ExportRegions) {
 | 
			
		||||
        const region = ExportRegions[RewardInfo.node];
 | 
			
		||||
@ -53,161 +303,16 @@ const getRewards = ({
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        if (region.cacheRewardManifest && RewardInfo.EnemyCachesFound) {
 | 
			
		||||
            console.log("cache rewards", RewardInfo.EnemyCachesFound);
 | 
			
		||||
            const deck = ExportRewards[region.cacheRewardManifest];
 | 
			
		||||
            for (let rotation = 0; rotation != RewardInfo.EnemyCachesFound; ++rotation) {
 | 
			
		||||
                const drop = getRandomRewardByChance(deck[rotation]);
 | 
			
		||||
                if (drop) {
 | 
			
		||||
                    console.log("cache drop", drop);
 | 
			
		||||
                    drops.push(drop);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    logger.debug("Mission rewards:", drops);
 | 
			
		||||
    return formatRewardsToInventoryType(drops);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const combineRewardAndLootInventory = (
 | 
			
		||||
    rewardInventory: IMissionInventoryUpdateRequest,
 | 
			
		||||
    lootInventory: IMissionInventoryUpdateRequest
 | 
			
		||||
) => {
 | 
			
		||||
    const missionCredits = lootInventory.RegularCredits || 0;
 | 
			
		||||
    const creditsBonus = rewardInventory.RegularCredits || 0;
 | 
			
		||||
    const totalCredits = missionCredits + creditsBonus;
 | 
			
		||||
    let FusionPoints = rewardInventory.FusionPoints || 0;
 | 
			
		||||
 | 
			
		||||
    // Discharge Endo picked up during the mission
 | 
			
		||||
    if (lootInventory.FusionBundles) {
 | 
			
		||||
        for (const fusionBundle of lootInventory.FusionBundles) {
 | 
			
		||||
            if (fusionBundle.ItemType in fusionBundles) {
 | 
			
		||||
                FusionPoints += fusionBundles[fusionBundle.ItemType] * fusionBundle.ItemCount;
 | 
			
		||||
            } else {
 | 
			
		||||
                logger.error(`unknown fusion bundle: ${fusionBundle.ItemType}`);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        lootInventory.FusionBundles = undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    lootInventory.RegularCredits = totalCredits;
 | 
			
		||||
    lootInventory.FusionPoints = FusionPoints;
 | 
			
		||||
    inventoryFields.forEach((field: IInventoryFieldType) => {
 | 
			
		||||
        if (rewardInventory[field] && !lootInventory[field]) {
 | 
			
		||||
            lootInventory[field] = [];
 | 
			
		||||
        }
 | 
			
		||||
        rewardInventory[field]?.forEach(item => lootInventory[field]!.push(item));
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        combinedInventoryChanges: lootInventory,
 | 
			
		||||
        TotalCredits: [totalCredits, totalCredits],
 | 
			
		||||
        CreditsBonus: [creditsBonus, creditsBonus],
 | 
			
		||||
        MissionCredits: [missionCredits, missionCredits],
 | 
			
		||||
        FusionPoints: FusionPoints
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getRotations = (rotationCount: number): number[] => {
 | 
			
		||||
    if (rotationCount === 0) return [0];
 | 
			
		||||
 | 
			
		||||
    const rotationPattern = [0, 0, 1, 2]; // A, A, B, C
 | 
			
		||||
    const rotatedValues = [];
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < rotationCount; i++) {
 | 
			
		||||
        rotatedValues.push(rotationPattern[i % rotationPattern.length]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return rotatedValues;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getRandomRewardByChance = (pool: IReward[]): IRngResult | undefined => {
 | 
			
		||||
    return getRandomReward(pool as IRngResult[]);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const creditBundles: Record<string, number> = {
 | 
			
		||||
    "/Lotus/StoreItems/Types/PickUps/Credits/1500Credits": 1500,
 | 
			
		||||
    "/Lotus/StoreItems/Types/PickUps/Credits/2000Credits": 2000,
 | 
			
		||||
    "/Lotus/StoreItems/Types/PickUps/Credits/2500Credits": 2500,
 | 
			
		||||
    "/Lotus/StoreItems/Types/PickUps/Credits/3000Credits": 3000,
 | 
			
		||||
    "/Lotus/StoreItems/Types/PickUps/Credits/4000Credits": 4000,
 | 
			
		||||
    "/Lotus/StoreItems/Types/PickUps/Credits/5000Credits": 5000,
 | 
			
		||||
    "/Lotus/StoreItems/Types/PickUps/Credits/7500Credits": 7500,
 | 
			
		||||
    "/Lotus/StoreItems/Types/PickUps/Credits/10000Credits": 10000,
 | 
			
		||||
    "/Lotus/StoreItems/Types/StoreItems/CreditBundles/Zariman/TableACreditsCommon": 15000,
 | 
			
		||||
    "/Lotus/StoreItems/Types/StoreItems/CreditBundles/Zariman/TableACreditsUncommon": 30000,
 | 
			
		||||
    "/Lotus/StoreItems/Types/PickUps/Credits/CorpusArenaCreditRewards/CorpusArenaRewardOneHard": 105000,
 | 
			
		||||
    "/Lotus/StoreItems/Types/PickUps/Credits/CorpusArenaCreditRewards/CorpusArenaRewardTwoHard": 175000,
 | 
			
		||||
    "/Lotus/StoreItems/Types/PickUps/Credits/CorpusArenaCreditRewards/CorpusArenaRewardThreeHard": 250000
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const fusionBundles: Record<string, number> = {
 | 
			
		||||
    "/Lotus/Upgrades/Mods/FusionBundles/CommonFusionBundle": 15,
 | 
			
		||||
    "/Lotus/Upgrades/Mods/FusionBundles/UncommonFusionBundle": 50,
 | 
			
		||||
    "/Lotus/Upgrades/Mods/FusionBundles/RareFusionBundle": 80
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const formatRewardsToInventoryType = (
 | 
			
		||||
    rewards: IRngResult[]
 | 
			
		||||
): { InventoryChanges: IMissionInventoryUpdateRequest; MissionRewards: IMissionRewardResponse[] } => {
 | 
			
		||||
    const InventoryChanges: IMissionInventoryUpdateRequest = {};
 | 
			
		||||
    const MissionRewards: IMissionRewardResponse[] = [];
 | 
			
		||||
    for (const reward of rewards) {
 | 
			
		||||
        if (reward.type in creditBundles) {
 | 
			
		||||
            InventoryChanges.RegularCredits ??= 0;
 | 
			
		||||
            InventoryChanges.RegularCredits += creditBundles[reward.type] * reward.itemCount;
 | 
			
		||||
        } else {
 | 
			
		||||
            const type = reward.type.replace("/Lotus/StoreItems/", "/Lotus/");
 | 
			
		||||
            if (type in fusionBundles) {
 | 
			
		||||
                InventoryChanges.FusionPoints ??= 0;
 | 
			
		||||
                InventoryChanges.FusionPoints += fusionBundles[type] * reward.itemCount;
 | 
			
		||||
            } else if (type in ExportUpgrades) {
 | 
			
		||||
                addRewardResponse(InventoryChanges, MissionRewards, type, reward.itemCount, "RawUpgrades");
 | 
			
		||||
            } else if (type in ExportGear) {
 | 
			
		||||
                addRewardResponse(InventoryChanges, MissionRewards, type, reward.itemCount, "Consumables");
 | 
			
		||||
            } else if (type in ExportRecipes) {
 | 
			
		||||
                addRewardResponse(InventoryChanges, MissionRewards, type, reward.itemCount, "Recipes");
 | 
			
		||||
            } else if (
 | 
			
		||||
                type in ExportRelics ||
 | 
			
		||||
                (type in ExportResources && ExportResources[type].productCategory == "MiscItems")
 | 
			
		||||
            ) {
 | 
			
		||||
                addRewardResponse(InventoryChanges, MissionRewards, type, reward.itemCount, "MiscItems");
 | 
			
		||||
            } else {
 | 
			
		||||
                logger.error(`rolled reward ${reward.itemCount}X ${reward.type} but unsure how to give it`);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return { InventoryChanges, MissionRewards };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const addRewardResponse = (
 | 
			
		||||
    InventoryChanges: IMissionInventoryUpdateRequest,
 | 
			
		||||
    MissionRewards: IMissionRewardResponse[],
 | 
			
		||||
    ItemType: string,
 | 
			
		||||
    ItemCount: number,
 | 
			
		||||
    InventoryCategory: IInventoryFieldType
 | 
			
		||||
) => {
 | 
			
		||||
    if (!ItemType) return;
 | 
			
		||||
 | 
			
		||||
    if (!InventoryChanges[InventoryCategory]) {
 | 
			
		||||
        InventoryChanges[InventoryCategory] = [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const existReward = InventoryChanges[InventoryCategory].find(item => item.ItemType === ItemType);
 | 
			
		||||
    if (existReward) {
 | 
			
		||||
        existReward.ItemCount += ItemCount;
 | 
			
		||||
        const missionReward = MissionRewards.find(missionReward => missionReward.TypeName === ItemType);
 | 
			
		||||
        if (missionReward) {
 | 
			
		||||
            missionReward.ItemCount += ItemCount;
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        InventoryChanges[InventoryCategory].push({ ItemType, ItemCount });
 | 
			
		||||
        MissionRewards.push({
 | 
			
		||||
            ItemCount,
 | 
			
		||||
            TweetText: ItemType, // ensure if/how this even still used, or if it's needed at all
 | 
			
		||||
            ProductCategory: InventoryCategory,
 | 
			
		||||
            StoreItem: ItemType.replace("/Lotus/", "/Lotus/StoreItems/"),
 | 
			
		||||
            TypeName: ItemType
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export { getRewards, combineRewardAndLootInventory };
 | 
			
		||||
    return drops;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
import { PersonalRooms } from "@/src/models/personalRoomsModel";
 | 
			
		||||
import { addItem, getInventory } from "@/src/services/inventoryService";
 | 
			
		||||
 | 
			
		||||
export const getPersonalRooms = async (accountId: string) => {
 | 
			
		||||
    const personalRooms = await PersonalRooms.findOne({ personalRoomsOwnerId: accountId });
 | 
			
		||||
@ -8,3 +9,18 @@ export const getPersonalRooms = async (accountId: string) => {
 | 
			
		||||
    }
 | 
			
		||||
    return personalRooms;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const updateShipFeature = async (accountId: string, shipFeature: string) => {
 | 
			
		||||
    const personalRooms = await getPersonalRooms(accountId);
 | 
			
		||||
 | 
			
		||||
    if (personalRooms.Ship.Features.includes(shipFeature)) {
 | 
			
		||||
        throw new Error(`ship feature ${shipFeature} already unlocked`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    personalRooms.Ship.Features.push(shipFeature);
 | 
			
		||||
    await personalRooms.save();
 | 
			
		||||
 | 
			
		||||
    const inventory = await getInventory(accountId);
 | 
			
		||||
    await addItem(inventory, shipFeature, -1);
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										34
									
								
								src/services/questService.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/services/questService.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
			
		||||
import { IInventoryDatabase, IQuestKeyDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
			
		||||
import { logger } from "@/src/utils/logger";
 | 
			
		||||
import { HydratedDocument } from "mongoose";
 | 
			
		||||
 | 
			
		||||
export const updateQuestKey = (
 | 
			
		||||
    inventory: HydratedDocument<IInventoryDatabase>,
 | 
			
		||||
    questKeyUpdate: IUpdateQuestRequest["QuestKeys"]
 | 
			
		||||
): void => {
 | 
			
		||||
    if (questKeyUpdate.length > 1) {
 | 
			
		||||
        logger.error(`more than 1 quest key not supported`);
 | 
			
		||||
        throw new Error("more than 1 quest key not supported");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const questKeyIndex = inventory.QuestKeys.findIndex(questKey => questKey.ItemType === questKeyUpdate[0].ItemType);
 | 
			
		||||
 | 
			
		||||
    if (questKeyIndex === -1) {
 | 
			
		||||
        throw new Error(`quest key ${questKeyUpdate[0].ItemType} not found`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    inventory.QuestKeys[questKeyIndex] = questKeyUpdate[0];
 | 
			
		||||
 | 
			
		||||
    if (questKeyUpdate[0].Completed) {
 | 
			
		||||
        inventory.QuestKeys[questKeyIndex].CompletionDate = new Date();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export interface IUpdateQuestRequest {
 | 
			
		||||
    QuestKeys: Omit<IQuestKeyDatabase, "CompletionDate">[];
 | 
			
		||||
    PS: string;
 | 
			
		||||
    questCompletion: boolean;
 | 
			
		||||
    PlayerShipEvents: unknown[];
 | 
			
		||||
    crossPlaySetting: string;
 | 
			
		||||
    DoQuestReward: boolean;
 | 
			
		||||
}
 | 
			
		||||
@ -43,6 +43,7 @@ export interface IInventoryDatabase
 | 
			
		||||
    GuildId?: Types.ObjectId; // GuildId changed from ?IOid to ?Types.ObjectId
 | 
			
		||||
    PendingRecipes: IPendingRecipe[];
 | 
			
		||||
    QuestKeys: IQuestKeyDatabase[];
 | 
			
		||||
    ActiveQuest: string;
 | 
			
		||||
    BlessingCooldown: Date;
 | 
			
		||||
    Ships: Types.ObjectId[];
 | 
			
		||||
    WeaponSkins: IWeaponSkinDatabase[];
 | 
			
		||||
@ -71,7 +72,7 @@ export interface IInventoryDatabase
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IQuestKeyDatabase {
 | 
			
		||||
    Progress?: IQuestProgress[];
 | 
			
		||||
    Progress?: IQuestStage[];
 | 
			
		||||
    unlock?: boolean;
 | 
			
		||||
    Completed?: boolean;
 | 
			
		||||
    CustomData?: string; //TODO: check whether this actually exists
 | 
			
		||||
@ -205,7 +206,7 @@ export interface IInventoryClient extends IDailyAffiliations {
 | 
			
		||||
    RawUpgrades: IRawUpgrade[];
 | 
			
		||||
    ReceivedStartingGear: boolean;
 | 
			
		||||
    Ships: IShipInventory[];
 | 
			
		||||
    QuestKeys: IQuestKeyResponse[];
 | 
			
		||||
    QuestKeys: IQuestKeyClient[];
 | 
			
		||||
    FlavourItems: IFlavourItem[];
 | 
			
		||||
    Scoops: IEquipmentDatabase[];
 | 
			
		||||
    TrainingRetriesLeft: number;
 | 
			
		||||
@ -889,14 +890,14 @@ export interface IPlayerSkills {
 | 
			
		||||
    LPS_DRIFT_ENDURANCE: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IQuestKeyResponse extends Omit<IQuestKeyDatabase, "CompletionDate"> {
 | 
			
		||||
export interface IQuestKeyClient extends Omit<IQuestKeyDatabase, "CompletionDate"> {
 | 
			
		||||
    CompletionDate?: IMongoDate;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IQuestProgress {
 | 
			
		||||
    c: number;
 | 
			
		||||
    i: boolean;
 | 
			
		||||
    m: boolean;
 | 
			
		||||
export interface IQuestStage {
 | 
			
		||||
    c?: number;
 | 
			
		||||
    i?: boolean;
 | 
			
		||||
    m?: boolean;
 | 
			
		||||
    b?: any[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,11 @@
 | 
			
		||||
export const inventoryFields = ["RawUpgrades", "MiscItems", "Consumables", "Recipes"] as const;
 | 
			
		||||
export type IInventoryFieldType = (typeof inventoryFields)[number];
 | 
			
		||||
 | 
			
		||||
export interface IMissionRewardResponse {
 | 
			
		||||
    StoreItem?: string;
 | 
			
		||||
    TypeName: string;
 | 
			
		||||
export interface IMissionReward {
 | 
			
		||||
    StoreItem: string;
 | 
			
		||||
    TypeName?: string;
 | 
			
		||||
    UpgradeLevel?: number;
 | 
			
		||||
    ItemCount: number;
 | 
			
		||||
    TweetText: string;
 | 
			
		||||
    ProductCategory: string;
 | 
			
		||||
    TweetText?: string;
 | 
			
		||||
    ProductCategory?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -3,16 +3,15 @@ import { ArtifactPolarity, IPolarity, IEquipmentClient } from "@/src/types/inven
 | 
			
		||||
import {
 | 
			
		||||
    IBooster,
 | 
			
		||||
    IChallengeProgress,
 | 
			
		||||
    IConsumable,
 | 
			
		||||
    IEvolutionProgress,
 | 
			
		||||
    IMiscItem,
 | 
			
		||||
    ITypeCount,
 | 
			
		||||
    IMission,
 | 
			
		||||
    IRawUpgrade,
 | 
			
		||||
    ISeasonChallenge,
 | 
			
		||||
    TSolarMapRegion,
 | 
			
		||||
    TEquipmentKey,
 | 
			
		||||
    IFusionTreasure
 | 
			
		||||
    IFusionTreasure,
 | 
			
		||||
    IQuestKeyClient
 | 
			
		||||
} from "./inventoryTypes/inventoryTypes";
 | 
			
		||||
 | 
			
		||||
export interface IThemeUpdateRequest {
 | 
			
		||||
@ -33,10 +32,13 @@ export interface IUpdateChallengeProgressRequest {
 | 
			
		||||
    SeasonChallengeCompletions: ISeasonChallenge[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IMissionInventoryUpdateRequest {
 | 
			
		||||
    rewardsMultiplier?: number;
 | 
			
		||||
    ActiveBoosters?: IBooster[];
 | 
			
		||||
export type IMissionInventoryUpdateRequest = {
 | 
			
		||||
    AffiliationChanges?: IAffiliationChange[];
 | 
			
		||||
    crossPlaySetting?: string;
 | 
			
		||||
    rewardsMultiplier?: number;
 | 
			
		||||
    GoalTag: string;
 | 
			
		||||
    LevelKeyName: string;
 | 
			
		||||
    ActiveBoosters?: IBooster[];
 | 
			
		||||
    Suits?: IEquipmentClient[];
 | 
			
		||||
    LongGuns?: IEquipmentClient[];
 | 
			
		||||
    Pistols?: IEquipmentClient[];
 | 
			
		||||
@ -52,21 +54,40 @@ export interface IMissionInventoryUpdateRequest {
 | 
			
		||||
    MoaPets?: IEquipmentClient[];
 | 
			
		||||
    FusionBundles?: ITypeCount[];
 | 
			
		||||
    RawUpgrades?: IRawUpgrade[];
 | 
			
		||||
    MiscItems?: IMiscItem[];
 | 
			
		||||
    Consumables?: IConsumable[];
 | 
			
		||||
    MiscItems?: ITypeCount[];
 | 
			
		||||
    Consumables?: ITypeCount[];
 | 
			
		||||
    FusionTreasures?: IFusionTreasure[];
 | 
			
		||||
    Recipes?: IConsumable[];
 | 
			
		||||
    Recipes?: ITypeCount[];
 | 
			
		||||
    QuestKeys?: IQuestKeyClient[];
 | 
			
		||||
    RegularCredits?: number;
 | 
			
		||||
    ChallengeProgress?: IChallengeProgress[];
 | 
			
		||||
    RewardInfo?: IMissionInventoryUpdateRequestRewardInfo;
 | 
			
		||||
    MissionFailed: boolean;
 | 
			
		||||
    MissionStatus: IMissionStatus;
 | 
			
		||||
    AliveTime: number;
 | 
			
		||||
    MissionTime: number;
 | 
			
		||||
    Missions?: IMission;
 | 
			
		||||
    EvolutionProgress?: IEvolutionProgress[];
 | 
			
		||||
    LastRegionPlayed?: TSolarMapRegion;
 | 
			
		||||
    GameModeId: number;
 | 
			
		||||
    hosts: string[];
 | 
			
		||||
    currentClients: unknown[];
 | 
			
		||||
    ChallengeProgress: IChallengeProgress[];
 | 
			
		||||
    PS: string;
 | 
			
		||||
    ActiveDojoColorResearch: string;
 | 
			
		||||
    RewardInfo?: IRewardInfo;
 | 
			
		||||
    ReceivedCeremonyMsg: boolean;
 | 
			
		||||
    LastCeremonyResetDate: number;
 | 
			
		||||
    MissionPTS: number;
 | 
			
		||||
    RepHash: string;
 | 
			
		||||
    EndOfMatchUpload: boolean;
 | 
			
		||||
    ObjectiveReached: boolean;
 | 
			
		||||
    sharedSessionId: string;
 | 
			
		||||
    FpsAvg: number;
 | 
			
		||||
    FpsMin: number;
 | 
			
		||||
    FpsMax: number;
 | 
			
		||||
    FpsSamples: number;
 | 
			
		||||
    EvolutionProgress?: IEvolutionProgress[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
    FusionPoints?: number; // Not a part of the request, but we put it in this struct as an intermediate storage.
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IMissionInventoryUpdateRequestRewardInfo {
 | 
			
		||||
export interface IRewardInfo {
 | 
			
		||||
    node: string;
 | 
			
		||||
    VaultsCracked?: number; // for Spy missions
 | 
			
		||||
    rewardTier?: number;
 | 
			
		||||
@ -82,15 +103,15 @@ export interface IMissionInventoryUpdateRequestRewardInfo {
 | 
			
		||||
    rewardSeed?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type IMissionStatus = "GS_SUCCESS" | "GS_FAILURE" | "GS_DUMPED" | "GS_QUIT" | "GS_INTERRUPTED";
 | 
			
		||||
 | 
			
		||||
export interface IInventorySlotsRequest {
 | 
			
		||||
    Bin: "PveBonusLoadoutBin";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IUpdateGlyphRequest {
 | 
			
		||||
    AvatarImageType: string;
 | 
			
		||||
    AvatarImage: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IUpgradesRequest {
 | 
			
		||||
    ItemCategory: TEquipmentKey;
 | 
			
		||||
    ItemId: IOid;
 | 
			
		||||
@ -98,7 +119,6 @@ export interface IUpgradesRequest {
 | 
			
		||||
    UpgradeVersion: number;
 | 
			
		||||
    Operations: IUpgradeOperation[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IUpgradeOperation {
 | 
			
		||||
    OperationType: string;
 | 
			
		||||
    UpgradeRequirement: string; // uniqueName of item being consumed
 | 
			
		||||
@ -106,3 +126,8 @@ export interface IUpgradeOperation {
 | 
			
		||||
    PolarizeValue: ArtifactPolarity;
 | 
			
		||||
    PolarityRemap: IPolarity[];
 | 
			
		||||
}
 | 
			
		||||
export interface IUnlockShipFeatureRequest {
 | 
			
		||||
    Feature: string;
 | 
			
		||||
    KeyChain: string;
 | 
			
		||||
    ChainStage: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										13
									
								
								static/fixed_responses/questCompletionRewards.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								static/fixed_responses/questCompletionRewards.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
			
		||||
{
 | 
			
		||||
  "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain": [
 | 
			
		||||
    {
 | 
			
		||||
      "ItemType": "/Lotus/Types/Keys/DuviriQuest/DuviriQuestKeyChain",
 | 
			
		||||
      "ItemCount": 1
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "ItemType": "/Lotus/Types/NeutralCreatures/ErsatzHorse/ErsatzHorsePowerSuit",
 | 
			
		||||
      "ItemCount": 1
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "/Lotus/Types/Keys/InfestedMicroplanetQuest/InfestedMicroplanetQuestKeyChain": [{ "ItemType": "/Lotus/Types/Recipes/WarframeRecipes/BrokenFrameBlueprint", "ItemCount": 1 }]
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user