返回提交历史
Modified
src/controllers/api/nemesisController.ts
+114
-3
Modified
src/helpers/nemesisHelpers.ts
+89
-3
Modified
src/services/missionInventoryUpdateService.ts
+1
-16
Modified
src/types/inventoryTypes/inventoryTypes.ts
+6
-1
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
代码差异
4 个文件
+210
-23
@@ -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",
@@ -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
};
@@ -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;
@@ -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;