merge upstream
This commit is contained in:
commit
eeb3e687cd
@ -26,7 +26,7 @@ app.use((req, _res, next) => {
|
||||
|
||||
app.use(bodyParser.raw());
|
||||
app.use(express.json({ limit: "4mb" }));
|
||||
app.use(bodyParser.text());
|
||||
app.use(bodyParser.text({ limit: "4mb" }));
|
||||
app.use(requestLogger);
|
||||
|
||||
app.use("/api", apiRouter);
|
||||
|
@ -14,7 +14,12 @@ import {
|
||||
ExportVirtuals
|
||||
} from "warframe-public-export-plus";
|
||||
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService";
|
||||
import { addMiscItems, allDailyAffiliationKeys, createLibraryDailyTask } from "@/src/services/inventoryService";
|
||||
import {
|
||||
addMiscItems,
|
||||
allDailyAffiliationKeys,
|
||||
cleanupInventory,
|
||||
createLibraryDailyTask
|
||||
} from "@/src/services/inventoryService";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { catBreadHash } from "@/src/helpers/stringHelpers";
|
||||
|
||||
@ -79,6 +84,8 @@ export const inventoryController: RequestHandler = async (request, response) =>
|
||||
}
|
||||
}
|
||||
|
||||
cleanupInventory(inventory);
|
||||
|
||||
inventory.NextRefill = new Date((Math.trunc(Date.now() / 86400000) + 1) * 86400000);
|
||||
await inventory.save();
|
||||
}
|
||||
|
@ -12,22 +12,17 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
|
||||
const request = JSON.parse(String(req.body)) as SaveDialogueRequest;
|
||||
if ("YearIteration" in request) {
|
||||
const inventory = await getInventory(accountId, "DialogueHistory");
|
||||
if (inventory.DialogueHistory) {
|
||||
inventory.DialogueHistory.YearIteration = request.YearIteration;
|
||||
} else {
|
||||
inventory.DialogueHistory = { YearIteration: request.YearIteration };
|
||||
}
|
||||
inventory.DialogueHistory ??= {};
|
||||
inventory.DialogueHistory.YearIteration = request.YearIteration;
|
||||
await inventory.save();
|
||||
res.end();
|
||||
} else {
|
||||
const inventory = await getInventory(accountId);
|
||||
if (!inventory.DialogueHistory) {
|
||||
throw new Error("bad inventory state");
|
||||
}
|
||||
const inventoryChanges: IInventoryChanges = {};
|
||||
const tomorrowAt0Utc = config.noKimCooldowns
|
||||
? Date.now()
|
||||
: (Math.trunc(Date.now() / 86400_000) + 1) * 86400_000;
|
||||
inventory.DialogueHistory ??= {};
|
||||
inventory.DialogueHistory.Dialogues ??= [];
|
||||
const dialogue = getDialogue(inventory, request.DialogueName);
|
||||
dialogue.Rank = request.Rank;
|
||||
|
@ -29,6 +29,7 @@ interface ListedItem {
|
||||
badReason?: "starter" | "frivolous" | "notraw";
|
||||
partType?: string;
|
||||
chainLength?: number;
|
||||
parazon?: boolean;
|
||||
}
|
||||
|
||||
const relicQualitySuffixes: Record<TRelicQuality, string> = {
|
||||
@ -68,7 +69,8 @@ const getItemListsController: RequestHandler = (req, response) => {
|
||||
if (item.productCategory != "SpecialItems") {
|
||||
res[item.productCategory].push({
|
||||
uniqueName,
|
||||
name: getString(item.name, lang)
|
||||
name: getString(item.name, lang),
|
||||
exalted: item.exalted
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -196,6 +198,9 @@ const getItemListsController: RequestHandler = (req, response) => {
|
||||
} else if (upgrade.upgradeEntries) {
|
||||
mod.badReason = "notraw";
|
||||
}
|
||||
if (upgrade.type == "PARAZON") {
|
||||
mod.parazon = true;
|
||||
}
|
||||
res.mods.push(mod);
|
||||
}
|
||||
for (const [uniqueName, upgrade] of Object.entries(ExportAvionics)) {
|
||||
|
@ -909,7 +909,7 @@ dialogueSchema.set("toJSON", {
|
||||
|
||||
const dialogueHistorySchema = new Schema<IDialogueHistoryDatabase>(
|
||||
{
|
||||
YearIteration: { type: Number, required: true },
|
||||
YearIteration: Number,
|
||||
Resets: Number,
|
||||
Dialogues: { type: [dialogueSchema], required: false }
|
||||
},
|
||||
|
@ -37,6 +37,7 @@ import {
|
||||
} from "../types/inventoryTypes/inventoryTypes";
|
||||
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
|
||||
import { ILoadoutConfigDatabase, ILoadoutDatabase } from "../types/saveLoadoutTypes";
|
||||
import { slotNames } from "../types/purchaseTypes";
|
||||
|
||||
const convertDate = (value: IMongoDate): Date => {
|
||||
return new Date(parseInt(value.$date.$numberLong));
|
||||
@ -212,20 +213,7 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
|
||||
replaceArray<IOperatorConfigDatabase>(db[key], client[key].map(convertOperatorConfig));
|
||||
}
|
||||
}
|
||||
for (const key of [
|
||||
"SuitBin",
|
||||
"WeaponBin",
|
||||
"SentinelBin",
|
||||
"SpaceSuitBin",
|
||||
"SpaceWeaponBin",
|
||||
"PvpBonusLoadoutBin",
|
||||
"PveBonusLoadoutBin",
|
||||
"RandomModBin",
|
||||
"MechBin",
|
||||
"CrewMemberBin",
|
||||
"OperatorAmpBin",
|
||||
"CrewShipSalvageBin"
|
||||
] as const) {
|
||||
for (const key of slotNames) {
|
||||
if (client[key] !== undefined) {
|
||||
replaceSlots(db[key], client[key]);
|
||||
}
|
||||
|
@ -87,9 +87,7 @@ export const createInventory = async (
|
||||
const inventory = new Inventory({
|
||||
accountOwnerId: accountOwnerId,
|
||||
LoadOutPresets: defaultItemReferences.loadOutPresetId,
|
||||
Ships: [defaultItemReferences.ship],
|
||||
PlayedParkourTutorial: config.skipTutorial,
|
||||
ReceivedStartingGear: config.skipTutorial
|
||||
Ships: [defaultItemReferences.ship]
|
||||
});
|
||||
|
||||
inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask();
|
||||
@ -102,6 +100,7 @@ export const createInventory = async (
|
||||
await addItem(inventory, "/Lotus/Types/Friendly/PlayerControllable/Weapons/DuviriDualSwords");
|
||||
|
||||
if (config.skipTutorial) {
|
||||
inventory.PlayedParkourTutorial = true;
|
||||
await addStartingGear(inventory);
|
||||
await completeQuest(inventory, "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain");
|
||||
|
||||
@ -1769,3 +1768,23 @@ export const setupKahlSyndicate = (inventory: TInventoryDatabaseDocument): void
|
||||
Tag: "KahlSyndicate"
|
||||
});
|
||||
};
|
||||
|
||||
export const cleanupInventory = (inventory: TInventoryDatabaseDocument): void => {
|
||||
let index = inventory.MiscItems.findIndex(x => x.ItemType == "");
|
||||
if (index != -1) {
|
||||
inventory.MiscItems.splice(index, 1);
|
||||
}
|
||||
|
||||
index = inventory.Affiliations.findIndex(x => x.Tag == "KahlSyndicate");
|
||||
if (index != -1 && !inventory.Affiliations[index].WeeklyMissions) {
|
||||
logger.debug(`KahlSyndicate seems broken, removing it and setting up again`);
|
||||
inventory.Affiliations.splice(index, 1);
|
||||
setupKahlSyndicate(inventory);
|
||||
}
|
||||
|
||||
const LibrarySyndicate = inventory.Affiliations.find(x => x.Tag == "LibrarySyndicate");
|
||||
if (LibrarySyndicate && LibrarySyndicate.FreeFavorsEarned) {
|
||||
logger.debug(`removing FreeFavorsEarned from LibrarySyndicate`);
|
||||
LibrarySyndicate.FreeFavorsEarned = undefined;
|
||||
}
|
||||
};
|
||||
|
@ -55,6 +55,7 @@ import { Loadout } from "../models/inventoryModels/loadoutModel";
|
||||
import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes";
|
||||
import { getLiteSortie, getWorldState, idToWeek } from "./worldStateService";
|
||||
import { config } from "./configService";
|
||||
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
|
||||
|
||||
const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] => {
|
||||
// For Spy missions, e.g. 3 vaults cracked = A, B, C
|
||||
@ -331,35 +332,36 @@ export const addMissionInventoryUpdates = async (
|
||||
case "LibraryScans":
|
||||
value.forEach(scan => {
|
||||
let synthesisIgnored = true;
|
||||
if (
|
||||
inventory.LibraryPersonalTarget &&
|
||||
libraryPersonalTargetToAvatar[inventory.LibraryPersonalTarget] == scan.EnemyType
|
||||
) {
|
||||
let progress = inventory.LibraryPersonalProgress.find(
|
||||
x => x.TargetType == inventory.LibraryPersonalTarget
|
||||
);
|
||||
if (!progress) {
|
||||
progress =
|
||||
inventory.LibraryPersonalProgress[
|
||||
inventory.LibraryPersonalProgress.push({
|
||||
TargetType: inventory.LibraryPersonalTarget,
|
||||
Scans: 0,
|
||||
Completed: false
|
||||
}) - 1
|
||||
];
|
||||
if (inventory.LibraryPersonalTarget) {
|
||||
const taskAvatar = libraryPersonalTargetToAvatar[inventory.LibraryPersonalTarget];
|
||||
const taskAvatars = libraryDailyTasks.find(x => x.indexOf(taskAvatar) != -1)!;
|
||||
if (taskAvatars.indexOf(scan.EnemyType) != -1) {
|
||||
let progress = inventory.LibraryPersonalProgress.find(
|
||||
x => x.TargetType == inventory.LibraryPersonalTarget
|
||||
);
|
||||
if (!progress) {
|
||||
progress =
|
||||
inventory.LibraryPersonalProgress[
|
||||
inventory.LibraryPersonalProgress.push({
|
||||
TargetType: inventory.LibraryPersonalTarget,
|
||||
Scans: 0,
|
||||
Completed: false
|
||||
}) - 1
|
||||
];
|
||||
}
|
||||
progress.Scans += scan.Count;
|
||||
if (
|
||||
progress.Scans >=
|
||||
(inventory.LibraryPersonalTarget ==
|
||||
"/Lotus/Types/Game/Library/Targets/DragonframeQuestTarget"
|
||||
? 3
|
||||
: 10)
|
||||
) {
|
||||
progress.Completed = true;
|
||||
}
|
||||
logger.debug(`synthesis of ${scan.EnemyType} added to personal target progress`);
|
||||
synthesisIgnored = false;
|
||||
}
|
||||
progress.Scans += scan.Count;
|
||||
if (
|
||||
progress.Scans >=
|
||||
(inventory.LibraryPersonalTarget ==
|
||||
"/Lotus/Types/Game/Library/Targets/DragonframeQuestTarget"
|
||||
? 3
|
||||
: 10)
|
||||
) {
|
||||
progress.Completed = true;
|
||||
}
|
||||
logger.debug(`synthesis of ${scan.EnemyType} added to personal target progress`);
|
||||
synthesisIgnored = false;
|
||||
}
|
||||
if (
|
||||
inventory.LibraryActiveDailyTaskInfo &&
|
||||
|
@ -4,14 +4,7 @@ import { unixTimesInMs } from "@/src/constants/timeConstants";
|
||||
import { config } from "@/src/services/configService";
|
||||
import { CRng } from "@/src/services/rngService";
|
||||
import { eMissionType, ExportNightwave, ExportRegions } from "warframe-public-export-plus";
|
||||
import {
|
||||
ICalendarDay,
|
||||
ICalendarSeason,
|
||||
ILiteSortie,
|
||||
ISeasonChallenge,
|
||||
ISortie,
|
||||
IWorldState
|
||||
} from "../types/worldStateTypes";
|
||||
import { ICalendarDay, ICalendarSeason, ILiteSortie, ISeasonChallenge, IWorldState } from "../types/worldStateTypes";
|
||||
|
||||
const sortieBosses = [
|
||||
"SORTIE_BOSS_HYENA",
|
||||
@ -174,7 +167,47 @@ const getSortieTime = (day: number): number => {
|
||||
return dayStart + (isDst ? 16 : 17) * 3600000;
|
||||
};
|
||||
|
||||
const pushSortieIfRelevant = (out: ISortie[], day: number): void => {
|
||||
const pushSyndicateMissions = (
|
||||
worldState: IWorldState,
|
||||
day: number,
|
||||
seed: number,
|
||||
idSuffix: string,
|
||||
syndicateTag: string
|
||||
): void => {
|
||||
const nodeOptions: string[] = [];
|
||||
for (const [key, value] of Object.entries(ExportRegions)) {
|
||||
if (
|
||||
value.name.indexOf("Archwing") == -1 && // no archwing
|
||||
value.systemIndex != 23 && // no 1999 stuff
|
||||
value.missionIndex != 10 && // Exclude MT_PVP (for relays)
|
||||
value.missionIndex != 23 && // no junctions
|
||||
value.missionIndex <= 28 // no railjack or some such
|
||||
) {
|
||||
nodeOptions.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
const rng = new CRng(seed);
|
||||
const nodes: string[] = [];
|
||||
for (let i = 0; i != 6; ++i) {
|
||||
const index = rng.randomInt(0, nodeOptions.length - 1);
|
||||
nodes.push(nodeOptions[index]);
|
||||
nodeOptions.splice(index, 1);
|
||||
}
|
||||
|
||||
const dayStart = getSortieTime(day);
|
||||
const dayEnd = getSortieTime(day + 1);
|
||||
worldState.SyndicateMissions.push({
|
||||
_id: { $oid: Math.trunc(dayStart / 1000).toString(16) + idSuffix },
|
||||
Activation: { $date: { $numberLong: dayStart.toString() } },
|
||||
Expiry: { $date: { $numberLong: dayEnd.toString() } },
|
||||
Tag: syndicateTag,
|
||||
Seed: seed,
|
||||
Nodes: nodes
|
||||
});
|
||||
};
|
||||
|
||||
const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
|
||||
const dayStart = getSortieTime(day);
|
||||
if (!isBeforeNextExpectedWorldStateRefresh(dayStart)) {
|
||||
return;
|
||||
@ -231,7 +264,9 @@ const pushSortieIfRelevant = (out: ISortie[], day: number): void => {
|
||||
value.name.indexOf("Archwing") == -1 &&
|
||||
value.missionIndex != 0 && // Exclude MT_ASSASSINATION
|
||||
value.missionIndex != 5 && // Exclude MT_CAPTURE
|
||||
value.missionIndex != 10 && // Exclude MT_PVP (for relays)
|
||||
value.missionIndex != 21 && // Exclude MT_PURIFY
|
||||
value.missionIndex != 22 && // Exclude MT_ARENA
|
||||
value.missionIndex != 23 && // Exclude MT_JUNCTION
|
||||
value.missionIndex <= 28
|
||||
) {
|
||||
@ -291,7 +326,7 @@ const pushSortieIfRelevant = (out: ISortie[], day: number): void => {
|
||||
missionTypes.add(missionType);
|
||||
}
|
||||
|
||||
out.push({
|
||||
worldState.Sorties.push({
|
||||
_id: { $oid: Math.trunc(dayStart / 1000).toString(16) + "d4d932c97c0a3acd" },
|
||||
Activation: { $date: { $numberLong: dayStart.toString() } },
|
||||
Expiry: { $date: { $numberLong: dayEnd.toString() } },
|
||||
@ -300,6 +335,13 @@ const pushSortieIfRelevant = (out: ISortie[], day: number): void => {
|
||||
Boss: boss,
|
||||
Variants: selectedNodes
|
||||
});
|
||||
|
||||
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa48049", "ArbitersSyndicate");
|
||||
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa4804a", "CephalonSudaSyndicate");
|
||||
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa4804e", "NewLokaSyndicate");
|
||||
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa48050", "PerrinSyndicate");
|
||||
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa4805e", "RedVeilSyndicate");
|
||||
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa48061", "SteelMeridianSyndicate");
|
||||
};
|
||||
|
||||
const dailyChallenges = Object.keys(ExportNightwave.challenges).filter(x =>
|
||||
@ -952,9 +994,9 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
|
||||
});
|
||||
}
|
||||
|
||||
// Sortie cycling every day
|
||||
pushSortieIfRelevant(worldState.Sorties, day - 1);
|
||||
pushSortieIfRelevant(worldState.Sorties, day);
|
||||
// Sortie & syndicate missions cycling every day (at 16:00 or 17:00 UTC depending on if London, OT is observing DST)
|
||||
pushSortieIfRelevant(worldState, day - 1);
|
||||
pushSortieIfRelevant(worldState, day);
|
||||
|
||||
// Archon Hunt cycling every week
|
||||
worldState.LiteSorties.push(getLiteSortie(week));
|
||||
|
@ -1130,13 +1130,13 @@ export interface IEndlessXpProgress {
|
||||
}
|
||||
|
||||
export interface IDialogueHistoryClient {
|
||||
YearIteration: number;
|
||||
YearIteration?: number;
|
||||
Resets?: number; // added in 38.5.0
|
||||
Dialogues?: IDialogueClient[];
|
||||
}
|
||||
|
||||
export interface IDialogueHistoryDatabase {
|
||||
YearIteration: number;
|
||||
YearIteration?: number;
|
||||
Resets?: number;
|
||||
Dialogues?: IDialogueDatabase[];
|
||||
}
|
||||
|
@ -103,6 +103,7 @@ export const slotNames = [
|
||||
"WeaponBin",
|
||||
"MechBin",
|
||||
"PveBonusLoadoutBin",
|
||||
"PvpBonusLoadoutBin",
|
||||
"SentinelBin",
|
||||
"SpaceSuitBin",
|
||||
"SpaceWeaponBin",
|
||||
|
@ -63,22 +63,6 @@
|
||||
}
|
||||
],
|
||||
"SyndicateMissions": [
|
||||
{
|
||||
"_id": { "$oid": "663a4fc5ba6f84724fa48049" },
|
||||
"Activation": { "$date": { "$numberLong": "1715097541439" } },
|
||||
"Expiry": { "$date": { "$numberLong": "2000000000000" } },
|
||||
"Tag": "ArbitersSyndicate",
|
||||
"Seed": 24491,
|
||||
"Nodes": ["SolNode223", "SolNode89", "SolNode146", "SolNode212", "SolNode167", "SolNode48", "SolNode78"]
|
||||
},
|
||||
{
|
||||
"_id": { "$oid": "663a4fc5ba6f84724fa4804a" },
|
||||
"Activation": { "$date": { "$numberLong": "1715097541439" } },
|
||||
"Expiry": { "$date": { "$numberLong": "2000000000000" } },
|
||||
"Tag": "CephalonSudaSyndicate",
|
||||
"Seed": 12770,
|
||||
"Nodes": ["SolNode36", "SolNode59", "SettlementNode12", "SolNode61", "SolNode12", "SolNode138", "SolNode72"]
|
||||
},
|
||||
{
|
||||
"_id": { "$oid": "663a4fc5ba6f84724fa4804c" },
|
||||
"Activation": { "$date": { "$numberLong": "1715097541439" } },
|
||||
@ -103,14 +87,6 @@
|
||||
"Seed": 50102,
|
||||
"Nodes": []
|
||||
},
|
||||
{
|
||||
"_id": { "$oid": "663a4fc5ba6f84724fa4804e" },
|
||||
"Activation": { "$date": { "$numberLong": "1715097541439" } },
|
||||
"Expiry": { "$date": { "$numberLong": "2000000000000" } },
|
||||
"Tag": "NewLokaSyndicate",
|
||||
"Seed": 16064,
|
||||
"Nodes": ["SolNode101", "SolNode224", "SolNode205", "SettlementNode2", "SolNode171", "SolNode188", "SolNode75"]
|
||||
},
|
||||
{
|
||||
"_id": { "$oid": "663a4fc5ba6f84724fa4804f" },
|
||||
"Activation": { "$date": { "$numberLong": "1715097541439" } },
|
||||
@ -119,14 +95,6 @@
|
||||
"Seed": 77721,
|
||||
"Nodes": []
|
||||
},
|
||||
{
|
||||
"_id": { "$oid": "663a4fc5ba6f84724fa48050" },
|
||||
"Activation": { "$date": { "$numberLong": "1715097541439" } },
|
||||
"Expiry": { "$date": { "$numberLong": "2000000000000" } },
|
||||
"Tag": "PerrinSyndicate",
|
||||
"Seed": 9940,
|
||||
"Nodes": ["SolNode39", "SolNode14", "SolNode203", "SolNode100", "SolNode130", "SolNode64", "SettlementNode15"]
|
||||
},
|
||||
{
|
||||
"_id": { "$oid": "663a4fc5ba6f84724fa48052" },
|
||||
"Activation": { "$date": { "$numberLong": "1715097541439" } },
|
||||
@ -255,14 +223,6 @@
|
||||
"Seed": 67257,
|
||||
"Nodes": []
|
||||
},
|
||||
{
|
||||
"_id": { "$oid": "663a4fc5ba6f84724fa4805e" },
|
||||
"Activation": { "$date": { "$numberLong": "1715097541439" } },
|
||||
"Expiry": { "$date": { "$numberLong": "2000000000000" } },
|
||||
"Tag": "RedVeilSyndicate",
|
||||
"Seed": 46649,
|
||||
"Nodes": ["SolNode226", "SolNode79", "SolNode216", "SettlementNode11", "SolNode56", "SolNode41", "SolNode23"]
|
||||
},
|
||||
{
|
||||
"_id": { "$oid": "663a4fc5ba6f84724fa48060" },
|
||||
"Activation": { "$date": { "$numberLong": "1715097541439" } },
|
||||
@ -270,14 +230,6 @@
|
||||
"Tag": "VoxSyndicate",
|
||||
"Seed": 77972,
|
||||
"Nodes": []
|
||||
},
|
||||
{
|
||||
"_id": { "$oid": "663a4fc5ba6f84724fa48061" },
|
||||
"Activation": { "$date": { "$numberLong": "1715097541439" } },
|
||||
"Expiry": { "$date": { "$numberLong": "2000000000000" } },
|
||||
"Tag": "SteelMeridianSyndicate",
|
||||
"Seed": 42366,
|
||||
"Nodes": ["SolNode27", "SolNode107", "SolNode214", "SettlementNode1", "SolNode177", "SolNode141", "SolNode408"]
|
||||
}
|
||||
],
|
||||
"ActiveMissions": [
|
||||
|
@ -85,6 +85,7 @@
|
||||
<input class="form-control" type="password" id="password" required />
|
||||
<br />
|
||||
<button class="btn btn-primary" type="submit" data-loc="login_loginButton"></button>
|
||||
<button class="btn btn-secondary" type="submit" onclick="registerSubmit = true;" data-loc="login_registerButton"></button>
|
||||
</form>
|
||||
</div>
|
||||
<div data-route="/webui/inventory" data-title="Inventory | OpenWF WebUI">
|
||||
@ -490,8 +491,9 @@
|
||||
</div>
|
||||
<div class="card mb-3">
|
||||
<h5 class="card-header" data-loc="general_bulkActions"></h5>
|
||||
<div class="card-body">
|
||||
<div class="card-body d-flex flex-wrap gap-2">
|
||||
<button class="btn btn-primary" onclick="doAddAllMods();" data-loc="mods_bulkAddMods"></button>
|
||||
<button class="btn btn-danger" onclick="doRemoveUnrankedMods();" data-loc="mods_removeUnranked"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,8 +1,15 @@
|
||||
let loginOrRegisterPending = false;
|
||||
window.registerSubmit = false;
|
||||
|
||||
function doLogin() {
|
||||
if (loginOrRegisterPending) {
|
||||
return;
|
||||
}
|
||||
loginOrRegisterPending = true;
|
||||
localStorage.setItem("email", $("#email").val());
|
||||
localStorage.setItem("password", $("#password").val());
|
||||
$("#email, #password").val("");
|
||||
loginFromLocalStorage();
|
||||
registerSubmit = false;
|
||||
}
|
||||
|
||||
function loginFromLocalStorage() {
|
||||
@ -37,12 +44,15 @@ function doLoginRequest(succ_cb, fail_cb) {
|
||||
s: "W0RFXVN0ZXZlIGxpa2VzIGJpZyBidXR0cw==", // signature of some kind
|
||||
lang: "en",
|
||||
date: 1501230947855458660, // ???
|
||||
ClientType: "webui",
|
||||
ClientType: registerSubmit ? "" : "webui",
|
||||
PS: "W0RFXVN0ZXZlIGxpa2VzIGJpZyBidXR0cw==" // anti-cheat data
|
||||
})
|
||||
});
|
||||
req.done(succ_cb);
|
||||
req.fail(fail_cb);
|
||||
req.always(() => {
|
||||
loginOrRegisterPending = false;
|
||||
});
|
||||
}
|
||||
|
||||
function revalidateAuthz(succ_cb) {
|
||||
@ -436,17 +446,35 @@ function updateInventory() {
|
||||
category != "SpaceSuits" &&
|
||||
category != "Sentinels" &&
|
||||
category != "Hoverboards" &&
|
||||
category != "MechSuits"
|
||||
category != "MechSuits" &&
|
||||
category != "MoaPets" &&
|
||||
category != "KubrowPets"
|
||||
) {
|
||||
maxXP /= 2;
|
||||
}
|
||||
|
||||
if (item.XP < maxXP) {
|
||||
let anyExaltedMissingXP = false;
|
||||
if (item.XP >= maxXP && "exalted" in itemMap[item.ItemType]) {
|
||||
for (const exaltedType of itemMap[item.ItemType].exalted) {
|
||||
const exaltedItem = data.SpecialItems.find(x => x.ItemType == exaltedType);
|
||||
if (exaltedItem) {
|
||||
const exaltedCap = itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000;
|
||||
if (exaltedItem.XP < exaltedCap) {
|
||||
anyExaltedMissingXP = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item.XP < maxXP || anyExaltedMissingXP) {
|
||||
const a = document.createElement("a");
|
||||
a.href = "#";
|
||||
a.onclick = function (event) {
|
||||
event.preventDefault();
|
||||
addGearExp(category, item.ItemId.$oid, maxXP - item.XP);
|
||||
if (item.XP < maxXP) {
|
||||
addGearExp(category, item.ItemId.$oid, maxXP - item.XP);
|
||||
}
|
||||
if ("exalted" in itemMap[item.ItemType]) {
|
||||
for (const exaltedType of itemMap[item.ItemType].exalted) {
|
||||
const exaltedItem = data.SpecialItems.find(x => x.ItemType == exaltedType);
|
||||
@ -786,7 +814,7 @@ function updateInventory() {
|
||||
{
|
||||
const td = document.createElement("td");
|
||||
td.classList = "text-end text-nowrap";
|
||||
{
|
||||
if (maxRank != 0) {
|
||||
const a = document.createElement("a");
|
||||
a.href = "#";
|
||||
a.onclick = function (event) {
|
||||
@ -1594,6 +1622,31 @@ function doAddAllMods() {
|
||||
});
|
||||
}
|
||||
|
||||
function doRemoveUnrankedMods() {
|
||||
revalidateAuthz(() => {
|
||||
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
|
||||
req.done(inventory => {
|
||||
window.itemListPromise.then(itemMap => {
|
||||
$.post({
|
||||
url: "/api/sell.php?" + window.authz,
|
||||
contentType: "text/plain",
|
||||
data: JSON.stringify({
|
||||
SellCurrency: "SC_RegularCredits",
|
||||
SellPrice: 0,
|
||||
Items: {
|
||||
Upgrades: inventory.RawUpgrades.filter(
|
||||
x => !itemMap[x.ItemType]?.parazon && x.ItemCount > 0
|
||||
).map(x => ({ String: x.ItemType, Count: x.ItemCount }))
|
||||
}
|
||||
})
|
||||
}).done(function () {
|
||||
updateInventory();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Powersuit Route
|
||||
|
||||
single.getRoute("#powersuit-route").on("beforeload", function () {
|
||||
|
@ -54,12 +54,13 @@ dict = {
|
||||
code_completed: `Abgeschlossen`,
|
||||
code_active: `Aktiv`,
|
||||
code_pigment: `Pigment`,
|
||||
code_mature: `[UNTRANSLATED] Mature for combat`,
|
||||
code_unmature: `[UNTRANSLATED] Regress genetic aging`,
|
||||
code_mature: `Für den Kampf auswachsen lassen`,
|
||||
code_unmature: `Genetisches Altern zurücksetzen`,
|
||||
login_description: `Melde dich mit deinem OpenWF-Account an (denselben Angaben wie im Spiel, wenn du dich mit diesem Server verbindest).`,
|
||||
login_emailLabel: `E-Mail-Adresse`,
|
||||
login_passwordLabel: `Passwort`,
|
||||
login_loginButton: `Anmelden`,
|
||||
login_registerButton: `[UNTRANSLATED] Register`,
|
||||
navbar_logout: `Abmelden`,
|
||||
navbar_renameAccount: `Account umbenennen`,
|
||||
navbar_deleteAccount: `Account löschen`,
|
||||
@ -82,7 +83,7 @@ dict = {
|
||||
inventory_operatorAmps: `Verstärker`,
|
||||
inventory_hoverboards: `K-Drives`,
|
||||
inventory_moaPets: `Moa`,
|
||||
inventory_kubrowPets: `[UNTRANSLATED] Beasts`,
|
||||
inventory_kubrowPets: `Bestien`,
|
||||
inventory_bulkAddSuits: `Fehlende Warframes hinzufügen`,
|
||||
inventory_bulkAddWeapons: `Fehlende Waffen hinzufügen`,
|
||||
inventory_bulkAddSpaceSuits: `Fehlende Archwings hinzufügen`,
|
||||
@ -115,6 +116,7 @@ dict = {
|
||||
mods_rivens: `Rivens`,
|
||||
mods_mods: `Mods`,
|
||||
mods_bulkAddMods: `Fehlende Mods hinzufügen`,
|
||||
mods_removeUnranked: `[UNTRANSLATED] Remove Unranked Mods`,
|
||||
cheats_administratorRequirement: `Du musst Administrator sein, um diese Funktion nutzen zu können. Um Administrator zu werden, füge <code>|DISPLAYNAME|</code> zu <code>administratorNames</code> in der config.json hinzu.`,
|
||||
cheats_server: `Server`,
|
||||
cheats_skipTutorial: `Tutorial überspringen`,
|
||||
|
@ -59,6 +59,7 @@ dict = {
|
||||
login_emailLabel: `Email address`,
|
||||
login_passwordLabel: `Password`,
|
||||
login_loginButton: `Login`,
|
||||
login_registerButton: `Register`,
|
||||
navbar_logout: `Logout`,
|
||||
navbar_renameAccount: `Rename Account`,
|
||||
navbar_deleteAccount: `Delete Account`,
|
||||
@ -114,6 +115,7 @@ dict = {
|
||||
mods_rivens: `Rivens`,
|
||||
mods_mods: `Mods`,
|
||||
mods_bulkAddMods: `Add Missing Mods`,
|
||||
mods_removeUnranked: `Remove Unranked Mods`,
|
||||
cheats_administratorRequirement: `You must be an administrator to use this feature. To become an administrator, add <code>|DISPLAYNAME|</code> to <code>administratorNames</code> in the config.json.`,
|
||||
cheats_server: `Server`,
|
||||
cheats_skipTutorial: `Skip Tutorial`,
|
||||
|
@ -60,6 +60,7 @@ dict = {
|
||||
login_emailLabel: `Dirección de correo electrónico`,
|
||||
login_passwordLabel: `Contraseña`,
|
||||
login_loginButton: `Iniciar sesión`,
|
||||
login_registerButton: `[UNTRANSLATED] Register`,
|
||||
navbar_logout: `Cerrar sesión`,
|
||||
navbar_renameAccount: `Renombrar cuenta`,
|
||||
navbar_deleteAccount: `Eliminar cuenta`,
|
||||
@ -115,6 +116,7 @@ dict = {
|
||||
mods_rivens: `Agrietados`,
|
||||
mods_mods: `Mods`,
|
||||
mods_bulkAddMods: `Agregar mods faltantes`,
|
||||
mods_removeUnranked: `[UNTRANSLATED] Remove Unranked Mods`,
|
||||
cheats_administratorRequirement: `Debes ser administrador para usar esta función. Para convertirte en administrador, agrega <code>|DISPLAYNAME|</code> a <code>administratorNames</code> en el archivo config.json.`,
|
||||
cheats_server: `Servidor`,
|
||||
cheats_skipTutorial: `Omitir tutorial`,
|
||||
|
@ -25,10 +25,10 @@ dict = {
|
||||
code_renamePrompt: `Nouveau nom :`,
|
||||
code_remove: `Retirer`,
|
||||
code_addItemsConfirm: `Ajouter |COUNT| items à l'inventaire ?`,
|
||||
code_succRankUp: `[UNTRANSLATED] Successfully ranked up.`,
|
||||
code_noEquipmentToRankUp: `No equipment to rank up.`,
|
||||
code_succRankUp: `Montée de niveau effectuée.`,
|
||||
code_noEquipmentToRankUp: `Aucun équipement à monter de niveau.`,
|
||||
code_succAdded: `Ajouté.`,
|
||||
code_succRemoved: `[UNTRANSLATED] Successfully removed.`,
|
||||
code_succRemoved: `Retiré.`,
|
||||
code_buffsNumber: `Nombre de buffs`,
|
||||
code_cursesNumber: `Nombre de débuffs`,
|
||||
code_rerollsNumber: `Nombre de rerolls`,
|
||||
@ -45,21 +45,22 @@ dict = {
|
||||
code_zanukaA: `Molosse Dorma`,
|
||||
code_zanukaB: `Molosse Bhaira`,
|
||||
code_zanukaC: `Molosse Hec`,
|
||||
code_stage: `[UNTRANSLATED] Stage`,
|
||||
code_complete: `[UNTRANSLATED] Complete`,
|
||||
code_nextStage: `[UNTRANSLATED] Next stage`,
|
||||
code_prevStage: `[UNTRANSLATED] Previous stage`,
|
||||
code_reset: `[UNTRANSLATED] Reset`,
|
||||
code_setInactive: `[UNTRANSLATED] Make the quest inactive`,
|
||||
code_completed: `[UNTRANSLATED] Completed`,
|
||||
code_active: `[UNTRANSLATED] Active`,
|
||||
code_stage: `Étape`,
|
||||
code_complete: `Compléter`,
|
||||
code_nextStage: `Étape suivante`,
|
||||
code_prevStage: `Étape précédente`,
|
||||
code_reset: `Réinitialiser`,
|
||||
code_setInactive: `Rendre la quête inactive`,
|
||||
code_completed: `Complétée`,
|
||||
code_active: `Active`,
|
||||
code_pigment: `Pigment`,
|
||||
code_mature: `[UNTRANSLATED] Mature for combat`,
|
||||
code_unmature: `[UNTRANSLATED] Regress genetic aging`,
|
||||
code_mature: `Maturer pour le combat`,
|
||||
code_unmature: `Régrésser l'âge génétique`,
|
||||
login_description: `Connexion avec les informations de connexion OpenWF.`,
|
||||
login_emailLabel: `Email`,
|
||||
login_passwordLabel: `Mot de passe`,
|
||||
login_loginButton: `Connexion`,
|
||||
login_registerButton: `[UNTRANSLATED] Register`,
|
||||
navbar_logout: `Déconnexion`,
|
||||
navbar_renameAccount: `Renommer le compte`,
|
||||
navbar_deleteAccount: `Supprimer le compte`,
|
||||
@ -82,7 +83,7 @@ dict = {
|
||||
inventory_operatorAmps: `Amplificateurs`,
|
||||
inventory_hoverboards: `K-Drives`,
|
||||
inventory_moaPets: `Moa`,
|
||||
inventory_kubrowPets: `[UNTRANSLATED] Beasts`,
|
||||
inventory_kubrowPets: `Bêtes`,
|
||||
inventory_bulkAddSuits: `Ajouter les Warframes manquantes`,
|
||||
inventory_bulkAddWeapons: `Ajouter les armes manquantes`,
|
||||
inventory_bulkAddSpaceSuits: `Ajouter les Archwings manquants`,
|
||||
@ -107,14 +108,15 @@ dict = {
|
||||
currency_PrimeTokens: `Aya Raffiné`,
|
||||
currency_owned: `|COUNT| possédés.`,
|
||||
powersuit_archonShardsLabel: `Emplacements de fragments d'Archonte`,
|
||||
powersuit_archonShardsDescription: `Slots illimités pour appliquer plusieurs améliorations.`,
|
||||
powersuit_archonShardsDescription2: `[UNTRANSLATED] Note that each archon shard takes some time to be applied when loading in.`,
|
||||
powersuit_archonShardsDescription: `Slots illimités pour appliquer plusieurs améliorations`,
|
||||
powersuit_archonShardsDescription2: `Un délai sera présent entre l'application des éclats et le chargement en jeu.`,
|
||||
mods_addRiven: `Ajouter un riven`,
|
||||
mods_fingerprint: `Empreinte`,
|
||||
mods_fingerprintHelp: `Besoin d'aide pour l'empreinte ?`,
|
||||
mods_rivens: `Rivens`,
|
||||
mods_mods: `Mods`,
|
||||
mods_bulkAddMods: `Ajouter les mods manquants`,
|
||||
mods_removeUnranked: `[UNTRANSLATED] Remove Unranked Mods`,
|
||||
cheats_administratorRequirement: `Rôle d'administrateur requis pour cette fonctionnalité. Ajoutez <code>|DISPLAYNAME|</code> à la ligne <code>administratorNames</code> dans le fichier config.json.`,
|
||||
cheats_server: `Serveur`,
|
||||
cheats_skipTutorial: `Passer le tutoriel`,
|
||||
@ -131,32 +133,32 @@ dict = {
|
||||
cheats_unlockAllFlavourItems: `Débloquer tous les <abbr title=\"Animations, Glyphes, Palettes, etc.\">Flavor Items</abbr>`,
|
||||
cheats_unlockAllSkins: `Débloquer tous les skins`,
|
||||
cheats_unlockAllCapturaScenes: `Débloquer toutes les scènes captura`,
|
||||
cheats_unlockAllDecoRecipes: `[UNTRANSLATED] Unlock All Dojo Deco Recipes`,
|
||||
cheats_unlockAllDecoRecipes: `Débloquer toutes les recherches dojo`,
|
||||
cheats_universalPolarityEverywhere: `Polarités universelles partout`,
|
||||
cheats_unlockDoubleCapacityPotatoesEverywhere: `Réacteurs et Catalyseurs partout`,
|
||||
cheats_unlockExilusEverywhere: `Adaptateurs Exilus partout`,
|
||||
cheats_unlockArcanesEverywhere: `Adaptateur d'Arcanes partout`,
|
||||
cheats_noDailyStandingLimits: `Pas de limite de réputation journalière`,
|
||||
cheats_noDailyFocusLimit: `[UNTRANSLATED] No Daily Focus Limits`,
|
||||
cheats_noArgonCrystalDecay: `[UNTRANSLATED] No Argon Crystal Decay`,
|
||||
cheats_noMasteryRankUpCooldown: `[UNTRANSLATED] No Mastery Rank Up Cooldown`,
|
||||
cheats_noVendorPurchaseLimits: `[UNTRANSLATED] No Vendor Purchase Limits`,
|
||||
cheats_noDeathMarks: `[UNTRANSLATED] No Death Marks`,
|
||||
cheats_noKimCooldowns: `[UNTRANSLATED] No KIM Cooldowns`,
|
||||
cheats_instantResourceExtractorDrones: `Ressources de drone d'extraction instantannées`,
|
||||
cheats_noResourceExtractorDronesDamage: `[UNTRANSLATED] No Resource Extractor Drones Damage`,
|
||||
cheats_noDojoRoomBuildStage: `No Dojo Room Build Stage`,
|
||||
cheats_noDojoDecoBuildStage: `[UNTRANSLATED] No Dojo Deco Build Stage`,
|
||||
cheats_fastDojoRoomDestruction: `[UNTRANSLATED] Fast Dojo Room Destruction`,
|
||||
cheats_noDailyStandingLimits: `Aucune limite de réputation journalière`,
|
||||
cheats_noDailyFocusLimit: `Aucune limite journalière de focus`,
|
||||
cheats_noArgonCrystalDecay: `Aucune désintégration des Cristaux d'Argon`,
|
||||
cheats_noMasteryRankUpCooldown: `Aucune attente pour la montée de rang de maîtrise`,
|
||||
cheats_noVendorPurchaseLimits: `Aucune limite d'achat chez les PNJ`,
|
||||
cheats_noDeathMarks: `Aucune marque d'assassin`,
|
||||
cheats_noKimCooldowns: `Aucun cooldown sur le KIM`,
|
||||
cheats_instantResourceExtractorDrones: `Ressources de drones d'extraction instantannées`,
|
||||
cheats_noResourceExtractorDronesDamage: `Aucun dégâts aux drones d'extraction de resources`,
|
||||
cheats_noDojoRoomBuildStage: `Aucune attente (construction des salles)`,
|
||||
cheats_noDojoDecoBuildStage: `Aucune attente (construction des décorations)`,
|
||||
cheats_fastDojoRoomDestruction: `Destruction de salle instantanée (Dojo)`,
|
||||
cheats_noDojoResearchCosts: `Aucun coût de recherche (Dojo)`,
|
||||
cheats_noDojoResearchTime: `Aucun temps de recherche (Dojo)`,
|
||||
cheats_fastClanAscension: `[UNTRANSLATED] Fast Clan Ascension`,
|
||||
cheats_spoofMasteryRank: `Spoofed Mastery Rank (-1 to disable)`,
|
||||
cheats_fastClanAscension: `Ascension de clan rapide`,
|
||||
cheats_spoofMasteryRank: `Rang de maîtrise personnalisé (-1 pour désactiver)`,
|
||||
cheats_saveSettings: `Sauvegarder les paramètres`,
|
||||
cheats_account: `Compte`,
|
||||
cheats_unlockAllFocusSchools: `Débloquer toutes les écoles de focus`,
|
||||
cheats_helminthUnlockAll: `Helminth niveau max`,
|
||||
cheats_intrinsicsUnlockAll: `[UNTRANSLATED] Max Rank All Intrinsics`,
|
||||
cheats_intrinsicsUnlockAll: `Inhérences niveau max`,
|
||||
cheats_changeSupportedSyndicate: `Allégeance`,
|
||||
cheats_changeButton: `Changer`,
|
||||
cheats_none: `Aucun`,
|
||||
|
@ -60,6 +60,7 @@ dict = {
|
||||
login_emailLabel: `Адрес электронной почты`,
|
||||
login_passwordLabel: `Пароль`,
|
||||
login_loginButton: `Войти`,
|
||||
login_registerButton: `[UNTRANSLATED] Register`,
|
||||
navbar_logout: `Выйти`,
|
||||
navbar_renameAccount: `Переименовать аккаунт`,
|
||||
navbar_deleteAccount: `Удалить аккаунт`,
|
||||
@ -108,13 +109,14 @@ dict = {
|
||||
currency_owned: `У тебя |COUNT|.`,
|
||||
powersuit_archonShardsLabel: `Ячейки осколков архонта`,
|
||||
powersuit_archonShardsDescription: `Вы можете использовать эти неограниченные ячейки для установки множества улучшений.`,
|
||||
powersuit_archonShardsDescription2: `[UNTRANSLATED] Note that each archon shard takes some time to be applied when loading in.`,
|
||||
powersuit_archonShardsDescription2: `Обратите внимание: каждый фрагмент архонта применяется с задержкой при загрузке.`,
|
||||
mods_addRiven: `Добавить Мод Разлома`,
|
||||
mods_fingerprint: `Отпечаток`,
|
||||
mods_fingerprintHelp: `Нужна помощь с отпечатком?`,
|
||||
mods_rivens: `Моды Разлома`,
|
||||
mods_mods: `Моды`,
|
||||
mods_bulkAddMods: `Добавить отсутствующие моды`,
|
||||
mods_removeUnranked: `[UNTRANSLATED] Remove Unranked Mods`,
|
||||
cheats_administratorRequirement: `Вы должны быть администратором для использования этой функции. Чтобы стать администратором, добавьте <code>\"|DISPLAYNAME|\"</code> в <code>administratorNames</code> в config.json.`,
|
||||
cheats_server: `Сервер`,
|
||||
cheats_skipTutorial: `Пропустить обучение`,
|
||||
@ -136,15 +138,15 @@ dict = {
|
||||
cheats_unlockDoubleCapacityPotatoesEverywhere: `Катализаторы везде`,
|
||||
cheats_unlockExilusEverywhere: `Адаптеры Эксилус везде`,
|
||||
cheats_unlockArcanesEverywhere: `Адаптеры для мистификаторов везде`,
|
||||
cheats_noDailyStandingLimits: `Без ежедневных ограничений репутации`,
|
||||
cheats_noDailyFocusLimit: `[UNTRANSLATED] No Daily Focus Limits`,
|
||||
cheats_noDailyStandingLimits: `Без ежедневных лимитов репутации`,
|
||||
cheats_noDailyFocusLimit: `Без ежедневных лимитов фокуса`,
|
||||
cheats_noArgonCrystalDecay: `Без распада аргоновых кристаллов`,
|
||||
cheats_noMasteryRankUpCooldown: `Повышение ранга мастерства без кулдауна`,
|
||||
cheats_noVendorPurchaseLimits: `Отсутствие лимитов на покупки у вендоров`,
|
||||
cheats_noDeathMarks: `[UNTRANSLATED] No Death Marks`,
|
||||
cheats_noKimCooldowns: `[UNTRANSLATED] No KIM Cooldowns`,
|
||||
cheats_noDeathMarks: `Без меток сметри`,
|
||||
cheats_noKimCooldowns: `Чаты KIM без кулдауна`,
|
||||
cheats_instantResourceExtractorDrones: `Мгновенные Экстракторы Ресурсов`,
|
||||
cheats_noResourceExtractorDronesDamage: `[UNTRANSLATED] No Resource Extractor Drones Damage`,
|
||||
cheats_noResourceExtractorDronesDamage: `Без урона по дронам-сборщикам`,
|
||||
cheats_noDojoRoomBuildStage: `Мгновенное Строительтво Комнат Додзё`,
|
||||
cheats_noDojoDecoBuildStage: `Мгновенное Строительтво Декораций Додзё`,
|
||||
cheats_fastDojoRoomDestruction: `Мгновенные Уничтожение Комнат Додзё`,
|
||||
|
@ -60,6 +60,7 @@ dict = {
|
||||
login_emailLabel: `电子邮箱`,
|
||||
login_passwordLabel: `密码`,
|
||||
login_loginButton: `登录`,
|
||||
login_registerButton: `[UNTRANSLATED] Register`,
|
||||
navbar_logout: `退出登录`,
|
||||
navbar_renameAccount: `重命名账户`,
|
||||
navbar_deleteAccount: `删除账户`,
|
||||
@ -115,6 +116,7 @@ dict = {
|
||||
mods_rivens: `裂罅MOD`,
|
||||
mods_mods: `Mods`,
|
||||
mods_bulkAddMods: `添加缺失MOD`,
|
||||
mods_removeUnranked: `[UNTRANSLATED] Remove Unranked Mods`,
|
||||
cheats_administratorRequirement: `您必须是管理员才能使用此功能。要成为管理员,请将 <code>|DISPLAYNAME|</code> 添加到 config.json 的 <code>administratorNames</code> 中。`,
|
||||
cheats_server: `服务器`,
|
||||
cheats_skipTutorial: `跳过教程`,
|
||||
|
Loading…
x
Reference in New Issue
Block a user