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 { ExportWeapons } from "warframe-public-export-plus";
import type { IMongoDate } from "../../types/commonTypes.ts";
import { toMongoDate } from "../../helpers/inventoryHelpers.ts";
import { SRng } from "../../services/rngService.ts";
import type { TArtifactPolarity } from "../../types/inventoryTypes/commonInventoryTypes.ts";
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import {
    addEquipment,
    applyDefaultUpgrades,
    getInventory,
    occupySlot,
    productCategoryToInventoryBin,
    updateCurrency
} from "../../services/inventoryService.ts";
import { getDefaultUpgrades } from "../../services/itemDataService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { sendWsBroadcastTo } from "../../services/wsService.ts";
import { modularWeaponTypes } from "../../helpers/modularWeaponHelper.ts";
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
import { eEquipmentFeatures } from "../../types/equipmentTypes.ts";
import { logger } from "../../utils/logger.ts";

export const modularWeaponSaleController: RequestHandler = async (req, res) => {
    const partTypeToParts: Record<string, string[]> = {};
    for (const [uniqueName, data] of Object.entries(ExportWeapons)) {
        if (
            data.partType &&
            data.platinumCost &&
            !data.excludeFromCodex // exclude pvp variants
        ) {
            partTypeToParts[data.partType] ??= [];
            partTypeToParts[data.partType].push(uniqueName);
        }
    }

    if (req.query.op == "SyncAll") {
        res.json({
            SaleInfos: getSaleInfos(partTypeToParts, Math.trunc(Date.now() / 86400000))
        });
    } else if (req.query.op == "Sync") {
        const payload = getJSONfromString<IModularWeaponSyncRequest>(String(req.body));
        const saleInfos = getSaleInfos(partTypeToParts, Math.trunc(Date.now() / 86400000));
        res.json({
            SaleInfo: saleInfos.find(x => x.Name == payload.SaleName)
        });
    } else if (req.query.op == "Purchase") {
        const accountId = await getAccountIdForRequest(req);
        const inventory = await getInventory(accountId, undefined);
        const payload = getJSONfromString<IModularWeaponPurchaseRequest>(String(req.body));
        const weaponInfo = getSaleInfos(partTypeToParts, payload.Revision).find(x => x.Name == payload.SaleName)!
            .Weapons[payload.ItemIndex];
        const category = modularWeaponTypes[weaponInfo.ItemType];
        const defaultUpgrades = getDefaultUpgrades(weaponInfo.ModularParts);
        let inventoryChanges: IInventoryChanges = {};
        const configs = applyDefaultUpgrades(inventory, defaultUpgrades, inventoryChanges);
        inventoryChanges = {
            ...inventoryChanges,
            ...addEquipment(inventory, category, weaponInfo.ItemType, {
                Features: eEquipmentFeatures.DOUBLE_CAPACITY | eEquipmentFeatures.GILDED,
                ItemName: payload.ItemName,
                Configs: configs,
                ModularParts: weaponInfo.ModularParts,
                Polarity: [
                    {
                        Slot: payload.PolarizeSlot,
                        Value: payload.PolarizeValue
                    }
                ]
            }),
            ...occupySlot(inventory, productCategoryToInventoryBin(category)!, true),
            ...updateCurrency(inventory, weaponInfo.PremiumPrice, true)
        };
        if (defaultUpgrades) {
            inventoryChanges.RawUpgrades = defaultUpgrades.map(x => ({ ItemType: x.ItemType, ItemCount: 1 }));
        }
        await inventory.save();
        res.json({
            InventoryChanges: inventoryChanges
        });
        sendWsBroadcastTo(accountId, { update_inventory: true });
    } else {
        if (req.body) {
            logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
        }
        throw new Error(`unknown modularWeaponSale op: ${String(req.query.op)}`);
    }
};

const getSaleInfos = (partTypeToParts: Record<string, string[]>, day: number): IModularWeaponSaleInfo[] => {
    const kitgunIsPrimary: boolean = (day & 1) != 0;
    return [
        getModularWeaponSale(
            partTypeToParts,
            day,
            "Ostron",
            ["LWPT_HILT", "LWPT_BLADE", "LWPT_HILT_WEIGHT"],
            () => "/Lotus/Weapons/Ostron/Melee/LotusModularWeapon"
        ),
        getModularWeaponSale(
            partTypeToParts,
            day,
            "SolarisUnitedHoverboard",
            ["LWPT_HB_DECK", "LWPT_HB_ENGINE", "LWPT_HB_FRONT", "LWPT_HB_JET"],
            () => "/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit"
        ),
        getModularWeaponSale(
            partTypeToParts,
            day,
            "SolarisUnitedMoaPet",
            ["LWPT_MOA_LEG", "LWPT_MOA_HEAD", "LWPT_MOA_ENGINE", "LWPT_MOA_PAYLOAD"],
            () => "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit"
        ),
        getModularWeaponSale(
            partTypeToParts,
            day,
            "SolarisUnitedKitGun",
            [
                kitgunIsPrimary ? "LWPT_GUN_PRIMARY_HANDLE" : "LWPT_GUN_SECONDARY_HANDLE",
                "LWPT_GUN_BARREL",
                "LWPT_GUN_CLIP"
            ],
            (parts: string[]) => {
                const barrel = parts[1];
                const gunType = ExportWeapons[barrel].gunType!;
                if (kitgunIsPrimary) {
                    return {
                        GT_RIFLE: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
                        GT_SHOTGUN: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun",
                        GT_BEAM: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam"
                    }[gunType];
                } else {
                    return {
                        GT_RIFLE: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary",
                        GT_SHOTGUN: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun",
                        GT_BEAM: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam"
                    }[gunType];
                }
            }
        )
    ];
};

const priceFactor: Record<string, number> = {
    Ostron: 0.9,
    SolarisUnitedHoverboard: 0.85,
    SolarisUnitedMoaPet: 0.95,
    SolarisUnitedKitGun: 0.9
};

const getModularWeaponSale = (
    partTypeToParts: Record<string, string[]>,
    day: number,
    name: string,
    partTypes: string[],
    getItemType: (parts: string[]) => string
): IModularWeaponSaleInfo => {
    const rng = new SRng(day);
    const parts = partTypes.map(partType => rng.randomElement(partTypeToParts[partType])!);
    let partsCost = 0;
    for (const part of parts) {
        partsCost += ExportWeapons[part].platinumCost!;
    }
    return {
        Name: name,
        Expiry: toMongoDate(new Date((day + 1) * 86400000)),
        Revision: day,
        Weapons: [
            {
                ItemType: getItemType(parts),
                PremiumPrice: Math.trunc(partsCost * priceFactor[name]),
                ModularParts: parts
            }
        ]
    };
};

interface IModularWeaponSyncRequest {
    SaleName: string;
}

interface IModularWeaponSaleInfo {
    Name: string;
    Expiry: IMongoDate;
    Revision: number;
    Weapons: IModularWeaponSaleItem[];
}

interface IModularWeaponSaleItem {
    ItemType: string;
    PremiumPrice: number;
    ModularParts: string[];
}

interface IModularWeaponPurchaseRequest {
    SaleName: string;
    ItemIndex: number;
    Revision: number;
    ItemName: string;
    PolarizeSlot: number;
    PolarizeValue: TArtifactPolarity;
}