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 { TFaction, TMissionType } from "warframe-public-export-plus";
import type { CalendarSeasonType, IConquest, IConquestMission, TConquestType } from "../types/worldStateTypes.ts";
import { mixSeeds, SRng } from "./rngService.ts";
import { EPOCH } from "../constants/timeConstants.ts";

const missionAndFactionTypes: Record<TConquestType, Partial<Record<TMissionType, TFaction[]>>> = {
    CT_LAB: {
        MT_EXTERMINATION: ["FC_MITW"],
        MT_SURVIVAL: ["FC_MITW"],
        MT_ALCHEMY: ["FC_MITW"],
        MT_DEFENSE: ["FC_MITW"],
        MT_ARTIFACT: ["FC_MITW"]
    },
    CT_HEX: {
        MT_EXTERMINATION: ["FC_SCALDRA", "FC_TECHROT"],
        MT_SURVIVAL: ["FC_SCALDRA", "FC_TECHROT"],
        MT_DEFENSE: ["FC_SCALDRA"],
        MT_ENDLESS_CAPTURE: ["FC_TECHROT"]
    }
};

const assassinationFactionOptions: Record<TConquestType, TFaction[]> = {
    CT_LAB: ["FC_MITW"],
    CT_HEX: ["FC_SCALDRA"]
};

type TConquestDifficulty = "CD_NORMAL" | "CD_HARD";

interface IConquestConditional {
    tag: string;
    missionType?: TMissionType;
    conquest?: TConquestType;
    difficulty?: TConquestDifficulty;
    season?: CalendarSeasonType;
}

const deviations: readonly IConquestConditional[] = [
    {
        tag: "AlchemicalShields",
        missionType: "MT_ALCHEMY"
    },
    {
        tag: "ContaminationZone",
        missionType: "MT_SURVIVAL",
        conquest: "CT_HEX"
    },
    {
        tag: "DoubleTrouble",
        missionType: "MT_ARTIFACT"
    },
    {
        tag: "EscalateImmediately",
        missionType: "MT_EXTERMINATION",
        conquest: "CT_HEX"
    },
    {
        tag: "EximusGrenadiers",
        missionType: "MT_ALCHEMY"
    },
    {
        tag: "FortifiedFoes",
        missionType: "MT_EXTERMINATION"
    },
    {
        tag: "FragileNodes",
        missionType: "MT_ARTIFACT"
    },
    {
        tag: "GrowingIncursion",
        missionType: "MT_EXTERMINATION",
        conquest: "CT_LAB"
    },
    {
        tag: "HarshWords",
        missionType: "MT_DEFENSE",
        conquest: "CT_LAB"
    },
    {
        tag: "HighScalingLegacyte",
        missionType: "MT_ENDLESS_CAPTURE",
        conquest: "CT_HEX"
    },
    {
        tag: "DoubleTroubleLegacyte",
        missionType: "MT_ENDLESS_CAPTURE",
        conquest: "CT_HEX"
    },
    {
        tag: "HostileSecurity",
        missionType: "MT_DEFENSE",
        conquest: "CT_LAB"
    },
    {
        tag: "InfiniteTide",
        missionType: "MT_ASSASSINATION",
        conquest: "CT_LAB"
    },
    {
        tag: "LostInTranslation",
        missionType: "MT_DEFENSE",
        conquest: "CT_LAB"
    },
    {
        tag: "MutatedEnemies",
        missionType: "MT_ENDLESS_CAPTURE",
        conquest: "CT_HEX"
    },
    {
        tag: "NecramechActivation",
        missionType: "MT_SURVIVAL",
        conquest: "CT_LAB"
    },
    {
        tag: "Reinforcements",
        missionType: "MT_ASSASSINATION",
        conquest: "CT_LAB"
    },
    {
        tag: "StickyFingers",
        missionType: "MT_ARTIFACT",
        conquest: "CT_LAB"
    },
    {
        tag: "TankStrongArmor",
        missionType: "MT_ASSASSINATION",
        conquest: "CT_HEX"
    },
    {
        tag: "TankReinforcements",
        missionType: "MT_ASSASSINATION",
        conquest: "CT_HEX"
    },
    {
        tag: "TankSuperToxic",
        missionType: "MT_ASSASSINATION",
        conquest: "CT_HEX"
    },
    {
        tag: "TechrotConjunction",
        missionType: "MT_SURVIVAL",
        conquest: "CT_HEX"
    },
    {
        tag: "UnpoweredCapsules",
        missionType: "MT_SURVIVAL",
        conquest: "CT_LAB"
    },
    {
        tag: "VolatileGrenades",
        missionType: "MT_ALCHEMY"
    },
    {
        tag: "GestatingTumors",
        missionType: "MT_SURVIVAL",
        conquest: "CT_HEX"
    },
    {
        tag: "ChemicalNoise",
        missionType: "MT_DEFENSE",
        conquest: "CT_HEX"
    },
    {
        tag: "ExplosiveEnergy",
        missionType: "MT_DEFENSE",
        conquest: "CT_HEX"
    },
    {
        tag: "DisruptiveSounds",
        missionType: "MT_DEFENSE",
        conquest: "CT_HEX"
    }
];

const risks: readonly IConquestConditional[] = [
    {
        tag: "Voidburst"
    },
    {
        tag: "RegeneratingEnemies"
    },
    {
        tag: "VoidAberration"
    },
    {
        tag: "ShieldedFoes"
    },
    {
        tag: "PointBlank"
    },
    {
        tag: "Deflectors",
        conquest: "CT_LAB"
    },
    {
        tag: "AcceleratedEnemies"
    },
    {
        tag: "DrainingResiduals"
    },
    {
        tag: "Quicksand"
    },
    {
        tag: "AntiMaterialWeapons",
        conquest: "CT_LAB"
    },
    {
        tag: "ExplosiveCrawlers",
        conquest: "CT_LAB"
    },
    {
        tag: "EMPBlackHole",
        conquest: "CT_LAB"
    },
    {
        tag: "ArtilleryBeacons",
        conquest: "CT_HEX"
    },
    {
        tag: "InfectedTechrot",
        conquest: "CT_HEX"
    },
    {
        tag: "BalloonFest",
        conquest: "CT_HEX"
    },
    {
        tag: "MiasmiteHive",
        conquest: "CT_HEX"
    },
    {
        tag: "CompetitionSpillover",
        conquest: "CT_HEX"
    },
    {
        tag: "HostileOvergrowth",
        conquest: "CT_HEX"
    },
    {
        tag: "MurmurIncursion",
        conquest: "CT_HEX"
    },
    {
        tag: "FactionSwarm_Techrot",
        conquest: "CT_HEX",
        difficulty: "CD_NORMAL"
    },
    {
        tag: "FactionSwarm_Scaldra",
        conquest: "CT_HEX",
        difficulty: "CD_NORMAL"
    },
    {
        tag: "HeavyWarfare",
        conquest: "CT_HEX"
    },
    {
        tag: "ArcadeAutomata",
        conquest: "CT_HEX",
        difficulty: "CD_NORMAL"
    },
    {
        tag: "EfervonFog",
        conquest: "CT_HEX"
    },
    {
        tag: "WinterFrost",
        conquest: "CT_HEX",
        season: "CST_WINTER"
    },
    {
        tag: "JadeSpring",
        conquest: "CT_HEX",
        season: "CST_SPRING"
    },
    {
        tag: "ExplosiveSummer",
        conquest: "CT_HEX",
        season: "CST_SUMMER"
    },
    {
        tag: "FallFog",
        conquest: "CT_HEX",
        season: "CST_FALL"
    }
];

const filterConditionals = (
    arr: readonly IConquestConditional[],
    missionType: TMissionType | null,
    conquest: TConquestType | null,
    difficulty: TConquestDifficulty | null,
    season: CalendarSeasonType | null
): string[] => {
    const applicable = [];
    for (const cond of arr) {
        if (
            (!cond.missionType || cond.missionType == missionType) &&
            (!cond.conquest || cond.conquest == conquest) &&
            (!cond.difficulty || cond.difficulty == difficulty) &&
            (!cond.season || cond.season == season)
        ) {
            applicable.push(cond.tag);
        }
    }
    return applicable;
};

const buildMission = (
    rng: SRng,
    conquest: TConquestType,
    missionType: TMissionType,
    faction: TFaction,
    season: CalendarSeasonType | null
): IConquestMission => {
    const deviation = rng.randomElement(filterConditionals(deviations, missionType, conquest, null, season))!;
    const easyRisk = rng.randomElement(filterConditionals(risks, missionType, conquest, "CD_NORMAL", season))!;
    const hardRiskOptions = filterConditionals(risks, missionType, conquest, "CD_HARD", season);
    {
        const i = hardRiskOptions.indexOf(easyRisk);
        if (i != -1) {
            hardRiskOptions.splice(i, 1);
        }
    }
    const hardRisk = rng.randomElement(hardRiskOptions)!;
    return {
        faction,
        missionType,
        difficulties: [
            {
                type: "CD_NORMAL",
                deviation,
                risks: [easyRisk]
            },
            {
                type: "CD_HARD",
                deviation,
                risks: [easyRisk, hardRisk]
            }
        ]
    };
};

const conquestStartingDay: Record<TConquestType, number> = {
    CT_LAB: 3703,
    CT_HEX: 4053
};

// This function produces identical results to clients pre-40.0.0.
const getFrameVariables = (conquestType: TConquestType, time: number): [string, string, string, string] => {
    const day = Math.floor((time - 1391990400_000) / 86400_000) - conquestStartingDay[conquestType];
    const week = Math.floor(day / 7) + 1;
    const frameVariables = [
        "Framecurse",
        "Knifestep",
        "Exhaustion",
        "Gearless",
        "TimeDilation",
        "Armorless",
        "Starvation",
        "ShieldDelay",
        "Withering",
        "ContactDamage",
        "AbilityLockout",
        "OperatorLockout",
        "EnergyStarved",
        "OverSensitive",
        "AntiGuard",
        "DecayingFlesh",
        "VoidEnergyOverload",
        "DullBlades",
        "Undersupplied"
    ];
    const mag = Math.floor(frameVariables.length / 4);
    const rng = new SRng(conquestStartingDay[conquestType] + Math.floor(week / mag));
    rng.shuffleArray(frameVariables);
    const i = week % mag;
    return [frameVariables[i], frameVariables[i + 1], frameVariables[i + 2], frameVariables[i + 3]];
};

export const getConquest = (
    conquestType: TConquestType,
    week: number,
    season: CalendarSeasonType | null
): IConquest => {
    const rng = new SRng(mixSeeds(conquestStartingDay[conquestType], week));

    const missions: IConquestMission[] = [];
    {
        const missionOptions = Object.entries(missionAndFactionTypes[conquestType]);
        {
            const i = rng.randomInt(0, missionOptions.length - 1);
            const [missionType, factionOptions] = missionOptions.splice(i, 1)[0];
            missions.push(
                buildMission(rng, conquestType, missionType as TMissionType, rng.randomElement(factionOptions)!, season)
            );
        }
        {
            const i = rng.randomInt(0, missionOptions.length - 1);
            const [missionType, factionOptions] = missionOptions.splice(i, 1)[0];
            missions.push(
                buildMission(rng, conquestType, missionType as TMissionType, rng.randomElement(factionOptions)!, season)
            );
        }
        missionOptions.push(["MT_ASSASSINATION", assassinationFactionOptions[conquestType]]);
        {
            const i = rng.randomInt(0, missionOptions.length - 1);
            const [missionType, factionOptions] = missionOptions.splice(i, 1)[0];
            missions.push(
                buildMission(rng, conquestType, missionType as TMissionType, rng.randomElement(factionOptions)!, season)
            );
        }
    }

    const weekStart = EPOCH + week * 604800000;
    const weekEnd = weekStart + 604800000;
    return {
        Activation: { $date: { $numberLong: weekStart.toString() } },
        Expiry: { $date: { $numberLong: weekEnd.toString() } },
        Type: conquestType,
        Missions: missions,
        Variables: getFrameVariables(conquestType, weekStart),
        RandomSeed: rng.randomInt(0, 1_000_000)
    };
};

export const getMissionTypeForLegacyOverride = (missionType: TMissionType, conquestType: TConquestType): string => {
    if (missionType == "MT_ENDLESS_CAPTURE") {
        return "EndlessCapture";
    }
    let str = missionType.substring(3, 4).toUpperCase() + missionType.substring(4).toLowerCase();
    if (str == "Artifact") {
        str = "Disruption";
    }
    if (str == "Defense" && conquestType == "CT_LAB") {
        str = "DualDefense";
    }
    return str;
};

export const factionToInt = (faction: TFaction | "FC_TENNO"): number => {
    switch (faction) {
        case "FC_GRINEER":
            return 0;
        case "FC_CORPUS":
            return 1;
        case "FC_INFESTATION":
            return 2;
        case "FC_OROKIN":
            return 3;
        case "FC_RED_VEIL":
            return 4;
        case "FC_SENTIENT":
            return 5;
        case "FC_NARMER":
            return 6;
        case "FC_MITW":
            return 7;
        case "FC_SCALDRA":
            return 8;
        case "FC_TECHROT":
            return 9;
        case "FC_DUVIRI":
            return 10;
    }
    throw new Error(`unexpected faction ${faction}`);
};