Compare commits

...

10 Commits

Author SHA1 Message Date
0fbf300d3e refactor: don't pass undefined to getRandomMissionRewards (#913)
Reviewed-on: OpenWF/SpaceNinjaServer#913
Co-authored-by: Ordis <134585663+OrdisPrime@users.noreply.github.com>
Co-committed-by: Ordis <134585663+OrdisPrime@users.noreply.github.com>
2025-02-06 07:11:31 -08:00
1fd801403f fix(webui): lowercase email address to match client (#912)
Reviewed-on: OpenWF/SpaceNinjaServer#912
Co-authored-by: Sainan <sainan@calamity.inc>
Co-committed-by: Sainan <sainan@calamity.inc>
2025-02-06 07:10:25 -08:00
78032f191c feat(webui): translations (#909)
Closes #900
Supersedes #903

Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>

Reviewed-on: OpenWF/SpaceNinjaServer#909
2025-02-06 07:00:21 -08:00
13c68a75c1 feat: initial stats save (#884)
Closes #203

Reviewed-on: http://209.141.38.3/OpenWF/SpaceNinjaServer/pulls/884
Reviewed-by: Sainan <sainan@calamity.inc>
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-02-06 04:42:59 -08:00
8175deb023 chore: get rid of instances of markModified (#908)
Closes #904

Reviewed-on: http://209.141.38.3/OpenWF/SpaceNinjaServer/pulls/908
Co-authored-by: Sainan <sainan@calamity.inc>
Co-committed-by: Sainan <sainan@calamity.inc>
2025-02-06 04:39:01 -08:00
1c82b90033 feat: obtaining crewship related items on mission update (#897)
Reviewed-on: http://209.141.38.3/OpenWF/SpaceNinjaServer/pulls/897
Reviewed-by: Sainan <sainan@calamity.inc>
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-02-05 12:23:35 -08:00
d396fe8b5c fix: handle acquisition of modular weapon parts (#906)
Fixes #905

Reviewed-on: http://209.141.38.3/OpenWF/SpaceNinjaServer/pulls/906
2025-02-05 09:00:20 -08:00
1351e73961 chore(webui): clarify what credentials are required (#902)
Reviewed-on: http://209.141.38.3/OpenWF/SpaceNinjaServer/pulls/902
2025-02-05 06:37:31 -08:00
4353c67867 fix: delete inbox messages when deleting account (#899)
Reviewed-on: http://209.141.38.3/OpenWF/SpaceNinjaServer/pulls/899
Co-authored-by: Sainan <sainan@calamity.inc>
Co-committed-by: Sainan <sainan@calamity.inc>
2025-02-05 05:54:24 -08:00
8633696dc8 chore: update tunablesController (#901)
Reviewed-on: http://209.141.38.3/OpenWF/SpaceNinjaServer/pulls/901
2025-02-04 19:13:48 -08:00
23 changed files with 1085 additions and 264 deletions

View File

@ -9,7 +9,8 @@
"build": "tsc && copyfiles static/webui/** build", "build": "tsc && copyfiles static/webui/** build",
"lint": "eslint --ext .ts .", "lint": "eslint --ext .ts .",
"lint:fix": "eslint --fix --ext .ts .", "lint:fix": "eslint --fix --ext .ts .",
"prettier": "prettier --write ." "prettier": "prettier --write .",
"update-translations": "cd scripts && node update-translations.js"
}, },
"license": "GNU", "license": "GNU",
"dependencies": { "dependencies": {

View File

@ -0,0 +1,46 @@
// Based on http://209.141.38.3/OpenWF/Translations/src/branch/main/update.php
// Converted via ChatGPT-4o
const fs = require('fs');
function extractStrings(content) {
const regex = /([a-zA-Z_]+): `([^`]*)`,/g;
let matches;
const strings = {};
while ((matches = regex.exec(content)) !== null) {
strings[matches[1]] = matches[2];
}
return strings;
}
const source = fs.readFileSync("../static/webui/translations/en.js", "utf8");
const sourceStrings = extractStrings(source);
const sourceLines = source.split("\n");
fs.readdirSync("../static/webui/translations").forEach(file => {
if (fs.lstatSync(`../static/webui/translations/${file}`).isFile() && file !== "en.js") {
const content = fs.readFileSync(`../static/webui/translations/${file}`, "utf8");
const targetStrings = extractStrings(content);
const contentLines = content.split("\n");
const fileHandle = fs.openSync(`../static/webui/translations/${file}`, "w");
fs.writeSync(fileHandle, contentLines[0] + "\n");
sourceLines.forEach(line => {
const strings = extractStrings(line);
if (Object.keys(strings).length > 0) {
Object.entries(strings).forEach(([key, value]) => {
if (targetStrings.hasOwnProperty(key)) {
fs.writeSync(fileHandle, `\t${key}: \`${targetStrings[key]}\`,\n`);
} else {
fs.writeSync(fileHandle, `\t${key}: \`[UNTRANSLATED] ${value}\`,\n`);
}
});
} else {
fs.writeSync(fileHandle, line + "\n");
}
});
fs.closeSync(fileHandle);
}
});

View File

@ -12,7 +12,7 @@ export const artifactsController: RequestHandler = async (req, res) => {
const { Upgrade, LevelDiff, Cost, FusionPointCost } = artifactsData; const { Upgrade, LevelDiff, Cost, FusionPointCost } = artifactsData;
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
const { Upgrades, RawUpgrades } = inventory; const { Upgrades } = inventory;
const { ItemType, UpgradeFingerprint, ItemId } = Upgrade; const { ItemType, UpgradeFingerprint, ItemId } = Upgrade;
const safeUpgradeFingerprint = UpgradeFingerprint || '{"lvl":0}'; const safeUpgradeFingerprint = UpgradeFingerprint || '{"lvl":0}';
@ -32,13 +32,7 @@ export const artifactsController: RequestHandler = async (req, res) => {
ItemType ItemType
}) - 1; }) - 1;
const rawItemIndex = RawUpgrades.findIndex(rawUpgrade => rawUpgrade.ItemType === ItemType); addMods(inventory, [{ ItemType, ItemCount: -1 }]);
RawUpgrades[rawItemIndex].ItemCount--;
if (RawUpgrades[rawItemIndex].ItemCount > 0) {
inventory.markModified(`RawUpgrades.${rawItemIndex}.UpgradeFingerprint`);
} else {
RawUpgrades.splice(rawItemIndex, 1);
}
} }
if (!config.infiniteCredits) { if (!config.infiniteCredits) {

View File

@ -2,11 +2,7 @@ import { RequestHandler } from "express";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { IMissionInventoryUpdateRequest } from "@/src/types/requestTypes"; import { IMissionInventoryUpdateRequest } from "@/src/types/requestTypes";
import { import { addMissionInventoryUpdates, addMissionRewards } from "@/src/services/missionInventoryUpdateService";
addMissionInventoryUpdates,
addMissionRewards,
calculateFinalCredits
} from "@/src/services/missionInventoryUpdateService";
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
import { getInventoryResponse } from "./inventoryController"; import { getInventoryResponse } from "./inventoryController";
@ -49,7 +45,7 @@ import { getInventoryResponse } from "./inventoryController";
- [ ] FpsMax - [ ] FpsMax
- [ ] FpsSamples - [ ] FpsSamples
*/ */
//move credit calc in here, return MissionRewards: [] if no reward info
// eslint-disable-next-line @typescript-eslint/no-misused-promises // eslint-disable-next-line @typescript-eslint/no-misused-promises
export const missionInventoryUpdateController: RequestHandler = async (req, res): Promise<void> => { export const missionInventoryUpdateController: RequestHandler = async (req, res): Promise<void> => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
@ -67,18 +63,8 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
}); });
return; return;
} }
const missionRewardsResults = await addMissionRewards(inventory, missionReport);
if (!missionRewardsResults) {
throw new Error("Failed to add mission rewards, should not happen");
}
const { MissionRewards, inventoryChanges, missionCompletionCredits } = missionRewardsResults;
//creditBonus is not correct for mirage mission 3 const { MissionRewards, inventoryChanges, credits } = await addMissionRewards(inventory, missionReport);
const credits = calculateFinalCredits(inventory, {
missionCompletionCredits,
missionDropCredits: missionReport.RegularCredits ?? 0,
rngRewardCredits: inventoryChanges.RegularCredits as number
});
await inventory.save(); await inventory.save();
const inventoryResponse = await getInventoryResponse(inventory, true); const inventoryResponse = await getInventoryResponse(inventory, true);
@ -90,7 +76,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
MissionRewards, MissionRewards,
...credits, ...credits,
...inventoryUpdates, ...inventoryUpdates,
FusionPoints: inventoryChanges.FusionPoints FusionPoints: inventoryChanges?.FusionPoints
}); });
}; };

View File

@ -1,19 +1,23 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { Account } from "@/src/models/loginModel"; import { Account } from "@/src/models/loginModel";
import { Inbox } from "@/src/models/inboxModel";
import { Inventory } from "@/src/models/inventoryModels/inventoryModel"; import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
import { Loadout } from "@/src/models/inventoryModels/loadoutModel"; import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
import { PersonalRooms } from "@/src/models/personalRoomsModel"; import { PersonalRooms } from "@/src/models/personalRoomsModel";
import { Ship } from "@/src/models/shipModel"; import { Ship } from "@/src/models/shipModel";
import { Stats } from "@/src/models/statsModel";
export const deleteAccountController: RequestHandler = async (req, res) => { export const deleteAccountController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
await Promise.all([ await Promise.all([
Account.deleteOne({ _id: accountId }), Account.deleteOne({ _id: accountId }),
Inbox.deleteMany({ ownerId: accountId }),
Inventory.deleteOne({ accountOwnerId: accountId }), Inventory.deleteOne({ accountOwnerId: accountId }),
Loadout.deleteOne({ loadoutOwnerId: accountId }), Loadout.deleteOne({ loadoutOwnerId: accountId }),
PersonalRooms.deleteOne({ personalRoomsOwnerId: accountId }), PersonalRooms.deleteOne({ personalRoomsOwnerId: accountId }),
Ship.deleteOne({ ShipOwnerId: accountId }) Ship.deleteOne({ ShipOwnerId: accountId }),
Stats.deleteOne({ accountOwnerId: accountId })
]); ]);
res.end(); res.end();
}; };

View File

@ -1,15 +1,23 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
// This endpoint is specific to the OpenWF Bootstrapper: https://openwf.io/bootstrapper-manual
interface ITunables { interface ITunables {
prohibit_skip_mission_start_timer?: any; prohibit_skip_mission_start_timer?: boolean;
prohibit_fov_override?: any; prohibit_fov_override?: boolean;
prohibit_freecam?: boolean;
prohibit_teleport?: boolean;
prohibit_scripts?: boolean;
} }
const tunablesController: RequestHandler = (_req, res) => { const tunablesController: RequestHandler = (_req, res) => {
const tunablesSet: ITunables = {}; const tunables: ITunables = {};
//tunablesSet.prohibit_skip_mission_start_timer = 1; //tunables.prohibit_skip_mission_start_timer = true;
//tunablesSet.prohibit_fov_override = 1; //tunables.prohibit_fov_override = true;
res.json(tunablesSet); //tunables.prohibit_freecam = true;
//tunables.prohibit_teleport = true;
//tunables.prohibit_scripts = true;
res.json(tunables);
}; };
export { tunablesController }; export { tunablesController };

View File

@ -1,6 +1,14 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getStats, uploadStats } from "@/src/services/statsService";
import { IStatsUpload } from "@/src/types/statTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
const uploadController: RequestHandler = (_req, res) => { const uploadController: RequestHandler = async (req, res) => {
const payload = getJSONfromString<IStatsUpload>(String(req.body));
const accountId = await getAccountIdForRequest(req);
const playerStats = await getStats(accountId);
await uploadStats(playerStats, payload);
res.status(200).end(); res.status(200).end();
}; };

View File

@ -1,31 +1,33 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { IStatsView } from "@/src/types/statTypes";
import { config } from "@/src/services/configService"; import { config } from "@/src/services/configService";
import allScans from "@/static/fixed_responses/allScans.json"; import allScans from "@/static/fixed_responses/allScans.json";
import { ExportEnemies } from "warframe-public-export-plus"; import { ExportEnemies } from "warframe-public-export-plus";
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
import { getStats } from "@/src/services/statsService";
import { IStatsView } from "@/src/types/statTypes";
const viewController: RequestHandler = async (req, res) => { const viewController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "XPInfo"); const inventory = await getInventory(accountId, "XPInfo");
const playerStats = await getStats(accountId);
const responseJson: IStatsView = {}; const responseJson: IStatsView = playerStats.toJSON();
responseJson.Weapons = []; responseJson.Weapons ??= [];
for (const item of inventory.XPInfo) { for (const item of inventory.XPInfo) {
responseJson.Weapons.push({ const weaponIndex = responseJson.Weapons.findIndex(element => element.type == item.ItemType);
type: item.ItemType, if (weaponIndex !== -1) {
xp: item.XP responseJson.Weapons[weaponIndex].xp == item.XP;
}); } else {
responseJson.Weapons.push({ type: item.ItemType, xp: item.XP });
}
} }
if (config.unlockAllScans) { if (config.unlockAllScans) {
const scans = new Set(allScans); const scans = new Set(allScans);
for (const type of Object.keys(ExportEnemies.avatars)) { for (const type of Object.keys(ExportEnemies.avatars)) {
if (!scans.has(type)) { if (!scans.has(type)) scans.add(type);
scans.add(type);
}
} }
responseJson.Scans = []; responseJson.Scans ??= [];
for (const type of scans) { for (const type of scans) {
responseJson.Scans.push({ type: type, scans: 9999 }); responseJson.Scans.push({ type: type, scans: 9999 });
} }

View File

@ -25,6 +25,7 @@ import {
ISettings, ISettings,
IInfestedFoundryDatabase, IInfestedFoundryDatabase,
IHelminthResource, IHelminthResource,
IMissionDatabase,
IConsumedSuit, IConsumedSuit,
IQuestStage, IQuestStage,
IQuestKeyDatabase, IQuestKeyDatabase,
@ -482,6 +483,15 @@ const helminthResourceSchema = new Schema<IHelminthResource>(
{ _id: false } { _id: false }
); );
const missionSchema = new Schema<IMissionDatabase>(
{
Tag: String,
Completes: { type: Number, default: 0 },
Tier: { type: Number, required: false }
},
{ _id: false }
);
const questProgressSchema = new Schema<IQuestStage>( const questProgressSchema = new Schema<IQuestStage>(
{ {
c: Number, c: Number,
@ -997,7 +1007,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
ShipDecorations: [typeCountSchema], ShipDecorations: [typeCountSchema],
//Railjack/Components(https://warframe.fandom.com/wiki/Railjack/Components) //Railjack/Components(https://warframe.fandom.com/wiki/Railjack/Components)
CrewShipRawSalvage: [Schema.Types.Mixed], CrewShipRawSalvage: [typeCountSchema],
//Default RailJack //Default RailJack
CrewShipAmmo: [typeCountSchema], CrewShipAmmo: [typeCountSchema],
@ -1010,7 +1020,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
CrewShipSalvagedWeapons: [Schema.Types.Mixed], CrewShipSalvagedWeapons: [Schema.Types.Mixed],
//Complete Mission\Quests //Complete Mission\Quests
Missions: [Schema.Types.Mixed], Missions: [missionSchema],
QuestKeys: [questKeysSchema], QuestKeys: [questKeysSchema],
ActiveQuest: { type: String, default: "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain" }, //TODO: check after mission starting gear ActiveQuest: { type: String, default: "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain" }, //TODO: check after mission starting gear
//item like DojoKey or Boss missions key //item like DojoKey or Boss missions key

101
src/models/statsModel.ts Normal file
View File

@ -0,0 +1,101 @@
import { Document, Schema, Types, model } from "mongoose";
import { IEnemy, IMission, IScan, ITutorial, IAbility, IWeapon, IStatsDatabase } from "@/src/types/statTypes";
const abilitySchema = new Schema<IAbility>(
{
type: { type: String, required: true },
used: Number
},
{ _id: false }
);
const enemySchema = new Schema<IEnemy>(
{
type: { type: String, required: true },
executions: Number,
headshots: Number,
kills: Number,
assists: Number,
deaths: Number
},
{ _id: false }
);
const missionSchema = new Schema<IMission>(
{
type: { type: String, required: true },
highScore: Number
},
{ _id: false }
);
const scanSchema = new Schema<IScan>(
{
type: { type: String, required: true },
scans: Number
},
{ _id: false }
);
const tutorialSchema = new Schema<ITutorial>(
{
stage: Number
},
{ _id: false }
);
const weaponSchema = new Schema<IWeapon>(
{
type: { type: String, required: true },
equipTime: Number,
hits: Number,
kills: Number,
xp: Number,
assists: Number,
headshots: Number,
fired: Number
},
{ _id: false }
);
const statsSchema = new Schema<IStatsDatabase>({
accountOwnerId: { type: Schema.Types.ObjectId, required: true },
CiphersSolved: Number,
CiphersFailed: Number,
CipherTime: Number,
Weapons: { type: [weaponSchema], default: [] },
Enemies: { type: [enemySchema], default: [] },
MeleeKills: Number,
MissionsCompleted: Number,
MissionsQuit: Number,
MissionsFailed: Number,
TimePlayedSec: Number,
PickupCount: Number,
Tutorial: { type: Map, of: tutorialSchema, default: {} },
Abilities: { type: [abilitySchema], default: [] },
Rating: Number,
Income: Number,
Rank: Number,
PlayerLevel: Number,
Scans: { type: [scanSchema], default: [] },
Missions: { type: [missionSchema], default: [] },
Deaths: Number,
HealCount: Number,
ReviveCount: Number
});
statsSchema.set("toJSON", {
transform(_document, returnedObject) {
delete returnedObject._id;
delete returnedObject.__v;
delete returnedObject.accountOwnerId;
}
});
export const Stats = model<IStatsDatabase>("Stats", statsSchema);
// eslint-disable-next-line @typescript-eslint/ban-types
export type TStatsDatabaseDocument = Document<unknown, {}, IStatsDatabase> & {
_id: Types.ObjectId;
__v: number;
} & IStatsDatabase;

View File

@ -54,4 +54,9 @@ webuiRouter.get("/webui/riven-tool/RivenParser.js", (_req, res) => {
res.sendFile(path.join(repoDir, "node_modules/warframe-riven-info/RivenParser.js")); res.sendFile(path.join(repoDir, "node_modules/warframe-riven-info/RivenParser.js"));
}); });
// Serve translations
webuiRouter.get("/translations/:file", (req, res) => {
res.sendFile(path.join(rootDir, `static/webui/translations/${req.params.file}`));
});
export { webuiRouter }; export { webuiRouter };

View File

@ -322,7 +322,6 @@ export const addItem = async (
} }
if (typeName in ExportWeapons) { if (typeName in ExportWeapons) {
const weapon = ExportWeapons[typeName]; const weapon = ExportWeapons[typeName];
// Many non-weapon items are "Pistols" in Public Export, so some duck typing is needed.
if (weapon.totalDamage != 0) { if (weapon.totalDamage != 0) {
const inventoryChanges = addEquipment(inventory, weapon.productCategory, typeName); const inventoryChanges = addEquipment(inventory, weapon.productCategory, typeName);
updateSlots(inventory, InventorySlot.WEAPONS, 0, 1); updateSlots(inventory, InventorySlot.WEAPONS, 0, 1);
@ -332,6 +331,20 @@ export const addItem = async (
WeaponBin: { count: 1, platinum: 0, Slots: -1 } WeaponBin: { count: 1, platinum: 0, Slots: -1 }
} }
}; };
} else {
// Modular weapon parts
const miscItemChanges = [
{
ItemType: typeName,
ItemCount: quantity
} satisfies IMiscItem
];
addMiscItems(inventory, miscItemChanges);
return {
InventoryChanges: {
MiscItems: miscItemChanges
}
};
} }
} }
if (typeName in creditBundles) { if (typeName in creditBundles) {
@ -893,7 +906,6 @@ export const addShipDecorations = (
if (itemIndex !== -1) { if (itemIndex !== -1) {
ShipDecorations[itemIndex].ItemCount += ItemCount; ShipDecorations[itemIndex].ItemCount += ItemCount;
inventory.markModified(`ShipDecorations.${itemIndex}.ItemCount`);
} else { } else {
ShipDecorations.push({ ItemCount, ItemType }); ShipDecorations.push({ ItemCount, ItemType });
} }
@ -908,13 +920,43 @@ export const addConsumables = (inventory: TInventoryDatabaseDocument, itemsArray
if (itemIndex !== -1) { if (itemIndex !== -1) {
Consumables[itemIndex].ItemCount += ItemCount; Consumables[itemIndex].ItemCount += ItemCount;
inventory.markModified(`Consumables.${itemIndex}.ItemCount`);
} else { } else {
Consumables.push({ ItemCount, ItemType }); Consumables.push({ ItemCount, ItemType });
} }
}); });
}; };
export const addCrewShipRawSalvage = (
inventory: TInventoryDatabaseDocument,
itemsArray: ITypeCount[] | undefined
): void => {
const { CrewShipRawSalvage } = inventory;
itemsArray?.forEach(({ ItemCount, ItemType }) => {
const itemIndex = CrewShipRawSalvage.findIndex(i => i.ItemType === ItemType);
if (itemIndex !== -1) {
CrewShipRawSalvage[itemIndex].ItemCount += ItemCount;
} else {
CrewShipRawSalvage.push({ ItemCount, ItemType });
}
});
};
export const addCrewShipAmmo = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[] | undefined): void => {
const { CrewShipAmmo } = inventory;
itemsArray?.forEach(({ ItemCount, ItemType }) => {
const itemIndex = CrewShipAmmo.findIndex(i => i.ItemType === ItemType);
if (itemIndex !== -1) {
CrewShipAmmo[itemIndex].ItemCount += ItemCount;
} else {
CrewShipAmmo.push({ ItemCount, ItemType });
}
});
};
export const addRecipes = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[] | undefined): void => { export const addRecipes = (inventory: TInventoryDatabaseDocument, itemsArray: ITypeCount[] | undefined): void => {
const { Recipes } = inventory; const { Recipes } = inventory;
@ -923,7 +965,6 @@ export const addRecipes = (inventory: TInventoryDatabaseDocument, itemsArray: IT
if (itemIndex !== -1) { if (itemIndex !== -1) {
Recipes[itemIndex].ItemCount += ItemCount; Recipes[itemIndex].ItemCount += ItemCount;
inventory.markModified(`Recipes.${itemIndex}.ItemCount`);
} else { } else {
Recipes.push({ ItemCount, ItemType }); Recipes.push({ ItemCount, ItemType });
} }
@ -962,7 +1003,6 @@ export const addFusionTreasures = (
if (itemIndex !== -1) { if (itemIndex !== -1) {
FusionTreasures[itemIndex].ItemCount += ItemCount; FusionTreasures[itemIndex].ItemCount += ItemCount;
inventory.markModified(`FusionTreasures.${itemIndex}.ItemCount`);
} else { } else {
FusionTreasures.push({ ItemCount, ItemType, Sockets }); FusionTreasures.push({ ItemCount, ItemType, Sockets });
} }
@ -1033,7 +1073,6 @@ export const addChallenges = (
if (itemIndex !== -1) { if (itemIndex !== -1) {
category[itemIndex].Progress += Progress; category[itemIndex].Progress += Progress;
inventory.markModified(`ChallengeProgress.${itemIndex}.ItemCount`);
} else { } else {
category.push({ Name, Progress }); category.push({ Name, Progress });
} }
@ -1046,7 +1085,6 @@ export const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag,
if (itemIndex !== -1) { if (itemIndex !== -1) {
Missions[itemIndex].Completes += Completes; Missions[itemIndex].Completes += Completes;
inventory.markModified(`Missions.${itemIndex}.Completes`);
} else { } else {
Missions.push({ Tag, Completes }); Missions.push({ Tag, Completes });
} }
@ -1062,7 +1100,6 @@ export const addBooster = (ItemType: string, time: number, inventory: TInventory
if (itemIndex !== -1) { if (itemIndex !== -1) {
const existingBooster = Boosters[itemIndex]; const existingBooster = Boosters[itemIndex];
existingBooster.ExpiryDate = Math.max(existingBooster.ExpiryDate, currentTime) + time; existingBooster.ExpiryDate = Math.max(existingBooster.ExpiryDate, currentTime) + time;
inventory.markModified(`Boosters.${itemIndex}.ExpiryDate`);
} else { } else {
Boosters.push({ ItemType, ExpiryDate: currentTime + time }); Boosters.push({ ItemType, ExpiryDate: currentTime + time });
} }

View File

@ -7,6 +7,7 @@ import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
import { PersonalRooms } from "@/src/models/personalRoomsModel"; import { PersonalRooms } from "@/src/models/personalRoomsModel";
import { Request } from "express"; import { Request } from "express";
import { config } from "@/src/services/configService"; import { config } from "@/src/services/configService";
import { createStats } from "@/src/services/statsService";
export const isCorrectPassword = (requestPassword: string, databasePassword: string): boolean => { export const isCorrectPassword = (requestPassword: string, databasePassword: string): boolean => {
return requestPassword === databasePassword; return requestPassword === databasePassword;
@ -24,6 +25,7 @@ export const createAccount = async (accountData: IDatabaseAccount): Promise<IDat
const shipId = await createShip(account._id); const shipId = await createShip(account._id);
await createInventory(account._id, { loadOutPresetId: loadoutId, ship: shipId }); await createInventory(account._id, { loadOutPresetId: loadoutId, ship: shipId });
await createPersonalRooms(account._id, shipId); await createPersonalRooms(account._id, shipId);
await createStats(account._id.toString());
return account.toJSON(); return account.toJSON();
} catch (error) { } catch (error) {
if (error instanceof Error) { if (error instanceof Error) {

View File

@ -6,6 +6,8 @@ import { equipmentKeys, IInventoryDatabase, TEquipmentKey } from "@/src/types/in
import { import {
addChallenges, addChallenges,
addConsumables, addConsumables,
addCrewShipAmmo,
addCrewShipRawSalvage,
addFocusXpIncreases, addFocusXpIncreases,
addFusionTreasures, addFusionTreasures,
addGearExpByCategory, addGearExpByCategory,
@ -122,6 +124,7 @@ export const addMissionInventoryUpdates = (
addMods(inventory, value); addMods(inventory, value);
break; break;
case "MiscItems": case "MiscItems":
case "BonusMiscItems":
addMiscItems(inventory, value); addMiscItems(inventory, value);
break; break;
case "Consumables": case "Consumables":
@ -136,6 +139,12 @@ export const addMissionInventoryUpdates = (
case "FusionTreasures": case "FusionTreasures":
addFusionTreasures(inventory, value); addFusionTreasures(inventory, value);
break; break;
case "CrewShipRawSalvage":
addCrewShipRawSalvage(inventory, value);
break;
case "CrewShipAmmo":
addCrewShipAmmo(inventory, value);
break;
case "FusionBundles": { case "FusionBundles": {
let fusionPoints = 0; let fusionPoints = 0;
for (const fusionBundle of value) { for (const fusionBundle of value) {
@ -191,11 +200,17 @@ export const addMissionInventoryUpdates = (
//TODO: return type of partial missioninventoryupdate response //TODO: return type of partial missioninventoryupdate response
export const addMissionRewards = async ( export const addMissionRewards = async (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
{ RewardInfo: rewardInfo, LevelKeyName: levelKeyName, Missions: missions }: IMissionInventoryUpdateRequest {
RewardInfo: rewardInfo,
LevelKeyName: levelKeyName,
Missions: missions,
RegularCredits: creditDrops
}: IMissionInventoryUpdateRequest
) => { ) => {
if (!rewardInfo) { if (!rewardInfo) {
logger.warn("no reward info provided"); //TODO: if there is a case where you can have credits collected during a mission but no rewardInfo, add credits needs to be handled earlier
return; logger.debug(`Mission ${missions!.Tag} did not have Reward Info `);
return { MissionRewards: [] };
} }
//TODO: check double reward merging //TODO: check double reward merging
@ -206,11 +221,12 @@ export const addMissionRewards = async (
const inventoryChanges: IInventoryChanges = {}; const inventoryChanges: IInventoryChanges = {};
let missionCompletionCredits = 0; let missionCompletionCredits = 0;
//inventory change is what the client has not rewarded itself, credit updates seem to be taken from totalCredits //inventory change is what the client has not rewarded itself, also the client needs to know the credit changes for display
if (levelKeyName) { if (levelKeyName) {
const fixedLevelRewards = getLevelKeyRewards(levelKeyName); const fixedLevelRewards = getLevelKeyRewards(levelKeyName);
//logger.debug(`fixedLevelRewards ${fixedLevelRewards}`); //logger.debug(`fixedLevelRewards ${fixedLevelRewards}`);
for (const reward of fixedLevelRewards) { for (const reward of fixedLevelRewards) {
//quest stage completion credit rewards
if (reward.rewardType == "RT_CREDITS") { if (reward.rewardType == "RT_CREDITS") {
inventory.RegularCredits += reward.amount; inventory.RegularCredits += reward.amount;
missionCompletionCredits += reward.amount; missionCompletionCredits += reward.amount;
@ -223,6 +239,16 @@ export const addMissionRewards = async (
} }
} }
for (const reward of MissionRewards) {
//TODO: additem should take in storeItems
const inventoryChange = await addItem(inventory, reward.StoreItem.replace("StoreItems/", ""), reward.ItemCount);
//TODO: combineInventoryChanges improve type safety, merging 2 of the same item?
//TODO: check for the case when two of the same item are added, combineInventoryChanges should merge them, but the client also merges them
//TODO: some conditional types to rule out binchanges?
combineInventoryChanges(inventoryChanges, inventoryChange.InventoryChanges);
}
//node based credit rewards for mission completion
if (missions) { if (missions) {
const node = getNode(missions.Tag); const node = getNode(missions.Tag);
@ -234,27 +260,24 @@ export const addMissionRewards = async (
} }
} }
//TODO: resolve issue with creditbundles //creditBonus is not correct for mirage mission 3
for (const reward of MissionRewards) { const credits = addCredits(inventory, {
//TODO: additem should take in storeItems missionCompletionCredits,
const inventoryChange = await addItem(inventory, reward.StoreItem.replace("StoreItems/", ""), reward.ItemCount); missionDropCredits: creditDrops ?? 0,
//TODO: combineInventoryChanges improve type safety, merging 2 of the same item? rngRewardCredits: inventoryChanges.RegularCredits ?? 0
//TODO: check for the case when two of the same item are added, combineInventoryChanges should merge them });
//TODO: some conditional types to rule out binchanges?
combineInventoryChanges(inventoryChanges, inventoryChange.InventoryChanges);
}
return { inventoryChanges, MissionRewards, missionCompletionCredits }; return { inventoryChanges, MissionRewards, credits };
}; };
//might not be faithful to original //slightly inaccurate compared to official
//TODO: consider ActiveBoosters //TODO: consider ActiveBoosters
export const calculateFinalCredits = ( export const addCredits = (
inventory: HydratedDocument<IInventoryDatabase>, inventory: HydratedDocument<IInventoryDatabase>,
{ {
missionDropCredits, missionDropCredits,
missionCompletionCredits, missionCompletionCredits,
rngRewardCredits = 0 rngRewardCredits
}: { missionDropCredits: number; missionCompletionCredits: number; rngRewardCredits: number } }: { missionDropCredits: number; missionCompletionCredits: number; rngRewardCredits: number }
) => { ) => {
const hasDailyCreditBonus = true; const hasDailyCreditBonus = true;
@ -267,7 +290,7 @@ export const calculateFinalCredits = (
}; };
if (hasDailyCreditBonus) { if (hasDailyCreditBonus) {
inventory.RegularCredits += totalCredits; inventory.RegularCredits += missionCompletionCredits;
finalCredits.CreditBonus[1] *= 2; finalCredits.CreditBonus[1] *= 2;
finalCredits.MissionCredits[1] *= 2; finalCredits.MissionCredits[1] *= 2;
finalCredits.TotalCredits[1] *= 2; finalCredits.TotalCredits[1] *= 2;

View File

@ -0,0 +1,283 @@
import { Stats, TStatsDatabaseDocument } from "@/src/models/statsModel";
import { IStatsUpload } from "@/src/types/statTypes";
export const createStats = async (accountId: string): Promise<TStatsDatabaseDocument> => {
const stats = new Stats({ accountOwnerId: accountId });
await stats.save();
return stats;
};
export const getStats = async (accountOwnerId: string): Promise<TStatsDatabaseDocument> => {
let stats = await Stats.findOne({ accountOwnerId: accountOwnerId });
if (!stats) stats = await createStats(accountOwnerId);
return stats;
};
export const uploadStats = async (playerStats: TStatsDatabaseDocument, payload: IStatsUpload): Promise<void> => {
if (payload.add) {
const {
MISSION_COMPLETE,
PICKUP_ITEM,
SCAN,
USE_ABILITY,
FIRE_WEAPON,
HIT_ENTITY_ITEM,
HEADSHOT_ITEM,
KILL_ENEMY_ITEM,
KILL_ENEMY,
EXECUTE_ENEMY,
HEADSHOT,
DIE,
MELEE_KILL,
INCOME,
CIPHER
} = payload.add;
if (MISSION_COMPLETE) {
for (const [key, value] of Object.entries(MISSION_COMPLETE)) {
switch (key) {
case "GS_SUCCESS":
playerStats.MissionsCompleted ??= 0;
playerStats.MissionsCompleted += value;
break;
case "GS_QUIT":
playerStats.MissionsQuit ??= 0;
playerStats.MissionsQuit += value;
break;
case "GS_FAILURE":
playerStats.MissionsFailed ??= 0;
playerStats.MissionsFailed += value;
break;
}
}
}
if (PICKUP_ITEM) {
for (const value of Object.values(PICKUP_ITEM)) {
playerStats.PickupCount ??= 0;
playerStats.PickupCount += value;
}
}
if (SCAN) {
playerStats.Scans ??= [];
for (const [key, scans] of Object.entries(SCAN)) {
const scan = playerStats.Scans.find(element => element.type === key);
if (scan) {
scan.scans ??= 0;
scan.scans += scans;
} else {
playerStats.Scans.push({ type: key, scans });
}
}
}
if (USE_ABILITY) {
playerStats.Abilities ??= [];
for (const [key, used] of Object.entries(USE_ABILITY)) {
const ability = playerStats.Abilities.find(element => element.type === key);
if (ability) {
ability.used ??= 0;
ability.used += used;
} else {
playerStats.Abilities.push({ type: key, used });
}
}
}
if (FIRE_WEAPON) {
playerStats.Weapons ??= [];
for (const [key, fired] of Object.entries(FIRE_WEAPON)) {
const weapon = playerStats.Weapons.find(element => element.type === key);
if (weapon) {
weapon.fired ??= 0;
weapon.fired += fired;
} else {
playerStats.Weapons.push({ type: key, fired });
}
}
}
if (HIT_ENTITY_ITEM) {
playerStats.Weapons ??= [];
for (const [key, hits] of Object.entries(HIT_ENTITY_ITEM)) {
const weapon = playerStats.Weapons.find(element => element.type === key);
if (weapon) {
weapon.hits ??= 0;
weapon.hits += hits;
} else {
playerStats.Weapons.push({ type: key, hits });
}
}
}
if (HEADSHOT_ITEM) {
playerStats.Weapons ??= [];
for (const [key, headshots] of Object.entries(HEADSHOT_ITEM)) {
const weapon = playerStats.Weapons.find(element => element.type === key);
if (weapon) {
weapon.headshots ??= 0;
weapon.headshots += headshots;
} else {
playerStats.Weapons.push({ type: key, headshots });
}
}
}
if (KILL_ENEMY_ITEM) {
playerStats.Weapons ??= [];
for (const [key, kills] of Object.entries(KILL_ENEMY_ITEM)) {
const weapon = playerStats.Weapons.find(element => element.type === key);
if (weapon) {
weapon.kills ??= 0;
weapon.kills += kills;
} else {
playerStats.Weapons.push({ type: key, kills });
}
}
}
if (KILL_ENEMY) {
playerStats.Enemies ??= [];
for (const [key, kills] of Object.entries(KILL_ENEMY)) {
const enemy = playerStats.Enemies.find(element => element.type === key);
if (enemy) {
enemy.kills ??= 0;
enemy.kills += kills;
} else {
playerStats.Enemies.push({ type: key, kills });
}
}
}
if (EXECUTE_ENEMY) {
playerStats.Enemies ??= [];
for (const [key, executions] of Object.entries(EXECUTE_ENEMY)) {
const enemy = playerStats.Enemies.find(element => element.type === key);
if (enemy) {
enemy.executions ??= 0;
enemy.executions += executions;
} else {
playerStats.Enemies.push({ type: key, executions });
}
}
}
if (HEADSHOT) {
playerStats.Enemies ??= [];
for (const [key, headshots] of Object.entries(HEADSHOT)) {
const enemy = playerStats.Enemies.find(element => element.type === key);
if (enemy) {
enemy.headshots ??= 0;
enemy.headshots += headshots;
} else {
playerStats.Enemies.push({ type: key, headshots });
}
}
}
if (DIE) {
playerStats.Enemies ??= [];
for (const [key, deaths] of Object.entries(DIE)) {
playerStats.Deaths ??= 0;
playerStats.Deaths += deaths;
const enemy = playerStats.Enemies.find(element => element.type === key);
if (enemy) {
enemy.deaths ??= 0;
enemy.deaths += deaths;
} else {
playerStats.Enemies.push({ type: key, deaths });
}
}
}
if (MELEE_KILL) {
playerStats.MeleeKills ??= 0;
for (const kills of Object.values(MELEE_KILL)) {
playerStats.MeleeKills += kills;
}
}
if (INCOME) {
playerStats.Income ??= 0;
playerStats.Income += INCOME;
}
if (CIPHER) {
if (CIPHER["0"] > 0) {
playerStats.CiphersFailed ??= 0;
playerStats.CiphersFailed += CIPHER["0"];
}
if (CIPHER["1"] > 0) {
playerStats.CiphersSolved ??= 0;
playerStats.CiphersSolved += CIPHER["1"];
}
}
}
if (payload.timers) {
const { EQUIP_WEAPON, CURRENT_MISSION_TIME, CIPHER_TIME } = payload.timers;
if (EQUIP_WEAPON) {
playerStats.Weapons ??= [];
for (const [key, equipTime] of Object.entries(EQUIP_WEAPON)) {
const weapon = playerStats.Weapons.find(element => element.type === key);
if (weapon) {
weapon.equipTime ??= 0;
weapon.equipTime += equipTime;
} else {
playerStats.Weapons.push({ type: key, equipTime });
}
}
}
if (CURRENT_MISSION_TIME) {
playerStats.TimePlayedSec ??= 0;
playerStats.TimePlayedSec += CURRENT_MISSION_TIME;
}
if (CIPHER_TIME) {
playerStats.CipherTime ??= 0;
playerStats.CipherTime += CIPHER_TIME;
}
}
if (payload.max) {
const { WEAPON_XP, MISSION_SCORE } = payload.max;
if (WEAPON_XP) {
playerStats.Weapons ??= [];
for (const [key, xp] of Object.entries(WEAPON_XP)) {
const weapon = playerStats.Weapons.find(element => element.type === key);
if (weapon) {
weapon.xp = xp;
} else {
playerStats.Weapons.push({ type: key, xp });
}
}
}
if (MISSION_SCORE) {
playerStats.Missions ??= [];
for (const [key, highScore] of Object.entries(MISSION_SCORE)) {
const mission = playerStats.Missions.find(element => element.type === key);
if (mission) {
mission.highScore = highScore;
} else {
playerStats.Missions.push({ type: key, highScore });
}
}
}
}
if (payload.set) {
const { ELO_RATING, RANK, PLAYER_LEVEL } = payload.set;
if (ELO_RATING) playerStats.Rating = ELO_RATING;
if (RANK) playerStats.Rank = RANK;
if (PLAYER_LEVEL) playerStats.PlayerLevel = PLAYER_LEVEL;
}
await playerStats.save();
};

View File

@ -753,10 +753,13 @@ export interface ILotusCustomization extends IItemConfig {
Persona: string; Persona: string;
} }
export interface IMission { export interface IMissionDatabase {
Tag: string;
Completes: number; Completes: number;
Tier?: number; Tier?: number;
Tag: string; }
export interface IMission extends IMissionDatabase {
RewardsCooldownTime?: IMongoDate; RewardsCooldownTime?: IMongoDate;
} }

View File

@ -35,18 +35,23 @@ export interface IUpdateChallengeProgressRequest {
} }
export type IMissionInventoryUpdateRequest = { export type IMissionInventoryUpdateRequest = {
MiscItems?: ITypeCount[];
Recipes?: ITypeCount[];
FusionBundles?: ITypeCount[];
Consumables?: ITypeCount[];
FusionBundels?: ITypeCount[];
CrewShipRawSalvage?: ITypeCount[];
CrewShipAmmo?: ITypeCount[];
BonusMiscItems?: ITypeCount[];
AffiliationChanges?: IAffiliationChange[]; AffiliationChanges?: IAffiliationChange[];
crossPlaySetting?: string; crossPlaySetting?: string;
rewardsMultiplier?: number; rewardsMultiplier?: number;
GoalTag: string; GoalTag: string;
LevelKeyName: string; LevelKeyName: string;
ActiveBoosters?: IBooster[]; ActiveBoosters?: IBooster[];
FusionBundles?: ITypeCount[];
RawUpgrades?: IRawUpgrade[]; RawUpgrades?: IRawUpgrade[];
MiscItems?: ITypeCount[];
Consumables?: ITypeCount[];
FusionTreasures?: IFusionTreasure[]; FusionTreasures?: IFusionTreasure[];
Recipes?: ITypeCount[];
QuestKeys?: Omit<IQuestKeyDatabase, "CompletionDate">[]; QuestKeys?: Omit<IQuestKeyDatabase, "CompletionDate">[];
RegularCredits?: number; RegularCredits?: number;
MissionFailed: boolean; MissionFailed: boolean;

View File

@ -1,3 +1,5 @@
import { Types } from "mongoose";
export interface IStatsView { export interface IStatsView {
CiphersSolved?: number; CiphersSolved?: number;
CiphersFailed?: number; CiphersFailed?: number;
@ -23,28 +25,32 @@ export interface IStatsView {
ReviveCount?: number; ReviveCount?: number;
} }
export interface IStatsDatabase extends IStatsView {
accountOwnerId: Types.ObjectId;
}
export interface IAbility { export interface IAbility {
used: number;
type: string; type: string;
used: number;
} }
export interface IEnemy { export interface IEnemy {
type: string;
executions?: number; executions?: number;
headshots?: number; headshots?: number;
kills?: number; kills?: number;
type: string;
assists?: number; assists?: number;
deaths?: number; deaths?: number;
} }
export interface IMission { export interface IMission {
highScore: number;
type: string; type: string;
highScore: number;
} }
export interface IScan { export interface IScan {
scans: number;
type: string; type: string;
scans: number;
} }
export interface ITutorial { export interface ITutorial {
@ -52,12 +58,91 @@ export interface ITutorial {
} }
export interface IWeapon { export interface IWeapon {
type: string;
equipTime?: number; equipTime?: number;
hits?: number; hits?: number;
kills?: number; kills?: number;
xp?: number; xp?: number;
assists?: number; assists?: number;
type: string;
headshots?: number; headshots?: number;
fired?: number; fired?: number;
} }
export interface IStatsUpload {
displayName: string;
guildId?: string;
PS?: string;
add?: IStatsAdd;
set?: IStatsSet;
max?: IStatsMax;
timers?: IStatsTimers;
}
export interface IStatsAdd {
GEAR_USED?: IUploadEntry;
SCAN?: IUploadEntry;
MISSION_COMPLETE?: IUploadEntry;
HEADSHOT_ITEM?: IUploadEntry;
HEADSHOT?: IUploadEntry;
PLAYER_COUNT?: IUploadEntry;
HOST_MIGRATION?: IUploadEntry;
PICKUP_ITEM?: IUploadEntry;
FIRE_WEAPON?: IUploadEntry;
HIT_ENTITY_ITEM?: IUploadEntry;
DESTROY_DECORATION?: IUploadEntry;
KILL_ENEMY?: IUploadEntry;
TAKE_DAMAGE?: IUploadEntry;
SQUAD_KILL_ENEMY?: IUploadEntry;
RECEIVE_UPGRADE?: IUploadEntry;
USE_ABILITY?: IUploadEntry;
SQUAD_VIP_KILL?: IUploadEntry;
HEAL_BUDDY?: IUploadEntry;
INCOME?: number;
CIPHER?: IUploadEntry;
EQUIP_COSMETIC?: IUploadEntry;
EQUIP_UPGRADE?: IUploadEntry;
KILL_BOSS?: IUploadEntry;
MISSION_TYPE?: IUploadEntry;
MISSION_FACTION?: IUploadEntry;
MISSION_PLAYED?: IUploadEntry;
MISSION_PLAYED_TIME?: IUploadEntry;
MEDALS_TOP?: IUploadEntry;
INPUT_ACTIVITY_TIME?: IUploadEntry;
KILL_ENEMY_ITEM?: IUploadEntry;
TAKE_DAMAGE_ITEM?: IUploadEntry;
SQUAD_KILL_ENEMY_ITEM?: IUploadEntry;
MELEE_KILL?: IUploadEntry;
SQUAD_MELEE_KILL?: IUploadEntry;
MELEE_KILL_ITEM?: IUploadEntry;
SQUAD_MELEE_KILL_ITEM?: IUploadEntry;
DIE?: IUploadEntry;
DIE_ITEM?: IUploadEntry;
EXECUTE_ENEMY?: IUploadEntry;
EXECUTE_ENEMY_ITEM?: IUploadEntry;
}
export interface IUploadEntry {
[key: string]: number;
}
export interface IStatsMax {
WEAPON_XP?: IUploadEntry;
MISSION_SCORE?: IUploadEntry;
}
export interface IStatsSet {
ELO_RATING?: number;
RANK?: number;
PLAYER_LEVEL?: number;
}
export interface IStatsTimers {
IN_SHIP_TIME?: number;
IN_SHIP_VIEW_TIME?: IUploadEntry;
EQUIP_WEAPON?: IUploadEntry;
MISSION_TIME?: IUploadEntry;
REGION_TIME?: IUploadEntry;
PLATFORM_TIME?: IUploadEntry;
CURRENT_MISSION_TIME?: number;
CIPHER_TIME?: number;
}

View File

@ -34,13 +34,13 @@
<li><a class="dropdown-item" href="#" data-lang="th" onclick="event.preventDefault();setLanguage('th');">แบบไทย</a></li> <li><a class="dropdown-item" href="#" data-lang="th" onclick="event.preventDefault();setLanguage('th');">แบบไทย</a></li>
</ul> </ul>
</li> </li>
<li class="nav-item dropdown"> <li class="nav-item dropdown user-dropdown">
<button class="nav-link dropdown-toggle displayname" data-bs-toggle="dropdown" aria-expanded="false"></button> <button class="nav-link dropdown-toggle displayname" data-bs-toggle="dropdown" aria-expanded="false"></button>
<ul class="dropdown-menu dropdown-menu-end"> <ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="/webui/" onclick="logout();">Logout</a></li> <li><a class="dropdown-item" href="/webui/" onclick="logout();" data-loc="navbar_logout"></a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="event.preventDefault();renameAccount();">Rename Account</a></li> <li><a class="dropdown-item" href="#" onclick="event.preventDefault();renameAccount();" data-loc="navbar_renameAccount"></a></li>
<li><a class="dropdown-item" href="#" onclick="event.preventDefault();deleteAccount();">Delete Account</a></li> <li><a class="dropdown-item" href="#" onclick="event.preventDefault();deleteAccount();" data-loc="navbar_deleteAccount"></a></li>
</ul> </ul>
</li> </li>
</ul> </ul>
@ -56,16 +56,16 @@
<div class="navbar p-0"> <div class="navbar p-0">
<ul class="navbar-nav justify-content-end"> <ul class="navbar-nav justify-content-end">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/webui/inventory" data-bs-dismiss="offcanvas" data-bs-target="#sidebar">Inventory</a> <a class="nav-link" href="/webui/inventory" data-bs-dismiss="offcanvas" data-bs-target="#sidebar" data-loc="navbar_inventory"></a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/webui/mods" data-bs-dismiss="offcanvas" data-bs-target="#sidebar">Mods</a> <a class="nav-link" href="/webui/mods" data-bs-dismiss="offcanvas" data-bs-target="#sidebar" data-loc="navbar_mods"></a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/webui/cheats" data-bs-dismiss="offcanvas" data-bs-target="#sidebar">Cheats</a> <a class="nav-link" href="/webui/cheats" data-bs-dismiss="offcanvas" data-bs-target="#sidebar" data-loc="navbar_cheats"></a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/webui/import" data-bs-dismiss="offcanvas" data-bs-target="#sidebar">Import</a> <a class="nav-link" href="/webui/import" data-bs-dismiss="offcanvas" data-bs-target="#sidebar" data-loc="navbar_import"></a>
</li> </li>
</ul> </ul>
</div> </div>
@ -73,38 +73,35 @@
</div> </div>
<div class="w-100"> <div class="w-100">
<div data-route="/webui/" data-title="Login | OpenWF WebUI"> <div data-route="/webui/" data-title="Login | OpenWF WebUI">
<p>Login using your OpenWF account credentials.</p> <p data-loc="login_description"></p>
<form onsubmit="doLogin();return false;"> <form onsubmit="doLogin();return false;">
<label for="email">Email address</label> <label for="email" data-loc="login_emailLabel"></label>
<input class="form-control" type="email" id="email" required /> <input class="form-control" type="email" id="email" required />
<br /> <br />
<label for="password">Password</label> <label for="password" data-loc="login_passwordLabel"></label>
<input class="form-control" type="password" id="password" required /> <input class="form-control" type="password" id="password" required />
<br /> <br />
<button class="btn btn-primary" type="submit">Login</button> <button class="btn btn-primary" type="submit" data-loc="login_loginButton"></button>
</form> </form>
</div> </div>
<div data-route="/webui/inventory" data-title="Inventory | OpenWF WebUI"> <div data-route="/webui/inventory" data-title="Inventory | OpenWF WebUI">
<p class="mb-3"> <p class="mb-3" data-loc="general_inventoryUpdateNote"></p>
Note: Changes made here will only be reflected in-game when the game re-downloads your
inventory. Visiting the navigation should be the easiest way to trigger that.
</p>
<div class="card mb-3"> <div class="card mb-3">
<h5 class="card-header">Add Items</h5> <h5 class="card-header" data-loc="inventory_addItems"></h5>
<form class="card-body input-group" onsubmit="doAcquireMiscItems();return false;"> <form class="card-body input-group" onsubmit="doAcquireMiscItems();return false;">
<input class="form-control" id="miscitem-count" type="number" min="1" value="1" /> <input class="form-control" id="miscitem-count" type="number" min="1" value="1" />
<input class="form-control w-50" id="miscitem-type" list="datalist-miscitems" /> <input class="form-control w-50" id="miscitem-type" list="datalist-miscitems" />
<button class="btn btn-primary" type="submit">Add</button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
</div> </div>
<div class="row g-3"> <div class="row g-3">
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card mb-3" style="height: 400px;">
<h5 class="card-header">Warframes</h5> <h5 class="card-header" data-loc="inventory_suits"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('Suits');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('Suits');return false;">
<input class="form-control" id="acquire-type-Suits" list="datalist-Suits" /> <input class="form-control" id="acquire-type-Suits" list="datalist-Suits" />
<button class="btn btn-primary" type="submit">Add</button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<table class="table table-hover w-100"> <table class="table table-hover w-100">
<tbody id="Suits-list"></tbody> <tbody id="Suits-list"></tbody>
@ -114,11 +111,11 @@
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card mb-3" style="height: 400px;">
<h5 class="card-header">Primary Weapons</h5> <h5 class="card-header" data-loc="inventory_longGuns"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('LongGuns');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('LongGuns');return false;">
<input class="form-control" id="acquire-type-LongGuns" list="datalist-LongGuns" /> <input class="form-control" id="acquire-type-LongGuns" list="datalist-LongGuns" />
<button class="btn btn-primary" type="submit">Add</button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<table class="table table-hover w-100"> <table class="table table-hover w-100">
<tbody id="LongGuns-list"></tbody> <tbody id="LongGuns-list"></tbody>
@ -130,11 +127,11 @@
<div class="row g-3"> <div class="row g-3">
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card mb-3" style="height: 400px;">
<h5 class="card-header">Secondary Weapons</h5> <h5 class="card-header" data-loc="inventory_pistols"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('Pistols');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('Pistols');return false;">
<input class="form-control" id="acquire-type-Pistols" list="datalist-Pistols" /> <input class="form-control" id="acquire-type-Pistols" list="datalist-Pistols" />
<button class="btn btn-primary" type="submit">Add</button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<table class="table table-hover w-100"> <table class="table table-hover w-100">
<tbody id="Pistols-list"></tbody> <tbody id="Pistols-list"></tbody>
@ -144,11 +141,11 @@
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card mb-3" style="height: 400px;">
<h5 class="card-header">Melee Weapons</h5> <h5 class="card-header" data-loc="inventory_melee"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('Melee');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('Melee');return false;">
<input class="form-control" id="acquire-type-Melee" list="datalist-Melee" /> <input class="form-control" id="acquire-type-Melee" list="datalist-Melee" />
<button class="btn btn-primary" type="submit">Add</button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<table class="table table-hover w-100"> <table class="table table-hover w-100">
<tbody id="Melee-list"></tbody> <tbody id="Melee-list"></tbody>
@ -160,11 +157,11 @@
<div class="row g-3"> <div class="row g-3">
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card mb-3" style="height: 400px;">
<h5 class="card-header">Archwing</h5> <h5 class="card-header" data-loc="inventory_spaceSuits"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceSuits');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceSuits');return false;">
<input class="form-control" id="acquire-type-SpaceSuits" list="datalist-SpaceSuits" /> <input class="form-control" id="acquire-type-SpaceSuits" list="datalist-SpaceSuits" />
<button class="btn btn-primary" type="submit">Add</button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<table class="table table-hover w-100"> <table class="table table-hover w-100">
<tbody id="SpaceSuits-list"></tbody> <tbody id="SpaceSuits-list"></tbody>
@ -174,11 +171,11 @@
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card mb-3" style="height: 400px;">
<h5 class="card-header">Archwing Primary Weapons</h5> <h5 class="card-header" data-loc="inventory_spaceGuns"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceGuns');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceGuns');return false;">
<input class="form-control" id="acquire-type-SpaceGuns" list="datalist-SpaceGuns" /> <input class="form-control" id="acquire-type-SpaceGuns" list="datalist-SpaceGuns" />
<button class="btn btn-primary" type="submit">Add</button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<table class="table table-hover w-100"> <table class="table table-hover w-100">
<tbody id="SpaceGuns-list"></tbody> <tbody id="SpaceGuns-list"></tbody>
@ -190,11 +187,11 @@
<div class="row g-3"> <div class="row g-3">
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card mb-3" style="height: 400px;">
<h5 class="card-header">Archwing Melee Weapons</h5> <h5 class="card-header" data-loc="inventory_spaceMelee"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceMelee');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceMelee');return false;">
<input class="form-control" id="acquire-type-SpaceMelee" list="datalist-SpaceMelee" /> <input class="form-control" id="acquire-type-SpaceMelee" list="datalist-SpaceMelee" />
<button class="btn btn-primary" type="submit">Add</button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<table class="table table-hover w-100"> <table class="table table-hover w-100">
<tbody id="SpaceMelee-list"></tbody> <tbody id="SpaceMelee-list"></tbody>
@ -204,11 +201,11 @@
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card mb-3" style="height: 400px;">
<h5 class="card-header">Necramechs</h5> <h5 class="card-header" data-loc="inventory_mechSuits"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('MechSuits');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('MechSuits');return false;">
<input class="form-control" id="acquire-type-MechSuits" list="datalist-MechSuits" /> <input class="form-control" id="acquire-type-MechSuits" list="datalist-MechSuits" />
<button class="btn btn-primary" type="submit">Add</button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<table class="table table-hover w-100"> <table class="table table-hover w-100">
<tbody id="MechSuits-list"></tbody> <tbody id="MechSuits-list"></tbody>
@ -220,11 +217,11 @@
<div class="row g-3"> <div class="row g-3">
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card mb-3" style="height: 400px;">
<h5 class="card-header">Sentinels</h5> <h5 class="card-header" data-loc="inventory_sentinels"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('Sentinels');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('Sentinels');return false;">
<input class="form-control" id="acquire-type-Sentinels" list="datalist-Sentinels" /> <input class="form-control" id="acquire-type-Sentinels" list="datalist-Sentinels" />
<button class="btn btn-primary" type="submit">Add</button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<table class="table table-hover w-100"> <table class="table table-hover w-100">
<tbody id="Sentinels-list"></tbody> <tbody id="Sentinels-list"></tbody>
@ -234,11 +231,11 @@
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card mb-3" style="height: 400px;">
<h5 class="card-header">Sentinel Weapons</h5> <h5 class="card-header" data-loc="inventory_sentinelWeapons"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('SentinelWeapons');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('SentinelWeapons');return false;">
<input class="form-control" id="acquire-type-SentinelWeapons" list="datalist-SentinelWeapons" /> <input class="form-control" id="acquire-type-SentinelWeapons" list="datalist-SentinelWeapons" />
<button class="btn btn-primary" type="submit">Add</button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<table class="table table-hover w-100"> <table class="table table-hover w-100">
<tbody id="SentinelWeapons-list"></tbody> <tbody id="SentinelWeapons-list"></tbody>
@ -250,7 +247,7 @@
<div class="row g-3"> <div class="row g-3">
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card mb-3" style="height: 400px;">
<h5 class="card-header">Amps</h5> <h5 class="card-header" data-loc="inventory_operatorAmps"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<table class="table table-hover w-100"> <table class="table table-hover w-100">
<tbody id="OperatorAmps-list"></tbody> <tbody id="OperatorAmps-list"></tbody>
@ -260,7 +257,7 @@
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card mb-3" style="height: 400px;">
<h5 class="card-header">K-Drives</h5> <h5 class="card-header" data-loc="inventory_hoverboards"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<table class="table table-hover w-100"> <table class="table table-hover w-100">
<tbody id="Hoverboards-list"></tbody> <tbody id="Hoverboards-list"></tbody>
@ -270,23 +267,23 @@
</div> </div>
</div> </div>
<div class="card mb-3"> <div class="card mb-3">
<h5 class="card-header">Bulk Actions</h5> <h5 class="card-header" data-loc="general_bulkActions"></h5>
<div class="card-body"> <div class="card-body">
<div class="mb-2 d-flex flex-wrap gap-2"> <div class="mb-2 d-flex flex-wrap gap-2">
<button class="btn btn-primary" onclick="addMissingEquipment(['Suits']);">Add Missing Warframes</button> <button class="btn btn-primary" onclick="addMissingEquipment(['Suits']);" data-loc="inventory_bulkAddSuits"></button>
<button class="btn btn-primary" onclick="addMissingEquipment(['Melee', 'LongGuns', 'Pistols']);">Add Missing Weapons</button> <button class="btn btn-primary" onclick="addMissingEquipment(['Melee', 'LongGuns', 'Pistols']);" data-loc="inventory_bulkAddWeapons"></button>
<button class="btn btn-primary" onclick="addMissingEquipment(['SpaceSuits']);">Add Missing Archwings</button> <button class="btn btn-primary" onclick="addMissingEquipment(['SpaceSuits']);" data-loc="inventory_bulkAddSpaceSuits"></button>
<button class="btn btn-primary" onclick="addMissingEquipment(['SpaceGuns', 'SpaceMelee']);">Add Missing Archwing Weapons</button> <button class="btn btn-primary" onclick="addMissingEquipment(['SpaceGuns', 'SpaceMelee']);" data-loc="inventory_bulkAddSpaceWeapons"></button>
<button class="btn btn-primary" onclick="addMissingEquipment(['Sentinels']);">Add Missing Sentinels</button> <button class="btn btn-primary" onclick="addMissingEquipment(['Sentinels']);" data-loc="inventory_bulkAddSentinels"></button>
<button class="btn btn-primary" onclick="addMissingEquipment(['SentinelWeapons']);">Add Missing Sentinel Weapons</button> <button class="btn btn-primary" onclick="addMissingEquipment(['SentinelWeapons']);" data-loc="inventory_bulkAddSentinelWeapons"></button>
</div> </div>
<div class="mb-2 d-flex flex-wrap gap-2"> <div class="mb-2 d-flex flex-wrap gap-2">
<button class="btn btn-success" onclick="maxRankAllEquipment(['Suits']);">Max Rank All Warframes</button> <button class="btn btn-success" onclick="maxRankAllEquipment(['Suits']);" data-loc="inventory_bulkRankUpSuits"></button>
<button class="btn btn-success" onclick="maxRankAllEquipment(['Melee', 'LongGuns', 'Pistols']);">Max Rank All Weapons</button> <button class="btn btn-success" onclick="maxRankAllEquipment(['Melee', 'LongGuns', 'Pistols']);" data-loc="inventory_bulkRankUpWeapons"></button>
<button class="btn btn-success" onclick="maxRankAllEquipment(['SpaceSuits']);">Max Rank All Archwings</button> <button class="btn btn-success" onclick="maxRankAllEquipment(['SpaceSuits']);" data-loc="inventory_bulkRankUpSpaceSuits"></button>
<button class="btn btn-success" onclick="maxRankAllEquipment(['SpaceGuns', 'SpaceMelee']);">Max Rank All Archwing Weapons</button> <button class="btn btn-success" onclick="maxRankAllEquipment(['SpaceGuns', 'SpaceMelee']);" data-loc="inventory_bulkRankUpSpaceWeapons"></button>
<button class="btn btn-success" onclick="maxRankAllEquipment(['Sentinels']);">Max Rank All Sentinels</button> <button class="btn btn-success" onclick="maxRankAllEquipment(['Sentinels']);" data-loc="inventory_bulkRankUpSentinels"></button>
<button class="btn btn-success" onclick="maxRankAllEquipment(['SentinelWeapons']);">Max Rank All Sentinel Weapons</button> <button class="btn btn-success" onclick="maxRankAllEquipment(['SentinelWeapons']);" data-loc="inventory_bulkRankUpSentinelWeapons"></button>
</div> </div>
</div> </div>
</div> </div>
@ -295,14 +292,14 @@
<h3 class="mb-0"></h3> <h3 class="mb-0"></h3>
<p class="text-body-secondary"></p> <p class="text-body-secondary"></p>
<div class="card mb-3"> <div class="card mb-3">
<h5 class="card-header">Archon Shard Slots</h5> <h5 class="card-header" data-loc="powersuit_archonShardsLabel"></h5>
<div class="card-body"> <div class="card-body">
<p>You can use these unlimited slots to apply a wide range of upgrades.</p> <p data-loc="powersuit_archonShardsDescription"></p>
<form class="input-group mb-3" onsubmit="doPushArchonCrystalUpgrade();return false;"> <form class="input-group mb-3" onsubmit="doPushArchonCrystalUpgrade();return false;">
<input type="number" id="archon-crystal-add-count" min="1" max="10000" value="1" class="form-control" style="max-width:100px" /> <input type="number" id="archon-crystal-add-count" min="1" max="10000" value="1" class="form-control" style="max-width:100px" />
<span class="input-group-text">x</span> <span class="input-group-text">x</span>
<input class="form-control" list="datalist-archonCrystalUpgrades" /> <input class="form-control" list="datalist-archonCrystalUpgrades" />
<button class="btn btn-primary" type="submit">Add</button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<table class="table table-hover w-100"> <table class="table table-hover w-100">
<tbody id="crystals-list"></tbody> <tbody id="crystals-list"></tbody>
@ -311,14 +308,11 @@
</div> </div>
</div> </div>
<div data-route="/webui/mods" data-title="Mods | OpenWF WebUI"> <div data-route="/webui/mods" data-title="Mods | OpenWF WebUI">
<p class="mb-3"> <p class="mb-3" data-loc="general_inventoryUpdateNote"></p>
Note: Changes made here will only be reflected in-game when the game re-downloads your
inventory. Visiting the navigation should be the easiest way to trigger that.
</p>
<div class="row g-3"> <div class="row g-3">
<div class="col-xxl-6"> <div class="col-xxl-6">
<div class="card mb-3"> <div class="card mb-3">
<h5 class="card-header">Add Riven</h5> <h5 class="card-header" data-loc="mods_addRiven"></h5>
<form class="card-body" onsubmit="doAcquireRiven();return false;"> <form class="card-body" onsubmit="doAcquireRiven();return false;">
<select class="form-control mb-3" id="addriven-type"> <select class="form-control mb-3" id="addriven-type">
<option value="LotusArchgunRandomModRare">LotusArchgunRandomModRare</option> <option value="LotusArchgunRandomModRare">LotusArchgunRandomModRare</option>
@ -329,13 +323,13 @@
<option value="LotusShotgunRandomModRare">LotusShotgunRandomModRare</option> <option value="LotusShotgunRandomModRare">LotusShotgunRandomModRare</option>
<option value="PlayerMeleeWeaponRandomModRare">PlayerMeleeWeaponRandomModRare</option> <option value="PlayerMeleeWeaponRandomModRare">PlayerMeleeWeaponRandomModRare</option>
</select> </select>
<textarea id="addriven-fingerprint" class="form-control mb-3" placeholder="Fingerprint"></textarea> <textarea id="addriven-fingerprint" class="form-control mb-3" data-loc-placeholder_"mods.fingerprint"></textarea>
<button class="btn btn-primary" style="margin-right: 5px" type="submit">Add</button> <button class="btn btn-primary" style="margin-right: 5px" type="submit" data-loc="general_addButton"></button>
<a href="riven-tool/" target="_blank">Need help with the fingerprint?</a> <a href="riven-tool/" target="_blank" data-loc="mods_fingerprintHelp"></a>
</form> </form>
</div> </div>
<div class="card mb-3"> <div class="card mb-3">
<h5 class="card-header">Rivens</h5> <h5 class="card-header" data-loc="mods_rivens"></h5>
<div class="card-body"> <div class="card-body">
<table class="table table-hover w-100"> <table class="table table-hover w-100">
<tbody id="riven-list"></tbody> <tbody id="riven-list"></tbody>
@ -345,12 +339,12 @@
</div> </div>
<div class="col-xxl-6"> <div class="col-xxl-6">
<div class="card mb-3"> <div class="card mb-3">
<h5 class="card-header">Mods</h5> <h5 class="card-header" data-loc="mods_mods"></h5>
<div class="card-body"> <div class="card-body">
<form class="input-group mb-3" onsubmit="doAcquireMod();return false;"> <form class="input-group mb-3" onsubmit="doAcquireMod();return false;">
<input class="form-control" id="mod-count" type="number" min="1" value="1"/> <input class="form-control" id="mod-count" type="number" min="1" value="1"/>
<input class="form-control w-50" id="mod-to-acquire" list="datalist-mods" /> <input class="form-control w-50" id="mod-to-acquire" list="datalist-mods" />
<button class="btn btn-primary" type="submit">Add</button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<table class="table table-hover w-100"> <table class="table table-hover w-100">
<tbody id="mods-list"></tbody> <tbody id="mods-list"></tbody>
@ -358,9 +352,9 @@
</div> </div>
</div> </div>
<div class="card mb-3"> <div class="card mb-3">
<h5 class="card-header">Bulk Actions</h5> <h5 class="card-header" data-loc="general_bulkActions"></h5>
<div class="card-body"> <div class="card-body">
<button class="btn btn-primary" onclick="doAddAllMods();">Add Missing Mods</button> <button class="btn btn-primary" onclick="doAddAllMods();" data-loc="mods_bulkAddMods"></button>
</div> </div>
</div> </div>
</div> </div>
@ -373,127 +367,113 @@
<h5 class="card-header">Server</h5> <h5 class="card-header">Server</h5>
<div class="card-body"> <div class="card-body">
<div id="server-settings-no-perms" class="d-none"> <div id="server-settings-no-perms" class="d-none">
<p class="card-text">You must be an administrator to use this feature. To become an administrator, add <code>"<span class="displayname"></span>"</code> to <code>administratorNames</code> in the config.json.</p> <p class="card-text" data-loc="cheats_administratorRequirement"></p>
</div> </div>
<form id="server-settings" class="d-none" onsubmit="doChangeSettings();return false;"> <form id="server-settings" class="d-none" onsubmit="doChangeSettings();return false;">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="skipTutorial" /> <input class="form-check-input" type="checkbox" id="skipTutorial" />
<label class="form-check-label" for="skipTutorial">Skip Tutorial</label> <label class="form-check-label" for="skipTutorial" data-loc="cheats_skipTutorial"></label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="skipAllDialogue" /> <input class="form-check-input" type="checkbox" id="skipAllDialogue" />
<label class="form-check-label" for="skipAllDialogue">Skip All Dialogue</label> <label class="form-check-label" for="skipAllDialogue" data-loc="cheats_skipAllDialogue"></label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockAllScans" /> <input class="form-check-input" type="checkbox" id="unlockAllScans" />
<label class="form-check-label" for="unlockAllScans">Unlock All Scans</label> <label class="form-check-label" for="unlockAllScans" data-loc="cheats_unlockAllScans"></label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockAllMissions" /> <input class="form-check-input" type="checkbox" id="unlockAllMissions" />
<label class="form-check-label" for="unlockAllMissions">Unlock All Missions</label> <label class="form-check-label" for="unlockAllMissions" data-loc="cheats_unlockAllMissions"></label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockAllQuests" /> <input class="form-check-input" type="checkbox" id="unlockAllQuests" />
<label class="form-check-label" for="unlockAllQuests">Unlock All Quests</label> <label class="form-check-label" for="unlockAllQuests" data-loc="cheats_unlockAllQuests"></label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="completeAllQuests" /> <input class="form-check-input" type="checkbox" id="completeAllQuests" />
<label class="form-check-label" for="completeAllQuests">Complete All Quests</label> <label class="form-check-label" for="completeAllQuests" data-loc="cheats_completeAllQuests"></label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="infiniteCredits" /> <input class="form-check-input" type="checkbox" id="infiniteCredits" />
<label class="form-check-label" for="infiniteCredits">Infinite Credits</label> <label class="form-check-label" for="infiniteCredits" data-loc="cheats_infiniteCredits"></label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="infinitePlatinum" /> <input class="form-check-input" type="checkbox" id="infinitePlatinum" />
<label class="form-check-label" for="infinitePlatinum">Infinite Platinum</label> <label class="form-check-label" for="infinitePlatinum" data-loc="cheats_infinitePlatinum"></label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="infiniteEndo" /> <input class="form-check-input" type="checkbox" id="infiniteEndo" />
<label class="form-check-label" for="infiniteEndo">Infinite Endo</label> <label class="form-check-label" for="infiniteEndo" data-loc="cheats_infiniteEndo"></label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="infiniteRegalAya" /> <input class="form-check-input" type="checkbox" id="infiniteRegalAya" />
<label class="form-check-label" for="infiniteRegalAya">Infinite Regal Aya</label> <label class="form-check-label" for="infiniteRegalAya" data-loc="cheats_infiniteRegalAya"></label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockAllShipFeatures" /> <input class="form-check-input" type="checkbox" id="unlockAllShipFeatures" />
<label class="form-check-label" for="unlockAllShipFeatures">Unlock All Ship Features</label> <label class="form-check-label" for="unlockAllShipFeatures" data-loc="cheats_unlockAllShipFeatures"></label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockAllShipDecorations" /> <input class="form-check-input" type="checkbox" id="unlockAllShipDecorations" />
<label class="form-check-label" for="unlockAllShipDecorations">Unlock All Ship Decorations</label> <label class="form-check-label" for="unlockAllShipDecorations" data-loc="cheats_unlockAllShipDecorations"></label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockAllFlavourItems" /> <input class="form-check-input" type="checkbox" id="unlockAllFlavourItems" />
<label class="form-check-label" for="unlockAllFlavourItems"> <label class="form-check-label" for="unlockAllFlavourItems" data-loc="cheats_unlockAllFlavourItems"></label>
Unlock All <abbr title="Animation Sets, Glyphs, Plattes, etc.">Flavor Items</abbr>
</label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockAllSkins" /> <input class="form-check-input" type="checkbox" id="unlockAllSkins" />
<label class="form-check-label" for="unlockAllSkins">Unlock All Skins</label> <label class="form-check-label" for="unlockAllSkins" data-loc="cheats_unlockAllSkins"></label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockAllCapturaScenes" /> <input class="form-check-input" type="checkbox" id="unlockAllCapturaScenes" />
<label class="form-check-label" for="unlockAllCapturaScenes">Unlock All Captura Scenes</label> <label class="form-check-label" for="unlockAllCapturaScenes" data-loc="cheats_unlockAllCapturaScenes"></label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="universalPolarityEverywhere" /> <input class="form-check-input" type="checkbox" id="universalPolarityEverywhere" />
<label class="form-check-label" for="universalPolarityEverywhere"> <label class="form-check-label" for="universalPolarityEverywhere" data-loc="cheats_universalPolarityEverywhere"></label>
Universal Polarity Everywhere
</label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockDoubleCapacityPotatoesEverywhere" /> <input class="form-check-input" type="checkbox" id="unlockDoubleCapacityPotatoesEverywhere" />
<label class="form-check-label" for="unlockDoubleCapacityPotatoesEverywhere"> <label class="form-check-label" for="unlockDoubleCapacityPotatoesEverywhere" data-loc="cheats_unlockDoubleCapacityPotatoesEverywhere"></label>
Potatoes Everywhere
</label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockExilusEverywhere" /> <input class="form-check-input" type="checkbox" id="unlockExilusEverywhere" />
<label class="form-check-label" for="unlockExilusEverywhere"> <label class="form-check-label" for="unlockExilusEverywhere" data-loc="cheats_unlockExilusEverywhere"></label>
Exilus Adapters Everywhere
</label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockArcanesEverywhere" /> <input class="form-check-input" type="checkbox" id="unlockArcanesEverywhere" />
<label class="form-check-label" for="unlockArcanesEverywhere"> <label class="form-check-label" for="unlockArcanesEverywhere" data-loc="cheats_unlockArcanesEverywhere"></label>
Arcane Adapters Everywhere
</label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="noDailyStandingLimits" /> <input class="form-check-input" type="checkbox" id="noDailyStandingLimits" />
<label class="form-check-label" for="noDailyStandingLimits"> <label class="form-check-label" for="noDailyStandingLimits" data-loc="cheats_noDailyStandingLimits"></label>
No Daily Standing Limits
</label>
</div> </div>
<div class="form-group mt-2"> <div class="form-group mt-2">
<label class="form-label" for="spoofMasteryRank"> <label class="form-label" for="spoofMasteryRank" data-loc="cheats_spoofMasteryRank"></label>
Spoofed Mastery Rank (-1 to disable)
</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>
<button class="btn btn-primary mt-3" type="submit">Save Settings</button> <button class="btn btn-primary mt-3" type="submit" data-loc="cheats_saveSettings"></button>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="card mb-3"> <div class="card mb-3">
<h5 class="card-header">Account</h5> <h5 class="card-header" data-loc="cheats_account"></h5>
<div class="card-body"> <div class="card-body">
<p><button class="btn btn-primary" onclick="doUnlockAllFocusSchools();">Unlock All Focus Schools</button></p> <p><button class="btn btn-primary" onclick="doUnlockAllFocusSchools();" data-loc="cheats_unlockAllFocusSchools"></button></p>
<button class="btn btn-primary" onclick="doHelminthUnlockAll();">Fully Level Up Helminth</button> <button class="btn btn-primary" onclick="doHelminthUnlockAll();" data-loc="cheats_helminthUnlockAll"></button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div data-route="/webui/import" data-title="Import | OpenWF WebUI"> <div data-route="/webui/import" data-title="Import | OpenWF WebUI">
<p>You can provide a full or partial inventory response (client respresentation) here. All fields that are supported by the importer <b>will be overwritten</b> in your account.</p> <p data-loc="import_importNote"></p>
<textarea class="form-control" id="import-inventory"></textarea> <textarea class="form-control" id="import-inventory"></textarea>
<button class="btn btn-primary mt-3" onclick="doImport();">Submit</button> <button class="btn btn-primary mt-3" onclick="doImport();" data-loc="import_submit"></button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -14,6 +14,9 @@ function loginFromLocalStorage() {
$(".displayname").text(data.DisplayName); $(".displayname").text(data.DisplayName);
window.accountId = data.id; window.accountId = data.id;
window.authz = "accountId=" + data.id + "&nonce=" + data.Nonce; window.authz = "accountId=" + data.id + "&nonce=" + data.Nonce;
if (window.dict) {
updateLocElements();
}
updateInventory(); updateInventory();
}, },
() => { () => {
@ -28,7 +31,7 @@ function doLoginRequest(succ_cb, fail_cb) {
url: "/api/login.php", url: "/api/login.php",
contentType: "text/plain", contentType: "text/plain",
data: JSON.stringify({ data: JSON.stringify({
email: localStorage.getItem("email"), email: localStorage.getItem("email").toLowerCase(),
password: wp.encSync(localStorage.getItem("password"), "hex"), password: wp.encSync(localStorage.getItem("password"), "hex"),
time: parseInt(new Date() / 1000), time: parseInt(new Date() / 1000),
s: "W0RFXVN0ZXZlIGxpa2VzIGJpZyBidXR0cw==", // signature of some kind s: "W0RFXVN0ZXZlIGxpa2VzIGJpZyBidXR0cw==", // signature of some kind
@ -50,7 +53,7 @@ function revalidateAuthz(succ_cb) {
}, },
() => { () => {
logout(); logout();
alert("Your credentials are no longer valid."); alert(loc("code_nonValidAuthz"));
single.loadRoute("/webui/"); // Show login screen single.loadRoute("/webui/"); // Show login screen
} }
); );
@ -62,24 +65,17 @@ function logout() {
} }
function renameAccount() { function renameAccount() {
const newname = window.prompt("What would you like to change your account name to?"); const newname = window.prompt(loc("code_changeNameConfirm"));
if (newname) { if (newname) {
fetch("/custom/renameAccount?" + window.authz + "&newname=" + newname).then(() => { fetch("/custom/renameAccount?" + window.authz + "&newname=" + newname).then(() => {
$(".displayname").text(newname); $(".displayname").text(newname);
updateLocElements();
}); });
} }
} }
function deleteAccount() { function deleteAccount() {
if ( if (window.confirm(loc("code_deleteAccountConfirm"))) {
window.confirm(
"Are you sure you want to delete your account " +
document.querySelector(".displayname").textContent +
" (" +
localStorage.getItem("email") +
")? This action cannot be undone."
)
) {
fetch("/custom/deleteAccount?" + window.authz).then(() => { fetch("/custom/deleteAccount?" + window.authz).then(() => {
logout(); logout();
single.loadRoute("/webui/"); // Show login screen single.loadRoute("/webui/"); // Show login screen
@ -110,55 +106,80 @@ single.on("route_load", function (event) {
} }
}); });
function loc(tag) {
return ((window.dict ?? {})[tag] ?? tag)
.split("|DISPLAYNAME|").join(document.querySelector(".displayname").textContent)
.split("|EMAIL|").join(localStorage.getItem("email"));
}
function updateLocElements() {
document.querySelectorAll("[data-loc]").forEach(elm => {
elm.innerHTML = loc(elm.getAttribute("data-loc"));
});
}
function setActiveLanguage(lang) { function setActiveLanguage(lang) {
window.lang = lang; window.lang = lang;
const lang_name = document.querySelector("[data-lang=" + lang + "]").textContent; const lang_name = document.querySelector("[data-lang=" + lang + "]").textContent;
document.getElementById("active-lang-name").textContent = lang_name; document.getElementById("active-lang-name").textContent = lang_name;
document.querySelector("[data-lang].active").classList.remove("active"); document.querySelector("[data-lang].active").classList.remove("active");
document.querySelector("[data-lang=" + lang + "]").classList.add("active"); document.querySelector("[data-lang=" + lang + "]").classList.add("active");
window.dictPromise = new Promise(resolve => {
const webui_lang = ["en", "ru"].indexOf(lang) == -1 ? "en" : lang;
const script = document.createElement("script");
script.src = "/translations/" + webui_lang + ".js";
script.onload = function() {
updateLocElements();
resolve(window.dict);
};
document.documentElement.appendChild(script);
});
} }
setActiveLanguage(localStorage.getItem("lang") ?? "en"); setActiveLanguage(localStorage.getItem("lang") ?? "en");
function setLanguage(lang) { function setLanguage(lang) {
setActiveLanguage(lang); setActiveLanguage(lang);
localStorage.setItem("lang", lang); localStorage.setItem("lang", lang);
fetchItemList(); if (window.authz) { // Not in prelogin state?
updateInventory(); fetchItemList();
updateInventory();
}
} }
let uniqueLevelCaps = {}; let uniqueLevelCaps = {};
function fetchItemList() { function fetchItemList() {
window.itemListPromise = new Promise(resolve => { window.itemListPromise = new Promise(resolve => {
const req = $.get("/custom/getItemLists?lang=" + window.lang); const req = $.get("/custom/getItemLists?lang=" + window.lang);
req.done(data => { req.done(async (data) => {
await dictPromise;
window.archonCrystalUpgrades = data.archonCrystalUpgrades; window.archonCrystalUpgrades = data.archonCrystalUpgrades;
const itemMap = { const itemMap = {
// Generics for rivens // Generics for rivens
"/Lotus/Weapons/Tenno/Archwing/Primary/ArchGun": { name: "Archgun" }, "/Lotus/Weapons/Tenno/Archwing/Primary/ArchGun": { name: loc("code_archgun") },
"/Lotus/Weapons/Tenno/Melee/PlayerMeleeWeapon": { name: "Melee" }, "/Lotus/Weapons/Tenno/Melee/PlayerMeleeWeapon": { name: loc("code_melee") },
"/Lotus/Weapons/Tenno/Pistol/LotusPistol": { name: "Pistol" }, "/Lotus/Weapons/Tenno/Pistol/LotusPistol": { name: loc("code_pistol") },
"/Lotus/Weapons/Tenno/Rifle/LotusRifle": { name: "Rifle" }, "/Lotus/Weapons/Tenno/Rifle/LotusRifle": { name: loc("code_rifle") },
"/Lotus/Weapons/Tenno/Shotgun/LotusShotgun": { name: "Shotgun" }, "/Lotus/Weapons/Tenno/Shotgun/LotusShotgun": { name: loc("code_shotgun") },
// Modular weapons // Modular weapons
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary": { name: "Kitgun" }, "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary": { name: loc("code_kitgun") },
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam": { name: "Kitgun" }, "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam": { name: loc("code_kitgun") },
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryLauncher": { name: "Kitgun" }, "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryLauncher": { name: loc("code_kitgun") },
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun": { name: "Kitgun" }, "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun": { name: loc("code_kitgun") },
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimarySniper": { name: "Kitgun" }, "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimarySniper": { name: loc("code_kitgun") },
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary": { name: "Kitgun" }, "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary": { name: loc("code_kitgun") },
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam": { name: "Kitgun" }, "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam": { name: loc("code_kitgun") },
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun": { name: "Kitgun" }, "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun": { name: loc("code_kitgun") },
"/Lotus/Weapons/Ostron/Melee/LotusModularWeapon": { name: "Zaw" }, "/Lotus/Weapons/Ostron/Melee/LotusModularWeapon": { name: loc("code_zaw") },
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/OperatorTrainingAmpWeapon": { "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/OperatorTrainingAmpWeapon": { name: loc("code_moteAmp") },
name: "Mote Amp" "/Lotus/Weapons/Sentients/OperatorAmplifiers/OperatorAmpWeapon": { name: loc("code_amp") },
}, "/Lotus/Weapons/Operator/Pistols/DrifterPistol/DrifterPistolPlayerWeapon": { name: loc("code_sirocco") },
"/Lotus/Weapons/Sentients/OperatorAmplifiers/OperatorAmpWeapon": { name: "Amp" }, "/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit": { name: loc("code_kdrive") },
"/Lotus/Weapons/Operator/Pistols/DrifterPistol/DrifterPistolPlayerWeapon": { name: "Sirocco" },
"/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit": { name: "K-Drive" },
// Missing in data sources // Missing in data sources
"/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser": { name: "Legendary Core" }, "/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser": { name: loc("code_legendaryCore") },
"/Lotus/Upgrades/CosmeticEnhancers/Peculiars/CyoteMod": { name: "Traumatic Peculiar" } "/Lotus/Upgrades/CosmeticEnhancers/Peculiars/CyoteMod": { name: loc("code_traumaticPeculiar") }
}; };
for (const [type, items] of Object.entries(data)) { for (const [type, items] of Object.entries(data)) {
if (type == "archonCrystalUpgrades") { if (type == "archonCrystalUpgrades") {
@ -173,7 +194,7 @@ function fetchItemList() {
} else if (type != "badItems") { } else if (type != "badItems") {
items.forEach(item => { items.forEach(item => {
if (item.uniqueName in data.badItems) { if (item.uniqueName in data.badItems) {
item.name += " (Imposter)"; item.name += " " + loc("code_badItem");
} else if (item.uniqueName.substr(0, 18) != "/Lotus/Types/Game/") { } else if (item.uniqueName.substr(0, 18) != "/Lotus/Types/Game/") {
const option = document.createElement("option"); const option = document.createElement("option");
option.setAttribute("data-key", item.uniqueName); option.setAttribute("data-key", item.uniqueName);
@ -272,7 +293,7 @@ function updateInventory() {
} }
} }
}; };
a.title = "Max Rank"; a.title = loc("code_maxRank");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.2L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"/></svg>`; a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.2L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"/></svg>`;
td.appendChild(a); td.appendChild(a);
} }
@ -287,12 +308,12 @@ function updateInventory() {
a.href = "#"; a.href = "#";
a.onclick = function (event) { a.onclick = function (event) {
event.preventDefault(); event.preventDefault();
const name = prompt("Enter new custom name:"); const name = prompt(loc("code_renamePrompt"));
if (name !== null) { if (name !== null) {
renameGear(category, item.ItemId.$oid, name); renameGear(category, item.ItemId.$oid, name);
} }
}; };
a.title = "Rename"; a.title = loc("code_rename");
a.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="M0 80V229.5c0 17 6.7 33.3 18.7 45.3l176 176c25 25 65.5 25 90.5 0L418.7 317.3c25-25 25-65.5 0-90.5l-176-176c-12-12-28.3-18.7-45.3-18.7H48C21.5 32 0 53.5 0 80zm112 32a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>`; a.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="M0 80V229.5c0 17 6.7 33.3 18.7 45.3l176 176c25 25 65.5 25 90.5 0L418.7 317.3c25-25 25-65.5 0-90.5l-176-176c-12-12-28.3-18.7-45.3-18.7H48C21.5 32 0 53.5 0 80zm112 32a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>`;
td.appendChild(a); td.appendChild(a);
} }
@ -303,7 +324,7 @@ function updateInventory() {
event.preventDefault(); event.preventDefault();
disposeOfGear(category, item.ItemId.$oid); disposeOfGear(category, item.ItemId.$oid);
}; };
a.title = "Remove"; a.title = loc("code_remove");
a.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>`; a.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>`;
td.appendChild(a); td.appendChild(a);
} }
@ -327,11 +348,11 @@ function updateInventory() {
const td = document.createElement("td"); const td = document.createElement("td");
td.textContent = itemMap[fingerprint.compat]?.name ?? fingerprint.compat; td.textContent = itemMap[fingerprint.compat]?.name ?? fingerprint.compat;
td.textContent += " " + RivenParser.parseRiven(rivenType, fingerprint, 1).name; td.textContent += " " + RivenParser.parseRiven(rivenType, fingerprint, 1).name;
td.innerHTML += " <span title='Number of buffs'>▲ " + fingerprint.buffs.length + "</span>"; td.innerHTML += " <span title='" + loc("code_buffsNumber") + "'>▲ " + fingerprint.buffs.length + "</span>";
td.innerHTML += td.innerHTML +=
" <span title='Number of curses'>▼ " + fingerprint.curses.length + "</span>"; " <span title='" + loc("code_cursesNumber") + "'>▼ " + fingerprint.curses.length + "</span>";
td.innerHTML += td.innerHTML +=
" <span title='Number of rerolls'>⟳ " + parseInt(fingerprint.rerolls) + "</span>"; " <span title='" + loc("code_rerollsNumber") + "'>⟳ " + parseInt(fingerprint.rerolls) + "</span>";
tr.appendChild(td); tr.appendChild(td);
} }
{ {
@ -349,7 +370,7 @@ function updateInventory() {
}) })
); );
a.target = "_blank"; a.target = "_blank";
a.title = "View Stats"; a.title = loc("code_viewStats");
a.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="M160 80c0-26.5 21.5-48 48-48h32c26.5 0 48 21.5 48 48V432c0 26.5-21.5 48-48 48H208c-26.5 0-48-21.5-48-48V80zM0 272c0-26.5 21.5-48 48-48H80c26.5 0 48 21.5 48 48V432c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V272zM368 96h32c26.5 0 48 21.5 48 48V432c0 26.5-21.5 48-48 48H368c-26.5 0-48-21.5-48-48V144c0-26.5 21.5-48 48-48z"/></svg>`; a.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="M160 80c0-26.5 21.5-48 48-48h32c26.5 0 48 21.5 48 48V432c0 26.5-21.5 48-48 48H208c-26.5 0-48-21.5-48-48V80zM0 272c0-26.5 21.5-48 48-48H80c26.5 0 48 21.5 48 48V432c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V272zM368 96h32c26.5 0 48 21.5 48 48V432c0 26.5-21.5 48-48 48H368c-26.5 0-48-21.5-48-48V144c0-26.5 21.5-48 48-48z"/></svg>`;
td.appendChild(a); td.appendChild(a);
} }
@ -360,7 +381,7 @@ function updateInventory() {
event.preventDefault(); event.preventDefault();
disposeOfGear("Upgrades", item.ItemId.$oid); disposeOfGear("Upgrades", item.ItemId.$oid);
}; };
a.title = "Remove"; a.title = loc("code_remove");
a.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>`; a.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>`;
td.appendChild(a); td.appendChild(a);
} }
@ -376,7 +397,7 @@ function updateInventory() {
{ {
const td = document.createElement("td"); const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType; td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
td.innerHTML += " <span title='Rank'>★ " + rank + "/" + maxRank + "</span>"; td.innerHTML += " <span title='" + loc("code_rank") + "'>★ " + rank + "/" + maxRank + "</span>";
tr.appendChild(td); tr.appendChild(td);
} }
{ {
@ -389,7 +410,7 @@ function updateInventory() {
event.preventDefault(); event.preventDefault();
setFingerprint(item.ItemType, item.ItemId, { lvl: maxRank }); setFingerprint(item.ItemType, item.ItemId, { lvl: maxRank });
}; };
a.title = "Max Rank"; a.title = loc("code_maxRank");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.2L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"/></svg>`; a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.2L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"/></svg>`;
td.appendChild(a); td.appendChild(a);
} }
@ -400,7 +421,7 @@ function updateInventory() {
event.preventDefault(); event.preventDefault();
disposeOfGear("Upgrades", item.ItemId.$oid); disposeOfGear("Upgrades", item.ItemId.$oid);
}; };
a.title = "Remove"; a.title = loc("code_remove");
a.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>`; a.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>`;
td.appendChild(a); td.appendChild(a);
} }
@ -415,7 +436,7 @@ function updateInventory() {
{ {
const td = document.createElement("td"); const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType; td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
td.innerHTML += " <span title='Rank'>★ 0/" + maxRank + "</span>"; td.innerHTML += " <span title='" + loc("code_rank") + "'>★ 0/" + maxRank + "</span>";
if (item.ItemCount > 1) { if (item.ItemCount > 1) {
td.innerHTML += " <span title='Count'>🗍 " + parseInt(item.ItemCount) + "</span>"; td.innerHTML += " <span title='Count'>🗍 " + parseInt(item.ItemCount) + "</span>";
} }
@ -431,7 +452,7 @@ function updateInventory() {
event.preventDefault(); event.preventDefault();
setFingerprint(item.ItemType, item.LastAdded, { lvl: maxRank }); setFingerprint(item.ItemType, item.LastAdded, { lvl: maxRank });
}; };
a.title = "Max Rank"; a.title = loc("code_maxRank");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.2L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"/></svg>`; a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.2L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"/></svg>`;
td.appendChild(a); td.appendChild(a);
} }
@ -442,7 +463,7 @@ function updateInventory() {
event.preventDefault(); event.preventDefault();
disposeOfItems("Upgrades", item.ItemType, item.ItemCount); disposeOfItems("Upgrades", item.ItemType, item.ItemCount);
}; };
a.title = "Remove"; a.title = loc("code_remove");
a.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>`; a.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>`;
td.appendChild(a); td.appendChild(a);
} }
@ -491,7 +512,7 @@ function updateInventory() {
event.preventDefault(); event.preventDefault();
doPopArchonCrystalUpgrade(upgradeType); doPopArchonCrystalUpgrade(upgradeType);
}; };
a.title = "Remove"; a.title = loc("code_remove");
a.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>`; a.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>`;
td.appendChild(a); td.appendChild(a);
} }
@ -572,7 +593,7 @@ function addMissingEquipment(categories) {
}); });
if ( if (
requests.length != 0 && requests.length != 0 &&
window.confirm("Are you sure you want to add " + requests.length + " items to your account?") window.confirm(loc("code_addItemsConfirm").split("|COUNT|").join(requests.length))
) { ) {
dispatchAddItemsRequestsBatch(requests); dispatchAddItemsRequestsBatch(requests);
} }
@ -627,7 +648,7 @@ function maxRankAllEquipment(categories) {
return sendBatchGearExp(batchData); return sendBatchGearExp(batchData);
} }
alert("No equipment to rank up."); alert(loc("code_noEquipmentToRankUp"));
}); });
}); });
} }
@ -743,7 +764,7 @@ function doAcquireMiscItems() {
} }
]) ])
}).done(function () { }).done(function () {
alert("Successfully added."); alert(loc("code_succAdded"));
}); });
}); });
} }
@ -938,13 +959,9 @@ function doUnlockAllFocusSchools() {
await unlockFocusSchool(upgradeType); await unlockFocusSchool(upgradeType);
} }
if (Object.keys(missingFocusUpgrades).length == 0) { if (Object.keys(missingFocusUpgrades).length == 0) {
alert("All focus schools are already unlocked."); alert(loc("code_focusAllUnlocked"));
} else { } else {
alert( alert(loc("code_focusUnlocked").split("|COUNT|").join(Object.keys(missingFocusUpgrades).length));
"Unlocked " +
Object.keys(missingFocusUpgrades).length +
" new focus schools! An inventory update will be needed for the changes to be reflected in-game. Visiting the navigation should be the easiest way to trigger that."
);
} }
}); });
}); });
@ -996,7 +1013,7 @@ function doAddAllMods() {
modsAll = Array.from(modsAll); modsAll = Array.from(modsAll);
if ( if (
modsAll.length != 0 && modsAll.length != 0 &&
window.confirm("Are you sure you want to add " + modsAll.length + " mods to your account?") window.confirm(loc("code_addModsConfirm").split("|COUNT|").join(modsAll.length))
) { ) {
$.post({ $.post({
url: "/custom/addItems?" + window.authz, url: "/custom/addItems?" + window.authz,
@ -1071,7 +1088,7 @@ function doImport() {
inventory: JSON.parse($("#import-inventory").val()) inventory: JSON.parse($("#import-inventory").val())
}) })
}).then(function () { }).then(function () {
alert("Successfully imported."); alert(loc("code_succImport"));
updateInventory(); updateInventory();
}); });
}); });

View File

@ -14,9 +14,7 @@
} }
} }
body:not(.logged-in) .navbar-toggler, body:not(.logged-in) .user-dropdown {
body:not(.logged-in) .nav-item.dropdown,
body:not(.logged-in) #refresh-note {
display: none; display: none;
} }

View File

@ -0,0 +1,111 @@
dict = {
general_inventoryUpdateNote: `Note: Changes made here will only be reflected in-game when the game re-downloads your inventory. Visiting the navigation should be the easiest way to trigger that.`,
general_addButton: `Add`,
general_bulkActions: `Bulk Actions`,
code_nonValidAuthz: `Your credentials are no longer valid.`,
code_changeNameConfirm: `What would you like to change your account name to?`,
code_deleteAccountConfirm: `Are you sure you want to delete your account |DISPLAYNAME| (|EMAIL|)? This action cannot be undone.`,
code_archgun: `Archgun`,
code_melee: `Melee`,
code_pistol: `Pistol`,
code_rifle: `Rifle`,
code_shotgun: `Shotgun`,
code_kitgun: `Kitgun`,
code_zaw: `Zaw`,
code_moteAmp: `Mote Amp`,
code_amp: `Amp`,
code_sirocco: `Sirocco`,
code_kDrive: `K-Drive`,
code_legendaryCore: `Legendary Core`,
code_traumaticPeculiar: `Traumatic Peculiar`,
code_badItem: `(Imposter)`,
code_maxRank: `Max Rank`,
code_rename: `Rename`,
code_renamePrompt: `Enter new custom name:`,
code_remove: `Remove`,
code_addItemsConfirm: `Are you sure you want to add |COUNT| items to your account?`,
code_noEquipmentToRankUp: `No equipment to rank up.`,
code_succAdded: `Successfully added.`,
code_buffsNumber: `Number of buffs`,
code_cursesNumber: `Number of curses`,
code_rerollsNumber: `Number of rerolls`,
code_viewStats: `View Stats`,
code_rank: `Rank`,
code_count: `Count`,
code_focusAllUnlocked: `All focus schools are already unlocked.`,
code_focusUnlocked: `Unlocked |COUNT| new focus schools! An inventory update will be needed for the changes to be reflected in-game. Visiting the navigation should be the easiest way to trigger that.`,
code_addModsConfirm: `Are you sure you want to add |COUNT| mods to your account?`,
code_succImport: `Successfully imported.`,
login_description: `Login using your OpenWF account credentials (same as in-game when connecting to this server).`,
login_emailLabel: `Email address`,
login_passwordLabel: `Password`,
login_loginButton: `Login`,
navbar_logout: `Logout`,
navbar_renameAccount: `Rename Account`,
navbar_deleteAccount: `Delete Account`,
navbar_inventory: `Inventory`,
navbar_mods: `Mods`,
navbar_cheats: `Cheats`,
navbar_import: `Import`,
inventory_addItems: `Add Items`,
inventory_suits: `Warframes`,
inventory_longGuns: `Primary Weapons`,
inventory_pistols: `Secondary Weapons`,
inventory_melee: `Melee Weapons`,
inventory_spaceSuits: `Archwings`,
inventory_spaceGuns: `Archwing Primary Weapons`,
inventory_spaceMelee: `Archwing Melee Weapons`,
inventory_mechSuits: `Necramechs`,
inventory_sentinels: `Sentinels`,
inventory_sentinelWeapons: `Sentinel Weapons`,
inventory_operatorAmps: `Amps`,
inventory_hoverboards: `K-Drives`,
inventory_bulkAddSuits: `Add Missing Warframes`,
inventory_bulkAddWeapons: `Add Missing Weapons`,
inventory_bulkAddSpaceSuits: `Add Missing Archwings`,
inventory_bulkAddSpaceWeapons: `Add Missing Archwing Weapons`,
inventory_bulkAddSentinels: `Add Missing Sentinels`,
inventory_bulkAddSentinelWeapons: `Add Missing Sentinel Weapons`,
inventory_bulkRankUpSuits: `Max Rank All Warframes`,
inventory_bulkRankUpWeapons: `Max Rank All Weapons`,
inventory_bulkRankUpSpaceSuits: `Max Rank All Archwings`,
inventory_bulkRankUpSpaceWeapons: `Max Rank All Archwing Weapons`,
inventory_bulkRankUpSentinels: `Max Rank All Sentinels`,
inventory_bulkRankUpSentinelWeapons: `Max Rank All Sentinel Weapons`,
powersuit_archonShardsLabel: `Archon Shard Slots`,
powersuit_archonShardsDescription: `You can use these unlimited slots to apply a wide range of upgrades`,
mods_addRiven: `Add Riven`,
mods_fingerprint: `Fingerprint`,
mods_fingerprintHelp: `Need help with the fingerprint?`,
mods_rivens: `Rivens`,
mods_mods: `Mods`,
mods_bulkAddMods: `Add Missing 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_skipTutorial: `Skip Tutorial`,
cheats_skipAllDialogue: `Skip All Dialogue`,
cheats_unlockAllScans: `Unlock All Scans`,
cheats_unlockAllMissions: `Unlock All Missions`,
cheats_unlockAllQuests: `Unlock All Quests`,
cheats_completeAllQuests: `Complete All Quests`,
cheats_infiniteCredits: `Infinite Credits`,
cheats_infinitePlatinum: `Infinite Platinum`,
cheats_infiniteEndo: `Infinite Endo`,
cheats_infiniteRegalAya: `Infinite Regal Aya`,
cheats_unlockAllShipFeatures: `Unlock All Ship Features`,
cheats_unlockAllShipDecorations: `Unlock All Ship Decorations`,
cheats_unlockAllFlavourItems: `Unlock All <abbr title=\"Animation Sets, Glyphs, Plattes, etc.\">Flavor Items</abbr>`,
cheats_unlockAllSkins: `Unlock All Skins`,
cheats_unlockAllCapturaScenes: `Unlock All Captura Scenes`,
cheats_universalPolarityEverywhere: `Universal Polarity Everywhere`,
cheats_unlockDoubleCapacityPotatoesEverywhere: `Potatoes Everywhere`,
cheats_unlockExilusEverywhere: `Exilus Adapters Everywhere`,
cheats_unlockArcanesEverywhere: `Arcane Adapters Everywhere`,
cheats_noDailyStandingLimits: `No Daily Standing Limits`,
cheats_spoofMasteryRank: `Spoofed Mastery Rank (-1 to disable)`,
cheats_saveSettings: `Save Settings`,
cheats_account: `Account`,
cheats_unlockAllFocusSchools: `Unlock All Focus Schools`,
cheats_helminthUnlockAll: `Fully Level Up Helminth`,
import_importNote: `You can provide a full or partial inventory response (client respresentation) here. All fields that are supported by the importer <b>will be overwritten</b> in your account.`,
import_submit: `Submit`,
}

View File

@ -0,0 +1,112 @@
// Russian translation by AMelonInsideLemon
dict = {
general_inventoryUpdateNote: `Примечание: изменения, внесенные здесь, отобразятся в игре только после повторной загрузки вашего инвентаря. Посещение навигации — самый простой способ этого добиться.`,
general_addButton: `Добавить`,
general_bulkActions: `Массовые действия`,
code_nonValidAuthz: `Ваши данные больше не действительны.`,
code_changeNameConfirm: `Какое имя вы хотите установить для своей учетной записи?`,
code_deleteAccountConfirm: `Вы уверены, что хотите удалить аккаунт |DISPLAYNAME| (|EMAIL|)? Это действие нельзя отменить.`,
code_archgun: `Арч-Пушка`,
code_melee: `Ближний бой`,
code_pistol: `Пистолет`,
code_rifle: `Винтовка`,
code_shotgun: `Дробовик`,
code_kitgun: `Китган`,
code_zaw: `Зо`,
code_moteAmp: `Пылинка`,
code_amp: `Усилитель`,
code_sirocco: `Сирокко`,
code_kDrive: `К-Драйв`,
code_legendaryCore: `Легендарное ядро`,
code_traumaticPeculiar: `Травмирующая Странность`,
code_badItem: `(Самозванец)`,
code_maxRank: `Максимальный ранг`,
code_rename: `Переименовать`,
code_renamePrompt: `Введите новое имя:`,
code_remove: `Удалить`,
code_addItemsConfirm: `Вы уверены, что хотите добавить |COUNT| предметов на ваш аккаунт?`,
code_noEquipmentToRankUp: `Нет снаряжения для повышения ранга.`,
code_succAdded: `Успешно добавлено.`,
code_buffsNumber: `Количество усилений`,
code_cursesNumber: `Количество проклятий`,
code_rerollsNumber: `Количество циклов`,
code_viewStats: `Просмотр характеристики`,
code_rank: `Ранг`,
code_count: `Количество`,
code_focusAllUnlocked: `Все школы фокуса уже разблокированы.`,
code_focusUnlocked: `Разблокировано |COUNT| новых школ фокуса! Для отображения изменений в игре потребуется обновление инвентаря. Посещение навигации — самый простой способ этого добиться.`,
code_addModsConfirm: `Вы уверены, что хотите добавить |COUNT| модов на ваш аккаунт?`,
code_succImport: `Успешно импортировано.`,
login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`,
login_emailLabel: `Адрес электронной почты`,
login_passwordLabel: `Пароль`,
login_loginButton: `Войти`,
navbar_logout: `Выйти`,
navbar_renameAccount: `Переименовать аккаунт`,
navbar_deleteAccount: `Удалить аккаунт`,
navbar_inventory: `Инвентарь`,
navbar_mods: `Моды`,
navbar_cheats: `Читы`,
navbar_import: `Импорт`,
inventory_addItems: `Добавить предметы`,
inventory_suits: `Варфреймы`,
inventory_longGuns: `Основное оружие`,
inventory_pistols: `Вторичное оружие`,
inventory_melee: `Оружие ближнего боя`,
inventory_spaceSuits: `Арчвинги`,
inventory_spaceGuns: `Оружие арчвинга`,
inventory_spaceMelee: `Оружие ближнего боя арчвинга`,
inventory_mechSuits: `Некрамехи`,
inventory_sentinels: `Стражи`,
inventory_sentinelWeapons: `Оружие стражей`,
inventory_operatorAmps: `Усилители`,
inventory_hoverboards: `К-Драйвы`,
inventory_bulkAddSuits: `Добавить отсутствующие варфреймы`,
inventory_bulkAddWeapons: `Добавить отсутствующее оружие`,
inventory_bulkAddSpaceSuits: `Добавить отсутствующие арчвинги`,
inventory_bulkAddSpaceWeapons: `Добавить отсутствующее оружие арчвингов`,
inventory_bulkAddSentinels: `Добавить отсутствующих стражей`,
inventory_bulkAddSentinelWeapons: `Добавить отсутствующее оружие стражей`,
inventory_bulkRankUpSuits: `Максимальный ранг всех варфреймов`,
inventory_bulkRankUpWeapons: `Максимальный ранг всего оружия`,
inventory_bulkRankUpSpaceSuits: `Максимальный ранг всех арчвингов`,
inventory_bulkRankUpSpaceWeapons: `Максимальный ранг всего оружия арчвингов`,
inventory_bulkRankUpSentinels: `Максимальный ранг всех стражей`,
inventory_bulkRankUpSentinelWeapons: `Максимальный ранг всего оружия стражей`,
powersuit_archonShardsLabel: `Ячейки осколков архонта`,
powersuit_archonShardsDescription: `Вы можете использовать эти неограниченные ячейки для установки множества улучшений.`,
mods_addRiven: `Добавить Мод Разлома`,
mods_fingerprint: `Отпечаток`,
mods_fingerprintHelp: `Нужна помощь с отпечатком?`,
mods_rivens: `Моды Разлома`,
mods_mods: `Моды`,
mods_bulkAddMods: `Добавить отсутствующие моды`,
cheats_administratorRequirement: `Вы должны быть администратором для использования этой функции. Чтобы стать администратором, добавьте <code>\"|DISPLAYNAME|\"</code> в <code>administratorNames</code> в config.json.`,
cheats_skipTutorial: `Пропустить обучение`,
cheats_skipAllDialogue: `Пропустить все диалоги`,
cheats_unlockAllScans: `Разблокировать все сканирования`,
cheats_unlockAllMissions: `Разблокировать все миссии`,
cheats_unlockAllQuests: `Разблокировать все квесты`,
cheats_completeAllQuests: `Завершить все квесты`,
cheats_infiniteCredits: `Бесконечные кредиты`,
cheats_infinitePlatinum: `Бесконечная платина`,
cheats_infiniteEndo: `Бесконечное эндо`,
cheats_infiniteRegalAya: `Бесконечная Королевская Айя`,
cheats_unlockAllShipFeatures: `Разблокировать все функции корабля`,
cheats_unlockAllShipDecorations: `Разблокировать все украшения корабля`,
cheats_unlockAllFlavourItems: `Разблокировать все <abbr title=\"Наборы анимаций, глифы, палитры и т. д.\">уникальные предметы</abbr>`,
cheats_unlockAllSkins: `Разблокировать все скины`,
cheats_unlockAllCapturaScenes: `Разблокировать все сцены Каптуры`,
cheats_universalPolarityEverywhere: `Универсальная полярность везде`,
cheats_unlockDoubleCapacityPotatoesEverywhere: `Катализаторы везде`,
cheats_unlockExilusEverywhere: `Адаптеры Эксилус везде`,
cheats_unlockArcanesEverywhere: `Адаптеры для мистификаторов везде`,
cheats_noDailyStandingLimits: `Без ежедневных ограничений репутации`,
cheats_spoofMasteryRank: `Подделанный ранг мастерства (-1 для отключения)`,
cheats_saveSettings: `Сохранить настройки`,
cheats_account: `Аккаунт`,
cheats_unlockAllFocusSchools: `Разблокировать все школы фокуса`,
cheats_helminthUnlockAll: `Полностью улучшить Гельминта`,
import_importNote: `Вы можете загрузить полный или частичный ответ инвентаря (клиентское представление) здесь. Все поддерживаемые поля <b>будут перезаписаны</b> в вашем аккаунте.`,
import_submit: `Отправить`,
}