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

XFESpaceNinjaServer

A simple server for a small space ninja game

公开
关注 0 Fork 0 Star 0
UTF-8
import type { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import {
    getInventory,
    addMods,
    addRecipes,
    addMiscItems,
    addConsumables,
    freeUpSlot,
    combineInventoryChanges,
    addCrewShipRawSalvage,
    addFusionPoints,
    addCrewShipFusionPoints,
    addFusionTreasures
} from "../../services/inventoryService.ts";
import { eInventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts";
import { ExportDojoRecipes } from "warframe-public-export-plus";
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
import type { TInventoryDatabaseDocument } from "../../models/inventoryModels/inventoryModel.ts";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
import { parseFusionTreasure, toOid } from "../../helpers/inventoryHelpers.ts";
import { logger } from "../../utils/logger.ts";

export const sellController: RequestHandler = async (req, res) => {
    const payload = JSON.parse(String(req.body)) as ISellRequest;
    logger.trace("sell request:", payload);
    const accountId = await getAccountIdForRequest(req);
    const requiredFields = new Set<keyof TInventoryDatabaseDocument>();
    let sellCurrency = "SC_RegularCredits";
    if (payload.SellCurrency) {
        sellCurrency = payload.SellCurrency;
    } else {
        if (payload.SellForFusionPoints || payload.SellForPrimeBucks) {
            if (payload.SellForFusionPoints) {
                sellCurrency = "SC_FusionPoints";
            }
            if (payload.SellForPrimeBucks) {
                sellCurrency = "SC_PrimeBucks";
            }
        }
    }
    if (sellCurrency == "SC_RegularCredits") {
        requiredFields.add("RegularCredits");
    } else if (sellCurrency == "SC_FusionPoints") {
        requiredFields.add("FusionPoints");
    } else if (sellCurrency == "SC_CrewShipFusionPoints") {
        requiredFields.add("CrewShipFusionPoints");
    } else {
        requiredFields.add("MiscItems");
    }
    for (const key of Object.keys(payload.Items)) {
        if (key == "FusionTreasures") {
            requiredFields.add("HybridFusionTreasures");
        } else {
            requiredFields.add(key as keyof TInventoryDatabaseDocument);
            if (key == "LongGuns") {
                requiredFields.add("Melee");
            }
        }
    }
    if (requiredFields.has("Upgrades")) {
        requiredFields.add("RawUpgrades");
    }
    if ("Suits" in payload.Items) {
        requiredFields.add(eInventorySlot.SUITS);
    }
    if ("LongGuns" in payload.Items || "Pistols" in payload.Items || "Melee" in payload.Items) {
        requiredFields.add(eInventorySlot.WEAPONS);
    }
    if ("SpaceSuits" in payload.Items) {
        requiredFields.add(eInventorySlot.SPACESUITS);
    }
    if ("SpaceGuns" in payload.Items || "SpaceMelee" in payload.Items) {
        requiredFields.add(eInventorySlot.SPACEWEAPONS);
    }
    if ("MechSuits" in payload.Items) {
        requiredFields.add(eInventorySlot.MECHSUITS);
    }
    if ("Sentinels" in payload.Items || "SentinelWeapons" in payload.Items || "MoaPets" in payload.Items) {
        requiredFields.add(eInventorySlot.SENTINELS);
    }
    if ("OperatorAmps" in payload.Items) {
        requiredFields.add(eInventorySlot.AMPS);
    }
    if ("Hoverboards" in payload.Items) {
        requiredFields.add(eInventorySlot.SPACESUITS);
    }
    if ("CrewMembers" in payload.Items) {
        requiredFields.add(eInventorySlot.CREWMEMBERS);
    }
    if ("CrewShipWeapons" in payload.Items || "CrewShipWeaponSkins" in payload.Items) {
        requiredFields.add(eInventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
        requiredFields.add("CrewShipRawSalvage");
        if ("CrewShipWeapons" in payload.Items) {
            requiredFields.add("CrewShipSalvagedWeapons");
        }
        if ("CrewShipWeaponSkins" in payload.Items) {
            requiredFields.add("CrewShipSalvagedWeaponSkins");
        }
    }
    if ("WeaponSkins" in payload.Items) {
        requiredFields.add("WeaponSkins");
    }
    const inventory = await getInventory(accountId, Array.from(requiredFields).join(" "));

    // Give currency
    if (sellCurrency == "SC_RegularCredits") {
        inventory.RegularCredits += payload.SellPrice;
    } else if (sellCurrency == "SC_FusionPoints") {
        addFusionPoints(inventory, payload.SellPrice);
    } else if (sellCurrency == "SC_CrewShipFusionPoints") {
        addCrewShipFusionPoints(inventory, payload.SellPrice);
    } else if (sellCurrency == "SC_PrimeBucks") {
        addMiscItems(inventory, [
            {
                ItemType: "/Lotus/Types/Items/MiscItems/PrimeBucks",
                ItemCount: payload.SellPrice
            }
        ]);
    } else if (sellCurrency == "SC_DistillPoints") {
        addMiscItems(inventory, [
            {
                ItemType: "/Lotus/Types/Items/MiscItems/DistillPoints",
                ItemCount: payload.SellPrice
            }
        ]);
    } else if (sellCurrency == "SC_Resources") {
        // Will add appropriate MiscItems from CrewShipWeapons or CrewShipWeaponSkins
    } else {
        throw new Error("Unknown SellCurrency: " + payload.SellCurrency);
    }

    const inventoryChanges: IInventoryChanges = {};

    // Remove item(s)
    for (const key in payload.Items) {
        switch (key) {
            case "Suits":
                payload.Items.Suits.forEach(sellItem => {
                    inventory.Suits.pull({ _id: sellItem.String });
                    freeUpSlot(inventory, eInventorySlot.SUITS);
                });
                break;
            case "LongGuns":
                payload.Items.LongGuns.forEach(sellItem => {
                    const item = inventory.LongGuns.id(sellItem.String)!;
                    if (item.AltWeaponModeId) {
                        inventory.Melee.pull({ _id: item.AltWeaponModeId });
                        freeUpSlot(inventory, eInventorySlot.WEAPONS);
                        inventoryChanges.RemovedIdItems ??= [];
                        inventoryChanges.RemovedIdItems.push({ ItemId: toOid(item.AltWeaponModeId) });
                    }
                    inventory.LongGuns.pull({ _id: sellItem.String });
                    freeUpSlot(inventory, eInventorySlot.WEAPONS);
                });
                break;
            case "Pistols":
                payload.Items.Pistols.forEach(sellItem => {
                    inventory.Pistols.pull({ _id: sellItem.String });
                    freeUpSlot(inventory, eInventorySlot.WEAPONS);
                });
                break;
            case "Melee":
                payload.Items.Melee.forEach(sellItem => {
                    inventory.Melee.pull({ _id: sellItem.String });
                    freeUpSlot(inventory, eInventorySlot.WEAPONS);
                });
                break;
            case "SpaceSuits":
                payload.Items.SpaceSuits.forEach(sellItem => {
                    inventory.SpaceSuits.pull({ _id: sellItem.String });
                    freeUpSlot(inventory, eInventorySlot.SPACESUITS);
                });
                break;
            case "SpaceGuns":
                payload.Items.SpaceGuns.forEach(sellItem => {
                    inventory.SpaceGuns.pull({ _id: sellItem.String });
                    freeUpSlot(inventory, eInventorySlot.SPACEWEAPONS);
                });
                break;
            case "SpaceMelee":
                payload.Items.SpaceMelee.forEach(sellItem => {
                    inventory.SpaceMelee.pull({ _id: sellItem.String });
                    freeUpSlot(inventory, eInventorySlot.SPACEWEAPONS);
                });
                break;
            case "MechSuits":
                payload.Items.MechSuits.forEach(sellItem => {
                    inventory.MechSuits.pull({ _id: sellItem.String });
                    freeUpSlot(inventory, eInventorySlot.MECHSUITS);
                });
                break;
            case "Sentinels":
                payload.Items.Sentinels.forEach(sellItem => {
                    inventory.Sentinels.pull({ _id: sellItem.String });
                    freeUpSlot(inventory, eInventorySlot.SENTINELS);
                });
                break;
            case "SentinelWeapons":
                payload.Items.SentinelWeapons.forEach(sellItem => {
                    inventory.SentinelWeapons.pull({ _id: sellItem.String });
                    freeUpSlot(inventory, eInventorySlot.SENTINELS);
                });
                break;
            case "MoaPets":
                payload.Items.MoaPets.forEach(sellItem => {
                    inventory.MoaPets.pull({ _id: sellItem.String });
                    freeUpSlot(inventory, eInventorySlot.SENTINELS);
                });
                break;
            case "OperatorAmps":
                payload.Items.OperatorAmps.forEach(sellItem => {
                    inventory.OperatorAmps.pull({ _id: sellItem.String });
                    freeUpSlot(inventory, eInventorySlot.AMPS);
                });
                break;
            case "Hoverboards":
                payload.Items.Hoverboards.forEach(sellItem => {
                    inventory.Hoverboards.pull({ _id: sellItem.String });
                    freeUpSlot(inventory, eInventorySlot.SPACESUITS);
                });
                break;
            case "Drones":
                payload.Items.Drones.forEach(sellItem => {
                    inventory.Drones.pull({ _id: sellItem.String });
                });
                break;
            case "KubrowPetPrints":
                payload.Items.KubrowPetPrints.forEach(sellItem => {
                    inventory.KubrowPetPrints.pull({ _id: sellItem.String });
                });
                break;
            case "CrewMembers":
                payload.Items.CrewMembers.forEach(sellItem => {
                    inventory.CrewMembers.pull({ _id: sellItem.String });
                    freeUpSlot(inventory, eInventorySlot.CREWMEMBERS);
                });
                break;
            case "CrewShipWeapons":
                payload.Items.CrewShipWeapons.forEach(sellItem => {
                    if (sellItem.String[0] == "/") {
                        addCrewShipRawSalvage(inventory, [
                            {
                                ItemType: sellItem.String,
                                ItemCount: sellItem.Count * -1
                            }
                        ]);
                    } else {
                        const index = inventory.CrewShipWeapons.findIndex(x => x._id.equals(sellItem.String));
                        if (index != -1) {
                            if (sellCurrency == "SC_Resources") {
                                refundPartialBuildCosts(
                                    inventory,
                                    inventory.CrewShipWeapons[index].ItemType,
                                    inventoryChanges
                                );
                            }
                            inventory.CrewShipWeapons.splice(index, 1);
                            freeUpSlot(inventory, eInventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
                        } else {
                            inventory.CrewShipSalvagedWeapons.pull({ _id: sellItem.String });
                        }
                    }
                });
                break;
            case "CrewShipWeaponSkins":
                payload.Items.CrewShipWeaponSkins.forEach(sellItem => {
                    if (sellItem.String[0] == "/") {
                        addCrewShipRawSalvage(inventory, [
                            {
                                ItemType: sellItem.String,
                                ItemCount: sellItem.Count * -1
                            }
                        ]);
                    } else {
                        const index = inventory.CrewShipWeaponSkins.findIndex(x => x._id.equals(sellItem.String));
                        if (index != -1) {
                            if (sellCurrency == "SC_Resources") {
                                refundPartialBuildCosts(
                                    inventory,
                                    inventory.CrewShipWeaponSkins[index].ItemType,
                                    inventoryChanges
                                );
                            }
                            inventory.CrewShipWeaponSkins.splice(index, 1);
                            freeUpSlot(inventory, eInventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
                        } else {
                            inventory.CrewShipSalvagedWeaponSkins.pull({ _id: sellItem.String });
                        }
                    }
                });
                break;
            case "Consumables":
                {
                    const consumablesChanges = [];
                    for (const sellItem of payload.Items.Consumables) {
                        consumablesChanges.push({
                            ItemType: sellItem.String,
                            ItemCount: sellItem.Count * -1
                        });
                    }
                    addConsumables(inventory, consumablesChanges);
                }
                break;
            case "Recipes":
                {
                    const recipeChanges = [];
                    for (const sellItem of payload.Items.Recipes) {
                        recipeChanges.push({
                            ItemType: sellItem.String,
                            ItemCount: sellItem.Count * -1
                        });
                    }
                    addRecipes(inventory, recipeChanges);
                }
                break;
            case "Upgrades":
                payload.Items.Upgrades.forEach(sellItem => {
                    if (sellItem.Count == 0 || !sellItem.String.includes("/")) {
                        inventory.Upgrades.pull({ _id: sellItem.String });
                    } else {
                        addMods(inventory, [
                            {
                                ItemType: sellItem.String,
                                ItemCount: sellItem.Count * -1
                            }
                        ]);
                    }
                });
                break;
            case "MiscItems":
                payload.Items.MiscItems.forEach(sellItem => {
                    addMiscItems(inventory, [
                        {
                            ItemType: sellItem.String,
                            ItemCount: sellItem.Count * -1
                        }
                    ]);
                });
                break;
            case "FusionTreasures":
                payload.Items.FusionTreasures.forEach(sellItem => {
                    if (sellItem.String.length == 24 && !sellItem.String.includes("/")) {
                        inventory.HybridFusionTreasures.pull({ _id: sellItem.String });
                    } else {
                        addFusionTreasures(inventory, [parseFusionTreasure(sellItem.String, sellItem.Count * -1)]);
                    }
                });
                break;
            case "WeaponSkins": // SNS specific field, only used by webui
                payload.Items.WeaponSkins.forEach(sellItem => {
                    inventory.WeaponSkins.pull({ _id: sellItem.String });
                });
                break;
            default:
                logger.warn(`unhandled sell category: ${key}`);
        }
    }

    await inventory.save();
    res.json({
        inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges"
    });
    broadcastInventoryUpdate(req);
};

interface ISellRequest {
    Items: Record<string, ISellItem[]>;
    SellPrice: number;
    SellCurrency?:
        | "SC_RegularCredits"
        | "SC_PrimeBucks"
        | "SC_FusionPoints"
        | "SC_DistillPoints"
        | "SC_CrewShipFusionPoints"
        | "SC_Resources"
        | "somethingelsewemightnotknowabout";
    buildLabel: string;
    // These are used in old builds (undetermined where it changed) instead of SellCurrency
    SellForPrimeBucks?: boolean;
    SellForFusionPoints?: boolean;
}

interface ISellItem {
    String: string; // oid or uniqueName
    Count: number;
}

const refundPartialBuildCosts = (
    inventory: TInventoryDatabaseDocument,
    itemType: string,
    inventoryChanges: IInventoryChanges
): void => {
    // House versions
    const research = Object.values(ExportDojoRecipes.research).find(x => x.resultType == itemType);
    if (research) {
        const miscItemChanges = research.ingredients.map(x => ({
            ItemType: x.ItemType,
            ItemCount: Math.trunc(x.ItemCount * 0.8)
        }));
        addMiscItems(inventory, miscItemChanges);
        combineInventoryChanges(inventoryChanges, { MiscItems: miscItemChanges });
        return;
    }

    // Sigma versions
    const recipe = Object.values(ExportDojoRecipes.fabrications).find(x => x.resultType == itemType);
    if (recipe) {
        const miscItemChanges = recipe.ingredients.map(x => ({
            ItemType: x.ItemType,
            ItemCount: Math.trunc(x.ItemCount * 0.8)
        }));
        addMiscItems(inventory, miscItemChanges);
        combineInventoryChanges(inventoryChanges, { MiscItems: miscItemChanges });
        return;
    }
};