merge upstream

This commit is contained in:
Animan8000 2025-06-13 02:27:28 -07:00
commit 0ea67b7f57
22 changed files with 416 additions and 117 deletions

View File

@ -14,7 +14,8 @@ SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [confi
- `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`. - `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`.
- `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`. - `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`.
- `worldState.lockTime` will lock the time provided in worldState if nonzero, e.g. `1743202800` for night in POE. - `worldState.eidolonOverride` can be set to `day` or `night` to lock the time to day/fass and night/vome on Plains of Eidolon/Cambion Drift.
- `worldState.vallisOverride` can be set to `warm` or `cold` to lock the temperature on Orb Vallis.
- `worldState.nightwaveOverride` will lock the nightwave season, assuming the client is new enough for it. Valid values: - `worldState.nightwaveOverride` will lock the nightwave season, assuming the client is new enough for it. Valid values:
- `RadioLegionIntermission13Syndicate` for Nora's Mix Vol. 9 - `RadioLegionIntermission13Syndicate` for Nora's Mix Vol. 9
- `RadioLegionIntermission12Syndicate` for Nora's Mix Vol. 8 - `RadioLegionIntermission12Syndicate` for Nora's Mix Vol. 8

View File

@ -55,7 +55,8 @@
"affinityBoost": false, "affinityBoost": false,
"resourceBoost": false, "resourceBoost": false,
"starDays": true, "starDays": true,
"lockTime": 0, "eidolonOverride": "",
"vallisOverride": "",
"nightwaveOverride": "" "nightwaveOverride": ""
} }
} }

8
package-lock.json generated
View File

@ -18,7 +18,7 @@
"morgan": "^1.10.0", "morgan": "^1.10.0",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"typescript": "^5.5", "typescript": "^5.5",
"warframe-public-export-plus": "^0.5.65", "warframe-public-export-plus": "^0.5.66",
"warframe-riven-info": "^0.1.2", "warframe-riven-info": "^0.1.2",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0" "winston-daily-rotate-file": "^5.0.0"
@ -3814,9 +3814,9 @@
} }
}, },
"node_modules/warframe-public-export-plus": { "node_modules/warframe-public-export-plus": {
"version": "0.5.65", "version": "0.5.66",
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.65.tgz", "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.66.tgz",
"integrity": "sha512-y/HN61lE5g8gx0Giutdl/jzQnQmw1u2uI0BiwKVW341nf42sKWQPsKsCVTL5x9MIDYyRCbFsMU+PazKC7byMdg==" "integrity": "sha512-AU7XQA96OfYrLm2RioCwDjjdI3IrsmUiqebXyE+bpM0iST+4x/NHu8LTRT4Oygfo/2OBtDYhib7G6re0EeAe5g=="
}, },
"node_modules/warframe-riven-info": { "node_modules/warframe-riven-info": {
"version": "0.1.2", "version": "0.1.2",

View File

@ -25,7 +25,7 @@
"morgan": "^1.10.0", "morgan": "^1.10.0",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"typescript": "^5.5", "typescript": "^5.5",
"warframe-public-export-plus": "^0.5.65", "warframe-public-export-plus": "^0.5.66",
"warframe-riven-info": "^0.1.2", "warframe-riven-info": "^0.1.2",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0" "winston-daily-rotate-file": "^5.0.0"

View File

@ -118,7 +118,7 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
break; break;
} }
} }
pet.Details!.Status = canSetActive ? Status.StatusAvailable : Status.StatusIncubating; pet.Details!.Status = canSetActive ? Status.StatusAvailable : Status.StatusStasis;
} else if (recipe.secretIngredientAction != "SIA_UNBRAND") { } else if (recipe.secretIngredientAction != "SIA_UNBRAND") {
InventoryChanges = { InventoryChanges = {
...InventoryChanges, ...InventoryChanges,

View File

@ -3,7 +3,7 @@ import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { ExportSyndicates, ISyndicateSacrifice } from "warframe-public-export-plus"; import { ExportSyndicates, ISyndicateSacrifice } from "warframe-public-export-plus";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService"; import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { addMiscItems, combineInventoryChanges, getInventory, updateCurrency } from "@/src/services/inventoryService"; import { addMiscItem, combineInventoryChanges, getInventory, updateCurrency } from "@/src/services/inventoryService";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { toStoreItem } from "@/src/services/itemDataService"; import { toStoreItem } from "@/src/services/itemDataService";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
@ -18,80 +18,83 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp
syndicate = inventory.Affiliations[inventory.Affiliations.push({ Tag: data.AffiliationTag, Standing: 0 }) - 1]; syndicate = inventory.Affiliations[inventory.Affiliations.push({ Tag: data.AffiliationTag, Standing: 0 }) - 1];
} }
const level = data.SacrificeLevel - (syndicate.Title ?? 0); const oldLevel = syndicate.Title ?? 0;
const levelIncrease = data.SacrificeLevel - oldLevel;
if (levelIncrease < 0) {
throw new Error(`syndicate sacrifice can not decrease level`);
}
if (levelIncrease > 1 && !data.AllowMultiple) {
throw new Error(`desired syndicate level is an increase of ${levelIncrease}, max. allowed increase is 1`);
}
const res: ISyndicateSacrificeResponse = { const res: ISyndicateSacrificeResponse = {
AffiliationTag: data.AffiliationTag, AffiliationTag: data.AffiliationTag,
InventoryChanges: {}, InventoryChanges: {},
Level: data.SacrificeLevel, Level: data.SacrificeLevel,
LevelIncrease: level <= 0 ? 1 : level, LevelIncrease: levelIncrease,
NewEpisodeReward: false NewEpisodeReward: false
}; };
// Process sacrifices and rewards for every level we're reaching
const manifest = ExportSyndicates[data.AffiliationTag]; const manifest = ExportSyndicates[data.AffiliationTag];
let sacrifice: ISyndicateSacrifice | undefined; for (let level = oldLevel + levelIncrease; level <= data.SacrificeLevel; ++level) {
let reward: string | undefined; let sacrifice: ISyndicateSacrifice | undefined;
if (data.SacrificeLevel == 0) { if (level == 0) {
sacrifice = manifest.initiationSacrifice; sacrifice = manifest.initiationSacrifice;
reward = manifest.initiationReward; if (manifest.initiationReward) {
syndicate.Initiated = true; combineInventoryChanges(
} else { res.InventoryChanges,
sacrifice = manifest.titles?.find(x => x.level == data.SacrificeLevel)?.sacrifice; (await handleStoreItemAcquisition(manifest.initiationReward, inventory)).InventoryChanges
} );
if (sacrifice) {
res.InventoryChanges = { ...updateCurrency(inventory, sacrifice.credits, false) };
const miscItemChanges = sacrifice.items.map(x => ({
ItemType: x.ItemType,
ItemCount: x.ItemCount * -1
}));
addMiscItems(inventory, miscItemChanges);
res.InventoryChanges.MiscItems = miscItemChanges;
}
syndicate.Title ??= 0;
syndicate.Title += 1;
if (reward) {
combineInventoryChanges(
res.InventoryChanges,
(await handleStoreItemAcquisition(reward, inventory)).InventoryChanges
);
}
// Quacks like a nightwave syndicate?
if (manifest.dailyChallenges) {
const title = manifest.titles!.find(x => x.level == syndicate.Title);
if (title) {
res.NewEpisodeReward = true;
let rewardType: string;
let rewardCount: number;
if (title.storeItemReward) {
rewardType = title.storeItemReward;
rewardCount = 1;
} else {
rewardType = toStoreItem(title.reward!.ItemType);
rewardCount = title.reward!.ItemCount;
} }
const rewardInventoryChanges = (await handleStoreItemAcquisition(rewardType, inventory, rewardCount)) syndicate.Initiated = true;
.InventoryChanges; } else {
if (Object.keys(rewardInventoryChanges).length == 0) { sacrifice = manifest.titles?.find(x => x.level == level)?.sacrifice;
logger.debug(`nightwave rank up reward did not seem to get added, giving 50 creds instead`);
const nightwaveCredsItemType = manifest.titles![0].reward!.ItemType;
rewardInventoryChanges.MiscItems = [{ ItemType: nightwaveCredsItemType, ItemCount: 50 }];
addMiscItems(inventory, rewardInventoryChanges.MiscItems);
}
combineInventoryChanges(res.InventoryChanges, rewardInventoryChanges);
} }
} else {
if (syndicate.Title > 0 && manifest.favours.find(x => x.rankUpReward && x.requiredLevel == syndicate.Title)) { if (sacrifice) {
syndicate.FreeFavorsEarned ??= []; updateCurrency(inventory, sacrifice.credits, false, res.InventoryChanges);
if (!syndicate.FreeFavorsEarned.includes(syndicate.Title)) {
syndicate.FreeFavorsEarned.push(syndicate.Title); for (const item of sacrifice.items) {
addMiscItem(inventory, item.ItemType, item.ItemCount * -1, res.InventoryChanges);
}
}
// Quacks like a nightwave syndicate?
if (manifest.dailyChallenges) {
const title = manifest.titles!.find(x => x.level == level);
if (title) {
res.NewEpisodeReward = true;
let rewardType: string;
let rewardCount: number;
if (title.storeItemReward) {
rewardType = title.storeItemReward;
rewardCount = 1;
} else {
rewardType = toStoreItem(title.reward!.ItemType);
rewardCount = title.reward!.ItemCount;
}
const rewardInventoryChanges = (await handleStoreItemAcquisition(rewardType, inventory, rewardCount))
.InventoryChanges;
if (Object.keys(rewardInventoryChanges).length == 0) {
logger.debug(`nightwave rank up reward did not seem to get added, giving 50 creds instead`);
const nightwaveCredsItemType = manifest.titles![0].reward!.ItemType;
addMiscItem(inventory, nightwaveCredsItemType, 50, rewardInventoryChanges);
}
combineInventoryChanges(res.InventoryChanges, rewardInventoryChanges);
}
} else {
if (level > 0 && manifest.favours.find(x => x.rankUpReward && x.requiredLevel == level)) {
syndicate.FreeFavorsEarned ??= [];
if (!syndicate.FreeFavorsEarned.includes(level)) {
syndicate.FreeFavorsEarned.push(level);
}
} }
} }
} }
// Commit
syndicate.Title = data.SacrificeLevel;
await inventory.save(); await inventory.save();
response.json(res); response.json(res);

View File

@ -35,6 +35,17 @@ const trainingResultController: RequestHandler = async (req, res): Promise<void>
inventory.PlayerLevel += 1; inventory.PlayerLevel += 1;
inventory.TradesRemaining += 1; inventory.TradesRemaining += 1;
if (inventory.PlayerLevel == 2) {
await createMessage(accountId, [
{
sndr: "/Lotus/Language/Game/Maroo",
msg: "/Lotus/Language/Clan/MarooClanSearchDesc",
sub: "/Lotus/Language/Clan/MarooClanSearchTitle",
icon: "/Lotus/Interface/Icons/Npcs/Maroo.png"
}
]);
}
await createMessage(accountId, [ await createMessage(accountId, [
{ {
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender", sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",

View File

@ -3,6 +3,7 @@ import { getDict, getItemName, getString } from "@/src/services/itemDataService"
import { import {
ExportArcanes, ExportArcanes,
ExportAvionics, ExportAvionics,
ExportBoosters,
ExportCustoms, ExportCustoms,
ExportDrones, ExportDrones,
ExportGear, ExportGear,
@ -55,6 +56,7 @@ interface ItemLists {
KubrowPets: ListedItem[]; KubrowPets: ListedItem[];
EvolutionProgress: ListedItem[]; EvolutionProgress: ListedItem[];
mods: ListedItem[]; mods: ListedItem[];
Boosters: ListedItem[];
} }
const relicQualitySuffixes: Record<TRelicQuality, string> = { const relicQualitySuffixes: Record<TRelicQuality, string> = {
@ -86,7 +88,8 @@ const getItemListsController: RequestHandler = (req, response) => {
QuestKeys: [], QuestKeys: [],
KubrowPets: [], KubrowPets: [],
EvolutionProgress: [], EvolutionProgress: [],
mods: [] mods: [],
Boosters: []
}; };
for (const [uniqueName, item] of Object.entries(ExportWarframes)) { for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
res[item.productCategory].push({ res[item.productCategory].push({
@ -296,6 +299,13 @@ const getItemListsController: RequestHandler = (req, response) => {
}); });
} }
for (const item of Object.values(ExportBoosters)) {
res.Boosters.push({
uniqueName: item.typeName,
name: getString(item.name, lang)
});
}
response.json(res); response.json(res);
}; };

View File

@ -0,0 +1,45 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService";
import { RequestHandler } from "express";
import { ExportBoosters } from "warframe-public-export-plus";
const I32_MAX = 0x7fffffff;
export const setBoosterController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const requests = req.body as { ItemType: string; ExpiryDate: number }[];
const inventory = await getInventory(accountId, "Boosters");
const boosters = inventory.Boosters;
if (
requests.some(request => {
if (typeof request.ItemType !== "string") return true;
if (Object.entries(ExportBoosters).find(([_, item]) => item.typeName === request.ItemType) === undefined)
return true;
if (typeof request.ExpiryDate !== "number") return true;
if (request.ExpiryDate < 0 || request.ExpiryDate > I32_MAX) return true;
return false;
})
) {
res.status(400).send("Invalid ItemType provided.");
return;
}
const now = Math.floor(Date.now() / 1000);
for (const { ItemType, ExpiryDate } of requests) {
if (ExpiryDate < now) {
// remove expired boosters
const index = boosters.findIndex(item => item.ItemType === ItemType);
if (index !== -1) {
boosters.splice(index, 1);
}
} else {
const boosterItem = boosters.find(item => item.ItemType === ItemType);
if (boosterItem) {
boosterItem.ExpiryDate = ExpiryDate;
} else {
boosters.push({ ItemType, ExpiryDate });
}
}
}
await inventory.save();
res.end();
};

View File

@ -23,6 +23,7 @@ import { setEvolutionProgressController } from "@/src/controllers/custom/setEvol
import { getConfigDataController } from "@/src/controllers/custom/getConfigDataController"; import { getConfigDataController } from "@/src/controllers/custom/getConfigDataController";
import { updateConfigDataController } from "@/src/controllers/custom/updateConfigDataController"; import { updateConfigDataController } from "@/src/controllers/custom/updateConfigDataController";
import { setBoosterController } from "../controllers/custom/setBoosterController";
const customRouter = express.Router(); const customRouter = express.Router();
@ -46,6 +47,7 @@ customRouter.post("/addXp", addXpController);
customRouter.post("/import", importController); customRouter.post("/import", importController);
customRouter.post("/manageQuests", manageQuestsController); customRouter.post("/manageQuests", manageQuestsController);
customRouter.post("/setEvolutionProgress", setEvolutionProgressController); customRouter.post("/setEvolutionProgress", setEvolutionProgressController);
customRouter.post("/setBooster", setBoosterController);
customRouter.get("/config", getConfigDataController); customRouter.get("/config", getConfigDataController);
customRouter.post("/config", updateConfigDataController); customRouter.post("/config", updateConfigDataController);

View File

@ -61,9 +61,11 @@ interface IConfig {
affinityBoost?: boolean; affinityBoost?: boolean;
resourceBoost?: boolean; resourceBoost?: boolean;
starDays?: boolean; starDays?: boolean;
lockTime?: number; eidolonOverride?: string;
vallisOverride?: string;
nightwaveOverride?: string; nightwaveOverride?: string;
}; };
nightwaveStandingMultiplier?: number;
} }
export const configPath = path.join(repoDir, "config.json"); export const configPath = path.join(repoDir, "config.json");

View File

@ -1525,7 +1525,8 @@ export const applyClientEquipmentUpdates = (
gearArray.forEach(({ ItemId, XP, InfestationDate }) => { gearArray.forEach(({ ItemId, XP, InfestationDate }) => {
const item = category.id(fromOid(ItemId)); const item = category.id(fromOid(ItemId));
if (!item) { if (!item) {
throw new Error(`No item with id ${fromOid(ItemId)} in ${categoryName}`); logger.warn(`Skipping unknown ${categoryName} item: id ${fromOid(ItemId)} not found`);
return;
} }
if (XP) { if (XP) {
@ -1772,13 +1773,14 @@ export const addChallenges = (
}) - 1 }) - 1
]; ];
} }
affiliation.Standing += meta.standing!;
const standingToAdd = meta.standing! * (config.nightwaveStandingMultiplier ?? 1);
affiliation.Standing += standingToAdd;
if (affiliationMods.length == 0) { if (affiliationMods.length == 0) {
affiliationMods.push({ Tag: nightwaveSyndicateTag }); affiliationMods.push({ Tag: nightwaveSyndicateTag });
} }
affiliationMods[0].Standing ??= 0; affiliationMods[0].Standing ??= 0;
affiliationMods[0].Standing += meta.standing!; affiliationMods[0].Standing += standingToAdd;
} }
} }
} }

View File

@ -233,7 +233,7 @@ export const isStoreItem = (type: string): boolean => {
}; };
export const toStoreItem = (type: string): string => { export const toStoreItem = (type: string): string => {
if (type.startsWith("/Lotus/Types/StoreItems/Boosters/")) { if (type.startsWith("/Lotus/Types/Boosters/")) {
const boosterEntry = Object.entries(ExportBoosters).find(arr => arr[1].typeName == type); const boosterEntry = Object.entries(ExportBoosters).find(arr => arr[1].typeName == type);
if (boosterEntry) { if (boosterEntry) {
return boosterEntry[0]; return boosterEntry[0];

View File

@ -19,6 +19,7 @@ import {
IWorldState IWorldState
} from "../types/worldStateTypes"; } from "../types/worldStateTypes";
import { version_compare } from "../helpers/inventoryHelpers"; import { version_compare } from "../helpers/inventoryHelpers";
import { logger } from "../utils/logger";
const sortieBosses = [ const sortieBosses = [
"SORTIE_BOSS_HYENA", "SORTIE_BOSS_HYENA",
@ -938,8 +939,61 @@ const getCalendarSeason = (week: number): ICalendarSeason => {
}; };
}; };
const doesTimeSatsifyConstraints = (timeSecs: number): boolean => {
if (config.worldState?.eidolonOverride) {
const eidolonEpoch = 1391992660;
const eidolonCycle = Math.trunc((timeSecs - eidolonEpoch) / 9000);
const eidolonCycleStart = eidolonEpoch + eidolonCycle * 9000;
const eidolonCycleEnd = eidolonCycleStart + 9000;
const eidolonCycleNightStart = eidolonCycleEnd - 3000;
if (config.worldState.eidolonOverride == "day") {
if (
//timeSecs < eidolonCycleStart ||
isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, eidolonCycleNightStart * 1000)
) {
return false;
}
} else {
if (
timeSecs < eidolonCycleNightStart ||
isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, eidolonCycleEnd * 1000)
) {
return false;
}
}
}
if (config.worldState?.vallisOverride) {
const vallisEpoch = 1541837628;
const vallisCycle = Math.trunc((timeSecs - vallisEpoch) / 1600);
const vallisCycleStart = vallisEpoch + vallisCycle * 1600;
const vallisCycleEnd = vallisCycleStart + 1600;
const vallisCycleColdStart = vallisCycleStart + 400;
if (config.worldState.vallisOverride == "cold") {
if (
timeSecs < vallisCycleColdStart ||
isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, vallisCycleEnd * 1000)
) {
return false;
}
} else {
if (
//timeSecs < vallisCycleStart ||
isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, vallisCycleColdStart * 1000)
) {
return false;
}
}
}
return true;
};
export const getWorldState = (buildLabel?: string): IWorldState => { export const getWorldState = (buildLabel?: string): IWorldState => {
const timeSecs = config.worldState?.lockTime || Math.round(Date.now() / 1000); let timeSecs = Math.round(Date.now() / 1000);
while (!doesTimeSatsifyConstraints(timeSecs)) {
timeSecs -= 60;
}
const timeMs = timeSecs * 1000; const timeMs = timeSecs * 1000;
const day = Math.trunc((timeMs - EPOCH) / 86400000); const day = Math.trunc((timeMs - EPOCH) / 86400000);
const week = Math.trunc(day / 7); const week = Math.trunc(day / 7);
@ -1275,7 +1329,13 @@ export const isArchwingMission = (node: IRegion): boolean => {
export const getNightwaveSyndicateTag = (buildLabel: string | undefined): string | undefined => { export const getNightwaveSyndicateTag = (buildLabel: string | undefined): string | undefined => {
if (config.worldState?.nightwaveOverride) { if (config.worldState?.nightwaveOverride) {
return config.worldState.nightwaveOverride; if (config.worldState.nightwaveOverride in nightwaveTagToSeason) {
return config.worldState.nightwaveOverride;
}
logger.warn(`ignoring invalid config value for worldState.nightwaveOverride`, {
value: config.worldState.nightwaveOverride,
valid_values: Object.keys(nightwaveTagToSeason)
});
} }
if (!buildLabel || version_compare(buildLabel, "2025.05.20.10.18") >= 0) { if (!buildLabel || version_compare(buildLabel, "2025.05.20.10.18") >= 0) {
return "RadioLegionIntermission13Syndicate"; return "RadioLegionIntermission13Syndicate";

View File

@ -416,6 +416,20 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_Boosters"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireBoosters();return false;">
<input class="form-control" id="acquire-type-Boosters" list="datalist-Boosters" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<table class="table table-hover w-100">
<tbody id="Boosters-list"></tbody>
</table>
</div>
</div>
</div>
</div> </div>
<div class="card mb-3"> <div class="card mb-3">
<h5 class="card-header" data-loc="general_bulkActions"></h5> <h5 class="card-header" data-loc="general_bulkActions"></h5>
@ -722,6 +736,10 @@
<label class="form-label" for="spoofMasteryRank" data-loc="cheats_spoofMasteryRank"></label> <label class="form-label" for="spoofMasteryRank" data-loc="cheats_spoofMasteryRank"></label>
<input class="form-control" id="spoofMasteryRank" type="number" min="-1" max="65535" /> <input class="form-control" id="spoofMasteryRank" type="number" min="-1" max="65535" />
</div> </div>
<div class="form-group mt-2">
<label class="form-label" for="nightwaveStandingMultiplier" data-loc="cheats_nightwaveStandingMultiplier"></label>
<input class="form-control" id="nightwaveStandingMultiplier" type="number" min="1" max="1000000" value="1" />
</div>
<button class="btn btn-primary mt-3" type="submit" data-loc="cheats_saveSettings"></button> <button class="btn btn-primary mt-3" type="submit" data-loc="cheats_saveSettings"></button>
</form> </form>
</div> </div>
@ -804,6 +822,7 @@
<datalist id="datalist-ModularParts-CATBROW_MUTAGEN"></datalist> <datalist id="datalist-ModularParts-CATBROW_MUTAGEN"></datalist>
<datalist id="datalist-ModularParts-KUBROW_ANTIGEN"></datalist> <datalist id="datalist-ModularParts-KUBROW_ANTIGEN"></datalist>
<datalist id="datalist-ModularParts-KUBROW_MUTAGEN"></datalist> <datalist id="datalist-ModularParts-KUBROW_MUTAGEN"></datalist>
<datalist id="datalist-Boosters"></datalist>
<script src="/webui/libs/jquery-3.6.0.min.js"></script> <script src="/webui/libs/jquery-3.6.0.min.js"></script>
<script src="/webui/libs/whirlpool-js.min.js"></script> <script src="/webui/libs/whirlpool-js.min.js"></script>
<script src="/webui/libs/single.js"></script> <script src="/webui/libs/single.js"></script>

View File

@ -1011,6 +1011,63 @@ function updateInventory() {
} }
} }
document.getElementById("changeSyndicate").value = data.SupportedSyndicate ?? ""; document.getElementById("changeSyndicate").value = data.SupportedSyndicate ?? "";
document.getElementById("Boosters-list").innerHTML = "";
const now = Math.floor(Date.now() / 1000);
data.Boosters.forEach(({ ItemType, ExpiryDate }) => {
if (ExpiryDate < now) {
// Booster has expired, skip it
return;
}
const tr = document.createElement("tr");
{
const td = document.createElement("td");
td.textContent = itemMap[ItemType]?.name ?? ItemType;
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
const timeString = formatDatetime("%Y-%m-%d %H:%M:%s", ExpiryDate * 1000);
const inlineForm = document.createElement("form");
const input = document.createElement("input");
inlineForm.style.display = "inline-block";
inlineForm.onsubmit = function (event) {
event.preventDefault();
doChangeBoosterExpiry(ItemType, input);
};
input.type = "datetime-local";
input.classList.add("form-control");
input.classList.add("form-control-sm");
input.value = timeString;
let changed = false;
input.onchange = function () {
changed = true;
};
input.onblur = function () {
if (changed) {
doChangeBoosterExpiry(ItemType, input);
}
};
inlineForm.appendChild(input);
td.appendChild(inlineForm);
const removeButton = document.createElement("a");
removeButton.title = loc("code_remove");
removeButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/></svg>`;
removeButton.href = "#";
removeButton.onclick = function (event) {
event.preventDefault();
setBooster(ItemType, 0);
};
td.appendChild(removeButton);
tr.appendChild(td);
}
document.getElementById("Boosters-list").appendChild(tr);
});
}); });
}); });
} }
@ -2027,3 +2084,77 @@ function handleModularSelection(category) {
}); });
}); });
} }
function setBooster(ItemType, ExpiryDate, callback) {
revalidateAuthz(() => {
$.post({
url: "/custom/setBooster?" + window.authz,
contentType: "application/json",
data: JSON.stringify([
{
ItemType,
ExpiryDate
}
])
}).done(function () {
updateInventory();
if (callback) callback();
});
});
}
function doAcquireBoosters() {
const uniqueName = getKey(document.getElementById("acquire-type-Boosters"));
if (!uniqueName) {
$("#acquire-type-Boosters").addClass("is-invalid").focus();
return;
}
const ExpiryDate = Date.now() / 1000 + 3 * 24 * 60 * 60; // default 3 days
setBooster(uniqueName, ExpiryDate, () => {
$("#acquire-type-Boosters").val("");
updateInventory();
});
}
function doChangeBoosterExpiry(ItemType, ExpiryDateInput) {
console.log("Changing booster expiry for", ItemType, "to", ExpiryDateInput.value);
// cast local datetime string to unix timestamp
const ExpiryDate = new Date(ExpiryDateInput.value).getTime() / 1000;
if (isNaN(ExpiryDate)) {
ExpiryDateInput.addClass("is-invalid").focus();
return false;
}
setBooster(ItemType, ExpiryDate);
return true;
}
function formatDatetime(fmt, date) {
if (typeof date === "number") date = new Date(date);
return fmt.replace(/(%[yY]|%m|%[Dd]|%H|%h|%M|%[Ss]|%[Pp])/g, match => {
switch (match) {
case "%Y":
return date.getFullYear().toString();
case "%y":
return date.getFullYear().toString().slice(-2);
case "%m":
return (date.getMonth() + 1).toString().padStart(2, "0");
case "%D":
case "%d":
return date.getDate().toString().padStart(2, "0");
case "%H":
return date.getHours().toString().padStart(2, "0");
case "%h":
return (date.getHours() % 12).toString().padStart(2, "0");
case "%M":
return date.getMinutes().toString().padStart(2, "0");
case "%S":
case "%s":
return date.getSeconds().toString().padStart(2, "0");
case "%P":
case "%p":
return date.getHours() < 12 ? "am" : "pm";
default:
return match;
}
});
}

View File

@ -98,6 +98,7 @@ dict = {
inventory_bulkRankUpSentinels: `Alle Wächter auf Max. Rang`, inventory_bulkRankUpSentinels: `Alle Wächter auf Max. Rang`,
inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`, inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`,
inventory_bulkRankUpEvolutionProgress: `Alle Incarnon-Entwicklungsfortschritte auf Max. Rang`, inventory_bulkRankUpEvolutionProgress: `Alle Incarnon-Entwicklungsfortschritte auf Max. Rang`,
inventory_Boosters: `[UNTRANSLATED] Boosters`,
quests_list: `Quests`, quests_list: `Quests`,
quests_completeAll: `Alle Quests abschließen`, quests_completeAll: `Alle Quests abschließen`,
@ -163,6 +164,7 @@ dict = {
cheats_noDojoResearchTime: `Keine Dojo-Forschungszeit`, cheats_noDojoResearchTime: `Keine Dojo-Forschungszeit`,
cheats_fastClanAscension: `Schneller Clan-Aufstieg`, cheats_fastClanAscension: `Schneller Clan-Aufstieg`,
cheats_spoofMasteryRank: `Gefälschter Meisterschaftsrang (-1 zum deaktivieren)`, cheats_spoofMasteryRank: `Gefälschter Meisterschaftsrang (-1 zum deaktivieren)`,
cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`,
cheats_saveSettings: `Einstellungen speichern`, cheats_saveSettings: `Einstellungen speichern`,
cheats_account: `Account`, cheats_account: `Account`,
cheats_unlockAllFocusSchools: `Alle Fokus-Schulen freischalten`, cheats_unlockAllFocusSchools: `Alle Fokus-Schulen freischalten`,

View File

@ -97,6 +97,7 @@ dict = {
inventory_bulkRankUpSentinels: `Max Rank All Sentinels`, inventory_bulkRankUpSentinels: `Max Rank All Sentinels`,
inventory_bulkRankUpSentinelWeapons: `Max Rank All Sentinel Weapons`, inventory_bulkRankUpSentinelWeapons: `Max Rank All Sentinel Weapons`,
inventory_bulkRankUpEvolutionProgress: `Max Rank All Incarnon Evolution Progress`, inventory_bulkRankUpEvolutionProgress: `Max Rank All Incarnon Evolution Progress`,
inventory_Boosters: `Boosters`,
quests_list: `Quests`, quests_list: `Quests`,
quests_completeAll: `Complete All Quests`, quests_completeAll: `Complete All Quests`,
@ -162,6 +163,7 @@ dict = {
cheats_noDojoResearchTime: `No Dojo Research Time`, cheats_noDojoResearchTime: `No Dojo Research Time`,
cheats_fastClanAscension: `Fast Clan Ascension`, cheats_fastClanAscension: `Fast Clan Ascension`,
cheats_spoofMasteryRank: `Spoofed Mastery Rank (-1 to disable)`, cheats_spoofMasteryRank: `Spoofed Mastery Rank (-1 to disable)`,
cheats_nightwaveStandingMultiplier: `Nightwave Standing Multiplier`,
cheats_saveSettings: `Save Settings`, cheats_saveSettings: `Save Settings`,
cheats_account: `Account`, cheats_account: `Account`,
cheats_unlockAllFocusSchools: `Unlock All Focus Schools`, cheats_unlockAllFocusSchools: `Unlock All Focus Schools`,

View File

@ -98,6 +98,7 @@ dict = {
inventory_bulkRankUpSentinels: `Maximizar rango de todos los centinelas`, inventory_bulkRankUpSentinels: `Maximizar rango de todos los centinelas`,
inventory_bulkRankUpSentinelWeapons: `Maximizar rango de todas las armas de centinela`, inventory_bulkRankUpSentinelWeapons: `Maximizar rango de todas las armas de centinela`,
inventory_bulkRankUpEvolutionProgress: `Maximizar todo el progreso de evolución Incarnon`, inventory_bulkRankUpEvolutionProgress: `Maximizar todo el progreso de evolución Incarnon`,
inventory_Boosters: `Potenciadores`,
quests_list: `Misiones`, quests_list: `Misiones`,
quests_completeAll: `Completar todas las misiones`, quests_completeAll: `Completar todas las misiones`,
@ -163,6 +164,7 @@ dict = {
cheats_noDojoResearchTime: `Sin tiempo de investigación del dojo`, cheats_noDojoResearchTime: `Sin tiempo de investigación del dojo`,
cheats_fastClanAscension: `Ascenso rápido del clan`, cheats_fastClanAscension: `Ascenso rápido del clan`,
cheats_spoofMasteryRank: `Rango de maestría simulado (-1 para desactivar)`, cheats_spoofMasteryRank: `Rango de maestría simulado (-1 para desactivar)`,
cheats_nightwaveStandingMultiplier: `Multiplicador de Reputación de Onda Nocturna`,
cheats_saveSettings: `Guardar configuración`, cheats_saveSettings: `Guardar configuración`,
cheats_account: `Cuenta`, cheats_account: `Cuenta`,
cheats_unlockAllFocusSchools: `Desbloquear todas las escuelas de enfoque`, cheats_unlockAllFocusSchools: `Desbloquear todas las escuelas de enfoque`,

View File

@ -98,6 +98,7 @@ dict = {
inventory_bulkRankUpSentinels: `Toutes les Sentinelles au rang max`, inventory_bulkRankUpSentinels: `Toutes les Sentinelles au rang max`,
inventory_bulkRankUpSentinelWeapons: `Toutes les armes de Sentinelles au rang max`, inventory_bulkRankUpSentinelWeapons: `Toutes les armes de Sentinelles au rang max`,
inventory_bulkRankUpEvolutionProgress: `Toutes les évolutions Incarnon au rang max`, inventory_bulkRankUpEvolutionProgress: `Toutes les évolutions Incarnon au rang max`,
inventory_Boosters: `[UNTRANSLATED] Boosters`,
quests_list: `Quêtes`, quests_list: `Quêtes`,
quests_completeAll: `Compléter toutes les quêtes`, quests_completeAll: `Compléter toutes les quêtes`,
@ -163,6 +164,7 @@ dict = {
cheats_noDojoResearchTime: `Aucun temps de recherche (Dojo)`, cheats_noDojoResearchTime: `Aucun temps de recherche (Dojo)`,
cheats_fastClanAscension: `Ascension de clan rapide`, cheats_fastClanAscension: `Ascension de clan rapide`,
cheats_spoofMasteryRank: `Rang de maîtrise personnalisé (-1 pour désactiver)`, cheats_spoofMasteryRank: `Rang de maîtrise personnalisé (-1 pour désactiver)`,
cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`,
cheats_saveSettings: `Sauvegarder les paramètres`, cheats_saveSettings: `Sauvegarder les paramètres`,
cheats_account: `Compte`, cheats_account: `Compte`,
cheats_unlockAllFocusSchools: `Débloquer toutes les écoles de focus`, cheats_unlockAllFocusSchools: `Débloquer toutes les écoles de focus`,

View File

@ -98,6 +98,7 @@ dict = {
inventory_bulkRankUpSentinels: `Максимальный ранг всех стражей`, inventory_bulkRankUpSentinels: `Максимальный ранг всех стражей`,
inventory_bulkRankUpSentinelWeapons: `Максимальный ранг всего оружия стражей`, inventory_bulkRankUpSentinelWeapons: `Максимальный ранг всего оружия стражей`,
inventory_bulkRankUpEvolutionProgress: `Максимальный ранг всех эволюций Инкарнонов`, inventory_bulkRankUpEvolutionProgress: `Максимальный ранг всех эволюций Инкарнонов`,
inventory_Boosters: `[UNTRANSLATED] Boosters`,
quests_list: `Квесты`, quests_list: `Квесты`,
quests_completeAll: `Завершить все квесты`, quests_completeAll: `Завершить все квесты`,
@ -163,6 +164,7 @@ dict = {
cheats_noDojoResearchTime: `Мгновенные Исследование Додзё`, cheats_noDojoResearchTime: `Мгновенные Исследование Додзё`,
cheats_fastClanAscension: `Мгновенное Вознесение Клана`, cheats_fastClanAscension: `Мгновенное Вознесение Клана`,
cheats_spoofMasteryRank: `Подделанный ранг мастерства (-1 для отключения)`, cheats_spoofMasteryRank: `Подделанный ранг мастерства (-1 для отключения)`,
cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`,
cheats_saveSettings: `Сохранить настройки`, cheats_saveSettings: `Сохранить настройки`,
cheats_account: `Аккаунт`, cheats_account: `Аккаунт`,
cheats_unlockAllFocusSchools: `Разблокировать все школы фокуса`, cheats_unlockAllFocusSchools: `Разблокировать все школы фокуса`,

View File

@ -1,4 +1,4 @@
// Chinese translation by meb154 // Chinese translation by meb154 & bishan178
dict = { dict = {
general_inventoryUpdateNote: `注意:此处所做的更改只有在游戏同步仓库后才会生效。您可以通过访问星图来触发仓库更新。`, general_inventoryUpdateNote: `注意:此处所做的更改只有在游戏同步仓库后才会生效。您可以通过访问星图来触发仓库更新。`,
general_addButton: `添加`, general_addButton: `添加`,
@ -18,7 +18,7 @@ dict = {
code_kDrive: `K式悬浮板`, code_kDrive: `K式悬浮板`,
code_legendaryCore: `传奇核心`, code_legendaryCore: `传奇核心`,
code_traumaticPeculiar: `创伤怪奇`, code_traumaticPeculiar: `创伤怪奇`,
code_starter: `|MOD| (有瑕疵的)`, code_starter: `|MOD|(有瑕疵的)`,
code_badItem: `(Imposter)`, code_badItem: `(Imposter)`,
code_maxRank: `满级`, code_maxRank: `满级`,
code_rename: `重命名`, code_rename: `重命名`,
@ -28,7 +28,7 @@ dict = {
code_succRankUp: `[UNTRANSLATED] Successfully ranked up.`, code_succRankUp: `[UNTRANSLATED] Successfully ranked up.`,
code_noEquipmentToRankUp: `没有可升级的装备。`, code_noEquipmentToRankUp: `没有可升级的装备。`,
code_succAdded: `已成功添加。`, code_succAdded: `已成功添加。`,
code_succRemoved: `[UNTRANSLATED] Successfully removed.`, code_succRemoved: `已成功移除。`,
code_buffsNumber: `增益数量`, code_buffsNumber: `增益数量`,
code_cursesNumber: `负面数量`, code_cursesNumber: `负面数量`,
code_rerollsNumber: `洗卡次数`, code_rerollsNumber: `洗卡次数`,
@ -39,27 +39,27 @@ dict = {
code_count: `数量`, code_count: `数量`,
code_focusAllUnlocked: `所有专精学派均已解锁。`, code_focusAllUnlocked: `所有专精学派均已解锁。`,
code_focusUnlocked: `已解锁 |COUNT| 个新专精学派!需要游戏内仓库更新才能生效,您可以通过访问星图来触发仓库更新。`, code_focusUnlocked: `已解锁 |COUNT| 个新专精学派!需要游戏内仓库更新才能生效,您可以通过访问星图来触发仓库更新。`,
code_addModsConfirm: `确定要向账户添加 |COUNT| 张MOD吗`, code_addModsConfirm: `确定要向账户添加 |COUNT| 张MOD吗?`,
code_succImport: `导入成功。`, code_succImport: `导入成功。`,
code_gild: `镀金`, code_gild: `镀金`,
code_moa: `恐鸟`, code_moa: `恐鸟`,
code_zanuka: `猎犬`, code_zanuka: `猎犬`,
code_stage: `[UNTRANSLATED] Stage`, code_stage: `阶段`,
code_complete: `[UNTRANSLATED] Complete`, code_complete: `完成`,
code_nextStage: `[UNTRANSLATED] Next stage`, code_nextStage: `下一阶段`,
code_prevStage: `[UNTRANSLATED] Previous stage`, code_prevStage: `上一阶段`,
code_reset: `[UNTRANSLATED] Reset`, code_reset: `重置`,
code_setInactive: `[UNTRANSLATED] Make the quest inactive`, code_setInactive: `使任务处于未激活状态`,
code_completed: `[UNTRANSLATED] Completed`, code_completed: `已完成`,
code_active: `[UNTRANSLATED] Active`, code_active: `正在执行`,
code_pigment: `颜料`, code_pigment: `颜料`,
code_mature: `[UNTRANSLATED] Mature for combat`, code_mature: `成长并战备`,
code_unmature: `[UNTRANSLATED] Regress genetic aging`, code_unmature: `逆转衰老基因`,
login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)。`, login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)。`,
login_emailLabel: `电子邮箱`, login_emailLabel: `电子邮箱`,
login_passwordLabel: `密码`, login_passwordLabel: `密码`,
login_loginButton: `登录`, login_loginButton: `登录`,
login_registerButton: `[UNTRANSLATED] Register`, login_registerButton: `注册账号`,
navbar_logout: `退出登录`, navbar_logout: `退出登录`,
navbar_renameAccount: `重命名账户`, navbar_renameAccount: `重命名账户`,
navbar_deleteAccount: `删除账户`, navbar_deleteAccount: `删除账户`,
@ -82,22 +82,23 @@ dict = {
inventory_operatorAmps: `增幅器`, inventory_operatorAmps: `增幅器`,
inventory_hoverboards: `K式悬浮板`, inventory_hoverboards: `K式悬浮板`,
inventory_moaPets: `恐鸟`, inventory_moaPets: `恐鸟`,
inventory_kubrowPets: `[UNTRANSLATED] Beasts`, inventory_kubrowPets: `动物同伴`,
inventory_evolutionProgress: `[UNTRANSLATED] Incarnon Evolution Progress`, inventory_evolutionProgress: `灵化之源进度`,
inventory_bulkAddSuits: `添加缺失战甲`, inventory_bulkAddSuits: `添加缺失战甲`,
inventory_bulkAddWeapons: `添加缺失武器`, inventory_bulkAddWeapons: `添加缺失武器`,
inventory_bulkAddSpaceSuits: `添加缺失Archwing`, inventory_bulkAddSpaceSuits: `添加缺失Archwing`,
inventory_bulkAddSpaceWeapons: `添加缺失Archwing武器`, inventory_bulkAddSpaceWeapons: `添加缺失Archwing武器`,
inventory_bulkAddSentinels: `添加缺失守护`, inventory_bulkAddSentinels: `添加缺失守护`,
inventory_bulkAddSentinelWeapons: `添加缺失守护武器`, inventory_bulkAddSentinelWeapons: `添加缺失守护武器`,
inventory_bulkAddEvolutionProgress: `[UNTRANSLATED] Add Missing Incarnon Evolution Progress`, inventory_bulkAddEvolutionProgress: `添加缺失的灵化之源`,
inventory_bulkRankUpSuits: `所有战甲升满级`, inventory_bulkRankUpSuits: `所有战甲升满级`,
inventory_bulkRankUpWeapons: `所有武器升满级`, inventory_bulkRankUpWeapons: `所有武器升满级`,
inventory_bulkRankUpSpaceSuits: `所有Archwing升满级`, inventory_bulkRankUpSpaceSuits: `所有Archwing升满级`,
inventory_bulkRankUpSpaceWeapons: `所有Archwing武器升满级`, inventory_bulkRankUpSpaceWeapons: `所有Archwing武器升满级`,
inventory_bulkRankUpSentinels: `所有守护升满级`, inventory_bulkRankUpSentinels: `所有守护升满级`,
inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`, inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`,
inventory_bulkRankUpEvolutionProgress: `[UNTRANSLATED] Max Rank All Incarnon Evolution Progress`, inventory_bulkRankUpEvolutionProgress: `所有灵化之源最大等级`,
inventory_Boosters: `加成器`,
quests_list: `任务`, quests_list: `任务`,
quests_completeAll: `完成所有任务`, quests_completeAll: `完成所有任务`,
@ -111,15 +112,15 @@ dict = {
currency_owned: `当前拥有 |COUNT|。`, currency_owned: `当前拥有 |COUNT|。`,
powersuit_archonShardsLabel: `执刑官源力石槽位`, powersuit_archonShardsLabel: `执刑官源力石槽位`,
powersuit_archonShardsDescription: `您可以使用这些无限插槽应用各种强化效果`, powersuit_archonShardsDescription: `您可以使用这些无限插槽应用各种强化效果`,
powersuit_archonShardsDescription2: `[UNTRANSLATED] Note that each archon shard takes some time to be applied when loading in.`, powersuit_archonShardsDescription2: `请注意, 在加载时, 每个执政官源力石都需要一定的时间来生效。`,
mods_addRiven: `添加裂罅MOD`, mods_addRiven: `添加裂罅MOD`,
mods_fingerprint: `印记`, mods_fingerprint: `印记`,
mods_fingerprintHelp: `需要印记相关的帮助?`, mods_fingerprintHelp: `需要印记相关的帮助?`,
mods_rivens: `裂罅MOD`, mods_rivens: `裂罅MOD`,
mods_mods: `Mods`, mods_mods: `Mods`,
mods_addMissingUnrankedMods: `[UNTRANSLATED] Add Missing Unranked Mods`, mods_addMissingUnrankedMods: `添加所有缺失的Mods`,
mods_removeUnranked: `[UNTRANSLATED] Remove Unranked Mods`, mods_removeUnranked: `删除所有未升级的Mods`,
mods_addMissingMaxRankMods: `[UNTRANSLATED] Add Missing Max Rank Mods`, mods_addMissingMaxRankMods: `添加所有缺失的满级Mods`,
cheats_administratorRequirement: `您必须是管理员才能使用此功能。要成为管理员,请将 <code>|DISPLAYNAME|</code> 添加到 config.json 的 <code>administratorNames</code> 中。`, cheats_administratorRequirement: `您必须是管理员才能使用此功能。要成为管理员,请将 <code>|DISPLAYNAME|</code> 添加到 config.json 的 <code>administratorNames</code> 中。`,
cheats_server: `服务器`, cheats_server: `服务器`,
cheats_skipTutorial: `跳过教程`, cheats_skipTutorial: `跳过教程`,
@ -131,43 +132,44 @@ dict = {
cheats_infiniteEndo: `无限内融核心`, cheats_infiniteEndo: `无限内融核心`,
cheats_infiniteRegalAya: `无限御品阿耶`, cheats_infiniteRegalAya: `无限御品阿耶`,
cheats_infiniteHelminthMaterials: `无限Helminth材料`, cheats_infiniteHelminthMaterials: `无限Helminth材料`,
cheats_claimingBlueprintRefundsIngredients: `[UNTRANSLATED] Claiming Blueprint Refunds Ingredients`, cheats_claimingBlueprintRefundsIngredients: `取消蓝图制造时返还材料`,
cheats_dontSubtractVoidTraces: `[UNTRANSLATED] Don't Subtract Void Traces`, cheats_dontSubtractVoidTraces: `虚空光体无消耗`,
cheats_dontSubtractConsumables: `[UNTRANSLATED] Don't Subtract Consumables`, cheats_dontSubtractConsumables: `消耗物品使用时无损耗`,
cheats_unlockAllShipFeatures: `解锁所有飞船功能`, cheats_unlockAllShipFeatures: `解锁所有飞船功能`,
cheats_unlockAllShipDecorations: `解锁所有飞船装饰`, cheats_unlockAllShipDecorations: `解锁所有飞船装饰`,
cheats_unlockAllFlavourItems: `解锁所有<abbr title=\"动画组合、图标、调色板等\">装饰物品</abbr>`, cheats_unlockAllFlavourItems: `解锁所有<abbr title=\"动画组合、图标、调色板等\">装饰物品</abbr>`,
cheats_unlockAllSkins: `解锁所有外观`, cheats_unlockAllSkins: `解锁所有外观`,
cheats_unlockAllCapturaScenes: `解锁所有Captura场景`, cheats_unlockAllCapturaScenes: `解锁所有Captura场景`,
cheats_unlockAllDecoRecipes: `[UNTRANSLATED] Unlock All Dojo Deco Recipes`, cheats_unlockAllDecoRecipes: `解锁所有道场配方`,
cheats_universalPolarityEverywhere: `全局万用极性`, cheats_universalPolarityEverywhere: `全局万用极性`,
cheats_unlockDoubleCapacityPotatoesEverywhere: `全物品自带Orokin反应堆`, cheats_unlockDoubleCapacityPotatoesEverywhere: `全物品自带Orokin反应堆`,
cheats_unlockExilusEverywhere: `全物品自带适配器`, cheats_unlockExilusEverywhere: `全物品自带适配器`,
cheats_unlockArcanesEverywhere: `全物品自带赋能适配器`, cheats_unlockArcanesEverywhere: `全物品自带赋能适配器`,
cheats_noDailyStandingLimits: `无每日声望限制`, cheats_noDailyStandingLimits: `无每日声望限制`,
cheats_noDailyFocusLimit: `[UNTRANSLATED] No Daily Focus Limits`, cheats_noDailyFocusLimit: `指挥官专精无每日获取上限`,
cheats_noArgonCrystalDecay: `[UNTRANSLATED] No Argon Crystal Decay`, cheats_noArgonCrystalDecay: `氩结晶无衰变`,
cheats_noMasteryRankUpCooldown: `[UNTRANSLATED] No Mastery Rank Up Cooldown`, cheats_noMasteryRankUpCooldown: `段位考核无冷却时间`,
cheats_noVendorPurchaseLimits: `[UNTRANSLATED] No Vendor Purchase Limits`, cheats_noVendorPurchaseLimits: `商城或商人无购买限制`,
cheats_noDeathMarks: `[UNTRANSLATED] No Death Marks`, cheats_noDeathMarks: `无死亡标记(不会被 Stalker/Grustrag 三霸/Zanuka 猎人等标记)`,
cheats_noKimCooldowns: `[UNTRANSLATED] No KIM Cooldowns`, cheats_noKimCooldowns: `无 KIM 冷却时间`,
cheats_syndicateMissionsRepeatable: `[UNTRANSLATED] Syndicate Missions Repeatable`, cheats_syndicateMissionsRepeatable: `集团任务可重复`,
cheats_instantFinishRivenChallenge: `[UNTRANSLATED] Instant Finish Riven Challenge`, cheats_instantFinishRivenChallenge: `立即完成裂罅挑战`,
cheats_instantResourceExtractorDrones: `即时资源采集无人机`, cheats_instantResourceExtractorDrones: `即时资源采集无人机`,
cheats_noResourceExtractorDronesDamage: `[UNTRANSLATED] No Resource Extractor Drones Damage`, cheats_noResourceExtractorDronesDamage: `资源提取器不会损毁`,
cheats_skipClanKeyCrafting: `[UNTRANSLATED] Skip Clan Key Crafting`, cheats_skipClanKeyCrafting: `跳过氏族钥匙制作, 进入道场无需氏族钥匙`,
cheats_noDojoRoomBuildStage: `无视道场房间建造阶段`, cheats_noDojoRoomBuildStage: `无视道场房间建造阶段`,
cheats_noDojoDecoBuildStage: `[UNTRANSLATED] No Dojo Deco Build Stage`, cheats_noDojoDecoBuildStage: `道场装饰建造立即完成`,
cheats_fastDojoRoomDestruction: `快速拆除道场房间`, cheats_fastDojoRoomDestruction: `快速拆除道场房间`,
cheats_noDojoResearchCosts: `无视道场研究消耗`, cheats_noDojoResearchCosts: `无视道场研究消耗`,
cheats_noDojoResearchTime: `无视道场研究时间`, cheats_noDojoResearchTime: `无视道场研究时间`,
cheats_fastClanAscension: `快速升级氏族`, cheats_fastClanAscension: `快速升级氏族`,
cheats_spoofMasteryRank: `伪造精通段位(-1为禁用`, cheats_spoofMasteryRank: `伪造精通段位(-1为禁用)`,
cheats_nightwaveStandingMultiplier: `午夜电波声望倍率`,
cheats_saveSettings: `保存设置`, cheats_saveSettings: `保存设置`,
cheats_account: `账户`, cheats_account: `账户`,
cheats_unlockAllFocusSchools: `解锁所有专精学派`, cheats_unlockAllFocusSchools: `解锁所有专精学派`,
cheats_helminthUnlockAll: `完全升级Helminth`, cheats_helminthUnlockAll: `完全升级Helminth`,
cheats_intrinsicsUnlockAll: `[UNTRANSLATED] Max Rank All Intrinsics`, cheats_intrinsicsUnlockAll: `所有内源之力最大等级`,
cheats_changeSupportedSyndicate: `支持的集团`, cheats_changeSupportedSyndicate: `支持的集团`,
cheats_changeButton: `更改`, cheats_changeButton: `更改`,
cheats_none: ``, cheats_none: ``,