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(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
Sainan <63328889+Sainan@users.noreply.github.com>
提交于

代码差异

16 个文件 +150 -93
Added src/controllers/api/retroactivelyApplyCheatController.ts +23 -0
@@ -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 };
Modified src/controllers/custom/setAccountCheatController.ts +8 -3
@@ -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) {
Modified src/routes/custom.ts +2 -0
@@ -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);
Added src/services/cheatsService.ts +92 -0
@@ -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 };
Modified src/services/missionInventoryUpdateService.ts +1 -1
@@ -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;
Modified src/services/wsService.ts +10 -6
@@ -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) {
Modified src/types/missionTypes.ts +0 -3
@@ -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;
Modified static/webui/index.html +1 -7
@@ -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>
Modified static/webui/script.js +6 -31
@@ -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 }
Modified static/webui/translations/de.js +1 -6
@@ -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`,
Modified static/webui/translations/en.js +1 -6
@@ -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`,
Modified static/webui/translations/es.js +1 -6
@@ -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`,
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