返回提交历史
Added
src/controllers/api/retroactivelyApplyCheatController.ts
+23
-0
Modified
src/controllers/custom/setAccountCheatController.ts
+8
-3
Modified
src/routes/custom.ts
+2
-0
Added
src/services/cheatsService.ts
+92
-0
Modified
src/services/missionInventoryUpdateService.ts
+1
-1
Modified
src/services/wsService.ts
+10
-6
Modified
src/types/missionTypes.ts
+0
-3
Modified
static/webui/index.html
+1
-7
Modified
static/webui/script.js
+6
-31
Modified
static/webui/translations/de.js
+1
-6
Modified
static/webui/translations/en.js
+1
-6
Modified
static/webui/translations/es.js
+1
-6
Modified
static/webui/translations/fr.js
+1
-6
Modified
static/webui/translations/ru.js
+1
-6
Modified
static/webui/translations/uk.js
+1
-6
Modified
static/webui/translations/zh.js
+1
-6
XFEstudio/XFESpaceNinjaServer
feat(webui): retroactive cheats (#3263)
Closes #3237 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/3263 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
e2753469
代码差异
16 个文件
+150
-93
@@ -0,0 +1,23 @@
1
import type { RequestHandler } from "express";
2
import { getAccountIdForRequest } from "../../services/loginService.ts";
3
import { getInventory } from "../../services/inventoryService.ts";
4
import { lockCheats } from "../../services/cheatsService.ts";
5
import type { IAccountCheats } from "../../types/inventoryTypes/inventoryTypes.ts";
6
import { sendWsBroadcastToGame } from "../../services/wsService.ts";
7
8
export const retroactivelyApplyCheatController: RequestHandler = async (req, res) => {
9
const accountId = await getAccountIdForRequest(req);
10
const meta = lockCheats[req.query.cheat as string as keyof IAccountCheats]!;
11
const inventory = await getInventory(accountId, meta.projection);
12
meta.cleanupInventory(inventory);
13
14
if (!meta.isInventoryInIdealState(inventory)) {
15
throw new Error(
16
`cleanupInventory for ${req.query.cheat as string} does not satsify its isInventoryInIdealState`
17
);
18
}
19
20
await inventory.save();
21
res.end();
22
sendWsBroadcastToGame(accountId, { sync_inventory: true }); // Not using broadcastInventoryUpdate because other webui tabs don't need to know.
23
};
@@ -4,18 +4,23 @@ import { sendWsBroadcastEx, sendWsBroadcastTo } from "../../services/wsService.t
4
4
import type { IAccountCheats } from "../../types/inventoryTypes/inventoryTypes.ts";
5
5
import type { RequestHandler } from "express";
6
6
import { logger } from "../../utils/logger.ts";
7
import { lockCheats } from "../../services/cheatsService.ts";
7
8
8
9
export const setAccountCheatController: RequestHandler = async (req, res) => {
9
const accountId = await getAccountIdForRequest(req);
10
10
const payload = req.body as ISetAccountCheatRequest;
11
const inventory = await getInventory(accountId, payload.key);
12
13
11
if (payload.value == undefined) {
14
12
logger.warn(`Aborting setting ${payload.key} as undefined!`);
15
13
return;
16
14
}
17
15
16
const accountId = await getAccountIdForRequest(req);
17
const meta = payload.value ? lockCheats[payload.key] : undefined;
18
const inventory = await getInventory(accountId, meta ? `${payload.key} ${meta.projection}` : payload.key);
19
18
20
inventory[payload.key] = payload.value as never;
21
if (meta && !meta.isInventoryInIdealState(inventory)) {
22
res.send("retroactivable");
23
}
19
24
await inventory.save();
20
25
res.end();
21
26
if (["infiniteCredits", "infinitePlatinum", "infiniteEndo", "infiniteRegalAya"].indexOf(payload.key) != -1) {
@@ -24,6 +24,7 @@ import { unlockAllShipFeaturesController } from "../controllers/custom/unlockAll
24
24
import { unlockAllCapturaScenesController } from "../controllers/custom/unlockAllCapturaScenesController.ts";
25
25
import { removeCustomizationController } from "../controllers/custom/removeCustomizationController.ts";
26
26
import { removeIsNewController } from "../controllers/custom/removeIsNewController.ts";
27
import { retroactivelyApplyCheatController } from "../controllers/api/retroactivelyApplyCheatController.ts";
27
28
28
29
import { abilityOverrideController } from "../controllers/custom/abilityOverrideController.ts";
29
30
import { createAccountController } from "../controllers/custom/createAccountController.ts";
@@ -78,6 +79,7 @@ customRouter.get("/unlockAllShipFeatures", unlockAllShipFeaturesController);
78
79
customRouter.get("/unlockAllCapturaScenes", unlockAllCapturaScenesController);
79
80
customRouter.get("/removeCustomization", removeCustomizationController);
80
81
customRouter.get("/removeIsNew", removeIsNewController);
82
customRouter.get("/retroactivelyApplyCheat", retroactivelyApplyCheatController);
81
83
82
84
customRouter.post("/abilityOverride", abilityOverrideController);
83
85
customRouter.post("/createAccount", createAccountController);
@@ -0,0 +1,92 @@
1
import type { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel.ts";
2
import type { IAccountCheats } from "../types/inventoryTypes/inventoryTypes.ts";
3
4
interface ILockCheat {
5
projection: string;
6
isInventoryInIdealState: (inventory: TInventoryDatabaseDocument) => boolean;
7
cleanupInventory: (inventory: TInventoryDatabaseDocument) => void;
8
}
9
10
export const lockCheats: Partial<Record<keyof IAccountCheats, ILockCheat>> = {
11
alertsRepeatable: {
12
projection: "CompletedAlerts PeriodicMissionCompletions",
13
isInventoryInIdealState: (inventory: TInventoryDatabaseDocument) =>
14
!inventory.CompletedAlerts.length && !inventory.PeriodicMissionCompletions.length,
15
cleanupInventory: (inventory: TInventoryDatabaseDocument) => {
16
inventory.CompletedAlerts.splice(0);
17
inventory.PeriodicMissionCompletions.splice(0);
18
}
19
},
20
syndicateMissionsRepeatable: {
21
projection: "CompletedSyndicates",
22
isInventoryInIdealState: (inventory: TInventoryDatabaseDocument) => !inventory.CompletedSyndicates.length,
23
cleanupInventory: (inventory: TInventoryDatabaseDocument) => {
24
inventory.CompletedSyndicates.splice(0);
25
}
26
},
27
noVendorPurchaseLimits: {
28
projection: "RecentVendorPurchases UsedDailyDeals",
29
isInventoryInIdealState: (inventory: TInventoryDatabaseDocument) =>
30
!inventory.RecentVendorPurchases?.length && !inventory.UsedDailyDeals.length,
31
cleanupInventory: (inventory: TInventoryDatabaseDocument) => {
32
inventory.RecentVendorPurchases?.splice(0);
33
inventory.UsedDailyDeals.splice(0);
34
}
35
},
36
noDeathMarks: {
37
projection: "DeathMarks Harvestable DeathSquadable",
38
isInventoryInIdealState: (inventory: TInventoryDatabaseDocument) =>
39
!inventory.DeathMarks.length && !inventory.Harvestable && !inventory.DeathSquadable,
40
cleanupInventory: (inventory: TInventoryDatabaseDocument) => {
41
inventory.DeathMarks.splice(0);
42
if (inventory.Harvestable) {
43
inventory.Harvestable = false;
44
}
45
if (inventory.DeathSquadable) {
46
inventory.DeathSquadable = false;
47
}
48
}
49
},
50
51
noMasteryRankUpCooldown: {
52
projection: "TrainingDate",
53
isInventoryInIdealState: (inventory: TInventoryDatabaseDocument) =>
54
inventory.TrainingDate.getTime() > Date.now(),
55
cleanupInventory: (inventory: TInventoryDatabaseDocument) => {
56
inventory.TrainingDate = new Date();
57
}
58
},
59
noBlessingCooldown: {
60
projection: "BlessingCooldown",
61
isInventoryInIdealState: (inventory: TInventoryDatabaseDocument) =>
62
(inventory.BlessingCooldown?.getTime() ?? 0) > Date.now(),
63
cleanupInventory: (inventory: TInventoryDatabaseDocument) => {
64
inventory.BlessingCooldown = new Date();
65
}
66
},
67
noKimCooldowns: {
68
projection: "DialogueHistory",
69
isInventoryInIdealState: (inventory: TInventoryDatabaseDocument) => {
70
if (inventory.DialogueHistory?.Dialogues) {
71
for (const diag of inventory.DialogueHistory.Dialogues) {
72
if (diag.AvailableDate.getTime() > Date.now() || diag.AvailableGiftDate.getTime() > Date.now()) {
73
return false;
74
}
75
}
76
}
77
return true;
78
},
79
cleanupInventory: (inventory: TInventoryDatabaseDocument) => {
80
if (inventory.DialogueHistory?.Dialogues) {
81
for (const diag of inventory.DialogueHistory.Dialogues) {
82
if (diag.AvailableDate.getTime() > Date.now()) {
83
diag.AvailableDate = new Date();
84
}
85
if (diag.AvailableGiftDate.getTime() > Date.now()) {
86
diag.AvailableGiftDate = new Date();
87
}
88
}
89
}
90
}
91
}
92
};
@@ -832,7 +832,7 @@ export const addMissionInventoryUpdates = async (
832
832
const factionSidedWith = clientProgress.AttackerScore ? invasion.Faction : invasion.DefenderFaction;
833
833
if (invasion.Faction != "FC_INFESTATION") {
834
834
const info = factionSidedWith != "FC_GRINEER" ? grineerDeathSquadInfo : corpusDeathSquadInfo;
835
if (!inventory[info.booleanKey]) {
835
if (!inventory[info.booleanKey] && !inventory.noDeathMarks) {
836
836
const numberKey = info.numberKey;
837
837
inventory[numberKey] ??= 0;
838
838
inventory[numberKey] += clientProgress.AttackerScore + clientProgress.DefenderScore;
@@ -70,11 +70,11 @@ interface IWsMsgFromClient {
70
70
sync_inventory?: boolean;
71
71
}
72
72
73
export interface IWsMsgToClient {
74
// common
73
export interface IWsMsgToClientCommon {
75
74
wsid?: number;
75
}
76
76
77
// to webui
77
export interface IWsMsgToClientWebui {
78
78
reload?: boolean;
79
79
ports?: {
80
80
http: number | undefined;
@@ -93,13 +93,17 @@ export interface IWsMsgToClient {
93
93
update_inventory?: boolean;
94
94
logged_out?: boolean;
95
95
have_game_ws?: boolean;
96
}
96
97
97
// to game/bootstrapper (https://openwf.io/bootstrapper-manual)
98
// specific to the bootstrapper (https://openwf.io/bootstrapper-manual)
99
export interface IWsMsgToClientGame {
98
100
sync_inventory?: boolean;
99
101
sync_world_state?: boolean;
100
102
tunables?: ITunables;
101
103
}
102
104
105
export type IWsMsgToClient = IWsMsgToClientCommon | IWsMsgToClientWebui | IWsMsgToClientGame;
106
103
107
const wsOnConnect = (ws: WebSocket, req: http.IncomingMessage): void => {
104
108
if (req.url == "/custom/selftest") {
105
109
ws.send("SpaceNinjaServer");
@@ -261,7 +265,7 @@ export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void
261
265
});
262
266
};
263
267
264
export const sendWsBroadcastToGame = (accountId: string, data: IWsMsgToClient): void => {
268
export const sendWsBroadcastToGame = (accountId: string, data: IWsMsgToClientGame): void => {
265
269
const msg = JSON.stringify(data);
266
270
forEachWsClient(client => {
267
271
if (client.isGame && client.accountId == accountId) {
@@ -279,7 +283,7 @@ export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId?: string, excl
279
283
});
280
284
};
281
285
282
export const sendWsBroadcastToWebui = (data: IWsMsgToClient, accountId?: string, excludeWsid?: number): void => {
286
export const sendWsBroadcastToWebui = (data: IWsMsgToClientWebui, accountId?: string, excludeWsid?: number): void => {
283
287
const msg = JSON.stringify(data);
284
288
forEachWsClient(client => {
285
289
if (!client.isGame && (!accountId || client.accountId == accountId) && client.id != excludeWsid) {
@@ -1,8 +1,5 @@
1
1
import type { IAffiliationMods, IInventoryChanges } from "./purchaseTypes.ts";
2
2
3
export const inventoryFields = ["RawUpgrades", "MiscItems", "Consumables", "Recipes"] as const;
4
export type IInventoryFieldType = (typeof inventoryFields)[number];
5
6
3
export interface IMissionReward {
7
4
StoreItem: string;
8
5
TypeName?: string;
@@ -1598,13 +1598,7 @@
1598
1598
<ul>
1599
1599
<li><a href="#" onclick="event.preventDefault();setImportSample('maxFocus');" data-loc="import_samples_maxFocus"></a></li>
1600
1600
<li><a href="#" onclick="event.preventDefault();setImportSample('accolades');" data-loc="import_samples_accolades"></a></li>
1601
<li><a href="#" onclick="event.preventDefault();setImportSample('removeAlertCompletions');" data-loc="import_samples_removeAlertCompletions"></a></li>
1602
<li><a href="#" onclick="event.preventDefault();setImportSample('removeSyndicateMissionCompletions');" data-loc="import_samples_removeSyndicateMissionCompletions"></a></li>
1603
<li><a href="#" onclick="event.preventDefault();setImportSample('removeMasteryRankUpCooldown');" data-loc="import_samples_removeMasteryRankUpCooldown"></a></li>
1604
<li><a href="#" onclick="event.preventDefault();setImportSample('removeVendorPurchaseLimits');" data-loc="import_samples_removeVendorPurchaseLimits"></a></li>
1605
<li><a href="#" onclick="event.preventDefault();setImportSample('removeDeathMarks');" data-loc="import_samples_removeDeathMarks"></a></li>
1606
<li><a href="#" onclick="event.preventDefault();setImportSample('removeBlessingCooldown');" data-loc="import_samples_removeBlessingCooldown"></a></li>
1607
<li><a href="#" onclick="event.preventDefault();setImportSample('maxStratos');" data-loc="import_samples_maxStratos"></a></li>
1601
<li><a href="#" onclick="event.preventDefault();setImportSample('maxStratos');" data-loc="import_samples_maxStratos"></a></li>
1608
1602
</ul>
1609
1603
</div>
1610
1604
</div>
@@ -3368,8 +3368,13 @@ document.querySelectorAll("#account-cheats input[type=checkbox]").forEach(elm =>
3368
3368
key: elm.id,
3369
3369
value: value
3370
3370
})
3371
}).done(() => {
3371
}).done(res => {
3372
3372
elm.checked = value;
3373
if (res == "retroactivable") {
3374
if (window.confirm(loc("cheats_retroactivePrompt"))) {
3375
$.get("/custom/retroactivelyApplyCheat?" + window.authz + "&cheat=" + elm.id);
3376
}
3377
}
3373
3378
});
3374
3379
});
3375
3380
};
@@ -4122,36 +4127,6 @@ const importSamples = {
4122
4127
},
4123
4128
Counselor: true
4124
4129
},
4125
removeAlertCompletions: {
4126
CompletedAlerts: [],
4127
PeriodicMissionCompletions: []
4128
},
4129
removeSyndicateMissionCompletions: {
4130
CompletedSyndicates: []
4131
},
4132
removeMasteryRankUpCooldown: {
4133
TrainingDate: {
4134
$date: {
4135
$numberLong: "0"
4136
}
4137
}
4138
},
4139
removeVendorPurchaseLimits: {
4140
RecentVendorPurchases: [],
4141
UsedDailyDeals: []
4142
},
4143
removeDeathMarks: {
4144
DeathMarks: [],
4145
Harvestable: false,
4146
DeathSquadable: false
4147
},
4148
removeBlessingCooldown: {
4149
BlessingCooldown: {
4150
$date: {
4151
$numberLong: "0"
4152
}
4153
}
4154
},
4155
4130
maxStratos: {
4156
4131
BountyScore: 39
4157
4132
}
@@ -274,6 +274,7 @@ dict = {
274
274
cheats_nightwaveStandingMultiplier: `Nightwave Ansehen Multiplikator`,
275
275
cheats_save: `Speichern`,
276
276
cheats_account: `Account`,
277
cheats_retroactivePrompt: `[UNTRANSLATED] Would you like to apply this cheat retroactively?`,
277
278
cheats_unlockAllFocusSchools: `Alle Fokus-Schulen freischalten`,
278
279
cheats_helminthUnlockAll: `Helminth vollständig aufleveln`,
279
280
cheats_addMissingSubsumedAbilities: `Fehlende konsumierte Fähigkeiten hinzufügen`,
@@ -379,12 +380,6 @@ dict = {
379
380
import_samples: `Beispiele:`,
380
381
import_samples_maxFocus: `Alle Fokus-Schulen maximiert`,
381
382
import_samples_accolades: `Auszeichnungen & Council Chat Zugang`,
382
import_samples_removeAlertCompletions: `Entferne abgeschlossene Alarmierungen`,
383
import_samples_removeSyndicateMissionCompletions: `Entferne abgeschlossene Syndikat-Missionen`,
384
import_samples_removeMasteryRankUpCooldown: `Entferne Wartezeit für Meisterschaftsrangaufstieg`,
385
import_samples_removeVendorPurchaseLimits: `Entferne Händler-Kaufbeschränkungen`,
386
import_samples_removeDeathMarks: `Entferne Todesmarkierungen`,
387
import_samples_removeBlessingCooldown: `Entferne Wartezeit für Gaben`,
388
383
import_samples_maxStratos: `Max. Rang-Symbol für Stratos Emblem`,
389
384
390
385
upgrade_Equilibrium: `+|VAL|% Energie bei Gesundheitskugeln, +|VAL|% Gesundheit bei Energiekugeln`,
@@ -273,6 +273,7 @@ dict = {
273
273
cheats_nightwaveStandingMultiplier: `Nightwave Standing Multiplier`,
274
274
cheats_save: `Save`,
275
275
cheats_account: `Account`,
276
cheats_retroactivePrompt: `Would you like to apply this cheat retroactively?`,
276
277
cheats_unlockAllFocusSchools: `Unlock All Focus Schools`,
277
278
cheats_helminthUnlockAll: `Fully Level Up Helminth`,
278
279
cheats_addMissingSubsumedAbilities: `Add Missing Subsumed Abilities`,
@@ -378,12 +379,6 @@ dict = {
378
379
import_samples: `Samples:`,
379
380
import_samples_maxFocus: `All Focus Schools Maxed Out`,
380
381
import_samples_accolades: `Accolades & Council Chat Access`,
381
import_samples_removeAlertCompletions: `Remove Alert Completions`,
382
import_samples_removeSyndicateMissionCompletions: `Remove Syndicate Mission Completions`,
383
import_samples_removeMasteryRankUpCooldown: `Remove Mastery Rank Up Cooldown`,
384
import_samples_removeVendorPurchaseLimits: `Remove Vendor Purchase Limits`,
385
import_samples_removeDeathMarks: `Remove Death Marks`,
386
import_samples_removeBlessingCooldown: `Remove Blessing Cooldown`,
387
382
import_samples_maxStratos: `Max Rank Stratos Emblem Icon`,
388
383
389
384
upgrade_Equilibrium: `+|VAL|% Energy from Health pickups, +|VAL|% Health from Energy pickups`,
@@ -274,6 +274,7 @@ dict = {
274
274
cheats_nightwaveStandingMultiplier: `Multiplicador de Reputación de Onda Nocturna`,
275
275
cheats_save: `Guardar`,
276
276
cheats_account: `Cuenta`,
277
cheats_retroactivePrompt: `[UNTRANSLATED] Would you like to apply this cheat retroactively?`,
277
278
cheats_unlockAllFocusSchools: `Desbloquear todas las escuelas de enfoque`,
278
279
cheats_helminthUnlockAll: `Subir al máximo el Helminto`,
279
280
cheats_addMissingSubsumedAbilities: `Agregar habilidades subsumidas faltantes`,
@@ -379,12 +380,6 @@ dict = {
379
380
import_samples: `Muestras:`,
380
381
import_samples_maxFocus: `Todas las escuelas de enfoque al máximo`,
381
382
import_samples_accolades: `[UNTRANSLATED] Accolades & Council Chat Access`,
382
import_samples_removeAlertCompletions: `[UNTRANSLATED] Remove Alert Completions`,
383
import_samples_removeSyndicateMissionCompletions: `[UNTRANSLATED] Remove Syndicate Mission Completions`,
384
import_samples_removeMasteryRankUpCooldown: `[UNTRANSLATED] Remove Mastery Rank Up Cooldown`,
385
import_samples_removeVendorPurchaseLimits: `[UNTRANSLATED] Remove Vendor Purchase Limits`,
386
import_samples_removeDeathMarks: `[UNTRANSLATED] Remove Death Marks`,
387
import_samples_removeBlessingCooldown: `[UNTRANSLATED] Remove Blessing Cooldown`,
388
383
import_samples_maxStratos: `[UNTRANSLATED] Max Rank Stratos Emblem Icon`,
389
384
390
385
upgrade_Equilibrium: `+|VAL|% de Energía al recoger salud, +|VAL|% de Salud al recoger energía`,