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

XFESpaceNinjaServer

A simple server for a small space ninja game

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/XFESpaceNinjaServer

feat: handle duet encounter (#1592)

Closes #1274 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/1592 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>

92d2616d
Sainan <63328889+Sainan@users.noreply.github.com>
提交于

代码差异

4 个文件 +210 -23
Modified src/controllers/api/nemesisController.ts +114 -3
@@ -1,10 +1,25 @@
1 import { getInfNodes, getNemesisPasscode } from "@/src/helpers/nemesisHelpers";
1 import {
2 consumeModCharge,
3 encodeNemesisGuess,
4 getInfNodes,
5 getNemesisPasscode,
6 IKnifeResponse
7 } from "@/src/helpers/nemesisHelpers";
2 8 import { getJSONfromString } from "@/src/helpers/stringHelpers";
9 import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
3 10 import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
4 11 import { getAccountIdForRequest } from "@/src/services/loginService";
5 12 import { SRng } from "@/src/services/rngService";
6 13 import { IMongoDate, IOid } from "@/src/types/commonTypes";
7 import { IInnateDamageFingerprint, InventorySlot, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
14 import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
15 import {
16 IInnateDamageFingerprint,
17 InventorySlot,
18 IUpgradeClient,
19 IWeaponSkinClient,
20 LoadoutIndex,
21 TEquipmentKey
22 } from "@/src/types/inventoryTypes/inventoryTypes";
8 23 import { logger } from "@/src/utils/logger";
9 24 import { RequestHandler } from "express";
10 25
@@ -49,7 +64,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
49 64 } else if ((req.query.mode as string) == "p") {
50 65 const inventory = await getInventory(accountId, "Nemesis");
51 66 const body = getJSONfromString<INemesisPrespawnCheckRequest>(String(req.body));
52 const passcode = getNemesisPasscode(inventory.Nemesis!.fp, inventory.Nemesis!.Faction);
67 const passcode = getNemesisPasscode(inventory.Nemesis!);
53 68 let guessResult = 0;
54 69 if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
55 70 for (let i = 0; i != 3; ++i) {
@@ -66,6 +81,88 @@ export const nemesisController: RequestHandler = async (req, res) => {
66 81 }
67 82 }
68 83 res.json({ GuessResult: guessResult });
84 } else if (req.query.mode == "r") {
85 const inventory = await getInventory(
86 accountId,
87 "Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
88 );
89 const body = getJSONfromString<INemesisRequiemRequest>(String(req.body));
90 if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
91 const guess: number[] = [body.guess & 0xf, (body.guess >> 4) & 0xf, (body.guess >> 8) & 0xf];
92 const passcode = getNemesisPasscode(inventory.Nemesis!)[0];
93
94 // Add to GuessHistory
95 const result1 = passcode == guess[0] ? 0 : 1;
96 const result2 = passcode == guess[1] ? 0 : 1;
97 const result3 = passcode == guess[2] ? 0 : 1;
98 inventory.Nemesis!.GuessHistory.push(
99 encodeNemesisGuess(guess[0], result1, guess[1], result2, guess[2], result3)
100 );
101
102 // Increase antivirus
103 let antivirusGain = 5;
104 const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
105 const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid);
106 const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0;
107 const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!;
108 const response: IKnifeResponse = {};
109 for (const upgrade of body.knife!.AttachedUpgrades) {
110 switch (upgrade.ItemType) {
111 case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod":
112 antivirusGain += 10;
113 consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
114 break;
115 case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndWeaponDamageOnUseMod":
116 antivirusGain += 10;
117 consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
118 break;
119 case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusLargeOnSingleUseMod": // Instant Secure
120 antivirusGain += 15;
121 consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
122 break;
123 case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusOnUseMod": // Immuno Shield
124 antivirusGain += 15;
125 consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
126 break;
127 case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod":
128 antivirusGain += 10;
129 consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
130 break;
131 }
132 }
133 inventory.Nemesis!.HenchmenKilled += antivirusGain;
134 if (inventory.Nemesis!.HenchmenKilled >= 100) {
135 inventory.Nemesis!.HenchmenKilled = 100;
136 inventory.Nemesis!.InfNodes = [
137 {
138 Node: "CrewBattleNode559",
139 Influence: 1
140 }
141 ];
142 inventory.Nemesis!.Weakened = true;
143 } else {
144 inventory.Nemesis!.InfNodes = getInfNodes("FC_INFESTATION", 0);
145 }
146
147 await inventory.save();
148 res.json(response);
149 } else {
150 const passcode = getNemesisPasscode(inventory.Nemesis!);
151 if (passcode[body.position] != body.guess) {
152 res.end();
153 } else {
154 inventory.Nemesis!.Rank += 1;
155 inventory.Nemesis!.InfNodes = getInfNodes(inventory.Nemesis!.Faction, inventory.Nemesis!.Rank);
156 await inventory.save();
157 res.json({ RankIncrease: 1 });
158 }
159 }
160 } else if ((req.query.mode as string) == "rs") {
161 // report spawn; POST but no application data in body
162 const inventory = await getInventory(accountId, "Nemesis");
163 inventory.Nemesis!.LastEnc = inventory.Nemesis!.MissionCount;
164 await inventory.save();
165 res.json({ LastEnc: inventory.Nemesis!.LastEnc });
69 166 } else if ((req.query.mode as string) == "s") {
70 167 const inventory = await getInventory(accountId, "Nemesis");
71 168 const body = getJSONfromString<INemesisStartRequest>(String(req.body));
@@ -173,6 +270,20 @@ interface INemesisPrespawnCheckRequest {
173 270 potency?: number[];
174 271 }
175 272
273 interface INemesisRequiemRequest {
274 guess: number; // grn/crp: 4 bits | coda: 3x 4 bits
275 position: number; // grn/crp: 0-2 | coda: 0
276 // knife field provided for coda only
277 knife?: {
278 Item: IEquipmentClient;
279 Skins: IWeaponSkinClient[];
280 ModSlot: number;
281 CustSlot: number;
282 AttachedUpgrades: IUpgradeClient[];
283 HiddenWhenHolstered: boolean;
284 };
285 }
286
176 287 const kuvaLichVersionSixWeapons = [
177 288 "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon",
178 289 "/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak",
Modified src/helpers/nemesisHelpers.ts +89 -3
@@ -1,6 +1,11 @@
1 1 import { ExportRegions } from "warframe-public-export-plus";
2 2 import { IInfNode } from "@/src/types/inventoryTypes/inventoryTypes";
3 3 import { SRng } from "@/src/services/rngService";
4 import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
5 import { logger } from "../utils/logger";
6 import { IOid } from "../types/commonTypes";
7 import { Types } from "mongoose";
8 import { addMods } from "../services/inventoryService";
4 9
5 10 export const getInfNodes = (faction: string, rank: number): IInfNode[] => {
6 11 const infNodes = [];
@@ -33,12 +38,93 @@ const systemIndexes: Record<string, number[]> = {
33 38 };
34 39
35 40 // Get a parazon 'passcode' based on the nemesis fingerprint so it's always the same for the same nemesis.
36 export const getNemesisPasscode = (fp: bigint, faction: string): number[] => {
37 const rng = new SRng(fp);
41 export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: string }): number[] => {
42 const rng = new SRng(nemesis.fp);
38 43 const passcode = [rng.randomInt(0, 7)];
39 if (faction != "FC_INFESTATION") {
44 if (nemesis.Faction != "FC_INFESTATION") {
40 45 passcode.push(rng.randomInt(0, 7));
41 46 passcode.push(rng.randomInt(0, 7));
42 47 }
43 48 return passcode;
44 49 };
50
51 export const encodeNemesisGuess = (
52 symbol1: number,
53 result1: number,
54 symbol2: number,
55 result2: number,
56 symbol3: number,
57 result3: number
58 ): number => {
59 return (
60 (symbol1 & 0xf) |
61 ((result1 & 3) << 12) |
62 ((symbol2 << 4) & 0xff) |
63 ((result2 << 14) & 0xffff) |
64 ((symbol3 & 0xf) << 8) |
65 ((result3 & 3) << 16)
66 );
67 };
68
69 export const decodeNemesisGuess = (val: number): number[] => {
70 return [val & 0xf, (val >> 12) & 3, (val & 0xff) >> 4, (val & 0xffff) >> 14, (val >> 8) & 0xf, (val >> 16) & 3];
71 };
72
73 export interface IKnifeResponse {
74 UpgradeIds?: string[];
75 UpgradeTypes?: string[];
76 UpgradeFingerprints?: { lvl: number }[];
77 UpgradeNew?: boolean[];
78 HasKnife?: boolean;
79 }
80
81 export const consumeModCharge = (
82 response: IKnifeResponse,
83 inventory: TInventoryDatabaseDocument,
84 upgrade: { ItemId: IOid; ItemType: string },
85 dataknifeUpgrades: string[]
86 ): void => {
87 response.UpgradeIds ??= [];
88 response.UpgradeTypes ??= [];
89 response.UpgradeFingerprints ??= [];
90 response.UpgradeNew ??= [];
91 response.HasKnife = true;
92
93 if (upgrade.ItemId.$oid != "000000000000000000000000") {
94 const dbUpgrade = inventory.Upgrades.id(upgrade.ItemId.$oid)!;
95 const fingerprint = JSON.parse(dbUpgrade.UpgradeFingerprint!) as { lvl: number };
96 fingerprint.lvl += 1;
97 dbUpgrade.UpgradeFingerprint = JSON.stringify(fingerprint);
98
99 response.UpgradeIds.push(upgrade.ItemId.$oid);
100 response.UpgradeTypes.push(upgrade.ItemType);
101 response.UpgradeFingerprints.push(fingerprint);
102 response.UpgradeNew.push(false);
103 } else {
104 const id = new Types.ObjectId();
105 inventory.Upgrades.push({
106 _id: id,
107 ItemType: upgrade.ItemType,
108 UpgradeFingerprint: `{"lvl":1}`
109 });
110
111 addMods(inventory, [
112 {
113 ItemType: upgrade.ItemType,
114 ItemCount: -1
115 }
116 ]);
117
118 const dataknifeRawUpgradeIndex = dataknifeUpgrades.indexOf(upgrade.ItemType);
119 if (dataknifeRawUpgradeIndex != -1) {
120 dataknifeUpgrades[dataknifeRawUpgradeIndex] = id.toString();
121 } else {
122 logger.warn(`${upgrade.ItemType} not found in dataknife config`);
123 }
124
125 response.UpgradeIds.push(id.toString());
126 response.UpgradeTypes.push(upgrade.ItemType);
127 response.UpgradeFingerprints.push({ lvl: 1 });
128 response.UpgradeNew.push(true);
129 }
130 };
Modified src/services/missionInventoryUpdateService.ts +1 -16
@@ -699,25 +699,10 @@ export const addMissionRewards = async (
699 699 }
700 700
701 701 if (inventory.Nemesis.Faction == "FC_INFESTATION") {
702 inventoryChanges.Nemesis.HenchmenKilled ??= 0;
703 inventoryChanges.Nemesis.MissionCount ??= 0;
704
705 inventory.Nemesis.HenchmenKilled += 5;
706 702 inventory.Nemesis.MissionCount += 1;
707 703
708 inventoryChanges.Nemesis.HenchmenKilled += 5;
704 inventoryChanges.Nemesis.MissionCount ??= 0;
709 705 inventoryChanges.Nemesis.MissionCount += 1;
710
711 if (inventory.Nemesis.HenchmenKilled >= 100) {
712 inventory.Nemesis.InfNodes = [
713 {
714 Node: "CrewBattleNode559",
715 Influence: 1
716 }
717 ];
718 inventory.Nemesis.Weakened = true;
719 inventoryChanges.Nemesis.Weakened = true;
720 }
721 706 }
722 707
723 708 inventoryChanges.Nemesis.InfNodes = inventory.Nemesis.InfNodes;
Modified src/types/inventoryTypes/inventoryTypes.ts +6 -1
@@ -157,6 +157,11 @@ export type TSolarMapRegion =
157 157
158 158 //TODO: perhaps split response and database into their own files
159 159
160 export enum LoadoutIndex {
161 NORMAL = 0,
162 DATAKNIFE = 7
163 }
164
160 165 export interface IDailyAffiliations {
161 166 DailyAffiliation: number;
162 167 DailyAffiliationPvp: number;
@@ -220,7 +225,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
220 225 ActiveQuest: string;
221 226 FlavourItems: IFlavourItem[];
222 227 LoadOutPresets: ILoadOutPresets;
223 CurrentLoadOutIds: IOid[]; //TODO: we store it in the database using this representation as well :/
228 CurrentLoadOutIds: IOid[]; // we store it in the database using this representation as well :/
224 229 Missions: IMission[];
225 230 RandomUpgradesIdentified?: number;
226 231 LastRegionPlayed: TSolarMapRegion;