返回提交历史
Modified
src/controllers/api/missionInventoryUpdateController.ts
+6
-2
Modified
src/helpers/general.ts
+4
-0
Modified
src/helpers/nemesisHelpers.ts
+19
-1
Modified
src/models/inventoryModels/inventoryModel.ts
+2
-0
Modified
src/services/missionInventoryUpdateService.ts
+44
-8
Modified
src/types/inventoryTypes/inventoryTypes.ts
+6
-3
Modified
src/types/missionTypes.ts
+36
-2
XFEstudio/SpaceNinjaServer
feat: nemesis credits tax (#3273)
Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/3273 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
b7eed4a2
代码差异
7 个文件
+117
-16
@@ -97,7 +97,9 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
97
97
credits,
98
98
AffiliationMods,
99
99
SyndicateXPItemReward,
100
ConquestCompletedMissionsCount
100
ConquestCompletedMissionsCount,
101
NemesisTaxInfo,
102
RecoveredItemInfo
101
103
} = await addMissionRewards(account, inventory, missionReport, firstCompletion);
102
104
handleConservation(inventory, missionReport, AffiliationMods); // Conservation reports have GS_SUCCESS
103
105
@@ -115,7 +117,9 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
115
117
//FusionPoints: inventoryChanges?.FusionPoints, // This in combination with InventoryJson or InventoryChanges seems to just double the number of endo shown, so unsure when this is needed.
116
118
SyndicateXPItemReward,
117
119
AffiliationMods,
118
ConquestCompletedMissionsCount
120
ConquestCompletedMissionsCount,
121
NemesisTaxInfo,
122
RecoveredItemInfo
119
123
};
120
124
if (
121
125
missionReport.BMI ||
@@ -70,3 +70,7 @@ export const isObject = (objectCandidate: unknown): objectCandidate is Record<st
70
70
!Array.isArray(objectCandidate)
71
71
);
72
72
};
73
74
export const lerp = (v0: number, v1: number, t: number): number => {
75
return v0 + t * (v1 - v0);
76
};
@@ -1,10 +1,12 @@
1
1
import { ExportRegions, ExportWarframes } from "warframe-public-export-plus";
2
import type { IInfNode, TNemesisFaction } from "../types/inventoryTypes/inventoryTypes.ts";
2
import type { IInfNode, INemesisDatabase, TNemesisFaction } from "../types/inventoryTypes/inventoryTypes.ts";
3
3
import { generateRewardSeed, getRewardAtPercentage, SRng } from "../services/rngService.ts";
4
4
import type { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel.ts";
5
5
import type { IOid } from "../types/commonTypes.ts";
6
6
import { isArchwingMission } from "../services/worldStateService.ts";
7
7
import gameToBuildVersion from "../constants/gameToBuildVersion.ts";
8
import type { INemesisTaxInfo } from "../types/missionTypes.ts";
9
import { lerp } from "./general.ts";
8
10
9
11
type TInnateDamageTag =
10
12
| "InnateElectricityDamage"
@@ -491,3 +493,19 @@ export const getInfestedLichItemRewards = (fp: bigint): string[] => {
491
493
const rotBReward = getRewardAtPercentage(infestedLichRotB, rng.randomFloat())!.type;
492
494
return [rotAReward, rotBReward];
493
495
};
496
497
export const getNemesisTaxInfo = (nemesis: INemesisDatabase): INemesisTaxInfo | undefined => {
498
if (nemesis.Faction == "FC_GRINEER") {
499
return {
500
TaxRate: nemesis.InfNodes.length * 0.007,
501
TaxCreditsOnly: false
502
};
503
}
504
if (nemesis.Faction == "FC_CORPUS") {
505
return {
506
TaxRate: lerp(0.5, 0.95, Math.min(nemesis.InfNodes.length / 10, 1)),
507
TaxCreditsOnly: true
508
};
509
}
510
return undefined;
511
};
@@ -1879,6 +1879,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
1879
1879
Nemesis: nemesisSchema,
1880
1880
NemesisHistory: { type: [nemesisSchema], default: undefined },
1881
1881
LastNemesisAllySpawnTime: { type: Date, default: undefined },
1882
NemesisTaxedCredits: Number,
1882
1883
1883
1884
//TradingRulesConfirmed,ShowFriendInvNotifications(Option->Social)
1884
1885
Settings: settingsSchema,
@@ -1990,6 +1991,7 @@ inventorySchema.set("toJSON", {
1990
1991
delete returnedObject.MissionRelicRewards;
1991
1992
delete returnedObject.HarvesterPoints;
1992
1993
delete returnedObject.DeathSquadPoints;
1994
delete returnedObject.NemesisTaxedCredits;
1993
1995
1994
1996
const inventoryDatabase = returnedObject as Partial<IInventoryDatabase>;
1995
1997
const inventoryResponse = returnedObject as IInventoryClient;
@@ -56,7 +56,7 @@ import { fromStoreItem, getLevelKeyRewards, isStoreItem, toStoreItem } from "./i
56
56
import type { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel.ts";
57
57
import { getEntriesUnsafe } from "../utils/ts-utils.ts";
58
58
import { handleStoreItemAcquisition } from "./purchaseService.ts";
59
import type { IMissionCredits, IMissionReward } from "../types/missionTypes.ts";
59
import type { IMissionCredits, IMissionReward, INemesisTaxInfo, IRecoveredItemInfo } from "../types/missionTypes.ts";
60
60
import { crackRelic } from "../helpers/relicHelper.ts";
61
61
import type { IMessageCreationTemplate } from "./inboxService.ts";
62
62
import { createMessage } from "./inboxService.ts";
@@ -69,7 +69,8 @@ import {
69
69
getInfNodes,
70
70
getKillTokenRewardCount,
71
71
getNemesisManifest,
72
getNemesisPasscode
72
getNemesisPasscode,
73
getNemesisTaxInfo
73
74
} from "../helpers/nemesisHelpers.ts";
74
75
import { Loadout } from "../models/inventoryModels/loadoutModel.ts";
75
76
import {
@@ -1026,6 +1027,8 @@ interface AddMissionRewardsReturnType {
1026
1027
AffiliationMods: IAffiliationMods[];
1027
1028
SyndicateXPItemReward?: number;
1028
1029
ConquestCompletedMissionsCount?: number;
1030
NemesisTaxInfo?: INemesisTaxInfo;
1031
RecoveredItemInfo?: IRecoveredItemInfo;
1029
1032
}
1030
1033
1031
1034
interface IConquestReward {
@@ -1189,6 +1192,7 @@ export const addMissionRewards = async (
1189
1192
{
1190
1193
wagerTier: wagerTier,
1191
1194
Nemesis: nemesis,
1195
NemesisKillConvert: NemesisKillConvert,
1192
1196
RewardInfo: rewardInfo,
1193
1197
LevelKeyName: levelKeyName,
1194
1198
Missions: missions,
@@ -1467,12 +1471,11 @@ export const addMissionRewards = async (
1467
1471
}
1468
1472
}
1469
1473
1474
let nodeControlledByNemesis = false;
1470
1475
if (inventory.Nemesis) {
1471
if (
1472
nemesis ||
1473
(inventory.Nemesis.Faction == "FC_INFESTATION" &&
1474
inventory.Nemesis.InfNodes.find(obj => obj.Node == rewardInfo.node))
1475
) {
1476
nodeControlledByNemesis = inventory.Nemesis.InfNodes.some(obj => obj.Node == rewardInfo.node);
1477
1478
if (nemesis || (inventory.Nemesis.Faction == "FC_INFESTATION" && nodeControlledByNemesis)) {
1476
1479
inventoryChanges.Nemesis ??= {};
1477
1480
const nodeIndex = inventory.Nemesis.InfNodes.findIndex(obj => obj.Node === rewardInfo.node);
1478
1481
if (nodeIndex !== -1) inventory.Nemesis.InfNodes.splice(nodeIndex, 1);
@@ -1624,6 +1627,37 @@ export const addMissionRewards = async (
1624
1627
rngRewardCredits: inventoryChanges.RegularCredits ?? 0
1625
1628
});
1626
1629
1630
const NemesisTaxInfo: INemesisTaxInfo | undefined = nodeControlledByNemesis
1631
? getNemesisTaxInfo(inventory.Nemesis!)
1632
: undefined;
1633
if (NemesisTaxInfo) {
1634
const taxedCredits = Math.round(credits.TotalCredits[0] * NemesisTaxInfo.TaxRate);
1635
NemesisTaxInfo.TaxedCredits = taxedCredits;
1636
inventory.RegularCredits -= taxedCredits;
1637
inventoryChanges.RegularCredits ??= 0;
1638
inventoryChanges.RegularCredits -= taxedCredits;
1639
credits.TotalCredits[1] -= taxedCredits;
1640
inventory.NemesisTaxedCredits ??= 0;
1641
inventory.NemesisTaxedCredits += taxedCredits;
1642
}
1643
1644
const RecoveredItemInfo: IRecoveredItemInfo | undefined = NemesisKillConvert
1645
? { RecoveredMiscItems: [] }
1646
: undefined;
1647
if (RecoveredItemInfo) {
1648
if (inventory.NemesisTaxedCredits) {
1649
RecoveredItemInfo.RecoveredCredits = inventory.NemesisTaxedCredits;
1650
inventory.RegularCredits += inventory.NemesisTaxedCredits;
1651
inventoryChanges.RegularCredits ??= 0;
1652
inventoryChanges.RegularCredits += inventory.NemesisTaxedCredits;
1653
credits.TotalCredits[0] += inventory.NemesisTaxedCredits;
1654
credits.TotalCredits[1] += inventory.NemesisTaxedCredits;
1655
credits.CreditsBonus[0] += inventory.NemesisTaxedCredits;
1656
credits.CreditsBonus[1] += inventory.NemesisTaxedCredits;
1657
inventory.NemesisTaxedCredits = undefined;
1658
}
1659
}
1660
1627
1661
if (strippedItems) {
1628
1662
if (endOfMatchUpload) {
1629
1663
for (const si of strippedItems) {
@@ -1725,7 +1759,9 @@ export const addMissionRewards = async (
1725
1759
credits,
1726
1760
AffiliationMods,
1727
1761
SyndicateXPItemReward,
1728
ConquestCompletedMissionsCount
1762
ConquestCompletedMissionsCount,
1763
NemesisTaxInfo,
1764
RecoveredItemInfo
1729
1765
};
1730
1766
};
1731
1767
@@ -142,7 +142,13 @@ export interface IInventoryDatabase
142
142
>,
143
143
InventoryDatabaseEquipment,
144
144
IAccountCheats {
145
// SNS-specific fields for server-side tracking
145
146
accountOwnerId: Types.ObjectId;
147
MissionRelicRewards?: ITypeCount[];
148
HarvesterPoints?: number;
149
DeathSquadPoints?: number;
150
NemesisTaxedCredits?: number;
151
146
152
Created: Date;
147
153
CurrentLoadOutIds: Types.ObjectId[] | IOid[]; // should be Types.ObjectId[] but might be IOid[] because of old commits
148
154
TrainingDate: Date;
@@ -183,7 +189,6 @@ export interface IInventoryDatabase
183
189
EndlessXP?: IEndlessXpProgressDatabase[];
184
190
DescentRewards?: IDescentCategoryRewardDatabase[];
185
191
PersonalGoalProgress?: IGoalProgressDatabase[];
186
MissionRelicRewards?: ITypeCount[];
187
192
FocusLoadouts?: IFocusLoadoutDatabase[];
188
193
ChallengeInstanceStates?: IChallengeInstanceStateDatabase[];
189
194
}
@@ -463,8 +468,6 @@ export interface IInventoryClient
463
468
PendingCoupon?: IPendingCouponClient;
464
469
Harvestable?: boolean;
465
470
DeathSquadable?: boolean;
466
HarvesterPoints?: number;
467
DeathSquadPoints?: number;
468
471
EndlessXP?: IEndlessXpProgressClient[];
469
472
DescentRewards?: IDescentCategoryRewardClient[];
470
473
DialogueHistory?: IDialogueHistoryClient;
@@ -1,3 +1,5 @@
1
import type { IMongoDate, IOid, ITypeCount } from "./commonTypes.ts";
2
import type { INemesisBaseClient } from "./inventoryTypes/inventoryTypes.ts";
1
3
import type { IAffiliationMods, IInventoryChanges } from "./purchaseTypes.ts";
2
4
3
5
export interface IMissionReward {
@@ -16,17 +18,32 @@ export interface IMissionReward {
16
18
export interface IMissionCredits {
17
19
MissionCredits: [number, number];
18
20
CreditsBonus: [number, number]; // "Credit Reward"; `CreditsBonus[1]` is `CreditsBonus[0] * 2` if DailyMissionBonus
19
TotalCredits: [number, number];
21
TotalCredits: [number, number]; // TotalCredits[0] - TaxedCredits = TotalCredits[1]
20
22
DailyMissionBonus?: boolean;
21
23
}
22
24
23
export interface IMissionInventoryUpdateResponseRailjackInterstitial extends Partial<IMissionCredits> {
25
// These fields will only be respected by the client when completing a showdown mission.
26
export interface INemsisEndBody {
27
WeaponRecipes?: {
28
ItemType: string;
29
CompletionDate: IMongoDate;
30
TargetFingerprint: string;
31
ItemId: IOid;
32
}[]; // TODO: We should probably send this sometimes when defeating a nemesis?
33
RecoveredItemInfo?: IRecoveredItemInfo;
34
EphemeraReward?: string; // TODO: We should probably send this sometimes when defeating a nemesis?
35
BaitReward?: "/Lotus/StoreItems/Types/Restoratives/Consumable/NemesisBait"; // TODO: We should probably send this sometimes when defeating a nemesis?
36
history?: INemesisBaseClient; // TODO: We should probably send this sometimes when defeating a nemesis?
37
}
38
39
export interface IMissionInventoryUpdateResponseRailjackInterstitial extends Partial<IMissionCredits>, INemsisEndBody {
24
40
ConquestCompletedMissionsCount?: number;
25
41
MissionRewards?: IMissionReward[];
26
42
InventoryChanges?: IInventoryChanges;
27
43
FusionPoints?: number;
28
44
SyndicateXPItemReward?: number;
29
45
AffiliationMods?: IAffiliationMods[];
46
NemesisTaxInfo?: INemesisTaxInfo;
30
47
}
31
48
32
49
export interface IMissionInventoryUpdateResponse extends IMissionInventoryUpdateResponseRailjackInterstitial {
@@ -36,3 +53,20 @@ export interface IMissionInventoryUpdateResponse extends IMissionInventoryUpdate
36
53
export interface IMissionInventoryUpdateResponseBackToDryDock {
37
54
InventoryJson: string;
38
55
}
56
57
export interface INemesisTaxInfo {
58
TaxRate: number; // client errors if this field is not given
59
TaxCreditsOnly: boolean; // always given in the response on live, but not used by the client
60
TaxedCredits?: number;
61
TaxedFusionPoints?: number;
62
TaxedCrewShipFusionPoints?: number;
63
TaxedCollectedItems?: IMissionReward[]; // these must also be present in MissionRewards array
64
TaxedMiscItems?: ITypeCount[]; // can also contain upgrade types
65
}
66
67
export interface IRecoveredItemInfo {
68
RecoveredMiscItems: ITypeCount[];
69
RecoveredCredits?: number;
70
RecoveredFusionPoints?: number;
71
RecoveredCollectedItems?: ITypeCount[]; // not added to MissionRewards in this case
72
}