Compare commits

..

No commits in common. "main" and "main" have entirely different histories.
main ... main

101 changed files with 1771 additions and 5894 deletions

View File

@ -21,7 +21,7 @@
"@typescript-eslint/no-unsafe-argument": "error",
"@typescript-eslint/no-unsafe-call": "error",
"@typescript-eslint/no-unsafe-assignment": "error",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-explicit-any": "error",
"no-loss-of-precision": "error",
"@typescript-eslint/no-unnecessary-condition": "error",
"@typescript-eslint/no-base-to-string": "off",

View File

@ -14,7 +14,7 @@ jobs:
with:
node-version: ">=20.6.0"
- run: npm ci
- run: cp config-vanilla.json config.json
- run: cp config.json.example config.json
- run: npm run verify
- run: npm run lint:ci
- run: npm run prettier

View File

@ -2,4 +2,3 @@ src/routes/api.ts
static/webui/libs/
*.html
*.md
config-vanilla.json

View File

@ -7,6 +7,5 @@ WORKDIR /app
RUN npm i --omit=dev
RUN npm run build
RUN date '+%d %B %Y' > BUILD_DATE
ENTRYPOINT ["/app/docker-entrypoint.sh"]

View File

@ -10,7 +10,7 @@ To get an idea of what functionality you can expect to be missing [have a look t
## config.json
SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [config-vanilla.json](config-vanilla.json), which has most cheats disabled.
SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [config.json.example](config.json.example), which has most cheats disabled.
- `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`.
- `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`.

View File

@ -13,6 +13,11 @@
"skipTutorial": false,
"skipAllDialogue": false,
"unlockAllScans": false,
"infiniteCredits": false,
"infinitePlatinum": false,
"infiniteEndo": false,
"infiniteRegalAya": false,
"infiniteHelminthMaterials": false,
"claimingBlueprintRefundsIngredients": false,
"dontSubtractPurchaseCreditCost": false,
"dontSubtractPurchasePlatinumCost": false,
@ -65,20 +70,8 @@
"creditBoost": false,
"affinityBoost": false,
"resourceBoost": false,
"tennoLiveRelay": false,
"wolfHunt": false,
"longShadow": false,
"hallowedFlame": false,
"hallowedNightmares": false,
"hallowedNightmaresRewardsOverride": 0,
"proxyRebellion": false,
"proxyRebellionRewardsOverride": 0,
"starDays": true,
"galleonOfGhouls": 0,
"ghoulEmergenceOverride": null,
"plagueStarOverride": null,
"starDaysOverride": null,
"dogDaysOverride": null,
"dogDaysRewardsOverride": null,
"eidolonOverride": "",
"vallisOverride": "",
"duviriOverride": "",

View File

@ -2,7 +2,7 @@
set -e
if [ ! -f conf/config.json ]; then
jq --arg value "mongodb://openwfagent:spaceninjaserver@mongodb:27017/" '.mongodbUrl = $value' /app/config-vanilla.json > /app/conf/config.json
jq --arg value "mongodb://openwfagent:spaceninjaserver@mongodb:27017/" '.mongodbUrl = $value' /app/config.json.example > /app/conf/config.json
fi
exec npm run start -- --configPath conf/config.json

658
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,6 @@
"dev": "node scripts/dev.js",
"dev:bun": "bun scripts/dev.js",
"verify": "tsgo --noEmit",
"verify:tsc": "tsc --noEmit",
"bun-run": "bun src/index.ts",
"lint": "eslint --ext .ts .",
"lint:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .",
@ -40,7 +39,7 @@
"ncp": "^2.0.0",
"typescript": "^5.5",
"undici": "^7.10.0",
"warframe-public-export-plus": "^0.5.80",
"warframe-public-export-plus": "^0.5.77",
"warframe-riven-info": "^0.1.2",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0",

View File

@ -1,22 +0,0 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getPersonalRooms } from "@/src/services/personalRoomsService";
import { RequestHandler } from "express";
export const apartmentController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const personalRooms = await getPersonalRooms(accountId, "Apartment");
const response: IApartmentResponse = {};
if (req.query.backdrop !== undefined) {
response.NewBackdropItem = personalRooms.Apartment.VideoWallBackdrop = req.query.backdrop as string;
}
if (req.query.soundscape !== undefined) {
response.NewSoundscapeItem = personalRooms.Apartment.Soundscape = req.query.soundscape as string;
}
await personalRooms.save();
res.json(response);
};
interface IApartmentResponse {
NewBackdropItem?: string;
NewSoundscapeItem?: string;
}

View File

@ -3,6 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
import { IInventoryClient, IUpgradeClient } from "@/src/types/inventoryTypes/inventoryTypes";
import { addMods, getInventory } from "@/src/services/inventoryService";
import { config } from "@/src/services/configService";
export const artifactsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
@ -33,10 +34,10 @@ export const artifactsController: RequestHandler = async (req, res) => {
addMods(inventory, [{ ItemType, ItemCount: -1 }]);
}
if (!inventory.infiniteCredits) {
if (!config.infiniteCredits) {
inventory.RegularCredits -= Cost;
}
if (!inventory.infiniteEndo) {
if (!config.infiniteEndo) {
inventory.FusionPoints -= FusionPointCost;
}

View File

@ -14,9 +14,7 @@ import {
addRecipes,
occupySlot,
combineInventoryChanges,
addKubrowPetPrint,
addPowerSuit,
addEquipment
addKubrowPetPrint
} from "@/src/services/inventoryService";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { InventorySlot, IPendingRecipeDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
@ -24,7 +22,7 @@ import { toOid2 } from "@/src/helpers/inventoryHelpers";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { IRecipe } from "warframe-public-export-plus";
import { config } from "@/src/services/configService";
import { EquipmentFeatures, IEquipmentClient, Status } from "@/src/types/equipmentTypes";
import { IEquipmentClient, Status } from "@/src/types/equipmentTypes";
interface IClaimCompletedRecipeRequest {
RecipeIds: IOid[];
@ -126,122 +124,17 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!;
addKubrowPetPrint(inventory, pet, InventoryChanges);
} else if (recipe.secretIngredientAction != "SIA_UNBRAND") {
if (recipe.resultType == "/Lotus/Powersuits/Excalibur/ExcaliburUmbra") {
// Quite the special case here...
// We don't just get Umbra, but also Skiajati and Umbra Mods. Both items are max rank, potatoed, and with the mods are pre-installed.
// Source: https://wiki.warframe.com/w/The_Sacrifice, https://wiki.warframe.com/w/Excalibur/Umbra, https://wiki.warframe.com/w/Skiajati
const umbraModA = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModA",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
const umbraModB = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModB",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
const umbraModC = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModC",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
const sacrificeModA = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Sacrifice/MeleeSacrificeModA",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
const sacrificeModB = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Sacrifice/MeleeSacrificeModB",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
InventoryChanges.Upgrades ??= [];
InventoryChanges.Upgrades.push(umbraModA, umbraModB, umbraModC, sacrificeModA, sacrificeModB);
await addPowerSuit(
InventoryChanges = {
...InventoryChanges,
...(await addItem(
inventory,
"/Lotus/Powersuits/Excalibur/ExcaliburUmbra",
{
Configs: [
{
Upgrades: [
"",
"",
"",
"",
"",
umbraModA.ItemId.$oid,
umbraModB.ItemId.$oid,
umbraModC.ItemId.$oid
]
}
],
XP: 900_000,
Features: EquipmentFeatures.DOUBLE_CAPACITY
},
InventoryChanges
);
inventory.XPInfo.push({
ItemType: "/Lotus/Powersuits/Excalibur/ExcaliburUmbra",
XP: 900_000
});
addEquipment(
inventory,
"Melee",
"/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana",
{
Configs: [
{ Upgrades: ["", "", "", "", "", "", sacrificeModA.ItemId.$oid, sacrificeModB.ItemId.$oid] }
],
XP: 450_000,
Features: EquipmentFeatures.DOUBLE_CAPACITY
},
InventoryChanges
);
inventory.XPInfo.push({
ItemType: "/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana",
XP: 450_000
});
} else {
InventoryChanges = {
...InventoryChanges,
...(await addItem(
inventory,
recipe.resultType,
recipe.num,
false,
undefined,
pendingRecipe.TargetFingerprint
))
};
}
recipe.resultType,
recipe.num,
false,
undefined,
pendingRecipe.TargetFingerprint
))
};
}
if (
config.claimingBlueprintRefundsIngredients &&

View File

@ -1,4 +1,4 @@
import { checkCalendarAutoAdvance, getCalendarProgress, getInventory } from "@/src/services/inventoryService";
import { checkCalendarChallengeCompletion, getCalendarProgress, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { getWorldState } from "@/src/services/worldStateService";
@ -28,7 +28,7 @@ export const completeCalendarEventController: RequestHandler = async (req, res)
}
}
calendarProgress.SeasonProgress.LastCompletedDayIdx = dayIndex;
checkCalendarAutoAdvance(inventory, currentSeason);
checkCalendarChallengeCompletion(calendarProgress, currentSeason);
await inventory.save();
res.json({
InventoryChanges: inventoryChanges,

View File

@ -4,7 +4,8 @@ import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inven
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { IVeiledRivenFingerprint } from "@/src/helpers/rivenHelper";
import { createUnveiledRivenFingerprint } from "@/src/helpers/rivenHelper";
import { ExportUpgrades } from "warframe-public-export-plus";
export const completeRandomModChallengeController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
@ -26,11 +27,10 @@ export const completeRandomModChallengeController: RequestHandler = async (req,
inventoryChanges.MiscItems = miscItemChanges;
}
// Complete the riven challenge
// Update riven fingerprint to a randomised unveiled state
const upgrade = inventory.Upgrades.id(request.ItemId)!;
const fp = JSON.parse(upgrade.UpgradeFingerprint!) as IVeiledRivenFingerprint;
fp.challenge.Progress = fp.challenge.Required;
upgrade.UpgradeFingerprint = JSON.stringify(fp);
const meta = ExportUpgrades[upgrade.ItemType];
upgrade.UpgradeFingerprint = JSON.stringify(createUnveiledRivenFingerprint(meta));
await inventory.save();

View File

@ -1,4 +1,5 @@
import { RequestHandler } from "express";
import { config } from "@/src/services/configService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService";
@ -8,7 +9,7 @@ export const creditsController: RequestHandler = async (req, res) => {
getAccountIdForRequest(req),
getInventory(
req.query.accountId as string,
"RegularCredits TradesRemaining PremiumCreditsFree PremiumCredits infiniteCredits infinitePlatinum"
"RegularCredits TradesRemaining PremiumCreditsFree PremiumCredits"
)
])
)[1];
@ -20,10 +21,10 @@ export const creditsController: RequestHandler = async (req, res) => {
PremiumCredits: inventory.PremiumCredits
};
if (inventory.infiniteCredits) {
if (config.infiniteCredits) {
response.RegularCredits = 999999999;
}
if (inventory.infinitePlatinum) {
if (config.infinitePlatinum) {
response.PremiumCreditsFree = 0;
response.PremiumCredits = 999999999;
}

View File

@ -88,6 +88,7 @@ export const crewShipFusionController: RequestHandler = async (req, res) => {
}
}
superiorItem.UpgradeFingerprint = JSON.stringify(fingerprint);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
inventoryChanges[category] = [superiorItem.toJSON() as any];
await inventory.save();

View File

@ -3,13 +3,11 @@ import {
getGuildForRequestEx,
hasAccessToDojo,
hasGuildPermission,
refundDojoDeco,
removeDojoDeco
} from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express";
export const destroyDojoDecoController: RequestHandler = async (req, res) => {
@ -20,20 +18,9 @@ export const destroyDojoDecoController: RequestHandler = async (req, res) => {
res.json({ DojoRequestStatus: -1 });
return;
}
const request = JSON.parse(String(req.body)) as IDestroyDojoDecoRequest | IClearObstacleCourseRequest;
if ("DecoType" in request) {
removeDojoDeco(guild, request.ComponentId, request.DecoId);
} else if (request.Act == "cObst") {
const component = guild.DojoComponents.id(request.ComponentId)!;
if (component.Decos) {
for (const deco of component.Decos) {
refundDojoDeco(guild, component, deco);
}
component.Decos.splice(0, component.Decos.length);
}
} else {
logger.error(`unhandled destroyDojoDeco request`, request);
}
const request = JSON.parse(String(req.body)) as IDestroyDojoDecoRequest;
removeDojoDeco(guild, request.ComponentId, request.DecoId);
await guild.save();
res.json(await getDojoClient(guild, 0, request.ComponentId));
@ -44,8 +31,3 @@ interface IDestroyDojoDecoRequest {
ComponentId: string;
DecoId: string;
}
interface IClearObstacleCourseRequest {
ComponentId: string;
Act: "cObst" | "maybesomethingelsewedontknowabout";
}

View File

@ -2,14 +2,22 @@ import { RequestHandler } from "express";
import { ExportResources } from "warframe-public-export-plus";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { addFusionTreasures, addMiscItems, getInventory } from "@/src/services/inventoryService";
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import { parseFusionTreasure } from "@/src/helpers/inventoryHelpers";
import { IFusionTreasure, IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
interface IFusionTreasureRequest {
oldTreasureName: string;
newTreasureName: string;
}
const parseFusionTreasure = (name: string, count: number): IFusionTreasure => {
const arr = name.split("_");
return {
ItemType: arr[0],
Sockets: parseInt(arr[1], 16),
ItemCount: count
};
};
export const fusionTreasuresController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);

View File

@ -1,62 +0,0 @@
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService";
import { EPOCH, getSeasonChallengePools, getWorldState, pushWeeklyActs } from "@/src/services/worldStateService";
import { unixTimesInMs } from "@/src/constants/timeConstants";
import { ISeasonChallenge } from "@/src/types/worldStateTypes";
import { ExportChallenges } from "warframe-public-export-plus";
export const getPastWeeklyChallengesController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "SeasonChallengeHistory ChallengeProgress");
const worldState = getWorldState(undefined);
if (worldState.SeasonInfo) {
const pools = getSeasonChallengePools(worldState.SeasonInfo.AffiliationTag);
const nightwaveStartTimestamp = Number(worldState.SeasonInfo.Activation.$date.$numberLong);
const nightwaveSeason = worldState.SeasonInfo.Season;
const timeMs = worldState.Time * 1000;
const completedChallengesIds = new Set<string>();
inventory.SeasonChallengeHistory.forEach(challengeHistory => {
const entryNightwaveSeason = parseInt(challengeHistory.id.slice(0, 4), 10) - 1;
if (nightwaveSeason == entryNightwaveSeason) {
const meta = Object.entries(ExportChallenges).find(
([key]) => key.split("/").pop() === challengeHistory.challenge
);
if (meta) {
const [, challengeMeta] = meta;
const challengeProgress = inventory.ChallengeProgress.find(
c => c.Name === challengeHistory.challenge
);
if (challengeProgress && challengeProgress.Progress >= (challengeMeta.requiredCount ?? 1)) {
completedChallengesIds.add(challengeHistory.id);
}
}
}
});
const PastWeeklyChallenges: ISeasonChallenge[] = [];
let week = Math.trunc((timeMs - EPOCH) / unixTimesInMs.week) - 1;
while (EPOCH + week * unixTimesInMs.week >= nightwaveStartTimestamp && PastWeeklyChallenges.length < 3) {
const tempActs: ISeasonChallenge[] = [];
pushWeeklyActs(tempActs, pools, week, nightwaveStartTimestamp, nightwaveSeason);
for (const act of tempActs) {
if (!completedChallengesIds.has(act._id.$oid) && PastWeeklyChallenges.length < 3) {
if (act.Challenge.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanent")) {
act.Permanent = true;
}
PastWeeklyChallenges.push(act);
}
}
week--;
}
res.json({ PastWeeklyChallenges: PastWeeklyChallenges });
}
};

View File

@ -1,6 +1,5 @@
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { sendWsBroadcastTo } from "@/src/services/wsService";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
@ -74,5 +73,4 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
InventoryChanges: inventoryChanges,
AffiliationMods: affiliationMods
});
sendWsBroadcastTo(accountId, { update_inventory: true });
};

View File

@ -13,8 +13,7 @@ import {
addItems,
combineInventoryChanges,
getEffectiveAvatarImageType,
getInventory,
updateCurrency
getInventory
} from "@/src/services/inventoryService";
import { logger } from "@/src/utils/logger";
import { ExportFlavour } from "warframe-public-export-plus";
@ -101,9 +100,6 @@ export const inboxController: RequestHandler = async (req, res) => {
}
}
}
if (message.RegularCredits) {
updateCurrency(inventory, -message.RegularCredits, false, inventoryChanges);
}
await inventory.save();
res.json({ InventoryChanges: inventoryChanges });
} else if (latestClientMessageId) {

View File

@ -15,6 +15,7 @@ import { getRecipe } from "@/src/services/itemDataService";
import { toMongoDate, version_compare } from "@/src/helpers/inventoryHelpers";
import { logger } from "@/src/utils/logger";
import { colorToShard } from "@/src/helpers/shardHelper";
import { config } from "@/src/services/configService";
import {
addInfestedFoundryXP,
applyCheatsToInfestedFoundry,
@ -72,7 +73,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
addMiscItems(inventory, miscItemChanges);
// consume resources
if (!inventory.infiniteHelminthMaterials) {
if (!config.infiniteHelminthMaterials) {
let type: string;
let count: number;
if (account.BuildLabel && version_compare(account.BuildLabel, "2025.05.20.10.18") < 0) {
@ -98,7 +99,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
await inventory.save();
const infestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry!;
applyCheatsToInfestedFoundry(inventory, infestedFoundry);
applyCheatsToInfestedFoundry(infestedFoundry);
res.json({
InventoryChanges: {
MiscItems: miscItemChanges,
@ -128,14 +129,13 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
case "c": {
// consume items
const inventory = await getInventory(account._id.toString());
if (inventory.infiniteHelminthMaterials) {
if (config.infiniteHelminthMaterials) {
res.status(400).end();
return;
}
const request = getJSONfromString<IHelminthFeedRequest>(String(req.body));
const inventory = await getInventory(account._id.toString());
inventory.InfestedFoundry ??= {};
inventory.InfestedFoundry.Resources ??= [];
@ -240,7 +240,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
}
await inventory.save();
const infestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry!;
applyCheatsToInfestedFoundry(inventory, infestedFoundry);
applyCheatsToInfestedFoundry(infestedFoundry);
res.json({
InventoryChanges: {
InfestedFoundry: infestedFoundry
@ -254,7 +254,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
const request = getJSONfromString<IHelminthSubsumeRequest>(String(req.body));
const inventory = await getInventory(account._id.toString());
const recipe = getRecipe(request.Recipe)!;
if (!inventory.infiniteHelminthMaterials) {
if (!config.infiniteHelminthMaterials) {
for (const ingredient of recipe.secretIngredients!) {
const resource = inventory.InfestedFoundry!.Resources!.find(x => x.ItemType == ingredient.ItemType);
if (resource) {
@ -280,7 +280,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
freeUpSlot(inventory, InventorySlot.SUITS);
await inventory.save();
const infestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry!;
applyCheatsToInfestedFoundry(inventory, infestedFoundry);
applyCheatsToInfestedFoundry(infestedFoundry);
res.json({
InventoryChanges: {
Recipes: recipeChanges,
@ -307,7 +307,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
const recipeChanges = handleSubsumeCompletion(inventory);
await inventory.save();
const infestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry!;
applyCheatsToInfestedFoundry(inventory, infestedFoundry);
applyCheatsToInfestedFoundry(infestedFoundry);
res.json({
InventoryChanges: {
...currencyChanges,
@ -328,7 +328,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
suit.UpgradesExpiry = upgradesExpiry;
const recipeChanges = addInfestedFoundryXP(inventory.InfestedFoundry!, 4800_00);
addRecipes(inventory, recipeChanges);
if (!inventory.infiniteHelminthMaterials) {
if (!config.infiniteHelminthMaterials) {
for (let i = 0; i != request.ResourceTypes.length; ++i) {
inventory.InfestedFoundry!.Resources!.find(x => x.ItemType == request.ResourceTypes[i])!.Count -=
request.ResourceCosts[i];
@ -338,7 +338,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
inventory.InfestedFoundry!.InvigorationsApplied += 1;
await inventory.save();
const infestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry!;
applyCheatsToInfestedFoundry(inventory, infestedFoundry);
applyCheatsToInfestedFoundry(infestedFoundry);
res.json({
SuitId: request.SuitId,
OffensiveUpgrade: request.OffensiveUpgradeType,

View File

@ -6,21 +6,12 @@ import allDialogue from "@/static/fixed_responses/allDialogue.json";
import { ILoadoutDatabase } from "@/src/types/saveLoadoutTypes";
import { IInventoryClient, IShipInventory, equipmentKeys } from "@/src/types/inventoryTypes/inventoryTypes";
import { IPolarity, ArtifactPolarity } from "@/src/types/inventoryTypes/commonInventoryTypes";
import {
eFaction,
ExportCustoms,
ExportFlavour,
ExportResources,
ExportVirtuals,
ICountedItem
} from "warframe-public-export-plus";
import { ExportCustoms, ExportFlavour, ExportResources, ExportVirtuals } from "warframe-public-export-plus";
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService";
import {
addEmailItem,
addItem,
addMiscItems,
allDailyAffiliationKeys,
checkCalendarAutoAdvance,
cleanupInventory,
createLibraryDailyTask,
getCalendarProgress
@ -38,8 +29,6 @@ import { unixTimesInMs } from "@/src/constants/timeConstants";
import { DailyDeal } from "@/src/models/worldStateModel";
import { EquipmentFeatures } from "@/src/types/equipmentTypes";
import { generateRewardSeed } from "@/src/services/rngService";
import { getInvasionByOid, getWorldState } from "@/src/services/worldStateService";
import { createMessage } from "@/src/services/inboxService";
export const inventoryController: RequestHandler = async (request, response) => {
const account = await getAccountForRequest(request);
@ -122,61 +111,58 @@ export const inventoryController: RequestHandler = async (request, response) =>
}
}
// TODO: Setup CalendarProgress as part of 1999 mission completion?
if (inventory.CalendarProgress) {
const previousYearIteration = inventory.CalendarProgress.Iteration;
getCalendarProgress(inventory); // handle year rollover; the client expects to receive an inventory with an up-to-date CalendarProgress
const previousYearIteration = inventory.CalendarProgress?.Iteration;
// We need to do the following to ensure the in-game calendar does not break:
getCalendarProgress(inventory); // Keep the CalendarProgress up-to-date (at least for the current year iteration) (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2364)
checkCalendarAutoAdvance(inventory, getWorldState().KnownCalendarSeasons[0]); // Skip birthday events for characters if we do not have them unlocked yet (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2424)
// also handle sending of kiss cinematic at year rollover
if (
inventory.CalendarProgress!.Iteration != previousYearIteration &&
inventory.DialogueHistory &&
inventory.DialogueHistory.Dialogues
) {
let kalymos = false;
for (const { dialogueName, kissEmail } of [
{
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/ArthurDialogue_rom.dialogue",
kissEmail: "/Lotus/Types/Items/EmailItems/ArthurKissEmailItem"
},
{
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/EleanorDialogue_rom.dialogue",
kissEmail: "/Lotus/Types/Items/EmailItems/EleanorKissEmailItem"
},
{
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/LettieDialogue_rom.dialogue",
kissEmail: "/Lotus/Types/Items/EmailItems/LettieKissEmailItem"
},
{
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/JabirDialogue_rom.dialogue",
kissEmail: "/Lotus/Types/Items/EmailItems/AmirKissEmailItem"
},
{
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/AoiDialogue_rom.dialogue",
kissEmail: "/Lotus/Types/Items/EmailItems/AoiKissEmailItem"
},
{
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/QuincyDialogue_rom.dialogue",
kissEmail: "/Lotus/Types/Items/EmailItems/QuincyKissEmailItem"
}
]) {
const dialogue = inventory.DialogueHistory.Dialogues.find(x => x.DialogueName == dialogueName);
if (dialogue) {
if (dialogue.Rank == 7) {
await addEmailItem(inventory, kissEmail);
kalymos = false;
break;
// also handle sending of kiss cinematic at year rollover
if (
inventory.CalendarProgress.Iteration != previousYearIteration &&
inventory.DialogueHistory &&
inventory.DialogueHistory.Dialogues
) {
let kalymos = false;
for (const { dialogueName, kissEmail } of [
{
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/ArthurDialogue_rom.dialogue",
kissEmail: "/Lotus/Types/Items/EmailItems/ArthurKissEmailItem"
},
{
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/EleanorDialogue_rom.dialogue",
kissEmail: "/Lotus/Types/Items/EmailItems/EleanorKissEmailItem"
},
{
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/LettieDialogue_rom.dialogue",
kissEmail: "/Lotus/Types/Items/EmailItems/LettieKissEmailItem"
},
{
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/JabirDialogue_rom.dialogue",
kissEmail: "/Lotus/Types/Items/EmailItems/AmirKissEmailItem"
},
{
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/AoiDialogue_rom.dialogue",
kissEmail: "/Lotus/Types/Items/EmailItems/AoiKissEmailItem"
},
{
dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/QuincyDialogue_rom.dialogue",
kissEmail: "/Lotus/Types/Items/EmailItems/QuincyKissEmailItem"
}
if (dialogue.Rank == 6) {
kalymos = true;
]) {
const dialogue = inventory.DialogueHistory.Dialogues.find(x => x.DialogueName == dialogueName);
if (dialogue) {
if (dialogue.Rank == 7) {
await addEmailItem(inventory, kissEmail);
kalymos = false;
break;
}
if (dialogue.Rank == 6) {
kalymos = true;
}
}
}
}
if (kalymos) {
await addEmailItem(inventory, "/Lotus/Types/Items/EmailItems/KalymosKissEmailItem");
if (kalymos) {
await addEmailItem(inventory, "/Lotus/Types/Items/EmailItems/KalymosKissEmailItem");
}
}
}
@ -195,63 +181,6 @@ export const inventoryController: RequestHandler = async (request, response) =>
//await inventory.save();
}
for (let i = 0; i != inventory.QualifyingInvasions.length; ) {
const qi = inventory.QualifyingInvasions[i];
const invasion = getInvasionByOid(qi.invasionId.toString());
if (!invasion) {
logger.debug(`removing QualifyingInvasions entry for unknown invasion: ${qi.invasionId.toString()}`);
inventory.QualifyingInvasions.splice(i, 1);
continue;
}
if (invasion.Completed) {
let factionSidedWith: string | undefined;
let battlePay: ICountedItem[] | undefined;
if (qi.AttackerScore >= 3) {
factionSidedWith = invasion.Faction;
battlePay = invasion.AttackerReward.countedItems;
logger.debug(`invasion pay from ${factionSidedWith}`, { battlePay });
} else if (qi.DefenderScore >= 3) {
factionSidedWith = invasion.DefenderFaction;
battlePay = invasion.DefenderReward.countedItems;
logger.debug(`invasion pay from ${factionSidedWith}`, { battlePay });
}
if (factionSidedWith) {
if (battlePay) {
// Decoupling rewards from the inbox message because it may delete itself without being read
for (const item of battlePay) {
await addItem(inventory, item.ItemType, item.ItemCount);
}
await createMessage(account._id, [
{
sndr: eFaction.find(x => x.tag == factionSidedWith)?.name ?? factionSidedWith, // TOVERIFY
msg: `/Lotus/Language/G1Quests/${factionSidedWith}_InvasionThankyouMessageBody`,
sub: `/Lotus/Language/G1Quests/${factionSidedWith}_InvasionThankyouMessageSubject`,
countedAtt: battlePay,
attVisualOnly: true,
icon:
factionSidedWith == "FC_GRINEER"
? "/Lotus/Interface/Icons/Npcs/EliteRifleLancerAvatar.png" // Source: https://www.reddit.com/r/Warframe/comments/1aj4usx/battle_pay_worth_10_plat/, https://www.youtube.com/watch?v=XhNZ6ai6BOY
: "/Lotus/Interface/Icons/Npcs/CrewmanNormal.png", // My best source for this is https://www.youtube.com/watch?v=rxrCCFm73XE around 1:37
// TOVERIFY: highPriority?
endDate: new Date(Date.now() + 86400_000) // TOVERIFY: This type of inbox message seems to automatically delete itself. We'll just delete it after 24 hours, but it's not clear if this is correct.
}
]);
}
if (invasion.Faction != "FC_INFESTATION") {
// Sided with grineer -> opposed corpus -> send zanuka (harvester)
// Sided with corpus -> opposed grineer -> send g3 (death squad)
inventory[factionSidedWith != "FC_GRINEER" ? "DeathSquadable" : "Harvestable"] = true;
// TOVERIFY: Should this happen earlier?
// TOVERIFY: Should this send an (ephemeral) email?
}
}
logger.debug(`removing QualifyingInvasions entry for completed invasion: ${qi.invasionId.toString()}`);
inventory.QualifyingInvasions.splice(i, 1);
continue;
}
++i;
}
if (inventory.LastInventorySync) {
const lastSyncDuviriMood = Math.trunc(inventory.LastInventorySync.getTimestamp().getTime() / 7200000);
const currentDuviriMood = Math.trunc(Date.now() / 7200000);
@ -295,17 +224,17 @@ export const getInventoryResponse = async (
};
}
if (inventory.infiniteCredits) {
if (config.infiniteCredits) {
inventoryResponse.RegularCredits = 999999999;
}
if (inventory.infinitePlatinum) {
if (config.infinitePlatinum) {
inventoryResponse.PremiumCreditsFree = 0;
inventoryResponse.PremiumCredits = 999999999;
}
if (inventory.infiniteEndo) {
if (config.infiniteEndo) {
inventoryResponse.FusionPoints = 999999999;
}
if (inventory.infiniteRegalAya) {
if (config.infiniteRegalAya) {
inventoryResponse.PrimeTokens = 999999999;
}
@ -335,17 +264,6 @@ export const getInventoryResponse = async (
for (const uniqueName in ExportFlavour) {
inventoryResponse.FlavourItems.push({ ItemType: uniqueName });
}
} else if (config.worldState?.baroTennoConRelay) {
[
"/Lotus/Types/Items/Events/TennoConRelay2022EarlyAccess",
"/Lotus/Types/Items/Events/TennoConRelay2023EarlyAccess",
"/Lotus/Types/Items/Events/TennoConRelay2024EarlyAccess",
"/Lotus/Types/Items/Events/TennoConRelay2025EarlyAccess"
].forEach(uniqueName => {
if (!inventoryResponse.FlavourItems.some(x => x.ItemType == uniqueName)) {
inventoryResponse.FlavourItems.push({ ItemType: uniqueName });
}
});
}
if (config.unlockAllSkins) {
@ -450,7 +368,7 @@ export const getInventoryResponse = async (
}
if (inventoryResponse.InfestedFoundry) {
applyCheatsToInfestedFoundry(inventory, inventoryResponse.InfestedFoundry);
applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry);
}
// Set 2FA enabled so trading post can be used

View File

@ -2,7 +2,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory, updateCurrency, updateSlots } from "@/src/services/inventoryService";
import { RequestHandler } from "express";
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
import { exhaustive } from "@/src/utils/ts-utils";
import { logger } from "@/src/utils/logger";
/*
loadout slots are additionally purchased slots only
@ -22,44 +22,13 @@ export const inventorySlotsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const body = JSON.parse(req.body as string) as IInventorySlotsRequest;
let price;
let amount;
switch (body.Bin) {
case InventorySlot.SUITS:
case InventorySlot.MECHSUITS:
case InventorySlot.PVE_LOADOUTS:
case InventorySlot.CREWMEMBERS:
price = 20;
amount = 1;
break;
case InventorySlot.SPACESUITS:
price = 12;
amount = 1;
break;
case InventorySlot.WEAPONS:
case InventorySlot.SPACEWEAPONS:
case InventorySlot.SENTINELS:
case InventorySlot.RJ_COMPONENT_AND_ARMAMENTS:
case InventorySlot.AMPS:
price = 12;
amount = 2;
break;
case InventorySlot.RIVENS:
price = 60;
amount = 3;
break;
default:
exhaustive(body.Bin);
throw new Error(`unexpected slot purchase of type ${body.Bin as string}`);
if (body.Bin != InventorySlot.SUITS && body.Bin != InventorySlot.PVE_LOADOUTS) {
logger.warn(`unexpected slot purchase of type ${body.Bin}, account may be overcharged`);
}
const inventory = await getInventory(accountId);
const currencyChanges = updateCurrency(inventory, price, true);
updateSlots(inventory, body.Bin, amount, amount);
const currencyChanges = updateCurrency(inventory, 20, true);
updateSlots(inventory, body.Bin, 1, 1);
await inventory.save();
res.json({ InventoryChanges: currencyChanges });

View File

@ -130,7 +130,7 @@ const createLoginResponse = (
resp.Groups = [];
}
if (version_compare(buildLabel, "2021.04.13.19.58") >= 0) {
resp.DTLS = 0; // bit 0 enables DTLS. if enabled, additional bits can be set, e.g. bit 2 to enable logging. on live, the value is 99.
resp.DTLS = 99;
}
if (version_compare(buildLabel, "2022.04.29.12.53") >= 0) {
resp.ClientType = account.ClientType;

View File

@ -6,11 +6,7 @@ import { addMissionInventoryUpdates, addMissionRewards } from "@/src/services/mi
import { getInventory } from "@/src/services/inventoryService";
import { getInventoryResponse } from "@/src/controllers/api/inventoryController";
import { logger } from "@/src/utils/logger";
import {
IMissionInventoryUpdateResponse,
IMissionInventoryUpdateResponseBackToDryDock,
IMissionInventoryUpdateResponseRailjackInterstitial
} from "@/src/types/missionTypes";
import { IMissionInventoryUpdateResponse } from "@/src/types/missionTypes";
import { sendWsBroadcastTo } from "@/src/services/wsService";
import { generateRewardSeed } from "@/src/services/rngService";
@ -99,9 +95,11 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
inventory.RewardSeed = generateRewardSeed();
}
await inventory.save();
const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel);
//TODO: figure out when to send inventory. it is needed for many cases.
const deltas: IMissionInventoryUpdateResponseRailjackInterstitial = {
res.json({
InventoryJson: JSON.stringify(inventoryResponse),
InventoryChanges: inventoryChanges,
MissionRewards,
...credits,
@ -110,25 +108,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
SyndicateXPItemReward,
AffiliationMods,
ConquestCompletedMissionsCount
};
if (missionReport.RJ) {
logger.debug(`railjack interstitial request, sending only deltas`, deltas);
res.json(deltas);
} else if (missionReport.RewardInfo) {
logger.debug(`classic mission completion, sending everything`);
const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel);
res.json({
InventoryJson: JSON.stringify(inventoryResponse),
...deltas
} satisfies IMissionInventoryUpdateResponse);
} else {
logger.debug(`no reward info, assuming this wasn't a mission completion and we should just sync inventory`);
const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel);
res.json({
InventoryJson: JSON.stringify(inventoryResponse)
} satisfies IMissionInventoryUpdateResponseBackToDryDock);
}
} satisfies IMissionInventoryUpdateResponse);
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
};

View File

@ -1,6 +1,5 @@
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { sendWsBroadcastTo } from "@/src/services/wsService";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import {
getInventory,
@ -195,5 +194,4 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
MiscItems: miscItemChanges
}
});
sendWsBroadcastTo(accountId, { update_inventory: true });
};

View File

@ -15,7 +15,6 @@ import {
} from "@/src/services/inventoryService";
import { getDefaultUpgrades } from "@/src/services/itemDataService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { sendWsBroadcastTo } from "@/src/services/wsService";
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { EquipmentFeatures } from "@/src/types/equipmentTypes";
@ -69,7 +68,6 @@ export const modularWeaponSaleController: RequestHandler = async (req, res) => {
res.json({
InventoryChanges: inventoryChanges
});
sendWsBroadcastTo(accountId, { update_inventory: true });
} else {
throw new Error(`unknown modularWeaponSale op: ${String(req.query.op)}`);
}

View File

@ -1,52 +1,25 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { addConsumables, getInventory } from "@/src/services/inventoryService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IPlayerSkills } from "@/src/types/inventoryTypes/inventoryTypes";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { RequestHandler } from "express";
export const playerSkillsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "PlayerSkills Consumables");
const inventory = await getInventory(accountId, "PlayerSkills");
const request = getJSONfromString<IPlayerSkillsRequest>(String(req.body));
const oldRank: number = inventory.PlayerSkills[request.Skill as keyof IPlayerSkills];
const cost = (request.Pool == "LPP_DRIFTER" ? drifterCosts[oldRank] : 1 << oldRank) * 1000;
inventory.PlayerSkills[request.Pool as keyof IPlayerSkills] -= cost;
inventory.PlayerSkills[request.Skill as keyof IPlayerSkills]++;
const inventoryChanges: IInventoryChanges = {};
if (request.Skill == "LPS_COMMAND") {
if (inventory.PlayerSkills.LPS_COMMAND == 9) {
const consumablesChanges = [
{
ItemType: "/Lotus/Types/Restoratives/Consumable/CrewmateBall",
ItemCount: 1
}
];
addConsumables(inventory, consumablesChanges);
inventoryChanges.Consumables = consumablesChanges;
}
} else if (request.Skill == "LPS_DRIFT_RIDING") {
if (inventory.PlayerSkills.LPS_DRIFT_RIDING == 9) {
const consumablesChanges = [
{
ItemType: "/Lotus/Types/Restoratives/ErsatzSummon",
ItemCount: 1
}
];
addConsumables(inventory, consumablesChanges);
inventoryChanges.Consumables = consumablesChanges;
}
}
await inventory.save();
res.json({
Pool: request.Pool,
PoolInc: -cost,
Skill: request.Skill,
Rank: oldRank + 1,
InventoryChanges: inventoryChanges
Rank: oldRank + 1
});
};

View File

@ -11,7 +11,7 @@ export const projectionManagerController: RequestHandler = async (req, res) => {
const [era, category, currentQuality] = parseProjection(request.projectionType);
const upgradeCost = config.dontSubtractVoidTraces
? 0
: qualityNumberToCost[request.qualityTag] - qualityNumberToCost[qualityKeywordToNumber[currentQuality]];
: (request.qualityTag - qualityKeywordToNumber[currentQuality]) * 25;
const newProjectionType = findProjection(era, category, qualityNumberToKeyword[request.qualityTag]);
addMiscItems(inventory, [
{
@ -49,7 +49,6 @@ const qualityKeywordToNumber: Record<VoidProjectionQuality, number> = {
VPQ_GOLD: 2,
VPQ_PLATINUM: 3
};
const qualityNumberToCost = [0, 25, 50, 100];
// e.g. "/Lotus/Types/Game/Projections/T2VoidProjectionProteaPrimeDBronze" -> ["Lith", "W5", "VPQ_BRONZE"]
const parseProjection = (typeName: string): [string, string, VoidProjectionQuality] => {

View File

@ -1,7 +1,6 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { sendWsBroadcastTo } from "@/src/services/wsService";
import { RequestHandler } from "express";
export const releasePetController: RequestHandler = async (req, res) => {
@ -20,7 +19,6 @@ export const releasePetController: RequestHandler = async (req, res) => {
await inventory.save();
res.json({ inventoryChanges }); // Not a mistake; it's "inventoryChanges" here.
sendWsBroadcastTo(accountId, { update_inventory: true });
};
interface IReleasePetRequest {

View File

@ -1,5 +0,0 @@
import { RequestHandler } from "express";
export const resetQuestProgressController: RequestHandler = (_req, res) => {
res.send("1").end();
};

View File

@ -9,16 +9,13 @@ import {
freeUpSlot,
combineInventoryChanges,
addCrewShipRawSalvage,
addFusionPoints,
addCrewShipFusionPoints,
addFusionTreasures
addFusionPoints
} from "@/src/services/inventoryService";
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
import { ExportDojoRecipes } from "warframe-public-export-plus";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { sendWsBroadcastEx } from "@/src/services/wsService";
import { parseFusionTreasure } from "@/src/helpers/inventoryHelpers";
import { sendWsBroadcastTo } from "@/src/services/wsService";
export const sellController: RequestHandler = async (req, res) => {
const payload = JSON.parse(String(req.body)) as ISellRequest;
@ -29,8 +26,6 @@ export const sellController: RequestHandler = async (req, res) => {
requiredFields.add("RegularCredits");
} else if (payload.SellCurrency == "SC_FusionPoints") {
requiredFields.add("FusionPoints");
} else if (payload.SellCurrency == "SC_CrewShipFusionPoints") {
requiredFields.add("CrewShipFusionPoints");
} else {
requiredFields.add("MiscItems");
}
@ -84,8 +79,6 @@ export const sellController: RequestHandler = async (req, res) => {
inventory.RegularCredits += payload.SellPrice;
} else if (payload.SellCurrency == "SC_FusionPoints") {
addFusionPoints(inventory, payload.SellPrice);
} else if (payload.SellCurrency == "SC_CrewShipFusionPoints") {
addCrewShipFusionPoints(inventory, payload.SellPrice);
} else if (payload.SellCurrency == "SC_PrimeBucks") {
addMiscItems(inventory, [
{
@ -297,17 +290,12 @@ export const sellController: RequestHandler = async (req, res) => {
]);
});
}
if (payload.Items.FusionTreasures) {
payload.Items.FusionTreasures.forEach(sellItem => {
addFusionTreasures(inventory, [parseFusionTreasure(sellItem.String, sellItem.Count * -1)]);
});
}
await inventory.save();
res.json({
inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges"
});
sendWsBroadcastEx({ update_inventory: true }, accountId, parseInt(String(req.query.wsid)));
sendWsBroadcastTo(accountId, { update_inventory: true });
};
interface ISellRequest {
@ -334,7 +322,6 @@ interface ISellRequest {
CrewMembers?: ISellItem[];
CrewShipWeapons?: ISellItem[];
CrewShipWeaponSkins?: ISellItem[];
FusionTreasures?: ISellItem[];
};
SellPrice: number;
SellCurrency:
@ -343,8 +330,7 @@ interface ISellRequest {
| "SC_FusionPoints"
| "SC_DistillPoints"
| "SC_CrewShipFusionPoints"
| "SC_Resources"
| "somethingelsewemightnotknowabout";
| "SC_Resources";
buildLabel: string;
}

View File

@ -1,17 +1,20 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IShipDecorationsRequest, IResetShipDecorationsRequest } from "@/src/types/personalRoomsTypes";
import { IShipDecorationsRequest } from "@/src/types/personalRoomsTypes";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express";
import { handleResetShipDecorations, handleSetShipDecorations } from "@/src/services/shipCustomizationsService";
import { handleSetShipDecorations } from "@/src/services/shipCustomizationsService";
export const shipDecorationsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
if (req.query.reset == "1") {
const request = JSON.parse(req.body as string) as IResetShipDecorationsRequest;
const response = await handleResetShipDecorations(accountId, request);
res.send(response);
} else {
const shipDecorationsRequest = JSON.parse(req.body as string) as IShipDecorationsRequest;
const shipDecorationsRequest = JSON.parse(req.body as string) as IShipDecorationsRequest;
try {
const placedDecoration = await handleSetShipDecorations(accountId, shipDecorationsRequest);
res.send(placedDecoration);
} catch (error: unknown) {
if (error instanceof Error) {
logger.error(`error in shipDecorationsController: ${error.message}`);
res.status(400).json({ error: error.message });
}
}
};

View File

@ -10,7 +10,6 @@ import { logger } from "@/src/utils/logger";
export const updateChallengeProgressController: RequestHandler = async (req, res) => {
const challenges = getJSONfromString<IUpdateChallengeProgressRequest>(String(req.body));
const account = await getAccountForRequest(req);
logger.debug(`challenge report:`, challenges);
const inventory = await getInventory(
account._id.toString(),
@ -18,7 +17,7 @@ export const updateChallengeProgressController: RequestHandler = async (req, res
);
let affiliationMods: IAffiliationMods[] = [];
if (challenges.ChallengeProgress) {
affiliationMods = await addChallenges(
affiliationMods = addChallenges(
account,
inventory,
challenges.ChallengeProgress,

View File

@ -7,6 +7,7 @@ import { addMiscItems, addRecipes, getInventory, updateCurrency } from "@/src/se
import { getRecipeByResult } from "@/src/services/itemDataService";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { addInfestedFoundryXP, applyCheatsToInfestedFoundry } from "@/src/services/infestedFoundryService";
import { config } from "@/src/services/configService";
import { sendWsBroadcastTo } from "@/src/services/wsService";
import { EquipmentFeatures, IEquipmentDatabase } from "@/src/types/equipmentTypes";
@ -51,7 +52,7 @@ export const upgradesController: RequestHandler = async (req, res) => {
const recipe = getRecipeByResult(operation.UpgradeRequirement)!;
for (const ingredient of recipe.ingredients) {
totalPercentagePointsConsumed += ingredient.ItemCount / 10;
if (!inventory.infiniteHelminthMaterials) {
if (!config.infiniteHelminthMaterials) {
inventory.InfestedFoundry!.Resources!.find(x => x.ItemType == ingredient.ItemType)!.Count -=
ingredient.ItemCount;
}
@ -68,7 +69,7 @@ export const upgradesController: RequestHandler = async (req, res) => {
inventoryChanges.Recipes = recipeChanges;
inventoryChanges.InfestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry;
applyCheatsToInfestedFoundry(inventory, inventoryChanges.InfestedFoundry!);
applyCheatsToInfestedFoundry(inventoryChanges.InfestedFoundry!);
} else
switch (operation.UpgradeRequirement) {
case "/Lotus/Types/Items/MiscItems/OrokinReactor":

View File

@ -1,33 +0,0 @@
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express";
export const abilityOverrideController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const request = req.body as IAbilityOverrideRequest;
if (request.category === "Suits") {
const inventory = await getInventory(accountId, request.category);
const item = inventory[request.category].id(request.oid);
if (item) {
if (request.action == "set") {
item.Configs[request.configIndex].AbilityOverride = request.AbilityOverride;
} else {
item.Configs[request.configIndex].AbilityOverride = undefined;
}
await inventory.save();
}
}
res.end();
};
interface IAbilityOverrideRequest {
category: TEquipmentKey;
oid: string;
action: "set" | "remove";
configIndex: number;
AbilityOverride: {
Ability: string;
Index: number;
};
}

View File

@ -1,65 +0,0 @@
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express";
export const changeModularPartsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const request = req.body as IUpdateFingerPrintRequest;
const inventory = await getInventory(accountId, request.category);
const item = inventory[request.category].id(request.oid);
if (item) {
item.ModularParts = request.modularParts;
request.modularParts.forEach(part => {
const categoryMap = mapping[part];
if (categoryMap && categoryMap[request.category]) {
item.ItemType = categoryMap[request.category]!;
}
});
await inventory.save();
}
res.end();
};
interface IUpdateFingerPrintRequest {
category: TEquipmentKey;
oid: string;
modularParts: string[];
}
const mapping: Partial<Record<string, Partial<Record<TEquipmentKey, string>>>> = {
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelAPart": {
LongGuns: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun",
Pistols: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun"
},
"/Lotus/Weapons/Infested/Pistols/InfKitGun/Barrels/InfBarrelEgg/InfModularBarrelEggPart": {
LongGuns: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun",
Pistols: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun"
},
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelBPart": {
LongGuns: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
Pistols: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary"
},
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelCPart": {
LongGuns: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
Pistols: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary"
},
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelDPart": {
LongGuns: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam",
Pistols: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam"
},
"/Lotus/Weapons/Infested/Pistols/InfKitGun/Barrels/InfBarrelBeam/InfModularBarrelBeamPart": {
LongGuns: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam",
Pistols: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam"
},
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA": {
MoaPets: "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit"
},
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB": {
MoaPets: "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit"
},
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC": {
MoaPets: "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit"
}
};

View File

@ -1,8 +1,8 @@
import { RequestHandler } from "express";
import { config, syncConfigWithDatabase } from "@/src/services/configService";
import { config } from "@/src/services/configService";
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
import { saveConfig } from "@/src/services/configWriterService";
import { sendWsBroadcastEx } from "@/src/services/wsService";
import { saveConfig } from "@/src/services/configWatcherService";
import { sendWsBroadcastExcept } from "@/src/services/wsService";
export const getConfigController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
@ -25,8 +25,7 @@ export const setConfigController: RequestHandler = async (req, res) => {
const [obj, idx] = configIdToIndexable(id);
obj[idx] = value;
}
sendWsBroadcastEx({ config_reloaded: true }, undefined, parseInt(String(req.query.wsid)));
syncConfigWithDatabase();
sendWsBroadcastExcept(parseInt(String(req.query.wsid)), { config_reloaded: true });
await saveConfig();
res.end();
} else {
@ -38,8 +37,6 @@ const configIdToIndexable = (id: string): [Record<string, boolean | string | num
let obj = config as unknown as Record<string, never>;
const arr = id.split(".");
while (arr.length > 1) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
obj[arr[0]] ??= {} as never;
obj = obj[arr[0]];
arr.splice(0, 1);
}

View File

@ -11,7 +11,6 @@ import { GuildMember } from "@/src/models/guildModel";
import { Leaderboard } from "@/src/models/leaderboardModel";
import { deleteGuild } from "@/src/services/guildService";
import { Friendship } from "@/src/models/friendModel";
import { sendWsBroadcastTo } from "@/src/services/wsService";
export const deleteAccountController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
@ -37,8 +36,5 @@ export const deleteAccountController: RequestHandler = async (req, res) => {
Ship.deleteMany({ ShipOwnerId: accountId }),
Stats.deleteOne({ accountOwnerId: accountId })
]);
sendWsBroadcastTo(accountId, { logged_out: true });
res.end();
};

View File

@ -1,34 +0,0 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService";
import { RequestHandler } from "express";
const DEFAULT_UPGRADE_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
export const editSuitInvigorationUpgradeController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const { oid, data } = req.body as {
oid: string;
data?: {
DefensiveUpgrade: string;
OffensiveUpgrade: string;
UpgradesExpiry?: number;
};
};
const inventory = await getInventory(accountId);
const suit = inventory.Suits.id(oid)!;
if (data) {
suit.DefensiveUpgrade = data.DefensiveUpgrade;
suit.OffensiveUpgrade = data.OffensiveUpgrade;
if (data.UpgradesExpiry) {
suit.UpgradesExpiry = new Date(data.UpgradesExpiry);
} else {
suit.UpgradesExpiry = new Date(Date.now() + DEFAULT_UPGRADE_EXPIRY_MS);
}
} else {
suit.DefensiveUpgrade = undefined;
suit.OffensiveUpgrade = undefined;
suit.UpgradesExpiry = undefined;
}
await inventory.save();
res.end();
};

View File

@ -1,7 +1,6 @@
import { RequestHandler } from "express";
import { getDict, getItemName, getString } from "@/src/services/itemDataService";
import {
ExportAbilities,
ExportArcanes,
ExportAvionics,
ExportBoosters,
@ -58,7 +57,6 @@ interface ItemLists {
mods: ListedItem[];
Boosters: ListedItem[];
VarziaOffers: ListedItem[];
Abilities: ListedItem[];
//circuitGameModes: ListedItem[];
}
@ -96,8 +94,7 @@ const getItemListsController: RequestHandler = (req, response) => {
EvolutionProgress: [],
mods: [],
Boosters: [],
VarziaOffers: [],
Abilities: []
VarziaOffers: []
/*circuitGameModes: [
{
uniqueName: "Survival",
@ -135,12 +132,6 @@ const getItemListsController: RequestHandler = (req, response) => {
name: getString(item.name, lang),
exalted: item.exalted
});
item.abilities.forEach(ability => {
res.Abilities.push({
uniqueName: ability.uniqueName,
name: getString(ability.name || uniqueName, lang)
});
});
}
for (const [uniqueName, item] of Object.entries(ExportSentinels)) {
if (item.productCategory == "Sentinels" || item.productCategory == "KubrowPets") {
@ -153,21 +144,18 @@ const getItemListsController: RequestHandler = (req, response) => {
}
for (const [uniqueName, item] of Object.entries(ExportWeapons)) {
if (item.partType) {
if (!uniqueName.split("/")[7]?.startsWith("PvPVariant")) {
// not a pvp variant
if (!uniqueName.startsWith("/Lotus/Types/Items/Deimos/")) {
res.ModularParts.push({
uniqueName,
name: getString(item.name, lang),
partType: item.partType
});
}
if (uniqueName.split("/")[5] != "SentTrainingAmplifier") {
res.miscitems.push({
uniqueName: uniqueName,
name: getString(item.name, lang)
});
}
if (!uniqueName.startsWith("/Lotus/Types/Items/Deimos/")) {
res.ModularParts.push({
uniqueName,
name: getString(item.name, lang),
partType: item.partType
});
}
if (uniqueName.split("/")[5] != "SentTrainingAmplifier") {
res.miscitems.push({
uniqueName: uniqueName,
name: getString(item.name, lang)
});
}
} else if (item.totalDamage !== 0) {
if (
@ -360,13 +348,6 @@ const getItemListsController: RequestHandler = (req, response) => {
});
}
for (const [uniqueName, ability] of Object.entries(ExportAbilities)) {
res.Abilities.push({
uniqueName,
name: getString(ability.name || uniqueName, lang)
});
}
response.json(res);
};

View File

@ -1,7 +1,7 @@
import { RequestHandler } from "express";
import { getAccountForRequest, isAdministrator, isNameTaken } from "@/src/services/loginService";
import { config } from "@/src/services/configService";
import { saveConfig } from "@/src/services/configWriterService";
import { saveConfig } from "@/src/services/configWatcherService";
export const renameAccountController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);

View File

@ -1,18 +0,0 @@
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IAccountCheats } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express";
export const setAccountCheatController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const payload = req.body as ISetAccountCheatRequest;
const inventory = await getInventory(accountId, payload.key);
inventory[payload.key] = payload.value;
await inventory.save();
res.end();
};
interface ISetAccountCheatRequest {
key: keyof IAccountCheats;
value: boolean;
}

View File

@ -141,7 +141,7 @@ export const getProfileViewingDataGetController: RequestHandler = async (req, re
}
}
} else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
combinedStats[arrayName].push(entry as any);
}
}

View File

@ -1,7 +1,6 @@
import { IMongoDate, IOid, IOidWithLegacySupport } from "@/src/types/commonTypes";
import { Types } from "mongoose";
import { TRarity } from "warframe-public-export-plus";
import { IFusionTreasure } from "@/src/types/inventoryTypes/inventoryTypes";
export const version_compare = (a: string, b: string): number => {
const a_digits = a
@ -52,20 +51,6 @@ export const fromMongoDate = (date: IMongoDate): Date => {
return new Date(parseInt(date.$date.$numberLong));
};
export const parseFusionTreasure = (name: string, count: number): IFusionTreasure => {
const arr = name.split("_");
return {
ItemType: arr[0],
Sockets: parseInt(arr[1], 16),
ItemCount: count
};
};
export type TTraitsPool = Record<
"Colors" | "EyeColors" | "FurPatterns" | "BodyTypes" | "Heads" | "Tails",
{ type: string; rarity: TRarity }[]
>;
export const kubrowWeights: Record<TRarity, number> = {
COMMON: 6,
UNCOMMON: 4,
@ -80,126 +65,126 @@ export const kubrowFurPatternsWeights: Record<TRarity, number> = {
LEGENDARY: 1
};
export const catbrowDetails: TTraitsPool = {
export const catbrowDetails = {
Colors: [
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseA", rarity: "COMMON" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseB", rarity: "COMMON" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseC", rarity: "COMMON" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseD", rarity: "COMMON" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseA", rarity: "COMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseB", rarity: "COMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseC", rarity: "COMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseD", rarity: "COMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryA", rarity: "UNCOMMON" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryB", rarity: "UNCOMMON" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryC", rarity: "UNCOMMON" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryD", rarity: "UNCOMMON" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryA", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryB", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryC", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryD", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryA", rarity: "RARE" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryB", rarity: "RARE" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryC", rarity: "RARE" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryD", rarity: "RARE" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryA", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryB", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryC", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryD", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsA", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsB", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsC", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsD", rarity: "LEGENDARY" }
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsA", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsB", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsC", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsD", rarity: "LEGENDARY" as TRarity }
],
EyeColors: [
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesA", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesB", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesC", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesD", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesE", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesF", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesG", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesH", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesI", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesJ", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesK", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesL", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesM", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesN", rarity: "LEGENDARY" }
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesA", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesB", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesC", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesD", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesE", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesF", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesG", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesH", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesI", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesJ", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesK", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesL", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesM", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesN", rarity: "LEGENDARY" as TRarity }
],
FurPatterns: [{ type: "/Lotus/Types/Game/CatbrowPet/Patterns/CatbrowPetPatternA", rarity: "COMMON" }],
FurPatterns: [{ type: "/Lotus/Types/Game/CatbrowPet/Patterns/CatbrowPetPatternA", rarity: "COMMON" as TRarity }],
BodyTypes: [
{ type: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetRegularBodyType", rarity: "UNCOMMON" },
{ type: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetRegularBodyType", rarity: "LEGENDARY" }
{ type: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetRegularBodyType", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetRegularBodyType", rarity: "LEGENDARY" as TRarity }
],
Heads: [
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadA", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadB", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadC", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadD", rarity: "LEGENDARY" }
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadA", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadB", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadC", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadD", rarity: "LEGENDARY" as TRarity }
],
Tails: [
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailA", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailB", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailC", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailD", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailE", rarity: "LEGENDARY" }
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailA", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailB", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailC", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailD", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailE", rarity: "LEGENDARY" as TRarity }
]
};
export const kubrowDetails: TTraitsPool = {
export const kubrowDetails = {
Colors: [
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneA", rarity: "UNCOMMON" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneB", rarity: "UNCOMMON" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneC", rarity: "UNCOMMON" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneD", rarity: "UNCOMMON" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneE", rarity: "UNCOMMON" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneF", rarity: "UNCOMMON" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneG", rarity: "UNCOMMON" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneH", rarity: "UNCOMMON" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneA", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneB", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneC", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneD", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneE", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneF", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneG", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneH", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidA", rarity: "RARE" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidB", rarity: "RARE" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidC", rarity: "RARE" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidD", rarity: "RARE" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidE", rarity: "RARE" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidF", rarity: "RARE" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidG", rarity: "RARE" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidH", rarity: "RARE" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidA", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidB", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidC", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidD", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidE", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidF", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidG", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidH", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantA", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantB", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantC", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantD", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantE", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantF", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantG", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantH", rarity: "LEGENDARY" }
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantA", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantB", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantC", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantD", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantE", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantF", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantG", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantH", rarity: "LEGENDARY" as TRarity }
],
EyeColors: [
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesA", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesB", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesC", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesD", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesE", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesF", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesG", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesH", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesI", rarity: "LEGENDARY" }
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesA", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesB", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesC", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesD", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesE", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesF", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesG", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesH", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesI", rarity: "LEGENDARY" as TRarity }
],
FurPatterns: [
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternB", rarity: "UNCOMMON" },
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternA", rarity: "UNCOMMON" },
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternB", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternA", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternC", rarity: "RARE" },
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternD", rarity: "RARE" },
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternC", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternD", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternE", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternF", rarity: "LEGENDARY" }
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternE", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternF", rarity: "LEGENDARY" as TRarity }
],
BodyTypes: [
{ type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetRegularBodyType", rarity: "UNCOMMON" },
{ type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetHeavyBodyType", rarity: "LEGENDARY" },
{ type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetThinBodyType", rarity: "LEGENDARY" }
{ type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetRegularBodyType", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetHeavyBodyType", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetThinBodyType", rarity: "LEGENDARY" as TRarity }
],
Heads: [],

View File

@ -22,11 +22,8 @@ export interface INemesisManifest {
ephemeraTypes?: Record<TInnateDamageTag, string>;
firstKillReward: string;
firstConvertReward: string;
killMessageSubject: string;
killMessageBody: string;
convertMessageSubject: string;
convertMessageBody: string;
convertMessageIcon: string;
messageTitle: string;
messageBody: string;
minBuild: string;
}
@ -60,11 +57,8 @@ class KuvaLichManifest implements INemesisManifest {
};
firstKillReward = "/Lotus/StoreItems/Upgrades/Skins/Clan/LichKillerBadgeItem";
firstConvertReward = "/Lotus/StoreItems/Upgrades/Skins/Sigils/KuvaLichSigil";
killMessageSubject = "/Lotus/Language/Inbox/VanquishKuvaMsgTitle";
killMessageBody = "/Lotus/Language/Inbox/VanquishLichMsgBody";
convertMessageSubject = "/Lotus/Language/Kingpins/InboxKuvaConvertedSubject";
convertMessageBody = "/Lotus/Language/Kingpins/InboxKuvaConvertedBody";
convertMessageIcon = "/Lotus/Interface/Graphics/WorldStatePanel/Grineer.png";
messageTitle = "/Lotus/Language/Inbox/VanquishKuvaMsgTitle";
messageBody = "/Lotus/Language/Inbox/VanquishLichMsgBody";
minBuild = "2019.10.31.22.42"; // 26.0.0
}
@ -137,11 +131,8 @@ class LawyerManifest implements INemesisManifest {
};
firstKillReward = "/Lotus/StoreItems/Upgrades/Skins/Clan/CorpusLichBadgeItem";
firstConvertReward = "/Lotus/StoreItems/Upgrades/Skins/Sigils/CorpusLichSigil";
killMessageSubject = "/Lotus/Language/Inbox/VanquishLawyerMsgTitle";
killMessageBody = "/Lotus/Language/Inbox/VanquishLichMsgBody";
convertMessageSubject = "/Lotus/Language/Kingpins/InboxSisterConvertedSubject";
convertMessageBody = "/Lotus/Language/Kingpins/InboxSisterConvertedBody";
convertMessageIcon = "/Lotus/Interface/Graphics/WorldStatePanel/Corpus.png";
messageTitle = "/Lotus/Language/Inbox/VanquishLawyerMsgTitle";
messageBody = "/Lotus/Language/Inbox/VanquishLichMsgBody";
minBuild = "2021.07.05.17.03"; // 30.5.0
}
@ -175,11 +166,8 @@ class InfestedLichManfest implements INemesisManifest {
ephemeraChance = 0;
firstKillReward = "/Lotus/StoreItems/Upgrades/Skins/Sigils/InfLichVanquishedSigil";
firstConvertReward = "/Lotus/StoreItems/Upgrades/Skins/Sigils/InfLichConvertedSigil";
killMessageSubject = "/Lotus/Language/Inbox/VanquishBandMsgTitle";
killMessageBody = "/Lotus/Language/Inbox/VanquishBandMsgBody";
convertMessageSubject = "/Lotus/Language/Kingpins/InboxBandConvertedSubject";
convertMessageBody = "/Lotus/Language/Kingpins/InboxBandConvertedBody";
convertMessageIcon = "/Lotus/Interface/Graphics/WorldStatePanel/Infested.png";
messageTitle = "/Lotus/Language/Inbox/VanquishBandMsgTitle";
messageBody = "/Lotus/Language/Inbox/VanquishBandMsgBody";
minBuild = "2025.03.18.09.51"; // 38.5.0
}

View File

@ -1,17 +1,5 @@
import { SlotPurchase, SlotPurchaseName } from "@/src/types/purchaseTypes";
export const slotPurchaseNameToSlotName: SlotPurchase = {
SuitSlotItem: { name: "SuitBin", purchaseQuantity: 1 },
TwoSentinelSlotItem: { name: "SentinelBin", purchaseQuantity: 2 },
TwoWeaponSlotItem: { name: "WeaponBin", purchaseQuantity: 2 },
SpaceSuitSlotItem: { name: "SpaceSuitBin", purchaseQuantity: 1 },
TwoSpaceWeaponSlotItem: { name: "SpaceWeaponBin", purchaseQuantity: 2 },
MechSlotItem: { name: "MechBin", purchaseQuantity: 1 },
TwoOperatorWeaponSlotItem: { name: "OperatorAmpBin", purchaseQuantity: 2 },
RandomModSlotItem: { name: "RandomModBin", purchaseQuantity: 3 },
TwoCrewShipSalvageSlotItem: { name: "CrewShipSalvageBin", purchaseQuantity: 2 },
CrewMemberSlotItem: { name: "CrewMemberBin", purchaseQuantity: 1 }
};
import { slotPurchaseNameToSlotName } from "@/src/services/purchaseService";
import { SlotPurchaseName } from "@/src/types/purchaseTypes";
export const isSlotPurchaseName = (slotPurchaseName: string): slotPurchaseName is SlotPurchaseName => {
return slotPurchaseName in slotPurchaseNameToSlotName;

View File

@ -23,16 +23,10 @@ export const crackRelic = async (
weights = { COMMON: 0, UNCOMMON: 0, RARE: 1, LEGENDARY: 0 };
}
logger.debug(`opening a relic of quality ${relic.quality}; rarity weights are`, weights);
let reward = getRandomWeightedReward(
const reward = getRandomWeightedReward(
ExportRewards[relic.rewardManifest][0] as { type: string; itemCount: number; rarity: TRarity }[], // rarity is nullable in PE+ typings, but always present for relics
weights
)!;
if (config.relicRewardItemCountMultiplier !== undefined && (config.relicRewardItemCountMultiplier ?? 1) != 1) {
reward = {
...reward,
itemCount: reward.itemCount * config.relicRewardItemCountMultiplier
};
}
logger.debug(`relic rolled`, reward);
participant.Reward = reward.type;
@ -49,7 +43,13 @@ export const crackRelic = async (
// Give reward
combineInventoryChanges(
inventoryChanges,
(await handleStoreItemAcquisition(reward.type, inventory, reward.itemCount)).InventoryChanges
(
await handleStoreItemAcquisition(
reward.type,
inventory,
reward.itemCount * (config.relicRewardItemCountMultiplier ?? 1)
)
).InventoryChanges
);
return reward;

View File

@ -1,5 +1,5 @@
// First, init config.
import { config, configPath, loadConfig, syncConfigWithDatabase } from "@/src/services/configService";
import { config, configPath, loadConfig } from "@/src/services/configService";
import fs from "fs";
try {
loadConfig();
@ -7,7 +7,7 @@ try {
if (fs.existsSync("config.json")) {
console.log("Failed to load " + configPath + ": " + (e as Error).message);
} else {
console.log("Failed to load " + configPath + ". You can copy config-vanilla.json to create your config file.");
console.log("Failed to load " + configPath + ". You can copy config.json.example to create your config file.");
}
process.exit(1);
}
@ -18,23 +18,17 @@ logger.info("Starting up...");
// Proceed with normal startup: bring up config watcher service, validate config, connect to MongoDB, and finally start listening for HTTP.
import mongoose from "mongoose";
import path from "path";
import { JSONStringify } from "json-with-bigint";
import { startWebServer } from "@/src/services/webService";
import { validateConfig } from "@/src/services/configWatcherService";
import { updateWorldStateCollections } from "@/src/services/worldStateService";
import { repoDir } from "@/src/helpers/pathHelper";
JSON.stringify = JSONStringify; // Patch JSON.stringify to work flawlessly with Bigints.
import { syncConfigWithDatabase, validateConfig } from "@/src/services/configWatcherService";
import { updateWorldStateCollections } from "@/src/services/worldStateService";
// Patch JSON.stringify to work flawlessly with Bigints.
JSON.stringify = JSONStringify;
validateConfig();
fs.readFile(path.join(repoDir, "BUILD_DATE"), "utf-8", (err, data) => {
if (!err) {
logger.info(`Docker image was built on ${data.trim()}`);
}
});
mongoose
.connect(config.mongodbUrl)
.then(() => {

View File

@ -1,11 +1,16 @@
import { NextFunction, Request, Response } from "express";
import { logError } from "@/src/utils/logger";
import { logger } from "@/src/utils/logger";
export const errorHandler = (err: Error, req: Request, res: Response, _next: NextFunction): void => {
if (err.message == "Invalid accountId-nonce pair") {
res.status(400).send("Log-in expired");
} else if (err.stack) {
const stackArr = err.stack.split("\n");
stackArr[0] += ` while processing ${req.path} request`;
logger.error(stackArr.join("\n"));
res.status(500).end();
} else {
logError(err, `processing ${req.path} request`);
logger.error(`uncaught error while processing ${req.path} request: ${err.message}`);
res.status(500).end();
}
};

View File

@ -4,12 +4,8 @@ import { typeCountSchema } from "@/src/models/inventoryModels/inventoryModel";
import { IMongoDate, IOid, ITypeCount } from "@/src/types/commonTypes";
export interface IMessageClient
extends Omit<
IMessageDatabase,
"_id" | "globaUpgradeId" | "date" | "startDate" | "endDate" | "ownerId" | "attVisualOnly" | "expiry"
> {
extends Omit<IMessageDatabase, "_id" | "date" | "startDate" | "endDate" | "ownerId" | "attVisualOnly" | "expiry"> {
_id?: IOid;
globaUpgradeId?: IOid; // [sic]
date: IMongoDate;
startDate?: IMongoDate;
endDate?: IMongoDate;
@ -18,7 +14,6 @@ export interface IMessageClient
export interface IMessageDatabase extends IMessage {
ownerId: Types.ObjectId;
globaUpgradeId?: Types.ObjectId; // [sic]
date: Date; //created at
attVisualOnly?: boolean;
_id: Types.ObjectId;
@ -47,7 +42,6 @@ export interface IMessage {
acceptAction?: string;
declineAction?: string;
hasAccountAction?: boolean;
RegularCredits?: number;
}
export interface Arg {
@ -107,7 +101,6 @@ const giftSchema = new Schema<IGift>(
const messageSchema = new Schema<IMessageDatabase>(
{
ownerId: Schema.Types.ObjectId,
globaUpgradeId: Schema.Types.ObjectId,
sndr: String,
msg: String,
cinematic: String,
@ -140,8 +133,7 @@ const messageSchema = new Schema<IMessageDatabase>(
contextInfo: String,
acceptAction: String,
declineAction: String,
hasAccountAction: Boolean,
RegularCredits: Number
hasAccountAction: Boolean
},
{ id: false }
);
@ -152,7 +144,7 @@ messageSchema.virtual("messageId").get(function (this: IMessageDatabase) {
messageSchema.set("toJSON", {
virtuals: true,
transform(_document, returnedObject: Record<string, any>) {
transform(_document, returnedObject) {
const messageDatabase = returnedObject as IMessageDatabase;
const messageClient = returnedObject as IMessageClient;
@ -162,10 +154,6 @@ messageSchema.set("toJSON", {
delete returnedObject.attVisualOnly;
delete returnedObject.expiry;
if (messageDatabase.globaUpgradeId) {
messageClient.globaUpgradeId = toOid(messageDatabase.globaUpgradeId);
}
messageClient.date = toMongoDate(messageDatabase.date);
if (messageDatabase.startDate && messageDatabase.endDate) {

View File

@ -121,7 +121,7 @@ import {
export const typeCountSchema = new Schema<ITypeCount>({ ItemType: String, ItemCount: Number }, { _id: false });
typeCountSchema.set("toJSON", {
transform(_doc, obj: Record<string, any>) {
transform(_doc, obj) {
if (obj.ItemCount > 2147483647) {
obj.ItemCount = 2147483647;
} else if (obj.ItemCount < -2147483648) {
@ -189,7 +189,7 @@ operatorConfigSchema.virtual("ItemId").get(function () {
operatorConfigSchema.set("toJSON", {
virtuals: true,
transform(_document, returnedObject: Record<string, any>) {
transform(_document, returnedObject) {
delete returnedObject._id;
delete returnedObject.__v;
}
@ -226,7 +226,7 @@ const ItemConfigSchema = new Schema<IItemConfig>(
);
ItemConfigSchema.set("toJSON", {
transform(_document, returnedObject: Record<string, any>) {
transform(_document, returnedObject) {
delete returnedObject.__v;
}
});
@ -261,7 +261,7 @@ RawUpgrades.virtual("LastAdded").get(function () {
RawUpgrades.set("toJSON", {
virtuals: true,
transform(_document, returnedObject: Record<string, any>) {
transform(_document, returnedObject) {
delete returnedObject._id;
delete returnedObject.__v;
}
@ -282,7 +282,7 @@ upgradeSchema.virtual("ItemId").get(function () {
upgradeSchema.set("toJSON", {
virtuals: true,
transform(_document, returnedObject: Record<string, any>) {
transform(_document, returnedObject) {
delete returnedObject._id;
delete returnedObject.__v;
}
@ -325,7 +325,7 @@ const crewMemberSchema = new Schema<ICrewMemberDatabase>(
crewMemberSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj: Record<string, any>) {
transform(_doc, obj) {
const db = obj as ICrewMemberDatabase;
const client = obj as ICrewMemberClient;
@ -353,7 +353,7 @@ const FlavourItemSchema = new Schema(
);
FlavourItemSchema.set("toJSON", {
transform(_document, returnedObject: Record<string, any>) {
transform(_document, returnedObject) {
delete returnedObject._id;
delete returnedObject.__v;
}
@ -367,7 +367,7 @@ FlavourItemSchema.set("toJSON", {
);
MailboxSchema.set("toJSON", {
transform(_document, returnedObject: Record<string, any>) {
transform(_document, returnedObject) {
const mailboxDatabase = returnedObject as HydratedDocument<IMailboxDatabase, { __v?: number }>;
delete mailboxDatabase.__v;
(returnedObject as IMailboxClient).LastInboxId = toOid(mailboxDatabase.LastInboxId);
@ -386,7 +386,7 @@ const DuviriInfoSchema = new Schema<IDuviriInfo>(
);
DuviriInfoSchema.set("toJSON", {
transform(_document, returnedObject: Record<string, any>) {
transform(_document, returnedObject) {
delete returnedObject.__v;
}
});
@ -416,7 +416,7 @@ const droneSchema = new Schema<IDroneDatabase>(
);
droneSchema.set("toJSON", {
virtuals: true,
transform(_document, obj: Record<string, any>) {
transform(_document, obj) {
const client = obj as IDroneClient;
const db = obj as IDroneDatabase;
@ -457,7 +457,7 @@ const personalGoalProgressSchema = new Schema<IPersonalGoalProgressDatabase>(
personalGoalProgressSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj: Record<string, any>) {
transform(_doc, obj) {
const db = obj as IPersonalGoalProgressDatabase;
const client = obj as IPersonalGoalProgressClient;
@ -502,7 +502,7 @@ StepSequencersSchema.virtual("ItemId").get(function () {
StepSequencersSchema.set("toJSON", {
virtuals: true,
transform(_document, returnedObject: Record<string, any>) {
transform(_document, returnedObject) {
delete returnedObject._id;
delete returnedObject.__v;
}
@ -516,7 +516,7 @@ const kubrowPetEggSchema = new Schema<IKubrowPetEggDatabase>(
);
kubrowPetEggSchema.set("toJSON", {
virtuals: true,
transform(_document, obj: Record<string, any>) {
transform(_document, obj) {
const client = obj as IKubrowPetEggClient;
const db = obj as IKubrowPetEggDatabase;
@ -586,7 +586,7 @@ personalTechProjectSchema.virtual("ItemId").get(function () {
personalTechProjectSchema.set("toJSON", {
virtuals: true,
transform(_doc, ret: Record<string, any>) {
transform(_doc, ret, _options) {
delete ret._id;
delete ret.__v;
@ -687,7 +687,7 @@ const questKeysSchema = new Schema<IQuestKeyDatabase>(
);
questKeysSchema.set("toJSON", {
transform(_doc, ret: Record<string, any>) {
transform(_doc, ret, _options) {
const questKeysDatabase = ret as IQuestKeyDatabase;
if (questKeysDatabase.CompletionDate) {
@ -709,7 +709,7 @@ const invasionProgressSchema = new Schema<IInvasionProgressDatabase>(
);
invasionProgressSchema.set("toJSON", {
transform(_doc, obj: Record<string, any>) {
transform(_doc, obj) {
const db = obj as IInvasionProgressDatabase;
const client = obj as IInvasionProgressClient;
@ -748,7 +748,7 @@ weaponSkinsSchema.virtual("ItemId").get(function () {
weaponSkinsSchema.set("toJSON", {
virtuals: true,
transform(_doc, ret: Record<string, any>) {
transform(_doc, ret, _options) {
delete ret._id;
delete ret.__v;
}
@ -772,7 +772,7 @@ const periodicMissionCompletionsSchema = new Schema<IPeriodicMissionCompletionDa
);
periodicMissionCompletionsSchema.set("toJSON", {
transform(_doc, ret: Record<string, any>) {
transform(_doc, ret, _options) {
const periodicMissionCompletionDatabase = ret as IPeriodicMissionCompletionDatabase;
(periodicMissionCompletionDatabase as unknown as IPeriodicMissionCompletionResponse).date = toMongoDate(
@ -849,7 +849,7 @@ const endlessXpProgressSchema = new Schema<IEndlessXpProgressDatabase>(
);
endlessXpProgressSchema.set("toJSON", {
transform(_doc, ret: Record<string, any>) {
transform(_doc, ret) {
const db = ret as IEndlessXpProgressDatabase;
const client = ret as IEndlessXpProgressClient;
@ -898,7 +898,7 @@ const crewShipMemberSchema = new Schema<ICrewShipMemberDatabase>(
);
crewShipMemberSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj: Record<string, any>) {
transform(_doc, obj) {
const db = obj as ICrewShipMemberDatabase;
const client = obj as ICrewShipMemberClient;
if (db.ItemId) {
@ -951,7 +951,7 @@ const dialogueSchema = new Schema<IDialogueDatabase>(
);
dialogueSchema.set("toJSON", {
virtuals: true,
transform(_doc, ret: Record<string, any>) {
transform(_doc, ret) {
const db = ret as IDialogueDatabase;
const client = ret as IDialogueClient;
@ -997,7 +997,7 @@ const kubrowPetPrintSchema = new Schema<IKubrowPetPrintDatabase>({
});
kubrowPetPrintSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj: Record<string, any>) {
transform(_doc, obj) {
const db = obj as IKubrowPetPrintDatabase;
const client = obj as IKubrowPetPrintClient;
@ -1025,7 +1025,7 @@ const detailsSchema = new Schema<IKubrowPetDetailsDatabase>(
);
detailsSchema.set("toJSON", {
transform(_doc, returnedObject: Record<string, any>) {
transform(_doc, returnedObject) {
delete returnedObject.__v;
const db = returnedObject as IKubrowPetDetailsDatabase;
@ -1081,7 +1081,7 @@ EquipmentSchema.virtual("ItemId").get(function () {
EquipmentSchema.set("toJSON", {
virtuals: true,
transform(_document, returnedObject: Record<string, any>) {
transform(_document, returnedObject) {
delete returnedObject._id;
delete returnedObject.__v;
@ -1132,7 +1132,7 @@ pendingRecipeSchema.virtual("ItemId").get(function () {
pendingRecipeSchema.set("toJSON", {
virtuals: true,
transform(_document, returnedObject: Record<string, any>) {
transform(_document, returnedObject) {
delete returnedObject._id;
delete returnedObject.__v;
delete returnedObject.LongGuns;
@ -1170,7 +1170,7 @@ const infestedFoundrySchema = new Schema<IInfestedFoundryDatabase>(
);
infestedFoundrySchema.set("toJSON", {
transform(_doc, ret: Record<string, any>) {
transform(_doc, ret, _options) {
if (ret.AbilityOverrideUnlockCooldown) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
ret.AbilityOverrideUnlockCooldown = toMongoDate(ret.AbilityOverrideUnlockCooldown);
@ -1216,8 +1216,8 @@ const calenderProgressSchema = new Schema<ICalendarProgress>(
},
SeasonProgress: {
SeasonType: { type: String, required: true },
LastCompletedDayIdx: { type: Number, default: -1 },
LastCompletedChallengeDayIdx: { type: Number, default: -1 },
LastCompletedDayIdx: { type: Number, default: 0 },
LastCompletedChallengeDayIdx: { type: Number, default: 0 },
ActivatedChallenges: { type: [String], default: [] }
}
},
@ -1243,7 +1243,7 @@ const vendorPurchaseHistoryEntrySchema = new Schema<IVendorPurchaseHistoryEntryD
);
vendorPurchaseHistoryEntrySchema.set("toJSON", {
transform(_doc, obj: Record<string, any>) {
transform(_doc, obj) {
const db = obj as IVendorPurchaseHistoryEntryDatabase;
const client = obj as IVendorPurchaseHistoryEntryClient;
client.Expiry = toMongoDate(db.Expiry);
@ -1286,7 +1286,7 @@ const pendingCouponSchema = new Schema<IPendingCouponDatabase>(
);
pendingCouponSchema.set("toJSON", {
transform(_doc, ret: Record<string, any>) {
transform(_doc, ret, _options) {
(ret as IPendingCouponClient).Expiry = toMongoDate((ret as IPendingCouponDatabase).Expiry);
}
});
@ -1353,7 +1353,7 @@ const nemesisSchema = new Schema<INemesisDatabase>(
nemesisSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj: Record<string, any>) {
transform(_doc, obj) {
const db = obj as INemesisDatabase;
const client = obj as INemesisClient;
@ -1383,7 +1383,7 @@ const lastSortieRewardSchema = new Schema<ILastSortieRewardDatabase>(
lastSortieRewardSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj: Record<string, any>) {
transform(_doc, obj) {
const db = obj as ILastSortieRewardDatabase;
const client = obj as ILastSortieRewardClient;
@ -1425,14 +1425,6 @@ const hubNpcCustomizationSchema = new Schema<IHubNpcCustomization>(
const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
{
accountOwnerId: Schema.Types.ObjectId,
// SNS account cheats
infiniteCredits: Boolean,
infinitePlatinum: Boolean,
infiniteEndo: Boolean,
infiniteRegalAya: Boolean,
infiniteHelminthMaterials: Boolean,
SubscribedToEmails: { type: Number, default: 0 },
SubscribedToEmailsPersonalized: { type: Number, default: 0 },
RewardSeed: BigInt,
@ -1445,8 +1437,6 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
PremiumCreditsFree: { type: Number, default: 0 },
//Endo
FusionPoints: { type: Number, default: 0 },
//Dirac
CrewShipFusionPoints: { type: Number, default: 0 },
//Regal Aya
PrimeTokens: { type: Number, default: 0 },
@ -1800,7 +1790,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
);
inventorySchema.set("toJSON", {
transform(_document, returnedObject: Record<string, any>) {
transform(_document, returnedObject) {
delete returnedObject._id;
delete returnedObject.__v;
delete returnedObject.accountOwnerId;

View File

@ -49,7 +49,7 @@ loadoutConfigSchema.virtual("ItemId").get(function () {
loadoutConfigSchema.set("toJSON", {
virtuals: true,
transform(_doc, ret: Record<string, any>) {
transform(_doc, ret, _options) {
delete ret._id;
delete ret.__v;
}
@ -62,7 +62,6 @@ export const loadoutSchema = new Schema<ILoadoutDatabase, loadoutModelType>({
NORMAL_PVP: [loadoutConfigSchema],
LUNARO: [loadoutConfigSchema],
OPERATOR: [loadoutConfigSchema],
GEAR: [loadoutConfigSchema],
KDRIVE: [loadoutConfigSchema],
DATAKNIFE: [loadoutConfigSchema],
MECH: [loadoutConfigSchema],
@ -72,7 +71,7 @@ export const loadoutSchema = new Schema<ILoadoutDatabase, loadoutModelType>({
});
loadoutSchema.set("toJSON", {
transform(_doc, ret: Record<string, any>) {
transform(_doc, ret, _options) {
delete ret._id;
delete ret.__v;
delete ret.loadoutOwnerId;
@ -89,7 +88,6 @@ type loadoutDocumentProps = {
NORMAL_PVP: Types.DocumentArray<ILoadoutConfigDatabase>;
LUNARO: Types.DocumentArray<ILoadoutConfigDatabase>;
OPERATOR: Types.DocumentArray<ILoadoutConfigDatabase>;
GEAR: Types.DocumentArray<ILoadoutConfigDatabase>;
KDRIVE: Types.DocumentArray<ILoadoutConfigDatabase>;
DATAKNIFE: Types.DocumentArray<ILoadoutConfigDatabase>;
MECH: Types.DocumentArray<ILoadoutConfigDatabase>;

View File

@ -32,7 +32,7 @@ const databaseAccountSchema = new Schema<IDatabaseAccountJson>(
);
databaseAccountSchema.set("toJSON", {
transform(_document, returnedObject: Record<string, any>) {
transform(_document, returnedObject) {
delete returnedObject._id;
delete returnedObject.__v;
},

View File

@ -55,7 +55,7 @@ placedDecosSchema.virtual("id").get(function (this: IPlacedDecosDatabase) {
placedDecosSchema.set("toJSON", {
virtuals: true,
transform(_document, returnedObject: Record<string, any>) {
transform(_document, returnedObject) {
delete returnedObject._id;
}
});
@ -78,7 +78,7 @@ const favouriteLoadoutSchema = new Schema<IFavouriteLoadoutDatabase>(
);
favouriteLoadoutSchema.set("toJSON", {
virtuals: true,
transform(_document, returnedObject: Record<string, any>) {
transform(_document, returnedObject) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
returnedObject.LoadoutId = toOid(returnedObject.LoadoutId);
}
@ -95,7 +95,7 @@ const plantSchema = new Schema<IPlantDatabase>(
plantSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj: Record<string, any>) {
transform(_doc, obj) {
const client = obj as IPlantClient;
const db = obj as IPlantDatabase;
@ -122,9 +122,7 @@ const apartmentSchema = new Schema<IApartmentDatabase>(
{
Rooms: [roomSchema],
FavouriteLoadouts: [favouriteLoadoutSchema],
Gardening: gardeningSchema,
VideoWallBackdrop: String,
Soundscape: String
Gardening: gardeningSchema
},
{ _id: false }
);
@ -158,7 +156,7 @@ const orbiterSchema = new Schema<IOrbiterDatabase>(
);
orbiterSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj: Record<string, any>) {
transform(_doc, obj) {
const db = obj as IOrbiterDatabase;
const client = obj as IOrbiterClient;

View File

@ -22,7 +22,7 @@ shipSchema.virtual("ItemId").get(function () {
shipSchema.set("toJSON", {
virtuals: true,
transform(_document, returnedObject: Record<string, any>) {
transform(_document, returnedObject) {
const shipResponse = returnedObject as IShipInventory;
const shipDatabase = returnedObject as IShipDatabase;
delete returnedObject._id;

View File

@ -101,7 +101,7 @@ const statsSchema = new Schema<IStatsDatabase>({
});
statsSchema.set("toJSON", {
transform(_document, returnedObject: Record<string, any>) {
transform(_document, returnedObject) {
delete returnedObject._id;
delete returnedObject.__v;
delete returnedObject.accountOwnerId;

View File

@ -10,7 +10,6 @@ import { addPendingFriendController } from "@/src/controllers/api/addPendingFrie
import { addToAllianceController } from "@/src/controllers/api/addToAllianceController";
import { addToGuildController } from "@/src/controllers/api/addToGuildController";
import { adoptPetController } from "@/src/controllers/api/adoptPetController";
import { apartmentController } from "@/src/controllers/api/apartmentController";
import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController";
import { archonFusionController } from "@/src/controllers/api/archonFusionController";
import { artifactsController } from "@/src/controllers/api/artifactsController";
@ -66,7 +65,6 @@ import { getGuildLogController } from "@/src/controllers/api/getGuildLogControll
import { getIgnoredUsersController } from "@/src/controllers/api/getIgnoredUsersController";
import { getNewRewardSeedController } from "@/src/controllers/api/getNewRewardSeedController";
import { getProfileViewingDataPostController } from "@/src/controllers/dynamic/getProfileViewingDataController";
import { getPastWeeklyChallengesController } from "@/src/controllers/api/getPastWeeklyChallengesController";
import { getShipController } from "@/src/controllers/api/getShipController";
import { getVendorInfoController } from "@/src/controllers/api/getVendorInfoController";
import { getVoidProjectionRewardsController } from "@/src/controllers/api/getVoidProjectionRewardsController";
@ -114,7 +112,6 @@ import { removeFromGuildController } from "@/src/controllers/api/removeFromGuild
import { removeIgnoredUserController } from "@/src/controllers/api/removeIgnoredUserController";
import { renamePetController } from "@/src/controllers/api/renamePetController";
import { rerollRandomModController } from "@/src/controllers/api/rerollRandomModController";
import { resetQuestProgressController } from "@/src/controllers/api/resetQuestProgressController";
import { retrievePetFromStasisController } from "@/src/controllers/api/retrievePetFromStasisController";
import { saveDialogueController } from "@/src/controllers/api/saveDialogueController";
import { saveLoadoutController } from "@/src/controllers/api/saveLoadoutController";
@ -170,7 +167,6 @@ const apiRouter = express.Router();
// get
apiRouter.get("/abandonLibraryDailyTask.php", abandonLibraryDailyTaskController);
apiRouter.get("/abortDojoComponentDestruction.php", abortDojoComponentDestructionController);
apiRouter.get("/apartment.php", apartmentController);
apiRouter.get("/cancelGuildAdvertisement.php", cancelGuildAdvertisementController);
apiRouter.get("/changeDojoRoot.php", changeDojoRootController);
apiRouter.get("/changeGuildRank.php", changeGuildRankController);
@ -196,7 +192,6 @@ apiRouter.get("/getGuildLog.php", getGuildLogController);
apiRouter.get("/getIgnoredUsers.php", getIgnoredUsersController);
apiRouter.get("/getMessages.php", inboxController); // unsure if this is correct, but needed for U17
apiRouter.get("/getNewRewardSeed.php", getNewRewardSeedController);
apiRouter.get("/getPastWeeklyChallenges.php", getPastWeeklyChallengesController)
apiRouter.get("/getShip.php", getShipController);
apiRouter.get("/getShipDecos.php", (_req, res) => { res.end(); }); // needed to log in on U22.8
apiRouter.get("/getVendorInfo.php", getVendorInfoController);
@ -214,7 +209,6 @@ apiRouter.get("/questControl.php", questControlController);
apiRouter.get("/queueDojoComponentDestruction.php", queueDojoComponentDestructionController);
apiRouter.get("/removeFriend.php", removeFriendGetController);
apiRouter.get("/removeFromAlliance.php", removeFromAllianceController);
apiRouter.get("/resetQuestProgress.php", resetQuestProgressController);
apiRouter.get("/setActiveQuest.php", setActiveQuestController);
apiRouter.get("/setActiveShip.php", setActiveShipController);
apiRouter.get("/setAllianceGuildPermissions.php", setAllianceGuildPermissionsController);

View File

@ -15,7 +15,6 @@ import { webuiFileChangeDetectedController } from "@/src/controllers/custom/webu
import { completeAllMissionsController } from "@/src/controllers/custom/completeAllMissionsController";
import { addMissingHelminthBlueprintsController } from "@/src/controllers/custom/addMissingHelminthBlueprintsController";
import { abilityOverrideController } from "@/src/controllers/custom/abilityOverrideController";
import { createAccountController } from "@/src/controllers/custom/createAccountController";
import { createMessageController } from "@/src/controllers/custom/createMessageController";
import { addCurrencyController } from "@/src/controllers/custom/addCurrencyController";
@ -26,9 +25,6 @@ import { manageQuestsController } from "@/src/controllers/custom/manageQuestsCon
import { setEvolutionProgressController } from "@/src/controllers/custom/setEvolutionProgressController";
import { setBoosterController } from "@/src/controllers/custom/setBoosterController";
import { updateFingerprintController } from "@/src/controllers/custom/updateFingerprintController";
import { changeModularPartsController } from "@/src/controllers/custom/changeModularPartsController";
import { editSuitInvigorationUpgradeController } from "@/src/controllers/custom/editSuitInvigorationUpgradeController";
import { setAccountCheatController } from "@/src/controllers/custom/setAccountCheatController";
import { getConfigController, setConfigController } from "@/src/controllers/custom/configController";
@ -49,7 +45,6 @@ customRouter.get("/webuiFileChangeDetected", webuiFileChangeDetectedController);
customRouter.get("/completeAllMissions", completeAllMissionsController);
customRouter.get("/addMissingHelminthBlueprints", addMissingHelminthBlueprintsController);
customRouter.post("/abilityOverride", abilityOverrideController);
customRouter.post("/createAccount", createAccountController);
customRouter.post("/createMessage", createMessageController);
customRouter.post("/addCurrency", addCurrencyController);
@ -60,9 +55,6 @@ customRouter.post("/manageQuests", manageQuestsController);
customRouter.post("/setEvolutionProgress", setEvolutionProgressController);
customRouter.post("/setBooster", setBoosterController);
customRouter.post("/updateFingerprint", updateFingerprintController);
customRouter.post("/changeModularParts", changeModularPartsController);
customRouter.post("/editSuitInvigorationUpgrade", editSuitInvigorationUpgradeController);
customRouter.post("/setAccountCheat", setAccountCheatController);
customRouter.post("/getConfig", getConfigController);
customRouter.post("/setConfig", setConfigController);

View File

@ -2,7 +2,6 @@ import fs from "fs";
import path from "path";
import { repoDir } from "@/src/helpers/pathHelper";
import { args } from "@/src/helpers/commandLineArguments";
import { Inbox } from "@/src/models/inboxModel";
export interface IConfig {
mongodbUrl: string;
@ -20,6 +19,11 @@ export interface IConfig {
skipTutorial?: boolean;
skipAllDialogue?: boolean;
unlockAllScans?: boolean;
infiniteCredits?: boolean;
infinitePlatinum?: boolean;
infiniteEndo?: boolean;
infiniteRegalAya?: boolean;
infiniteHelminthMaterials?: boolean;
claimingBlueprintRefundsIngredients?: boolean;
dontSubtractPurchaseCreditCost?: boolean;
dontSubtractPurchasePlatinumCost?: boolean;
@ -76,21 +80,8 @@ export interface IConfig {
creditBoost?: boolean;
affinityBoost?: boolean;
resourceBoost?: boolean;
tennoLiveRelay?: boolean;
baroTennoConRelay?: boolean;
wolfHunt?: boolean;
longShadow?: boolean;
hallowedFlame?: boolean;
hallowedNightmares?: boolean;
hallowedNightmaresRewardsOverride?: number;
proxyRebellion?: boolean;
proxyRebellionRewardsOverride?: number;
starDays?: boolean;
galleonOfGhouls?: number;
ghoulEmergenceOverride?: boolean;
plagueStarOverride?: boolean;
starDaysOverride?: boolean;
dogDaysOverride?: boolean;
dogDaysRewardsOverride?: number;
eidolonOverride?: string;
vallisOverride?: string;
duviriOverride?: string;
@ -122,26 +113,9 @@ export const loadConfig = (): void => {
// Set all values to undefined now so if the new config.json omits some fields that were previously present, it's correct in-memory.
for (const key of Object.keys(config)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
(config as any)[key] = undefined;
}
Object.assign(config, newConfig);
};
export const syncConfigWithDatabase = (): void => {
// Event messages are deleted after endDate. Since we don't use beginDate/endDate and instead have config toggles, we need to delete the messages once those bools are false.
// Also, for some reason, I can't just do `Inbox.deleteMany(...)`; - it needs this whole circus.
if (!config.worldState?.creditBoost) {
void Inbox.deleteMany({ globaUpgradeId: "5b23106f283a555109666672" }).then(() => {});
}
if (!config.worldState?.affinityBoost) {
void Inbox.deleteMany({ globaUpgradeId: "5b23106f283a555109666673" }).then(() => {});
}
if (!config.worldState?.resourceBoost) {
void Inbox.deleteMany({ globaUpgradeId: "5b23106f283a555109666674" }).then(() => {});
}
if (!config.worldState?.galleonOfGhouls) {
void Inbox.deleteMany({ goalTag: "GalleonRobbery" }).then(() => {});
}
};

View File

@ -1,13 +1,17 @@
import chokidar from "chokidar";
import fsPromises from "fs/promises";
import { logger } from "@/src/utils/logger";
import { config, configPath, loadConfig, syncConfigWithDatabase } from "@/src/services/configService";
import { saveConfig, shouldReloadConfig } from "@/src/services/configWriterService";
import { config, configPath, loadConfig } from "@/src/services/configService";
import { getWebPorts, startWebServer, stopWebServer } from "@/src/services/webService";
import { sendWsBroadcast } from "@/src/services/wsService";
import { Inbox } from "@/src/models/inboxModel";
import varzia from "@/static/fixed_responses/worldState/varzia.json";
let amnesia = false;
chokidar.watch(configPath).on("change", () => {
if (shouldReloadConfig()) {
if (amnesia) {
amnesia = false;
} else {
logger.info("Detected a change to config file, reloading its contents.");
try {
loadConfig();
@ -67,3 +71,15 @@ export const validateConfig = (): void => {
void saveConfig();
}
};
export const saveConfig = async (): Promise<void> => {
amnesia = true;
await fsPromises.writeFile(configPath, JSON.stringify(config, null, 2));
};
export const syncConfigWithDatabase = (): void => {
// Event messages are deleted after endDate. Since we don't use beginDate/endDate and instead have config toggles, we need to delete the messages once those bools are false.
if (!config.worldState?.galleonOfGhouls) {
void Inbox.deleteMany({ goalTag: "GalleonRobbery" }).then(() => {}); // For some reason, I can't just do `Inbox.deleteMany(...)`; it needs this whole circus.
}
};

View File

@ -1,17 +0,0 @@
import fsPromises from "fs/promises";
import { config, configPath } from "@/src/services/configService";
let amnesia = false;
export const saveConfig = async (): Promise<void> => {
amnesia = true;
await fsPromises.writeFile(configPath, JSON.stringify(config, null, 2));
};
export const shouldReloadConfig = (): boolean => {
if (amnesia) {
amnesia = false;
return false;
}
return true;
};

View File

@ -13,7 +13,6 @@ import {
IDojoComponentDatabase,
IDojoContributable,
IDojoDecoClient,
IDojoDecoDatabase,
IGuildClient,
IGuildMemberClient,
IGuildMemberDatabase,
@ -310,7 +309,7 @@ export const removeDojoRoom = async (
guild.DojoEnergy -= meta.energy;
}
moveResourcesToVault(guild, component);
component.Decos?.forEach(deco => refundDojoDeco(guild, component, deco));
component.Decos?.forEach(deco => moveResourcesToVault(guild, deco));
if (guild.RoomChanges) {
const index = guild.RoomChanges.findIndex(x => x.componentId.equals(component._id));
@ -345,14 +344,6 @@ export const removeDojoDeco = (
component.Decos!.findIndex(x => x._id.equals(decoId)),
1
)[0];
refundDojoDeco(guild, component, deco);
};
export const refundDojoDeco = (
guild: TGuildDatabaseDocument,
component: IDojoComponentDatabase,
deco: IDojoDecoDatabase
): void => {
const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type);
if (meta) {
if (meta.capacityCost) {
@ -378,7 +369,7 @@ export const refundDojoDeco = (
]);
}
}
moveResourcesToVault(guild, deco); // Refund resources spent on construction
moveResourcesToVault(guild, deco);
};
const moveResourcesToVault = (guild: TGuildDatabaseDocument, component: IDojoContributable): void => {

View File

@ -422,7 +422,6 @@ export const importLoadOutPresets = (db: ILoadoutDatabase, client: ILoadOutPrese
db.NORMAL_PVP = client.NORMAL_PVP.map(convertLoadOutConfig);
db.LUNARO = client.LUNARO.map(convertLoadOutConfig);
db.OPERATOR = client.OPERATOR.map(convertLoadOutConfig);
db.GEAR = client.GEAR.map(convertLoadOutConfig);
db.KDRIVE = client.KDRIVE.map(convertLoadOutConfig);
db.DATAKNIFE = client.DATAKNIFE.map(convertLoadOutConfig);
db.MECH = client.MECH.map(convertLoadOutConfig);

View File

@ -35,7 +35,7 @@ export const createNewEventMessages = async (req: Request): Promise<void> => {
const baroIndex = Math.trunc((Date.now() - 910800000) / (unixTimesInMs.day * 14));
const baroStart = baroIndex * (unixTimesInMs.day * 14) + 910800000;
const baroActualStart = baroStart + unixTimesInMs.day * (config.baroAlwaysAvailable ? 0 : 12);
if (Date.now() >= baroActualStart && account.LatestEventMessageDate.getTime() < baroActualStart) {
if (account.LatestEventMessageDate.getTime() < baroActualStart) {
newEventMessages.push({
sndr: "/Lotus/Language/G1Quests/VoidTraderName",
sub: "/Lotus/Language/CommunityMessages/VoidTraderAppearanceTitle",
@ -55,77 +55,20 @@ export const createNewEventMessages = async (req: Request): Promise<void> => {
}
// BUG: Deleting the inbox message manually means it'll just be automatically re-created. This is because we don't use startDate/endDate for these config-toggled events.
const promises = [];
if (config.worldState?.creditBoost) {
promises.push(
(async (): Promise<void> => {
if (!(await Inbox.exists({ ownerId: account._id, globaUpgradeId: "5b23106f283a555109666672" }))) {
newEventMessages.push({
globaUpgradeId: new Types.ObjectId("5b23106f283a555109666672"),
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sub: "/Lotus/Language/Items/EventDoubleCreditsName",
msg: "/Lotus/Language/Items/EventDoubleCreditsDesc",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
startDate: new Date(),
CrossPlatform: true
});
}
})()
);
}
if (config.worldState?.affinityBoost) {
promises.push(
(async (): Promise<void> => {
if (!(await Inbox.exists({ ownerId: account._id, globaUpgradeId: "5b23106f283a555109666673" }))) {
newEventMessages.push({
globaUpgradeId: new Types.ObjectId("5b23106f283a555109666673"),
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sub: "/Lotus/Language/Items/EventDoubleAffinityName",
msg: "/Lotus/Language/Items/EventDoubleAffinityDesc",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
startDate: new Date(),
CrossPlatform: true
});
}
})()
);
}
if (config.worldState?.resourceBoost) {
promises.push(
(async (): Promise<void> => {
if (!(await Inbox.exists({ ownerId: account._id, globaUpgradeId: "5b23106f283a555109666674" }))) {
newEventMessages.push({
globaUpgradeId: new Types.ObjectId("5b23106f283a555109666674"),
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sub: "/Lotus/Language/Items/EventDoubleResourceName",
msg: "/Lotus/Language/Items/EventDoubleResourceDesc",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
startDate: new Date(),
CrossPlatform: true
});
}
})()
);
}
if (config.worldState?.galleonOfGhouls) {
promises.push(
(async (): Promise<void> => {
if (!(await Inbox.exists({ ownerId: account._id, goalTag: "GalleonRobbery" }))) {
newEventMessages.push({
sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek",
sub: "/Lotus/Language/Events/GalleonRobberyIntroMsgTitle",
msg: "/Lotus/Language/Events/GalleonRobberyIntroMsgDesc",
icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png",
transmission: "/Lotus/Sounds/Dialog/GalleonOfGhouls/DGhoulsWeekOneInbox0010VayHek",
att: ["/Lotus/Upgrades/Skins/Events/OgrisOldSchool"],
startDate: new Date(),
goalTag: "GalleonRobbery"
});
}
})()
);
if (!(await Inbox.exists({ ownerId: account._id, goalTag: "GalleonRobbery" }))) {
newEventMessages.push({
sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek",
sub: "/Lotus/Language/Events/GalleonRobberyIntroMsgTitle",
msg: "/Lotus/Language/Events/GalleonRobberyIntroMsgDesc",
icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png",
transmission: "/Lotus/Sounds/Dialog/GalleonOfGhouls/DGhoulsWeekOneInbox0010VayHek",
att: ["/Lotus/Upgrades/Skins/Events/OgrisOldSchool"],
startDate: new Date(),
goalTag: "GalleonRobbery"
});
}
}
await Promise.all(promises);
if (newEventMessages.length === 0) {
return;

View File

@ -1,11 +1,8 @@
import { ExportRecipes } from "warframe-public-export-plus";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import {
IAccountCheats,
IInfestedFoundryClient,
IInfestedFoundryDatabase
} from "@/src/types/inventoryTypes/inventoryTypes";
import { IInfestedFoundryClient, IInfestedFoundryDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
import { addRecipes } from "@/src/services/inventoryService";
import { config } from "@/src/services/configService";
import { ITypeCount } from "@/src/types/commonTypes";
export const addInfestedFoundryXP = (infestedFoundry: IInfestedFoundryDatabase, delta: number): ITypeCount[] => {
@ -100,8 +97,8 @@ export const handleSubsumeCompletion = (inventory: TInventoryDatabaseDocument):
return recipeChanges;
};
export const applyCheatsToInfestedFoundry = (cheats: IAccountCheats, infestedFoundry: IInfestedFoundryClient): void => {
if (cheats.infiniteHelminthMaterials) {
export const applyCheatsToInfestedFoundry = (infestedFoundry: IInfestedFoundryClient): void => {
if (config.infiniteHelminthMaterials) {
infestedFoundry.Resources = [
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthCalx", Count: 1000 },
{ ItemType: "/Lotus/Types/Items/InfestedFoundry/HelminthBiotics", Count: 1000 },

View File

@ -25,8 +25,7 @@ import {
INemesisWeaponTargetFingerprint,
INemesisPetTargetFingerprint,
IDialogueDatabase,
IKubrowPetPrintClient,
equipmentKeys
IKubrowPetPrintClient
} from "@/src/types/inventoryTypes/inventoryTypes";
import { IGenericUpdate, IUpdateNodeIntrosResponse } from "@/src/types/genericUpdate";
import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "@/src/types/requestTypes";
@ -68,8 +67,7 @@ import {
kubrowDetails,
kubrowFurPatternsWeights,
kubrowWeights,
toOid,
TTraitsPool
toOid
} from "@/src/helpers/inventoryHelpers";
import { addQuestKey, completeQuest } from "@/src/services/questService";
import { handleBundleAcqusition } from "@/src/services/purchaseService";
@ -483,14 +481,11 @@ export const addItem = async (
if (quantity != 1) {
logger.warn(`adding 1 of ${typeName} ${targetFingerprint} even tho quantity ${quantity} was requested`);
}
const upgrade =
inventory.Upgrades[
inventory.Upgrades.push({
ItemType: typeName,
UpgradeFingerprint: targetFingerprint
}) - 1
];
return { Upgrades: [upgrade.toJSON<IUpgradeClient>()] };
inventory.Upgrades.push({
ItemType: typeName,
UpgradeFingerprint: targetFingerprint
});
return {}; // there's not exactly a common "InventoryChanges" format for these
}
const changes = [
{
@ -816,7 +811,7 @@ export const addItem = async (
if (!seed) {
throw new Error(`Expected crew member to have a seed`);
}
seed |= BigInt(Math.trunc(inventory.Created.getTime() / 1000) & 0xffffff) << 32n;
seed |= 0x33b81en << 32n;
return {
...addCrewMember(inventory, typeName, seed),
...occupySlot(inventory, InventorySlot.CREWMEMBERS, premiumPurchase)
@ -1053,21 +1048,6 @@ export const addSpaceSuit = (
return inventoryChanges;
};
const createRandomTraits = (kubrowPetName: string, traitsPool: TTraitsPool): ITraits => {
return {
BaseColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
SecondaryColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
TertiaryColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
AccentColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
EyeColor: getRandomWeightedReward(traitsPool.EyeColors, kubrowWeights)!.type,
FurPattern: getRandomWeightedReward(traitsPool.FurPatterns, kubrowFurPatternsWeights)!.type,
Personality: kubrowPetName,
BodyType: getRandomWeightedReward(traitsPool.BodyTypes, kubrowWeights)!.type,
Head: traitsPool.Heads.length ? getRandomWeightedReward(traitsPool.Heads, kubrowWeights)!.type : undefined,
Tail: traitsPool.Tails.length ? getRandomWeightedReward(traitsPool.Tails, kubrowWeights)!.type : undefined
};
};
export const addKubrowPet = (
inventory: TInventoryDatabaseDocument,
kubrowPetName: string,
@ -1084,6 +1064,7 @@ export const addKubrowPet = (
addSpecialItem(inventory, specialItem, inventoryChanges);
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const configs: IItemConfig[] = applyDefaultUpgrades(inventory, kubrowPet?.defaultUpgrades);
if (!details) {
@ -1093,10 +1074,9 @@ export const addKubrowPet = (
"/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit"
].includes(kubrowPetName);
const traitsPool = isCatbrow ? catbrowDetails : kubrowDetails;
let dominantTraits: ITraits;
let traits: ITraits;
if (kubrowPetName == "/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit") {
dominantTraits = {
traits = {
BaseColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseVampire",
SecondaryColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryVampire",
TertiaryColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryVampire",
@ -1109,35 +1089,19 @@ export const addKubrowPet = (
Tail: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailVampire"
};
} else {
dominantTraits = createRandomTraits(kubrowPetName, traitsPool);
if (kubrowPetName == "/Lotus/Types/Game/KubrowPet/ChargerKubrowPetPowerSuit") {
dominantTraits.BodyType = "/Lotus/Types/Game/KubrowPet/BodyTypes/ChargerKubrowPetBodyType";
dominantTraits.FurPattern = "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternInfested";
}
}
const recessiveTraits: ITraits = createRandomTraits(
getRandomElement(
isCatbrow
? [
"/Lotus/Types/Game/CatbrowPet/MirrorCatbrowPetPowerSuit",
"/Lotus/Types/Game/CatbrowPet/CheshireCatbrowPetPowerSuit"
]
: [
"/Lotus/Types/Game/KubrowPet/AdventurerKubrowPetPowerSuit",
"/Lotus/Types/Game/KubrowPet/FurtiveKubrowPetPowerSuit",
"/Lotus/Types/Game/KubrowPet/GuardKubrowPetPowerSuit",
"/Lotus/Types/Game/KubrowPet/HunterKubrowPetPowerSuit",
"/Lotus/Types/Game/KubrowPet/RetrieverKubrowPetPowerSuit"
]
)!,
traitsPool
);
for (const key of Object.keys(recessiveTraits) as (keyof ITraits)[]) {
// My heurstic approximation is a 20% chance for a dominant trait to be copied into the recessive traits. TODO: A more scientific statistical analysis maybe?
if (Math.random() < 0.2) {
recessiveTraits[key] = dominantTraits[key]!;
}
const traitsPool = isCatbrow ? catbrowDetails : kubrowDetails;
traits = {
BaseColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
SecondaryColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
TertiaryColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
AccentColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
EyeColor: getRandomWeightedReward(traitsPool.EyeColors, kubrowWeights)!.type,
FurPattern: getRandomWeightedReward(traitsPool.FurPatterns, kubrowFurPatternsWeights)!.type,
Personality: kubrowPetName,
BodyType: getRandomWeightedReward(traitsPool.BodyTypes, kubrowWeights)!.type,
Head: isCatbrow ? getRandomWeightedReward(traitsPool.Heads, kubrowWeights)!.type : undefined,
Tail: isCatbrow ? getRandomWeightedReward(traitsPool.Tails, kubrowWeights)!.type : undefined
};
}
details = {
@ -1149,8 +1113,8 @@ export const addKubrowPet = (
HatchDate: premiumPurchase ? new Date() : new Date(Date.now() + 10 * unixTimesInMs.hour), // On live, this seems to be somewhat randomised so that the pet hatches 9~11 hours after start.
IsMale: !!getRandomInt(0, 1),
Size: getRandomInt(70, 100) / 100,
DominantTraits: dominantTraits,
RecessiveTraits: recessiveTraits
DominantTraits: traits,
RecessiveTraits: traits
};
}
@ -1201,8 +1165,8 @@ export const updateSlots = (
}
};
const isCurrencyTracked = (inventory: TInventoryDatabaseDocument, usePremium: boolean): boolean => {
return usePremium ? !inventory.infinitePlatinum : !inventory.infiniteCredits;
const isCurrencyTracked = (usePremium: boolean): boolean => {
return usePremium ? !config.infinitePlatinum : !config.infiniteCredits;
};
export const updateCurrency = (
@ -1211,7 +1175,7 @@ export const updateCurrency = (
usePremium: boolean,
inventoryChanges: IInventoryChanges = {}
): IInventoryChanges => {
if (price != 0 && isCurrencyTracked(inventory, usePremium)) {
if (price != 0 && isCurrencyTracked(usePremium)) {
if (usePremium) {
if (inventory.PremiumCreditsFree > 0) {
const premiumCreditsFreeDelta = Math.min(price, inventory.PremiumCreditsFree) * -1;
@ -1242,15 +1206,6 @@ export const addFusionPoints = (inventory: TInventoryDatabaseDocument, add: numb
return add;
};
export const addCrewShipFusionPoints = (inventory: TInventoryDatabaseDocument, add: number): number => {
if (inventory.CrewShipFusionPoints + add > 2147483647) {
logger.warn(`capping CrewShipFusionPoints balance at 2147483647`);
add = 2147483647 - inventory.CrewShipFusionPoints;
}
inventory.CrewShipFusionPoints += add;
return add;
};
const standingLimitBinToInventoryKey: Record<
Exclude<TStandingLimitBin, "STANDING_LIMIT_BIN_NONE">,
keyof IDailyAffiliations
@ -1342,7 +1297,7 @@ export const addStanding = (
// TODO: AffiliationMods support (Nightwave).
export const updateGeneric = async (data: IGenericUpdate, accountId: string): Promise<IUpdateNodeIntrosResponse> => {
const inventory = await getInventory(accountId, "NodeIntrosCompleted MiscItems ShipDecorations");
const inventory = await getInventory(accountId, "NodeIntrosCompleted MiscItems");
// Make it an array for easier parsing.
if (typeof data.NodeIntrosCompleted === "string") {
@ -1351,15 +1306,7 @@ export const updateGeneric = async (data: IGenericUpdate, accountId: string): Pr
const inventoryChanges: IInventoryChanges = {};
for (const node of data.NodeIntrosCompleted) {
if (node == "TC2025") {
inventoryChanges.ShipDecorations = [
{
ItemType: "/Lotus/Types/Items/ShipDecos/TauGrineerLancerBobbleHead",
ItemCount: 1
}
];
addShipDecorations(inventory, inventoryChanges.ShipDecorations);
} else if (node == "KayaFirstVisitPack") {
if (node == "KayaFirstVisitPack") {
inventoryChanges.MiscItems = [
{
ItemType: "/Lotus/Types/Items/MiscItems/1999FixedStickersPack",
@ -1653,15 +1600,6 @@ export const addEmailItem = async (
return inventoryChanges;
};
const xpEarningParts: readonly string[] = [
"LWPT_BLADE",
"LWPT_GUN_BARREL",
"LWPT_AMP_OCULUS",
"LWPT_MOA_HEAD",
"LWPT_ZANUKA_HEAD",
"LWPT_HB_DECK"
];
export const applyClientEquipmentUpdates = (
inventory: TInventoryDatabaseDocument,
gearArray: IEquipmentClient[],
@ -1680,34 +1618,15 @@ export const applyClientEquipmentUpdates = (
item.XP ??= 0;
item.XP += XP;
if (
categoryName != "SpecialItems" ||
item.ItemType == "/Lotus/Powersuits/Khora/Kavat/KhoraKavatPowerSuit" ||
item.ItemType == "/Lotus/Powersuits/Khora/Kavat/KhoraPrimeKavatPowerSuit"
) {
let xpItemType = item.ItemType;
if (item.ModularParts) {
for (const part of item.ModularParts) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const partType = ExportWeapons[part]?.partType;
if (partType !== undefined && xpEarningParts.indexOf(partType) != -1) {
xpItemType = part;
break;
}
}
logger.debug(`adding xp to ${xpItemType} for modular item ${fromOid(ItemId)} (${item.ItemType})`);
}
const xpinfoIndex = inventory.XPInfo.findIndex(x => x.ItemType == xpItemType);
if (xpinfoIndex !== -1) {
const xpinfo = inventory.XPInfo[xpinfoIndex];
xpinfo.XP += XP;
} else {
inventory.XPInfo.push({
ItemType: xpItemType,
XP: XP
});
}
const xpinfoIndex = inventory.XPInfo.findIndex(x => x.ItemType == item.ItemType);
if (xpinfoIndex !== -1) {
const xpinfo = inventory.XPInfo[xpinfoIndex];
xpinfo.XP += XP;
} else {
inventory.XPInfo.push({
ItemType: item.ItemType,
XP: XP
});
}
}
@ -1912,90 +1831,25 @@ export const addLoreFragmentScans = (inventory: TInventoryDatabaseDocument, arr:
});
};
const challengeRewardsInboxMessages: Record<string, IMessageCreationTemplate> = {
SentEvoEphemeraRankOne: {
sub: "/Lotus/Language/Inbox/EvolvingEphemeraUnlockAName",
sndr: "/Lotus/Language/Bosses/Ordis",
msg: "/Lotus/Language/Inbox/EvolvingEphemeraUnlockADesc",
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
att: ["/Lotus/Upgrades/Skins/Effects/NarmerEvolvingEphemeraB"]
},
SentEvoEphemeraRankTwo: {
sub: "/Lotus/Language/Inbox/EvolvingEphemeraUnlockBName",
sndr: "/Lotus/Language/Bosses/Ordis",
msg: "/Lotus/Language/Inbox/EvolvingEphemeraUnlockBDesc",
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
att: ["/Lotus/Upgrades/Skins/Effects/NarmerEvolvingEphemeraC"]
},
SentEvoSyandanaRankOne: {
sub: "/Lotus/Language/Inbox/EvolvingSyandanaUnlockAName",
sndr: "/Lotus/Language/Bosses/Ordis",
msg: "/Lotus/Language/Inbox/EvolvingSyandanaUnlockADesc",
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
att: ["/Lotus/Upgrades/Skins/Scarves/NarmerEvolvingSyandanaBCape"]
},
SentEvoSyandanaRankTwo: {
sub: "/Lotus/Language/Inbox/EvolvingSyandanaUnlockBName",
sndr: "/Lotus/Language/Bosses/Ordis",
msg: "/Lotus/Language/Inbox/EvolvingSyandanaUnlockBDesc",
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
att: ["/Lotus/Upgrades/Skins/Scarves/NarmerEvolvingSyandanaCCape"]
},
SentEvoSekharaRankOne: {
sub: "/Lotus/Language/Inbox/EvolvingSekharaUnlockAName",
sndr: "/Lotus/Language/Bosses/Ordis",
msg: "/Lotus/Language/Inbox/EvolvingSekharaUnlockADesc",
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
att: ["/Lotus/Upgrades/Skins/Clan/ZarimanEvolvingSekharaBadgeItemB"]
},
SentEvoSekharaRankTwo: {
sub: "/Lotus/Language/Inbox/EvolvingSekharaUnlockBName",
sndr: "/Lotus/Language/Bosses/Ordis",
msg: "/Lotus/Language/Inbox/EvolvingSekharaUnlockBDesc",
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
att: ["/Lotus/Upgrades/Skins/Clan/ZarimanEvolvingSekharaBadgeItemC"]
}
};
export const addChallenges = async (
export const addChallenges = (
account: TAccountDocument,
inventory: TInventoryDatabaseDocument,
ChallengeProgress: IChallengeProgress[],
SeasonChallengeCompletions: ISeasonChallenge[] | undefined
): Promise<IAffiliationMods[]> => {
for (const { Name, Progress, Completed } of ChallengeProgress) {
let dbChallenge = inventory.ChallengeProgress.find(x => x.Name == Name);
if (dbChallenge) {
dbChallenge.Progress = Progress;
): IAffiliationMods[] => {
ChallengeProgress.forEach(({ Name, Progress }) => {
const itemIndex = inventory.ChallengeProgress.findIndex(i => i.Name === Name);
if (itemIndex !== -1) {
inventory.ChallengeProgress[itemIndex].Progress = Progress;
} else {
dbChallenge = { Name, Progress };
inventory.ChallengeProgress.push(dbChallenge);
inventory.ChallengeProgress.push({ Name, Progress });
}
if (Name.startsWith("Calendar")) {
addString(getCalendarProgress(inventory).SeasonProgress.ActivatedChallenges, Name);
}
if ((Completed?.length ?? 0) > (dbChallenge.Completed?.length ?? 0)) {
dbChallenge.Completed ??= [];
for (const completion of Completed!) {
if (dbChallenge.Completed.indexOf(completion) == -1) {
dbChallenge.Completed.push(completion);
if (completion == "challengeRewards") {
if (Name in challengeRewardsInboxMessages) {
await createMessage(account._id, [challengeRewardsInboxMessages[Name]]);
// Would love to somehow let the client know about inbox or inventory changes, but there doesn't seem to anything for updateChallengeProgress.
continue;
}
logger.warn(`ignoring unknown challenge completion`, { challenge: Name, completion });
dbChallenge.Completed = [];
}
}
}
} else {
dbChallenge.Completed = Completed;
}
}
});
const affiliationMods: IAffiliationMods[] = [];
if (SeasonChallengeCompletions) {
@ -2043,7 +1897,7 @@ export const addCalendarProgress = (inventory: TInventoryDatabaseDocument, value
calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx = currentSeason.Days.findIndex(
day => day.events.length != 0 && day.events[0].challenge == value[value.length - 1].challenge
);
checkCalendarAutoAdvance(inventory, currentSeason);
checkCalendarChallengeCompletion(calendarProgress, currentSeason);
};
export const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag, Completes, Tier }: IMission): void => {
@ -2191,21 +2045,6 @@ export const cleanupInventory = (inventory: TInventoryDatabaseDocument): void =>
inventory.LotusCustomization.syancol = {};
}
}
{
let numFixed = 0;
for (const equipmentKey of equipmentKeys) {
for (const item of inventory[equipmentKey]) {
if (item.ModularParts?.length === 0) {
item.ModularParts = undefined;
++numFixed;
}
}
}
if (numFixed != 0) {
logger.debug(`removed ModularParts from ${numFixed} non-modular items`);
}
}
};
export const getDialogue = (inventory: TInventoryDatabaseDocument, dialogueName: string): IDialogueDatabase => {
@ -2243,8 +2082,8 @@ export const getCalendarProgress = (inventory: TInventoryDatabaseDocument): ICal
},
SeasonProgress: {
SeasonType: currentSeason.Season,
LastCompletedDayIdx: -1,
LastCompletedChallengeDayIdx: -1,
LastCompletedDayIdx: 0,
LastCompletedChallengeDayIdx: 0,
ActivatedChallenges: []
}
};
@ -2265,44 +2104,16 @@ export const getCalendarProgress = (inventory: TInventoryDatabaseDocument): ICal
return inventory.CalendarProgress;
};
export const checkCalendarAutoAdvance = (
inventory: TInventoryDatabaseDocument,
export const checkCalendarChallengeCompletion = (
calendarProgress: ICalendarProgress,
currentSeason: ICalendarSeason
): void => {
const calendarProgress = inventory.CalendarProgress!;
for (
let dayIndex = calendarProgress.SeasonProgress.LastCompletedDayIdx + 1;
dayIndex != currentSeason.Days.length;
++dayIndex
) {
const dayIndex = calendarProgress.SeasonProgress.LastCompletedDayIdx + 1;
if (calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx >= dayIndex) {
const day = currentSeason.Days[dayIndex];
if (day.events.length == 0) {
// birthday
if (day.day == 1) {
// kaya
if ((inventory.Affiliations.find(x => x.Tag == "HexSyndicate")?.Title || 0) >= 4) {
break;
}
logger.debug(`cannot talk to kaya, skipping birthday`);
calendarProgress.SeasonProgress.LastCompletedDayIdx++;
} else if (day.day == 74 || day.day == 355) {
// minerva, velimir
if ((inventory.Affiliations.find(x => x.Tag == "HexSyndicate")?.Title || 0) >= 5) {
break;
}
logger.debug(`cannot talk to minerva/velimir, skipping birthday`);
calendarProgress.SeasonProgress.LastCompletedDayIdx++;
} else {
break;
}
} else if (day.events[0].type == "CET_CHALLENGE") {
if (calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx < dayIndex) {
break;
}
if (day.events.length != 0 && day.events[0].type == "CET_CHALLENGE") {
//logger.debug(`already completed the challenge, skipping ahead`);
calendarProgress.SeasonProgress.LastCompletedDayIdx++;
} else {
break;
}
}
};

View File

@ -32,7 +32,7 @@ export const getUsernameFromEmail = async (email: string): Promise<string> => {
name = nameFromEmail + suffix;
} while (await isNameTaken(name));
}
return name;
return nameFromEmail;
};
export const createAccount = async (accountData: IDatabaseAccountRequiredFields): Promise<IDatabaseAccountJson> => {

View File

@ -50,7 +50,7 @@ import { getEntriesUnsafe } from "@/src/utils/ts-utils";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { IMissionCredits, IMissionReward } from "@/src/types/missionTypes";
import { crackRelic } from "@/src/helpers/relicHelper";
import { createMessage, IMessageCreationTemplate } from "@/src/services/inboxService";
import { createMessage } from "@/src/services/inboxService";
import kuriaMessage50 from "@/static/fixed_responses/kuriaMessages/fiftyPercent.json";
import kuriaMessage75 from "@/static/fixed_responses/kuriaMessages/seventyFivePercent.json";
import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPercent.json";
@ -76,18 +76,13 @@ import {
} from "@/src/services/worldStateService";
import { config } from "@/src/services/configService";
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
import { IGoal, ISyndicateMissionInfo } from "@/src/types/worldStateTypes";
import { ISyndicateMissionInfo } from "@/src/types/worldStateTypes";
import { fromOid } from "@/src/helpers/inventoryHelpers";
import { TAccountDocument } from "@/src/services/loginService";
import { ITypeCount } from "@/src/types/commonTypes";
import { IEquipmentClient } from "@/src/types/equipmentTypes";
const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] => {
// Disruption missions just tell us (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2599)
if (rewardInfo.rewardTierOverrides) {
return rewardInfo.rewardTierOverrides;
}
// For Spy missions, e.g. 3 vaults cracked = A, B, C
if (rewardInfo.VaultsCracked) {
const rotations: number[] = [];
@ -97,23 +92,14 @@ const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[]
return rotations;
}
const region = ExportRegions[rewardInfo.node] as IRegion | undefined;
const missionIndex: number | undefined = region?.missionIndex;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const missionIndex: number | undefined = ExportRegions[rewardInfo.node]?.missionIndex;
// For Rescue missions
if (missionIndex == 3 && rewardInfo.rewardTier) {
return [rewardInfo.rewardTier];
}
// 'rewardQualifications' is unreliable for non-endless railjack missions (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2586, https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2612)
switch (region?.missionName) {
case "/Lotus/Language/Missions/MissionName_Railjack":
case "/Lotus/Language/Missions/MissionName_RailjackVolatile":
case "/Lotus/Language/Missions/MissionName_RailjackExterminate":
case "/Lotus/Language/Missions/MissionName_RailjackAssassinate":
return [0];
}
const rotationCount = rewardInfo.rewardQualifications?.length || 0;
// Empty or absent rewardQualifications should not give rewards when:
@ -306,7 +292,7 @@ export const addMissionInventoryUpdates = async (
addRecipes(inventory, value);
break;
case "ChallengeProgress":
await addChallenges(account, inventory, value, inventoryUpdates.SeasonChallengeCompletions);
addChallenges(account, inventory, value, inventoryUpdates.SeasonChallengeCompletions);
break;
case "FusionTreasures":
addFusionTreasures(inventory, value);
@ -572,7 +558,6 @@ export const addMissionInventoryUpdates = async (
}
]);
}
inventory.DeathSquadable = false;
break;
}
case "LockedWeaponGroup": {
@ -591,7 +576,7 @@ export const addMissionInventoryUpdates = async (
break;
}
case "IncHarvester": {
// Unsure what to do with this
inventory.Harvestable = true;
break;
}
case "CurrentLoadOutIds": {
@ -628,93 +613,37 @@ export const addMissionInventoryUpdates = async (
if (goal && goal.Personal) {
inventory.PersonalGoalProgress ??= [];
const goalProgress = inventory.PersonalGoalProgress.find(x => x.goalId.equals(goal._id.$oid));
if (!goalProgress) {
if (goalProgress) {
goalProgress.Best = Math.max(goalProgress.Best, uploadProgress.Best);
goalProgress.Count += uploadProgress.Count;
} else {
inventory.PersonalGoalProgress.push({
Best: uploadProgress.Best,
Count: uploadProgress.Count,
Tag: goal.Tag,
goalId: new Types.ObjectId(goal._id.$oid)
});
}
const currentNode = inventoryUpdates.RewardInfo!.node;
let currentMissionKey;
if (currentNode == goal.Node) {
currentMissionKey = goal.MissionKeyName;
} else if (goal.ConcurrentNodes && goal.ConcurrentMissionKeyNames) {
for (let i = 0; i < goal.ConcurrentNodes.length; i++) {
if (currentNode == goal.ConcurrentNodes[i]) {
currentMissionKey = goal.ConcurrentMissionKeyNames[i];
break;
}
}
}
if (currentMissionKey && currentMissionKey in goalMessagesByKey) {
const totalCount = (goalProgress?.Count ?? 0) + uploadProgress.Count;
let reward;
if (goal.InterimGoals && goal.InterimRewards) {
for (let i = 0; i < goal.InterimGoals.length; i++) {
if (
goal.InterimGoals[i] &&
goal.InterimGoals[i] <= totalCount &&
(!goalProgress || goalProgress.Count < goal.InterimGoals[i]) &&
goal.InterimRewards[i]
) {
reward = goal.InterimRewards[i];
break;
}
}
}
if (
!reward &&
goal.Goal &&
goal.Goal <= totalCount &&
(!goalProgress || goalProgress.Count < goal.Goal) &&
goal.Reward
goal.Reward &&
goal.Reward.items &&
goal.MissionKeyName &&
goal.MissionKeyName in goalMessagesByKey
) {
reward = goal.Reward;
}
if (
!reward &&
goal.BonusGoal &&
goal.BonusGoal <= totalCount &&
(!goalProgress || goalProgress.Count < goal.BonusGoal) &&
goal.BonusReward
) {
reward = goal.BonusReward;
}
if (reward) {
if (currentMissionKey in goalMessagesByKey) {
// Send reward via inbox
const info = goalMessagesByKey[currentMissionKey];
const message: IMessageCreationTemplate = {
// Send reward via inbox
const info = goalMessagesByKey[goal.MissionKeyName];
await createMessage(inventory.accountOwnerId, [
{
sndr: info.sndr,
msg: info.msg,
att: goal.Reward.items.map(x => (isStoreItem(x) ? fromStoreItem(x) : x)),
sub: info.sub,
icon: info.icon,
highPriority: true
};
if (reward.items) {
message.att = reward.items.map(x => (isStoreItem(x) ? fromStoreItem(x) : x));
}
if (reward.countedItems) {
message.countedAtt = reward.countedItems;
}
if (reward.credits) {
message.RegularCredits = reward.credits;
}
await createMessage(inventory.accountOwnerId, [message]);
}
]);
}
}
if (goalProgress) {
goalProgress.Best = Math.max(goalProgress.Best, uploadProgress.Best);
goalProgress.Count += uploadProgress.Count;
}
}
}
break;
@ -841,24 +770,26 @@ export const addMissionInventoryUpdates = async (
}
}
await createMessage(inventory.accountOwnerId, [
{
sndr: value.killed ? "/Lotus/Language/Bosses/Ordis" : value.nemesisName,
msg: value.killed ? manifest.killMessageBody : manifest.convertMessageBody,
arg: [
{
Key: "LICH_NAME",
Tag: value.nemesisName
}
],
att: att,
countedAtt: countedAtt,
attVisualOnly: true,
sub: value.killed ? manifest.killMessageSubject : manifest.convertMessageSubject,
icon: value.killed ? "/Lotus/Interface/Icons/Npcs/Ordis.png" : manifest.convertMessageIcon,
highPriority: true
}
]);
if (value.killed) {
await createMessage(inventory.accountOwnerId, [
{
sndr: "/Lotus/Language/Bosses/Ordis",
msg: manifest.messageBody,
arg: [
{
Key: "LICH_NAME",
Tag: value.nemesisName
}
],
att: att,
countedAtt: countedAtt,
attVisualOnly: true,
sub: manifest.messageTitle,
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
highPriority: true
}
]);
}
inventory.Nemesis = undefined;
}
@ -1040,8 +971,7 @@ export const addMissionRewards = async (
Missions: missions,
RegularCredits: creditDrops,
VoidTearParticipantsCurrWave: voidTearWave,
StrippedItems: strippedItems,
AffiliationChanges: AffiliationMods
StrippedItems: strippedItems
}: IMissionInventoryUpdateRequest,
firstCompletion: boolean
): Promise<AddMissionRewardsReturnType> => {
@ -1061,6 +991,7 @@ export const addMissionRewards = async (
);
logger.debug("random mission drops:", MissionRewards);
const inventoryChanges: IInventoryChanges = {};
const AffiliationMods: IAffiliationMods[] = [];
let SyndicateXPItemReward;
let ConquestCompletedMissionsCount;
@ -1069,16 +1000,8 @@ export const addMissionRewards = async (
if (rewardInfo.goalId) {
const goal = getWorldState().Goals.find(x => x._id.$oid == rewardInfo.goalId);
if (goal) {
if (rewardInfo.node == goal.Node && goal.MissionKeyName) levelKeyName = goal.MissionKeyName;
if (goal.ConcurrentNodes && goal.ConcurrentMissionKeyNames) {
for (let i = 0; i < goal.ConcurrentNodes.length && i < goal.ConcurrentMissionKeyNames.length; i++) {
if (rewardInfo.node == goal.ConcurrentNodes[i]) {
levelKeyName = goal.ConcurrentMissionKeyNames[i];
break;
}
}
}
if (goal?.MissionKeyName) {
levelKeyName = goal.MissionKeyName;
}
}
@ -1330,8 +1253,6 @@ export const addMissionRewards = async (
}
}
AffiliationMods ??= [];
if (rewardInfo.JobStage != undefined && rewardInfo.jobId) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [jobType, unkIndex, hubNode, syndicateMissionId] = rewardInfo.jobId.split("_");
@ -1339,29 +1260,9 @@ export const addMissionRewards = async (
if (syndicateMissionId) {
pushClassicBounties(syndicateMissions, idToBountyCycle(syndicateMissionId));
}
let syndicateEntry: ISyndicateMissionInfo | IGoal | undefined = syndicateMissions.find(
m => m._id.$oid === syndicateMissionId
);
if (
[
"/Lotus/Types/Gameplay/Eidolon/Jobs/Events/InfestedPlainsBounty",
"/Lotus/Types/Gameplay/Eidolon/Jobs/Events/GhoulAlertBounty"
].some(prefix => jobType.startsWith(prefix))
) {
const { Goals } = getWorldState(undefined);
syndicateEntry = Goals.find(m => m._id.$oid === syndicateMissionId);
if (syndicateEntry) syndicateEntry.Tag = syndicateEntry.JobAffiliationTag!;
}
const syndicateEntry = syndicateMissions.find(m => m._id.$oid === syndicateMissionId);
if (syndicateEntry && syndicateEntry.Jobs) {
let currentJob = syndicateEntry.Jobs[rewardInfo.JobTier!];
if (
[
"/Lotus/Types/Gameplay/Eidolon/Jobs/Events/InfestedPlainsBounty",
"/Lotus/Types/Gameplay/Eidolon/Jobs/Events/GhoulAlertBounty"
].some(prefix => jobType.startsWith(prefix))
) {
currentJob = syndicateEntry.Jobs.find(j => j.jobType === jobType)!;
}
if (syndicateEntry.Tag === "EntratiSyndicate") {
if (
[
@ -1381,7 +1282,9 @@ export const addMissionRewards = async (
}
}
}
let medallionAmount = Math.floor(currentJob.xpAmounts[rewardInfo.JobStage] / (rewardInfo.Q ? 0.8 : 1));
let medallionAmount = Math.floor(
Math.min(rewardInfo.JobStage, currentJob.xpAmounts.length - 1) / (rewardInfo.Q ? 0.8 : 1)
);
if (
["DeimosEndlessAreaDefenseBounty", "DeimosEndlessExcavateBounty", "DeimosEndlessPurifyBounty"].some(
ending => jobType.endsWith(ending)
@ -1400,45 +1303,35 @@ export const addMissionRewards = async (
ItemCount: medallionAmount
});
SyndicateXPItemReward = medallionAmount;
logger.debug(
`Giving ${medallionAmount} medallions for the ${rewardInfo.JobStage} stage of the ${rewardInfo.JobTier} tier bounty`
);
} else {
const specialCase = [
{ endings: ["Heists/HeistProfitTakerBountyOne"], stage: 2, amount: 1000 },
{ endings: ["Hunts/AllTeralystsHunt"], stage: 2, amount: 5000 },
{
endings: [
"Hunts/TeralystHunt",
"Heists/HeistProfitTakerBountyTwo",
"Heists/HeistProfitTakerBountyThree",
"Heists/HeistProfitTakerBountyFour",
"Heists/HeistExploiterBountyOne"
],
amount: 1000
}
];
const specialCaseReward = specialCase.find(
rule =>
rule.endings.some(e => jobType.endsWith(e)) &&
(rule.stage === undefined || rewardInfo.JobStage === rule.stage)
);
if (specialCaseReward) {
addStanding(inventory, syndicateEntry.Tag, specialCaseReward.amount, AffiliationMods);
} else {
if (rewardInfo.JobTier! >= 0) {
addStanding(
inventory,
syndicateEntry.Tag,
Math.floor(currentJob.xpAmounts[rewardInfo.JobStage] / (rewardInfo.Q ? 0.8 : 1)),
AffiliationMods
);
} else {
if (jobType.endsWith("Heists/HeistProfitTakerBountyOne") && rewardInfo.JobStage === 2) {
addStanding(inventory, syndicateEntry.Tag, 1000, AffiliationMods);
}
if (jobType.endsWith("Hunts/AllTeralystsHunt") && rewardInfo.JobStage === 2) {
addStanding(inventory, syndicateEntry.Tag, 5000, AffiliationMods);
}
if (
[
"Hunts/TeralystHunt",
"Heists/HeistProfitTakerBountyTwo",
"Heists/HeistProfitTakerBountyThree",
"Heists/HeistProfitTakerBountyFour",
"Heists/HeistExploiterBountyOne"
].some(ending => jobType.endsWith(ending))
) {
addStanding(inventory, syndicateEntry.Tag, 1000, AffiliationMods);
}
}
}
}
if (jobType == "/Lotus/Types/Gameplay/Eidolon/Jobs/NewbieJob") {
addStanding(inventory, "CetusSyndicate", Math.floor(200 / (rewardInfo.Q ? 0.8 : 1)), AffiliationMods);
}
}
if (rewardInfo.challengeMissionId) {
@ -1455,7 +1348,6 @@ export const addMissionRewards = async (
ItemCount: medallionAmount
});
SyndicateXPItemReward = medallionAmount;
logger.debug(`Giving ${medallionAmount} medallions for the ${tier} tier bounty`);
} else {
let standingAmount = (tier + 1) * 1000;
if (tier > 5) standingAmount = 7500; // InfestedLichBounty
@ -1744,10 +1636,7 @@ function getRandomMissionDrops(
rewardManifests = [
"/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriMurmurFinalSteelChestRewards"
];
} else if (
RewardInfo.T == 70 ||
RewardInfo.T == 6 // https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2526
) {
} else if (RewardInfo.T == 70) {
// Orowyrm chest, gives 10 Pathos Clamps, or 15 on Steel Path.
drops.push({
StoreItem: "/Lotus/StoreItems/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem",
@ -1769,19 +1658,7 @@ function getRandomMissionDrops(
if (syndicateMissionId) {
pushClassicBounties(syndicateMissions, idToBountyCycle(syndicateMissionId));
}
let syndicateEntry: ISyndicateMissionInfo | IGoal | undefined = syndicateMissions.find(
m => m._id.$oid === syndicateMissionId
);
if (
[
"/Lotus/Types/Gameplay/Eidolon/Jobs/Events/InfestedPlainsBounty",
"/Lotus/Types/Gameplay/Eidolon/Jobs/Events/GhoulAlertBounty"
].some(prefix => jobType.startsWith(prefix))
) {
const { Goals } = getWorldState(undefined);
syndicateEntry = Goals.find(m => m._id.$oid === syndicateMissionId);
if (syndicateEntry) syndicateEntry.Tag = syndicateEntry.JobAffiliationTag!;
}
const syndicateEntry = syndicateMissions.find(m => m._id.$oid === syndicateMissionId);
if (syndicateEntry && syndicateEntry.Jobs) {
let job = syndicateEntry.Jobs[RewardInfo.JobTier!];
@ -1866,14 +1743,6 @@ function getRandomMissionDrops(
}
}
}
if (
[
"/Lotus/Types/Gameplay/Eidolon/Jobs/Events/InfestedPlainsBounty",
"/Lotus/Types/Gameplay/Eidolon/Jobs/Events/GhoulAlertBounty"
].some(prefix => jobType.startsWith(prefix))
) {
job = syndicateEntry.Jobs.find(j => j.jobType === jobType)!;
}
rewardManifests = [job.rewards];
if (job.xpAmounts.length > 1) {
const curentStage = RewardInfo.JobStage! + 1;
@ -1901,11 +1770,6 @@ function getRandomMissionDrops(
}
}
}
if (jobType == "/Lotus/Types/Gameplay/Eidolon/Jobs/NewbieJob") {
rewardManifests = ["/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierATableARewards"];
rotations = [3];
if (RewardInfo.Q) rotations.push(3);
}
}
} else if (RewardInfo.challengeMissionId) {
const rewardTables: Record<string, string[]> = {
@ -2002,36 +1866,6 @@ function getRandomMissionDrops(
}
});
// Railjack Abandoned Cache Rewards, Rotation A (Mandatory Objectives)
if (RewardInfo.POICompletions) {
if (region.cacheRewardManifest) {
const deck = ExportRewards[region.cacheRewardManifest];
for (let cache = 0; cache != RewardInfo.POICompletions; ++cache) {
const drop = getRandomRewardByChance(deck[0]);
if (drop) {
drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount, FromEnemyCache: true });
}
}
} else {
logger.error(`POI completed, but there was no cache reward manifest at ${RewardInfo.node}`);
}
}
// Railjack Abandoned Cache Rewards, Rotation B (Optional Objectives)
if (RewardInfo.LootDungeonCompletions) {
if (region.cacheRewardManifest) {
const deck = ExportRewards[region.cacheRewardManifest];
for (let cache = 0; cache != RewardInfo.LootDungeonCompletions; ++cache) {
const drop = getRandomRewardByChance(deck[1]);
if (drop) {
drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount, FromEnemyCache: true });
}
}
} else {
logger.error(`Loot dungeon completed, but there was no cache reward manifest at ${RewardInfo.node}`);
}
}
if (region.cacheRewardManifest && RewardInfo.EnemyCachesFound) {
const deck = ExportRewards[region.cacheRewardManifest];
for (let rotation = 0; rotation != RewardInfo.EnemyCachesFound; ++rotation) {
@ -2245,143 +2079,5 @@ const goalMessagesByKey: Record<string, { sndr: string; msg: string; sub: string
msg: "/Lotus/Language/Messages/GalleonRobbery2025RewardMsgC",
sub: "/Lotus/Language/Messages/GalleonRobbery2025MissionTitleC",
icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png"
},
"/Lotus/Types/Keys/TacAlertKeyWaterFightA": {
sndr: "/Lotus/Language/Bosses/BossKelaDeThaym",
msg: "/Lotus/Language/Inbox/WaterFightRewardMsgA",
sub: "/Lotus/Language/Inbox/WaterFightRewardSubjectA",
icon: "/Lotus/Interface/Icons/Npcs/Grineer/KelaDeThaym.png"
},
"/Lotus/Types/Keys/TacAlertKeyWaterFightB": {
sndr: "/Lotus/Language/Bosses/BossKelaDeThaym",
msg: "/Lotus/Language/Inbox/WaterFightRewardMsgB",
sub: "/Lotus/Language/Inbox/WaterFightRewardSubjectB",
icon: "/Lotus/Interface/Icons/Npcs/Grineer/KelaDeThaym.png"
},
"/Lotus/Types/Keys/TacAlertKeyWaterFightC": {
sndr: "/Lotus/Language/Bosses/BossKelaDeThaym",
msg: "/Lotus/Language/Inbox/WaterFightRewardMsgC",
sub: "/Lotus/Language/Inbox/WaterFightRewardSubjectC",
icon: "/Lotus/Interface/Icons/Npcs/Grineer/KelaDeThaym.png"
},
"/Lotus/Types/Keys/TacAlertKeyWaterFightD": {
sndr: "/Lotus/Language/Bosses/BossKelaDeThaym",
msg: "/Lotus/Language/Inbox/WaterFightRewardMsgD",
sub: "/Lotus/Language/Inbox/WaterFightRewardSubjectD",
icon: "/Lotus/Interface/Icons/Npcs/Grineer/KelaDeThaym.png"
},
"/Lotus/Types/Keys/WolfTacAlertReduxA": {
sndr: "/Lotus/Language/Bosses/NoraNight",
msg: "/Lotus/Language/Inbox/WolfTacAlertBody",
sub: "/Lotus/Language/Inbox/WolfTacAlertTitle",
icon: "/Lotus/Interface/Icons/Npcs/Seasonal/NoraNight.png"
},
"/Lotus/Types/Keys/WolfTacAlertReduxB": {
sndr: "/Lotus/Language/Bosses/NoraNight",
msg: "/Lotus/Language/Inbox/WolfTacAlertBody",
sub: "/Lotus/Language/Inbox/WolfTacAlertTitle",
icon: "/Lotus/Interface/Icons/Npcs/Seasonal/NoraNight.png"
},
"/Lotus/Types/Keys/WolfTacAlertReduxD": {
sndr: "/Lotus/Language/Bosses/NoraNight",
msg: "/Lotus/Language/Inbox/WolfTacAlertBody",
sub: "/Lotus/Language/Inbox/WolfTacAlertTitle",
icon: "/Lotus/Interface/Icons/Npcs/Seasonal/NoraNight.png"
},
"/Lotus/Types/Keys/WolfTacAlertReduxC": {
sndr: "/Lotus/Language/Bosses/NoraNight",
msg: "/Lotus/Language/Inbox/WolfTacAlertBody",
sub: "/Lotus/Language/Inbox/WolfTacAlertTitle",
icon: "/Lotus/Interface/Icons/Npcs/Seasonal/NoraNight.png"
},
"/Lotus/Types/Keys/LanternEndlessEventKeyA": {
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/LanternEndlessEventKeyB": {
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/LanternEndlessEventKeyD": {
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/LanternEndlessEventKeyC": {
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyHalloween": {
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBonusBody",
sub: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBonusTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyHalloweenBonus": {
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBody",
sub: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyHalloweenTimeAttack": {
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBody",
sub: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyProxyRebellionOne": {
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/RazorbackArmadaRewardBody",
sub: "/Lotus/Language/G1Quests/GenericTacAlertSmallRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyProxyRebellionTwo": {
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/RazorbackArmadaRewardBody",
sub: "/Lotus/Language/G1Quests/GenericTacAlertSmallRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyProxyRebellionThree": {
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/RazorbackArmadaRewardBody",
sub: "/Lotus/Language/G1Quests/GenericTacAlertSmallRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyProxyRebellionFour": {
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/GenericTacAlertBadgeRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertBadgeRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyProjectNightwatchEasy": {
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/ProjectNightwatchRewardMsgA",
sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionOneTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyProjectNightwatch": {
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionRewardBody",
sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionTwoTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyProjectNightwatchHard": {
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionRewardBody",
sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionThreeTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyProjectNightwatchBonus": {
sndr: "/Lotus/Language/Bosses/Lotus",
msg: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionRewardBody",
sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionFourTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
}
};

View File

@ -1,4 +1,4 @@
import { parseSlotPurchaseName, slotPurchaseNameToSlotName } from "@/src/helpers/purchaseHelpers";
import { parseSlotPurchaseName } from "@/src/helpers/purchaseHelpers";
import { getSubstringFromKeyword } from "@/src/helpers/stringHelpers";
import {
addBooster,
@ -14,6 +14,7 @@ import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import {
IPurchaseRequest,
IPurchaseResponse,
SlotPurchase,
IInventoryChanges,
PurchaseSource,
IPurchaseParams
@ -327,7 +328,7 @@ export const handlePurchase = async (
purchaseResponse.InventoryChanges.MiscItems ??= [];
purchaseResponse.InventoryChanges.MiscItems.push(invItem);
}
} else if (!inventory.infiniteRegalAya) {
} else if (!config.infiniteRegalAya) {
inventory.PrimeTokens -= offer.PrimePrice! * purchaseRequest.PurchaseParams.Quantity;
purchaseResponse.InventoryChanges.PrimeTokens ??= 0;
@ -471,6 +472,19 @@ export const handleStoreItemAcquisition = async (
return purchaseResponse;
};
export const slotPurchaseNameToSlotName: SlotPurchase = {
SuitSlotItem: { name: "SuitBin", purchaseQuantity: 1 },
TwoSentinelSlotItem: { name: "SentinelBin", purchaseQuantity: 2 },
TwoWeaponSlotItem: { name: "WeaponBin", purchaseQuantity: 2 },
SpaceSuitSlotItem: { name: "SpaceSuitBin", purchaseQuantity: 1 },
TwoSpaceWeaponSlotItem: { name: "SpaceWeaponBin", purchaseQuantity: 2 },
MechSlotItem: { name: "MechBin", purchaseQuantity: 1 },
TwoOperatorWeaponSlotItem: { name: "OperatorAmpBin", purchaseQuantity: 2 },
RandomModSlotItem: { name: "RandomModBin", purchaseQuantity: 3 },
TwoCrewShipSalvageSlotItem: { name: "CrewShipSalvageBin", purchaseQuantity: 2 },
CrewMemberSlotItem: { name: "CrewMemberBin", purchaseQuantity: 1 }
};
// // extra = everything above the base +2 slots (depending on slot type)
// // new slot above base = extra + 1 and slots +1
// // new frame = slots -1
@ -567,7 +581,7 @@ const handleBoosterPackPurchase = async (
purchaseResponse.InventoryChanges,
await addItem(inventory, specialItemReward.Item)
);
atten.Atten = 0;
// TOVERIFY: Is the SpecialItemRewardAttenuation entry removed now?
} else {
atten.Atten += specialItemReward.PityIncreaseRate!;
}

View File

@ -236,7 +236,7 @@ const handleQuestCompletion = async (
setupKahlSyndicate(inventory);
}
// Whispers in the Walls is unlocked once The New War + Heart of Deimos are completed.
// Whispers in the Walls is unlocked once The New + Heart of Deimos are completed.
if (
doesQuestCompletionFinishSet(inventory, questKey, [
"/Lotus/Types/Keys/NewWarQuest/NewWarQuestKeyChain",

View File

@ -151,57 +151,4 @@ export class SRng {
arr[lastIdx] = tmp;
}
}
shuffledArray<T>(inarr: readonly T[]): T[] {
const arr = [...inarr];
this.shuffleArray(arr);
return arr;
}
}
export const sequentiallyUniqueRandomElement = <T>(
deck: readonly T[],
idx: number,
lookbehind: number,
seed: number = 0
): T | undefined => {
// This algorithm may modify a shuffle up to index `lookbehind + 1`. It assumes that the last `lookbehind` cards are not adjusted.
if (lookbehind + 1 >= deck.length - lookbehind) {
throw new Error(
`this algorithm cannot guarantee ${lookbehind} unique cards in a row with a deck of size ${deck.length}`
);
}
const iteration = Math.trunc(idx / deck.length);
const card = idx % deck.length;
const currentShuffle = new SRng(mixSeeds(new SRng(iteration).randomInt(0, 100_000), seed)).shuffledArray(deck);
if (card < currentShuffle.length - lookbehind) {
// We are indexing before the end of the deck, so adjustments may be needed to achieve uniqueness.
const window: T[] = [];
{
const previousShuffle = new SRng(
mixSeeds(new SRng(iteration - 1).randomInt(0, 100_000), seed)
).shuffledArray(deck);
for (let i = previousShuffle.length - lookbehind; i != previousShuffle.length; ++i) {
window.push(previousShuffle[i]);
}
}
// From this point on, `window.length == lookbehind` should hold.
for (let i = 0; i != lookbehind; ++i) {
if (window.indexOf(currentShuffle[i]) != -1) {
for (let j = i; ; ++j) {
// `j < currentShuffle.length - lookbehind` should hold.
if (window.indexOf(currentShuffle[j]) == -1) {
const tmp = currentShuffle[j];
currentShuffle[j] = currentShuffle[i];
currentShuffle[i] = tmp;
break;
}
}
}
window.splice(0, 1);
window.push(currentShuffle[i]);
}
}
return currentShuffle[card];
};

View File

@ -1,8 +1,6 @@
import { getPersonalRooms } from "@/src/services/personalRoomsService";
import { getShip } from "@/src/services/shipService";
import {
IResetShipDecorationsRequest,
IResetShipDecorationsResponse,
ISetPlacedDecoInfoRequest,
ISetShipCustomizationsRequest,
IShipDecorationsRequest,
@ -54,7 +52,12 @@ export const handleSetShipDecorations = async (
): Promise<IShipDecorationsResponse> => {
const personalRooms = await getPersonalRooms(accountId);
const rooms = getRoomsForBootLocation(personalRooms, placedDecoration);
const rooms =
placedDecoration.BootLocation == "SHOP"
? personalRooms.TailorShop.Rooms
: placedDecoration.IsApartment
? personalRooms.Apartment.Rooms
: personalRooms.Ship.Rooms;
const roomToPlaceIn = rooms.find(room => room.Name === placedDecoration.Room);
@ -156,6 +159,7 @@ export const handleSetShipDecorations = async (
if (!config.unlockAllShipDecorations) {
const inventory = await getInventory(accountId);
const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == placedDecoration.Type)![0];
if (placedDecoration.Sockets !== undefined) {
addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: placedDecoration.Sockets, ItemCount: -1 }]);
} else {
@ -188,62 +192,17 @@ export const handleSetShipDecorations = async (
const getRoomsForBootLocation = (
personalRooms: TPersonalRoomsDatabaseDocument,
request: { BootLocation?: TBootLocation; IsApartment?: boolean }
bootLocation: TBootLocation | undefined
): RoomsType[] => {
if (request.BootLocation == "SHOP") {
if (bootLocation == "SHOP") {
return personalRooms.TailorShop.Rooms;
}
if (request.BootLocation == "APARTMENT" || request.IsApartment) {
if (bootLocation == "APARTMENT") {
return personalRooms.Apartment.Rooms;
}
return personalRooms.Ship.Rooms;
};
export const handleResetShipDecorations = async (
accountId: string,
request: IResetShipDecorationsRequest
): Promise<IResetShipDecorationsResponse> => {
const [personalRooms, inventory] = await Promise.all([getPersonalRooms(accountId), getInventory(accountId)]);
const room = getRoomsForBootLocation(personalRooms, request).find(room => room.Name === request.Room);
if (!room) {
throw new Error(`unknown room: ${request.Room}`);
}
for (const deco of room.PlacedDecos) {
const entry = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type);
if (!entry) {
throw new Error(`unknown deco type: ${deco.Type}`);
}
const [itemType, meta] = entry;
if (meta.capacityCost === undefined) {
throw new Error(`unknown deco type: ${deco.Type}`);
}
// refund item
if (!config.unlockAllShipDecorations) {
if (deco.Sockets !== undefined) {
addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: deco.Sockets, ItemCount: 1 }]);
} else {
addShipDecorations(inventory, [{ ItemType: itemType, ItemCount: 1 }]);
}
}
// refund capacity
room.MaxCapacity += meta.capacityCost;
}
// empty room
room.PlacedDecos.splice(0, room.PlacedDecos.length);
await Promise.all([personalRooms.save(), inventory.save()]);
return {
ResetRoom: request.Room,
ClaimedDecos: [], // Not sure what this is for; the client already implies that the decos were returned to inventory.
NewCapacity: room.MaxCapacity
};
};
export const handleSetPlacedDecoInfo = async (accountId: string, req: ISetPlacedDecoInfoRequest): Promise<void> => {
if (req.GuildId && req.ComponentId) {
const guild = (await Guild.findById(req.GuildId))!;
@ -258,7 +217,7 @@ export const handleSetPlacedDecoInfo = async (accountId: string, req: ISetPlaced
const personalRooms = await getPersonalRooms(accountId);
const room = getRoomsForBootLocation(personalRooms, req).find(room => room.Name === req.Room);
const room = getRoomsForBootLocation(personalRooms, req.BootLocation).find(room => room.Name === req.Room);
if (!room) {
throw new Error(`unknown room: ${req.Room}`);
}

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,6 @@ import { Account } from "@/src/models/loginModel";
import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "@/src/services/loginService";
import { IDatabaseAccountJson } from "@/src/types/loginTypes";
import { HydratedDocument } from "mongoose";
import { logError } from "@/src/utils/logger";
let wsServer: ws.Server | undefined;
let wssServer: ws.Server | undefined;
@ -44,7 +43,7 @@ export const stopWsServers = (promises: Promise<void>[]): void => {
let lastWsid: number = 0;
interface IWsCustomData extends ws {
id: number;
id?: number;
accountId?: string;
}
@ -89,67 +88,63 @@ const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
ws.on("message", async msg => {
try {
const data = JSON.parse(String(msg)) as IWsMsgFromClient;
if (data.auth) {
let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email });
if (account) {
if (isCorrectPassword(data.auth.password, account.password)) {
if (!account.Nonce) {
account.ClientType = "webui";
account.Nonce = createNonce();
await (account as HydratedDocument<IDatabaseAccountJson>).save();
}
} else {
account = null;
const data = JSON.parse(String(msg)) as IWsMsgFromClient;
if (data.auth) {
let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email });
if (account) {
if (isCorrectPassword(data.auth.password, account.password)) {
if (!account.Nonce) {
account.ClientType = "webui";
account.Nonce = createNonce();
await (account as HydratedDocument<IDatabaseAccountJson>).save();
}
} else if (data.auth.isRegister) {
const name = await getUsernameFromEmail(data.auth.email);
account = await createAccount({
email: data.auth.email,
password: data.auth.password,
ClientType: "webui",
LastLogin: new Date(),
DisplayName: name,
Nonce: createNonce()
});
}
if (account) {
(ws as IWsCustomData).accountId = account.id;
ws.send(
JSON.stringify({
auth_succ: {
id: account.id,
DisplayName: account.DisplayName,
Nonce: account.Nonce
}
} satisfies IWsMsgToClient)
);
} else {
ws.send(
JSON.stringify({
auth_fail: {
isRegister: data.auth.isRegister
}
} satisfies IWsMsgToClient)
);
account = null;
}
} else if (data.auth.isRegister) {
const name = await getUsernameFromEmail(data.auth.email);
account = await createAccount({
email: data.auth.email,
password: data.auth.password,
ClientType: "webui",
LastLogin: new Date(),
DisplayName: name,
Nonce: createNonce()
});
}
if (data.logout) {
const accountId = (ws as IWsCustomData).accountId;
(ws as IWsCustomData).accountId = undefined;
await Account.updateOne(
{
_id: accountId,
ClientType: "webui"
},
{
Nonce: 0
}
if (account) {
(ws as IWsCustomData).accountId = account.id;
ws.send(
JSON.stringify({
auth_succ: {
id: account.id,
DisplayName: account.DisplayName,
Nonce: account.Nonce
}
} satisfies IWsMsgToClient)
);
} else {
ws.send(
JSON.stringify({
auth_fail: {
isRegister: data.auth.isRegister
}
} satisfies IWsMsgToClient)
);
}
} catch (e) {
logError(e as Error, `processing websocket message`);
}
if (data.logout) {
const accountId = (ws as IWsCustomData).accountId;
(ws as IWsCustomData).accountId = undefined;
await Account.updateOne(
{
_id: accountId,
ClientType: "webui"
},
{
Nonce: 0
}
);
}
});
};
@ -186,24 +181,18 @@ export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void
}
};
export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId?: string, excludeWsid?: number): void => {
export const sendWsBroadcastExcept = (wsid: number | undefined, data: IWsMsgToClient): void => {
const msg = JSON.stringify(data);
if (wsServer) {
for (const client of wsServer.clients) {
if (
(!accountId || (client as IWsCustomData).accountId == accountId) &&
(client as IWsCustomData).id != excludeWsid
) {
if ((client as IWsCustomData).id != wsid) {
client.send(msg);
}
}
}
if (wssServer) {
for (const client of wssServer.clients) {
if (
(!accountId || (client as IWsCustomData).accountId == accountId) &&
(client as IWsCustomData).id != excludeWsid
) {
if ((client as IWsCustomData).id != wsid) {
client.send(msg);
}
}

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Types } from "mongoose";
import { IOid, IMongoDate, IOidWithLegacySupport, ITypeCount } from "@/src/types/commonTypes";
import {
@ -19,15 +20,6 @@ export type InventoryDatabaseEquipment = {
[_ in TEquipmentKey]: IEquipmentDatabase[];
};
// Fields specific to SNS
export interface IAccountCheats {
infiniteCredits?: boolean;
infinitePlatinum?: boolean;
infiniteEndo?: boolean;
infiniteRegalAya?: boolean;
infiniteHelminthMaterials?: boolean;
}
export interface IInventoryDatabase
extends Omit<
IInventoryClient,
@ -70,8 +62,7 @@ export interface IInventoryDatabase
| "PersonalGoalProgress"
| TEquipmentKey
>,
InventoryDatabaseEquipment,
IAccountCheats {
InventoryDatabaseEquipment {
accountOwnerId: Types.ObjectId;
Created: Date;
TrainingDate: Date;
@ -225,7 +216,6 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
PremiumCredits: number;
PremiumCreditsFree: number;
FusionPoints: number;
CrewShipFusionPoints: number; //Dirac (pre-rework Railjack)
PrimeTokens: number;
SuitBin: ISlots;
WeaponBin: ISlots;
@ -530,8 +520,7 @@ export enum InventorySlot {
SENTINELS = "SentinelBin",
AMPS = "OperatorAmpBin",
RJ_COMPONENT_AND_ARMAMENTS = "CrewShipSalvageBin",
CREWMEMBERS = "CrewMemberBin",
RIVENS = "RandomModBin"
CREWMEMBERS = "CrewMemberBin"
}
export interface ISlots {

View File

@ -23,19 +23,12 @@ export interface IMissionCredits {
DailyMissionBonus?: boolean;
}
export interface IMissionInventoryUpdateResponseRailjackInterstitial extends Partial<IMissionCredits> {
export interface IMissionInventoryUpdateResponse extends Partial<IMissionCredits> {
ConquestCompletedMissionsCount?: number;
InventoryJson?: string;
MissionRewards?: IMissionReward[];
InventoryChanges?: IInventoryChanges;
FusionPoints?: number;
SyndicateXPItemReward?: number;
AffiliationMods?: IAffiliationMods[];
}
export interface IMissionInventoryUpdateResponse extends IMissionInventoryUpdateResponseRailjackInterstitial {
InventoryJson?: string;
}
export interface IMissionInventoryUpdateResponseBackToDryDock {
InventoryJson: string;
}

View File

@ -91,16 +91,12 @@ export interface IApartmentClient {
Gardening: IGardeningClient;
Rooms: IRoom[];
FavouriteLoadouts: IFavouriteLoadout[];
VideoWallBackdrop?: string;
Soundscape?: string;
}
export interface IApartmentDatabase {
Gardening: IGardeningDatabase;
Rooms: IRoom[];
FavouriteLoadouts: IFavouriteLoadoutDatabase[];
VideoWallBackdrop?: string;
Soundscape?: string;
}
export interface IPlacedDecosDatabase {
@ -154,17 +150,6 @@ export interface IShipDecorationsResponse {
NewRoom?: string;
}
export interface IResetShipDecorationsRequest {
Room: string;
BootLocation?: TBootLocation;
}
export interface IResetShipDecorationsResponse {
ResetRoom: string;
ClaimedDecos: [];
NewCapacity: number;
}
export interface ISetPlacedDecoInfoRequest {
DecoType: string;
DecoId: string;

View File

@ -8,8 +8,7 @@ import {
IRecentVendorPurchaseClient,
TEquipmentKey,
ICrewMemberClient,
IKubrowPetPrintClient,
IUpgradeClient
IKubrowPetPrintClient
} from "@/src/types/inventoryTypes/inventoryTypes";
export enum PurchaseSource {
@ -74,7 +73,6 @@ export type IInventoryChanges = {
InfestedFoundry?: IInfestedFoundryClient;
Drones?: IDroneClient[];
MiscItems?: IMiscItem[];
ShipDecorations?: ITypeCount[];
EmailItems?: ITypeCount[];
CrewShipRawSalvage?: ITypeCount[];
Nemesis?: Partial<INemesisClient>;
@ -82,7 +80,6 @@ export type IInventoryChanges = {
RecentVendorPurchases?: IRecentVendorPurchaseClient; // < 38.5.0
CrewMembers?: ICrewMemberClient[];
KubrowPetPrints?: IKubrowPetPrintClient[];
Upgrades?: IUpgradeClient[]; // TOVERIFY
} & Record<
Exclude<
string,

View File

@ -148,7 +148,6 @@ export type IMissionInventoryUpdateRequest = {
MultiProgress: unknown[];
}[];
InvasionProgress?: IInvasionProgressClient[];
RJ?: boolean;
ConquestMissionsCompleted?: number;
duviriSuitSelection?: string;
duviriPistolSelection?: string;
@ -185,10 +184,7 @@ export interface IRewardInfo {
NemesisHintProgress?: number;
EOM_AFK?: number;
rewardQualifications?: string; // did a Survival for 5 minutes and this was "1"
rewardTierOverrides?: number[]; // Disruption
PurgatoryRewardQualifications?: string;
POICompletions?: number;
LootDungeonCompletions?: number;
rewardSeed?: number | bigint;
periodicMissionTag?: string;
T?: number; // Duviri

View File

@ -79,7 +79,6 @@ export interface ILoadoutDatabase {
NORMAL_PVP: ILoadoutConfigDatabase[];
LUNARO: ILoadoutConfigDatabase[];
OPERATOR: ILoadoutConfigDatabase[];
GEAR: ILoadoutConfigDatabase[];
KDRIVE: ILoadoutConfigDatabase[];
DATAKNIFE: ILoadoutConfigDatabase[];
MECH: ILoadoutConfigDatabase[];

View File

@ -5,16 +5,13 @@ export interface IWorldState {
Version: number; // for goals
BuildLabel: string;
Time: number;
InGameMarket: IInGameMarket;
Goals: IGoal[];
Alerts: [];
Sorties: ISortie[];
LiteSorties: ILiteSortie[];
SyndicateMissions: ISyndicateMissionInfo[];
ActiveMissions: IFissure[];
FlashSales: IFlashSale[];
GlobalUpgrades: IGlobalUpgrade[];
Invasions: IInvasion[];
NodeOverrides: INodeOverride[];
VoidTraders: IVoidTrader[];
PrimeVaultTraders: IPrimeVaultTrader[];
@ -39,59 +36,19 @@ export interface IGoal {
_id: IOid;
Activation: IMongoDate;
Expiry: IMongoDate;
Count?: number;
Goal?: number;
InterimGoals?: number[];
BonusGoal?: number;
HealthPct?: number;
Success?: number;
Personal?: boolean;
Best?: boolean;
Bounty?: boolean; // Tactical Alert
Faction?: string;
Count: number;
Goal: number;
Success: number;
Personal: boolean;
Bounty?: boolean;
ClampNodeScores?: boolean;
Desc: string;
ToolTip?: string;
Transmission?: string;
InstructionalItem?: string;
Icon: string;
Tag: string;
PrereqGoalTags?: string[];
Node?: string;
VictimNode?: string;
ConcurrentMissionKeyNames?: string[];
ConcurrentNodeReqs?: number[];
ConcurrentNodes?: string[];
RegionIdx?: number;
Regions?: number[];
Node: string;
MissionKeyName?: string;
Reward?: IMissionReward;
InterimRewards?: IMissionReward[];
BonusReward?: IMissionReward;
JobAffiliationTag?: string;
Jobs?: ISyndicateJob[];
PreviousJobs?: ISyndicateJob[];
JobCurrentVersion?: IOid;
JobPreviousVersion?: IOid;
ScoreVar?: string;
ScoreMaxTag?: string;
NightLevel?: string;
}
export interface ISyndicateJob {
jobType?: string;
rewards: string;
masteryReq?: number;
minEnemyLevel: number;
maxEnemyLevel: number;
xpAmounts: number[];
endless?: boolean;
locationTag?: string;
isVault?: boolean;
requiredItems?: string[];
useRequiredItemsAsMiscItemFee?: boolean;
}
export interface ISyndicateMissionInfo {
@ -101,7 +58,17 @@ export interface ISyndicateMissionInfo {
Tag: string;
Seed: number;
Nodes: string[];
Jobs?: ISyndicateJob[];
Jobs?: {
jobType?: string;
rewards: string;
masteryReq: number;
minEnemyLevel: number;
maxEnemyLevel: number;
xpAmounts: number[];
endless?: boolean;
locationTag?: string;
isVault?: boolean;
}[];
}
export interface IGlobalUpgrade {
@ -115,28 +82,6 @@ export interface IGlobalUpgrade {
LocalizeDescTag: string;
}
export interface IInvasion {
_id: IOid;
Faction: string;
DefenderFaction: string;
Node: string;
Count: number;
Goal: number;
LocTag: string;
Completed: boolean;
ChainID: IOid;
AttackerReward: IMissionReward;
AttackerMissionInfo: IInvasionMissionInfo;
DefenderReward: IMissionReward;
DefenderMissionInfo: IInvasionMissionInfo;
Activation: IMongoDate;
}
export interface IInvasionMissionInfo {
seed: number;
faction: string;
}
export interface IFissure {
_id: IOid;
Region: number;
@ -297,7 +242,6 @@ export interface IEndlessXpChoice {
export interface ISeasonChallenge {
_id: IOid;
Daily?: boolean;
Permanent?: boolean; // only for getPastWeeklyChallenges response
Activation: IMongoDate;
Expiry: IMongoDate;
Challenge: string;
@ -336,37 +280,6 @@ export type TCircuitGameMode =
| "Assassination"
| "Alchemy";
export interface IFlashSale {
TypeName: string;
ShowInMarket: boolean;
HideFromMarket: boolean;
SupporterPack: boolean;
Discount: number;
BogoBuy: number;
BogoGet: number;
PremiumOverride: number;
RegularOverride: number;
ProductExpiryOverride?: IMongoDate;
StartDate: IMongoDate;
EndDate: IMongoDate;
}
export interface IInGameMarket {
LandingPage: ILandingPage;
}
export interface ILandingPage {
Categories: IGameMarketCategory[];
}
export interface IGameMarketCategory {
CategoryName: string;
Name: string;
Icon: string;
AddToMenu?: boolean;
Items?: string[];
}
export interface ITmp {
cavabegin: string;
PurchasePlatformLockEnabled: boolean; // Seems unused

View File

@ -108,13 +108,3 @@ errorLog.on("new", filename => logger.info(`Using error log file: ${filename}`))
combinedLog.on("new", filename => logger.info(`Using combined log file: ${filename}`));
errorLog.on("rotate", filename => logger.info(`Rotated error log file: ${filename}`));
combinedLog.on("rotate", filename => logger.info(`Rotated combined log file: ${filename}`));
export const logError = (err: Error, context: string): void => {
if (err.stack) {
const stackArr = err.stack.split("\n");
stackArr[0] += ` while ${context}`;
logger.error(stackArr.join("\n"));
} else {
logger.error(`uncaught error while ${context}: ${err.message}`);
}
};

View File

@ -3,5 +3,3 @@ type Entries<T, K extends keyof T = keyof T> = (K extends unknown ? [K, T[K]] :
export function getEntriesUnsafe<T extends object>(object: T): Entries<T> {
return Object.entries(object) as Entries<T>;
}
export const exhaustive = (_: never): void => {};

View File

@ -135,6 +135,5 @@
"/Lotus/Language/EntratiLab/EntratiGeneral/HumanLoidLoved",
"ConquestSetupIntro",
"EntratiLabConquestHardModeUnlocked",
"/Lotus/Language/Npcs/KonzuPostNewWar",
"/Lotus/Language/SolarisVenus/EudicoPostNewWar"
"/Lotus/Language/Npcs/KonzuPostNewWar"
]

View File

@ -16,10 +16,6 @@
{
"ItemType": "/Lotus/Types/Keys/1999PrologueQuest/1999PrologueQuestKeyChain",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Items/EmailItems/TennokaiEmailItem",
"ItemCount": 1
}
]
}

View File

@ -304,6 +304,7 @@
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/Seasonal/AvatarImageGlyphCookieKubrow", "PrimePrice": 80, "RegularPrice": 50000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/LisetScarf", "PrimePrice": 600, "RegularPrice": 400000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/SuitCustomizations/ColourPickerTwitchBItemA", "PrimePrice": 220, "RegularPrice": 220000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Effects/FootstepsMaple", "PrimePrice": 15, "RegularPrice": 1000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Clan/BaroKavatBadgeItem", "PrimePrice": 50, "RegularPrice": 50000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/BaroKavatSigil", "PrimePrice": 55, "RegularPrice": 45000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/WraithTurbinesScarf", "PrimePrice": 400, "RegularPrice": 500000 },
@ -362,6 +363,7 @@
{ "ItemType": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileInarosTomb", "PrimePrice": 325, "RegularPrice": 175000 },
{ "ItemType": "/Lotus/StoreItems/Types/Restoratives/Consumable/BaroFireWorksCrate", "PrimePrice": 50, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileOrokinExtraction", "PrimePrice": 325, "RegularPrice": 175000 },
{ "ItemType": "/Lotus/StoreItems/Types/Keys/MummyQuestKeyBlueprint", "PrimePrice": 100, "RegularPrice": 25000 },
{ "ItemType": "/Lotus/StoreItems/Types/Restoratives/Consumable/AssassinBait", "PrimePrice": 200, "RegularPrice": 125000 },
{ "ItemType": "/Lotus/StoreItems/Types/Restoratives/Consumable/AssassinBaitB", "PrimePrice": 200, "RegularPrice": 125000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationB", "PrimePrice": 100, "RegularPrice": 100000 },
@ -399,39 +401,7 @@
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationC", "PrimePrice": 100, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/PedistalPrime", "PrimePrice": 0, "RegularPrice": 1000000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/Emotes/BaroEmote", "PrimePrice": 0, "RegularPrice": 1000000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/EventSniperReloadDamageMod", "PrimePrice": 2995, "RegularPrice": 1000000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageAvaClemCommunityGlyph", "PrimePrice": 20, "RegularPrice": 33333 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TennoconConcert2025Display", "PrimePrice": 90, "RegularPrice": 125000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/SummerGameFestPoster", "PrimePrice": 90, "RegularPrice": 125000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/RathuumEventPoster", "PrimePrice": 90, "RegularPrice": 125000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/Factions/GlyphFactionCorpus", "PrimePrice": 70, "RegularPrice": 55000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/Factions/GlyphFactionEntrati", "PrimePrice": 99, "RegularPrice": 1900 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/Factions/GlyphFactionScaldra", "PrimePrice": 93, "RegularPrice": 1906 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/Factions/GlyphFactionTechrot", "PrimePrice": 98, "RegularPrice": 1901 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/Warframes/VorunaActionGlyph", "PrimePrice": 75, "RegularPrice": 60000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageVoidAngelBaro", "PrimePrice": 80, "RegularPrice": 50000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/1999DrippySigil", "PrimePrice": 50, "RegularPrice": 45000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Weapons/Rapier/CrpRapierSkin", "PrimePrice": 375, "RegularPrice": 400000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Events/OgrisOldSchool", "PrimePrice": 350, "RegularPrice": 325000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/PrismaLotusFlamesSigil", "PrimePrice": 55, "RegularPrice": 60000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponMeleeFactionDamageMurmursExpert", "PrimePrice": 375, "RegularPrice": 130000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponRecoilReductionModExpert", "PrimePrice": 300, "RegularPrice": 220000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/WeaponRecoilReductionModExpert", "PrimePrice": 300, "RegularPrice": 220000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponRecoilReductionModExpert", "PrimePrice": 300, "RegularPrice": 220000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/SongItems/TenthAnniversaryLoginSongItem", "PrimePrice": 145, "RegularPrice": 165000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/SongItems/AbyssofDagathSongItem", "PrimePrice": 150, "RegularPrice": 155000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/SongItems/ZarimanLoginSongItem", "PrimePrice": 160, "RegularPrice": 180000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/SongItems/DanteUnboundLoginSongItem", "PrimePrice": 150, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/SongItems/EmpyreanSongItem", "PrimePrice": 160, "RegularPrice": 155000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/SongItems/DeimosLoginSongItem", "PrimePrice": 155, "RegularPrice": 160000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/SongItems/JadeShadowsLoginSongItem", "PrimePrice": 150, "RegularPrice": 170000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/SongItems/WhispersInTheWallLoginSongItem", "PrimePrice": 165, "RegularPrice": 170000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/SongItems/CorpusRailjackLoginSongItem", "PrimePrice": 150, "RegularPrice": 165000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/SongItems/LotusEatersSongItem", "PrimePrice": 165, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/SongItems/KuvaLichLoginSongItem", "PrimePrice": 140, "RegularPrice": 170000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyBaro", "PrimePrice": 100, "RegularPrice": 125000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyInaros", "PrimePrice": 120, "RegularPrice": 90000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponPistolFactionDamageMurmursExpert", "PrimePrice": 375, "RegularPrice": 130000 }
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/EventSniperReloadDamageMod", "PrimePrice": 2995, "RegularPrice": 1000000 }
],
"allIfAny": [
[

View File

@ -1,114 +0,0 @@
{
"FC_CORPUS": [
"SettlementNode1",
"SettlementNode2",
"SettlementNode3",
"SettlementNode11",
"SettlementNode12",
"SettlementNode14",
"SettlementNode15",
"SettlementNode20",
"SolNode1",
"SolNode2",
"SolNode4",
"SolNode6",
"SolNode10",
"SolNode17",
"SolNode21",
"SolNode22",
"SolNode23",
"SolNode25",
"SolNode38",
"SolNode43",
"SolNode48",
"SolNode49",
"SolNode51",
"SolNode53",
"SolNode56",
"SolNode57",
"SolNode61",
"SolNode62",
"SolNode65",
"SolNode66",
"SolNode72",
"SolNode73",
"SolNode74",
"SolNode76",
"SolNode78",
"SolNode81",
"SolNode84",
"SolNode88",
"SolNode97",
"SolNode100",
"SolNode101",
"SolNode102",
"SolNode104",
"SolNode107",
"SolNode109",
"SolNode118",
"SolNode121",
"SolNode123",
"SolNode125",
"SolNode126",
"SolNode127",
"SolNode128",
"SolNode203",
"SolNode205",
"SolNode209",
"SolNode210",
"SolNode211",
"SolNode212",
"SolNode214",
"SolNode216",
"SolNode217",
"SolNode220"
],
"FC_GRINEER": [
"SolNode11",
"SolNode16",
"SolNode18",
"SolNode19",
"SolNode20",
"SolNode30",
"SolNode31",
"SolNode32",
"SolNode36",
"SolNode41",
"SolNode42",
"SolNode45",
"SolNode46",
"SolNode50",
"SolNode58",
"SolNode67",
"SolNode68",
"SolNode70",
"SolNode82",
"SolNode93",
"SolNode96",
"SolNode99",
"SolNode106",
"SolNode113",
"SolNode131",
"SolNode132",
"SolNode135",
"SolNode137",
"SolNode138",
"SolNode139",
"SolNode140",
"SolNode141",
"SolNode144",
"SolNode146",
"SolNode147",
"SolNode149",
"SolNode177",
"SolNode181",
"SolNode184",
"SolNode185",
"SolNode187",
"SolNode188",
"SolNode189",
"SolNode191",
"SolNode195",
"SolNode196"
]
}

View File

@ -1,190 +0,0 @@
{
"FC_GRINEER": {
"COMMON": [
{
"ItemType": "/Lotus/Types/Items/Research/ChemComponent",
"ItemCount": 3
}
],
"UNCOMMON": [
{
"ItemType": "/Lotus/Types/Recipes/Weapons/KarakWraithBlueprint",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/KarakWraithBarrel",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/KarakWraithReceiver",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/KarakWraithStock",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/StrunWraithBlueprint",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/StrunWraithBarrel",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/StrunWraithReceiver",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/StrunWraithStock",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/LatronWraithBlueprint",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/LatronWraithBarrel",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/LatronWraithReceiver",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/LatronWraithStock",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/TwinVipersWraithBlueprint",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/TwinVipersWraithBarrel",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/TwinVipersWraithLink",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/TwinVipersWraithReceiver",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/GrineerCombatKnifeSortieBlueprint",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/GrineerCombatKnifeHilt",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/GrineerCombatKnifeBlade",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/GrineerCombatKnifeHeatsink",
"ItemCount": 1
}
],
"RARE": [
{
"ItemType": "/Lotus/Types/Recipes/Components/OrokinCatalystBlueprint",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Components/OrokinReactorBlueprint",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Components/FormaBlueprint",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Components/UtilityUnlockerBlueprint",
"ItemCount": 1
}
]
},
"FC_CORPUS": {
"COMMON": [
{
"ItemType": "/Lotus/Types/Items/Research/EnergyComponent",
"ItemCount": 3
}
],
"UNCOMMON": [
{
"ItemType": "/Lotus/Types/Recipes/Weapons/DeraVandalBlueprint",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/DeraVandalBarrel",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/DeraVandalReceiver",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/DeraVandalStock",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/SnipetronVandalBlueprint",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/SnipetronVandalStock",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/SnipetronVandalReceiver",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Weapons/WeaponParts/SnipetronVandalBarrel",
"ItemCount": 1
}
],
"RARE": [
{
"ItemType": "/Lotus/Types/Recipes/Components/OrokinCatalystBlueprint",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Components/OrokinReactorBlueprint",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Components/FormaBlueprint",
"ItemCount": 1
},
{
"ItemType": "/Lotus/Types/Recipes/Components/UtilityUnlockerBlueprint",
"ItemCount": 1
}
]
},
"FC_INFESTATION": {
"COMMON": [
{
"ItemType": "/Lotus/Types/Items/Research/BioComponent",
"ItemCount": 1
}
],
"UNCOMMON": [
{
"ItemType": "/Lotus/Types/Items/Research/BioComponent",
"ItemCount": 2
}
],
"RARE": [
{
"ItemType": "/Lotus/Types/Items/MiscItems/InfestedAladCoordinate",
"ItemCount": 1
}
]
}
}

View File

@ -117,6 +117,46 @@
]
}
},
"Invasions": [
{
"_id": {
"$oid": "67c8ec8b3d0d86b236c1c18f"
},
"Faction": "FC_INFESTATION",
"DefenderFaction": "FC_CORPUS",
"Node": "SolNode53",
"Count": -28558,
"Goal": 30000,
"LocTag": "/Lotus/Language/Menu/InfestedInvasionBoss",
"Completed": false,
"ChainID": {
"$oid": "67c8b6a2bde0dfd0f7c1c18d"
},
"AttackerReward": [],
"AttackerMissionInfo": {
"seed": 488863,
"faction": "FC_CORPUS"
},
"DefenderReward": {
"countedItems": [
{
"ItemType": "/Lotus/Types/Items/Research/EnergyComponent",
"ItemCount": 3
}
]
},
"DefenderMissionInfo": {
"seed": 127653,
"faction": "FC_INFESTATION",
"missionReward": []
},
"Activation": {
"$date": {
"$numberLong": "1741221003031"
}
}
}
],
"SyndicateMissions": [
{
"_id": { "$oid": "663a4fc5ba6f84724fa4804c" },
@ -309,7 +349,7 @@
],
"PrimeAccessAvailability": { "State": "PRIME1" },
"PrimeVaultAvailabilities": [false, false, false, false, false],
"PrimeTokenAvailability": true,
"PrimeTokenAvailability": false,
"LibraryInfo": { "LastCompletedTargetType": "/Lotus/Types/Game/Library/Targets/Research7Target" },
"PVPChallengeInstances": [
{

View File

@ -388,11 +388,11 @@
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_hoverboards"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireModularEquipment('Hoverboards');return false;">
<input class="form-control" id="acquire-type-Hoverboards-HB_DECK" list="datalist-ModularParts-HB_DECK" />
<input class="form-control" id="acquire-type-Hoverboards-HB_ENGINE" list="datalist-ModularParts-HB_ENGINE" />
<input class="form-control" id="acquire-type-Hoverboards-HB_FRONT" list="datalist-ModularParts-HB_FRONT" />
<input class="form-control" id="acquire-type-Hoverboards-HB_JET" list="datalist-ModularParts-HB_JET" />
<form class="input-group mb-3" onsubmit="doAcquireModularEquipment('HoverBoards');return false;">
<input class="form-control" id="acquire-type-HoverBoards-HB_DECK" list="datalist-ModularParts-HB_DECK" />
<input class="form-control" id="acquire-type-HoverBoards-HB_ENGINE" list="datalist-ModularParts-HB_ENGINE" />
<input class="form-control" id="acquire-type-HoverBoards-HB_FRONT" list="datalist-ModularParts-HB_FRONT" />
<input class="form-control" id="acquire-type-HoverBoards-HB_JET" list="datalist-ModularParts-HB_JET" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<table class="table table-hover w-100">
@ -436,37 +436,29 @@
<h5 class="card-header" data-loc="general_bulkActions"></h5>
<div class="card-body">
<div class="mb-2 d-flex flex-wrap gap-2">
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['Suits']);" data-loc="inventory_bulkAddSuits"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['Melee', 'LongGuns', 'Pistols']);" data-loc="inventory_bulkAddWeapons"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['SpaceSuits']);" data-loc="inventory_bulkAddSpaceSuits"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['SpaceGuns', 'SpaceMelee']);" data-loc="inventory_bulkAddSpaceWeapons"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['Sentinels']);" data-loc="inventory_bulkAddSentinels"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['SentinelWeapons']);" data-loc="inventory_bulkAddSentinelWeapons"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEvolutionProgress);" data-loc="inventory_bulkAddEvolutionProgress"></button>
<button class="btn btn-primary" onclick="addMissingEquipment(['Suits']);" data-loc="inventory_bulkAddSuits"></button>
<button class="btn btn-primary" onclick="addMissingEquipment(['Melee', 'LongGuns', 'Pistols']);" data-loc="inventory_bulkAddWeapons"></button>
<button class="btn btn-primary" onclick="addMissingEquipment(['SpaceSuits']);" data-loc="inventory_bulkAddSpaceSuits"></button>
<button class="btn btn-primary" onclick="addMissingEquipment(['SpaceGuns', 'SpaceMelee']);" data-loc="inventory_bulkAddSpaceWeapons"></button>
<button class="btn btn-primary" onclick="addMissingEquipment(['Sentinels']);" data-loc="inventory_bulkAddSentinels"></button>
<button class="btn btn-primary" onclick="addMissingEquipment(['SentinelWeapons']);" data-loc="inventory_bulkAddSentinelWeapons"></button>
<button class="btn btn-primary" onclick="addMissingEvolutionProgress();" data-loc="inventory_bulkAddEvolutionProgress"></button>
</div>
<div class="mb-2 d-flex flex-wrap gap-2">
<button class="btn btn-success" onclick="debounce(maxRankAllEquipment, ['Suits']);" data-loc="inventory_bulkRankUpSuits"></button>
<button class="btn btn-success" onclick="debounce(maxRankAllEquipment, ['Melee', 'LongGuns', 'Pistols']);" data-loc="inventory_bulkRankUpWeapons"></button>
<button class="btn btn-success" onclick="debounce(maxRankAllEquipment, ['SpaceSuits']);" data-loc="inventory_bulkRankUpSpaceSuits"></button>
<button class="btn btn-success" onclick="debounce(maxRankAllEquipment, ['SpaceGuns', 'SpaceMelee']);" data-loc="inventory_bulkRankUpSpaceWeapons"></button>
<button class="btn btn-success" onclick="debounce(maxRankAllEquipment, ['Sentinels']);" data-loc="inventory_bulkRankUpSentinels"></button>
<button class="btn btn-success" onclick="debounce(maxRankAllEquipment, ['SentinelWeapons']);" data-loc="inventory_bulkRankUpSentinelWeapons"></button>
<button class="btn btn-success" onclick="debounce(maxRankAllEvolutions);" data-loc="inventory_bulkRankUpEvolutionProgress"></button>
<button class="btn btn-success" onclick="maxRankAllEquipment(['Suits']);" data-loc="inventory_bulkRankUpSuits"></button>
<button class="btn btn-success" onclick="maxRankAllEquipment(['Melee', 'LongGuns', 'Pistols']);" data-loc="inventory_bulkRankUpWeapons"></button>
<button class="btn btn-success" onclick="maxRankAllEquipment(['SpaceSuits']);" data-loc="inventory_bulkRankUpSpaceSuits"></button>
<button class="btn btn-success" onclick="maxRankAllEquipment(['SpaceGuns', 'SpaceMelee']);" data-loc="inventory_bulkRankUpSpaceWeapons"></button>
<button class="btn btn-success" onclick="maxRankAllEquipment(['Sentinels']);" data-loc="inventory_bulkRankUpSentinels"></button>
<button class="btn btn-success" onclick="maxRankAllEquipment(['SentinelWeapons']);" data-loc="inventory_bulkRankUpSentinelWeapons"></button>
<button class="btn btn-success" onclick="maxRankAllEvolutions();" data-loc="inventory_bulkRankUpEvolutionProgress"></button>
</div>
</div>
</div>
</div>
<div id="detailedView-route" data-route="/webui/detailedView" data-title="Inventory | OpenWF WebUI">
<h3 id="detailedView-loading" class="mb-0" data-loc="general_loading"></h3>
<h3 id="detailedView-title" class="mb-0"></h3>
<h3 class="mb-0"></h3>
<p class="text-body-secondary"></p>
<div id="loadout-card" class="card mb-3 d-none">
<h5 class="card-header" data-loc="detailedView_loadoutLabel"></h5>
<div class="card-body">
<ul class="nav nav-tabs" id="loadoutTabs"></ul>
<div class="tab-content mt-3" id="loadoutTabsContent"></div>
</div>
</div>
<div id="archonShards-card" class="card mb-3 d-none">
<h5 class="card-header" data-loc="detailedView_archonShardsLabel"></h5>
<div class="card-body">
@ -485,62 +477,6 @@
</table>
</div>
</div>
<div id="edit-suit-invigorations-card" class="card mb-3 d-none">
<h5 class="card-header" data-loc="detailedView_suitInvigorationLabel"></h5>
<div class="card-body">
<form onsubmit="submitSuitInvigorationUpgrade(event)">
<div class="mb-3">
<label for="invigoration-offensive" class="form-label" data-loc="invigorations_offensiveLabel"></label>
<select class="form-select" id="dv-invigoration-offensive">
<option value="" data-loc="general_none"></option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationPowerStrength" data-loc="invigorations_offensive_AbilityStrength"></option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationPowerRange" data-loc="invigorations_offensive_AbilityRange"></option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationPowerDuration" data-loc="invigorations_offensive_AbilityDuration"></option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationMeleeDamage" data-loc="invigorations_offensive_MeleeDamage"></option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationPrimaryDamage" data-loc="invigorations_offensive_PrimaryDamage"></option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationSecondaryDamage" data-loc="invigorations_offensive_SecondaryDamage"></option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationPrimaryCritChance" data-loc="invigorations_offensive_PrimaryCritChance"></option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationSecondaryCritChance" data-loc="invigorations_offensive_SecondaryCritChance"></option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationMeleeCritChance" data-loc="invigorations_offensive_MeleeCritChance"></option>
</select>
</div>
<div class="mb-3">
<label for="invigoration-defensive" class="form-label" data-loc="invigorations_defensiveLabel"></label>
<select class="form-select" id="dv-invigoration-defensive">
<option value="" data-loc="general_none"></option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationPowerEfficiency" data-loc="invigorations_utility_AbilityEfficiency"></option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationMovementSpeed" data-loc="invigorations_utility_SprintSpeed"></option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationParkourSpeed" data-loc="invigorations_utility_ParkourVelocity"></option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationHealth" data-loc="invigorations_utility_HealthMax"></option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationEnergy" data-loc="invigorations_utility_EnergyMax"></option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationStatusResistance" data-loc="invigorations_utility_StatusImmune"></option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationReloadSpeed" data-loc="invigorations_utility_ReloadSpeed"></option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationHealthRegen" data-loc="invigorations_utility_HealthRegen"></option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationArmor" data-loc="invigorations_utility_ArmorMax"></option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationJumps" data-loc="invigorations_utility_Jumps"></option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationEnergyRegen" data-loc="invigorations_utility_EnergyRegen"></option>
</select>
</div>
<div class="mb-3">
<label for="invigoration-expiry" class="form-label" data-loc="invigorations_expiryLabel"></label>
<input type="datetime-local" class="form-control" id="dv-invigoration-expiry" />
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary" data-loc="general_setButton"></button>
<button type="button" class="btn btn-danger" onclick="clearSuitInvigorationUpgrades()" data-loc="code_remove"></button>
</div>
</form>
</div>
</div>
<div id="modularParts-card" class="card mb-3 d-none">
<h5 class="card-header" data-loc="detailedView_modularPartsLabel"></h5>
<div class="card-body">
<form id="modularParts-form" class="input-group mb-3" onsubmit="handleModularPartsChange(event)"></form>
</div>
</div>
<div id="valenceBonus-card" class="card mb-3 d-none">
<h5 class="card-header" data-loc="detailedView_valenceBonusLabel"></h5>
<div class="card-body">
@ -549,7 +485,7 @@
<select class="form-control" id="valenceBonus-innateDamage"></select>
<input type="number" id="valenceBonus-procent" min="25" max="60" step="0.1" class="form-control" style="max-width:100px" />
<button class="btn btn-primary" type="submit" value="set" data-loc="general_setButton"></button>
<button class="btn btn-danger" type="submit" value="remove" data-loc="code_remove"></button>
<button class="btn btn-danger" type="submit" value="remove" data-loc="general_removeButton"></button>
</form>
</div>
</div>
@ -591,8 +527,8 @@
<form class="input-group mb-3" onsubmit="doAcquireMod();return false;">
<input class="form-control" id="mod-count" type="number" value="1"/>
<input class="form-control w-50" id="mod-to-acquire" list="datalist-mods" />
<button class="btn btn-success" onclick="window.maxed=true" type="submit" data-loc="mods_addMax"></button>
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
<button class="btn btn-success" onclick="window.maxed=true" data-loc="mods_addMax"></button>
</form>
<table class="table table-hover w-100">
<tbody id="mods-list"></tbody>
@ -663,6 +599,26 @@
<input class="form-check-input" type="checkbox" id="unlockAllScans" />
<label class="form-check-label" for="unlockAllScans" data-loc="cheats_unlockAllScans"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="infiniteCredits" />
<label class="form-check-label" for="infiniteCredits" data-loc="cheats_infiniteCredits"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="infinitePlatinum" />
<label class="form-check-label" for="infinitePlatinum" data-loc="cheats_infinitePlatinum"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="infiniteEndo" />
<label class="form-check-label" for="infiniteEndo" data-loc="cheats_infiniteEndo"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="infiniteRegalAya" />
<label class="form-check-label" for="infiniteRegalAya" data-loc="cheats_infiniteRegalAya"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="infiniteHelminthMaterials" />
<label class="form-check-label" for="infiniteHelminthMaterials" data-loc="cheats_infiniteHelminthMaterials"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="claimingBlueprintRefundsIngredients" />
<label class="form-check-label" for="claimingBlueprintRefundsIngredients" data-loc="cheats_claimingBlueprintRefundsIngredients"></label>
@ -847,21 +803,21 @@
<label class="form-label" for="spoofMasteryRank" data-loc="cheats_spoofMasteryRank"></label>
<div class="input-group">
<input class="form-control" id="spoofMasteryRank" type="number" min="-1" max="65535" data-default="-1" />
<button class="btn btn-secondary" type="submit" data-loc="cheats_save"></button>
<button class="btn btn-primary" type="submit" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2" onsubmit="doSaveConfigInt('relicRewardItemCountMultiplier'); return false;">
<label class="form-label" for="relicRewardItemCountMultiplier" data-loc="cheats_relicRewardItemCountMultiplier"></label>
<div class="input-group">
<input class="form-control" id="relicRewardItemCountMultiplier" type="number" min="1" max="1000000" data-default="1" />
<button class="btn btn-secondary" type="submit" data-loc="cheats_save"></button>
<button class="btn btn-primary" type="submit" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2" onsubmit="doSaveConfigInt('nightwaveStandingMultiplier'); return false;">
<label class="form-label" for="nightwaveStandingMultiplier" data-loc="cheats_nightwaveStandingMultiplier"></label>
<div class="input-group">
<input class="form-control" id="nightwaveStandingMultiplier" type="number" min="1" max="1000000" data-default="1" />
<button class="btn btn-secondary" type="submit" data-loc="cheats_save"></button>
<button class="btn btn-primary" type="submit" data-loc="cheats_save"></button>
</div>
</form>
</div>
@ -871,30 +827,9 @@
<div class="col-md-6">
<div class="card mb-3">
<h5 class="card-header" data-loc="cheats_account"></h5>
<div class="card-body" id="account-cheats">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="infiniteCredits" />
<label class="form-check-label" for="infiniteCredits" data-loc="cheats_infiniteCredits"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="infinitePlatinum" />
<label class="form-check-label" for="infinitePlatinum" data-loc="cheats_infinitePlatinum"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="infiniteEndo" />
<label class="form-check-label" for="infiniteEndo" data-loc="cheats_infiniteEndo"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="infiniteRegalAya" />
<label class="form-check-label" for="infiniteRegalAya" data-loc="cheats_infiniteRegalAya"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="infiniteHelminthMaterials" />
<label class="form-check-label" for="infiniteHelminthMaterials" data-loc="cheats_infiniteHelminthMaterials"></label>
</div>
<div class="mt-2 mb-2 d-flex flex-wrap gap-2">
<div class="card-body">
<div class="mb-2 d-flex flex-wrap gap-2">
<button class="btn btn-primary" onclick="debounce(doUnlockAllMissions);" data-loc="cheats_unlockAllMissions"></button>
<button class="btn btn-primary" onclick="debounce(markAllAsRead);" data-loc="cheats_markAllAsRead"></button>
<button class="btn btn-primary" onclick="doUnlockAllFocusSchools();" data-loc="cheats_unlockAllFocusSchools"></button>
<button class="btn btn-primary" onclick="doHelminthUnlockAll();" data-loc="cheats_helminthUnlockAll"></button>
<button class="btn btn-primary" onclick="debounce(addMissingHelminthRecipes);" data-loc="cheats_addMissingSubsumedAbilities"></button>
@ -926,120 +861,22 @@
<label class="form-check-label" for="worldState.resourceBoost" data-loc="worldState_resourceBoost"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.tennoLiveRelay" />
<label class="form-check-label" for="worldState.tennoLiveRelay" data-loc="worldState_tennoLiveRelay"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.baroTennoConRelay" />
<label class="form-check-label" for="worldState.baroTennoConRelay" data-loc="worldState_baroTennoConRelay"></label>
<input class="form-check-input" type="checkbox" id="worldState.starDays" />
<label class="form-check-label" for="worldState.starDays" data-loc="worldState_starDays"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.varziaFullyStocked" />
<label class="form-check-label" for="worldState.varziaFullyStocked" data-loc="worldState_varziaFullyStocked"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.wolfHunt" />
<label class="form-check-label" for="worldState.wolfHunt" data-loc="worldState_wolfHunt"></label>
<abbr data-loc-inc="worldState_galleonOfGhouls"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM320 200C333.3 200 344 210.7 344 224L344 336C344 349.3 333.3 360 320 360C306.7 360 296 349.3 296 336L296 224C296 210.7 306.7 200 320 200zM293.3 416C292.7 406.1 297.6 396.7 306.1 391.5C314.6 386.4 325.3 386.4 333.8 391.5C342.3 396.7 347.2 406.1 346.6 416C347.2 425.9 342.3 435.3 333.8 440.5C325.3 445.6 314.6 445.6 306.1 440.5C297.6 435.3 292.7 425.9 293.3 416z"/></svg></abbr>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.longShadow" />
<label class="form-check-label" for="worldState.longShadow" data-loc="worldState_longShadow"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.hallowedFlame" />
<label class="form-check-label" for="worldState.hallowedFlame" data-loc="worldState_hallowedFlame"></label>
<abbr data-loc-inc="worldState_hallowedNightmares|worldState_dogDays"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM320 200C333.3 200 344 210.7 344 224L344 336C344 349.3 333.3 360 320 360C306.7 360 296 349.3 296 336L296 224C296 210.7 306.7 200 320 200zM293.3 416C292.7 406.1 297.6 396.7 306.1 391.5C314.6 386.4 325.3 386.4 333.8 391.5C342.3 396.7 347.2 406.1 346.6 416C347.2 425.9 342.3 435.3 333.8 440.5C325.3 445.6 314.6 445.6 306.1 440.5C297.6 435.3 292.7 425.9 293.3 416z"/></svg></abbr>
</div>
<div class="form-group mt-2 d-flex gap-2">
<div class="flex-fill">
<label class="form-label" for="worldState.hallowedNightmares" data-loc="worldState_hallowedNightmares"></label>
<abbr data-loc-inc="worldState_hallowedFlame|worldState_dogDays"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM320 200C333.3 200 344 210.7 344 224L344 336C344 349.3 333.3 360 320 360C306.7 360 296 349.3 296 336L296 224C296 210.7 306.7 200 320 200zM293.3 416C292.7 406.1 297.6 396.7 306.1 391.5C314.6 386.4 325.3 386.4 333.8 391.5C342.3 396.7 347.2 406.1 346.6 416C347.2 425.9 342.3 435.3 333.8 440.5C325.3 445.6 314.6 445.6 306.1 440.5C297.6 435.3 292.7 425.9 293.3 416z"/></svg></abbr>
<select class="form-control" id="worldState.hallowedNightmares" data-default="false">
<option value="true" data-loc="enabled"></option>
<option value="false" data-loc="disabled"></option>
</select>
</div>
<div class="flex-fill">
<label class="form-label" for="worldState.hallowedNightmaresRewardsOverride" data-loc="worldState_hallowedNightmaresRewards"></label>
<select class="form-control" id="worldState.hallowedNightmaresRewardsOverride" data-default="0">
<option value="0" data-loc="worldState_from_year" data-loc-year="2018"></option>
<option value="1" data-loc="worldState_from_year" data-loc-year="2016"></option>
<option value="2" data-loc="worldState_from_year" data-loc-year="2015"></option>
</select>
</div>
</div>
<div class="form-group mt-2 d-flex gap-2">
<div class="flex-fill">
<label class="form-label" for="worldState.proxyRebellion" data-loc="worldState_proxyRebellion"></label>
<select class="form-control" id="worldState.proxyRebellion" data-default="false">
<option value="true" data-loc="enabled"></option>
<option value="false" data-loc="disabled"></option>
</select>
</div>
<div class="flex-fill">
<label class="form-label" for="worldState.proxyRebellionRewardsOverride" data-loc="worldState_proxyRebellionRewards"></label>
<select class="form-control" id="worldState.proxyRebellionRewardsOverride" data-default="0">
<option value="0" data-loc="worldState_from_year" data-loc-year="2019"></option>
<option value="1" data-loc="worldState_from_year" data-loc-year="2018"></option>
</select>
</div>
</div>
<div class="form-group mt-2">
<label class="form-label" for="worldState.galleonOfGhouls" data-loc="worldState_galleonOfGhouls"></label>
<abbr data-loc-inc="worldState_wolfHunt"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM320 200C333.3 200 344 210.7 344 224L344 336C344 349.3 333.3 360 320 360C306.7 360 296 349.3 296 336L296 224C296 210.7 306.7 200 320 200zM293.3 416C292.7 406.1 297.6 396.7 306.1 391.5C314.6 386.4 325.3 386.4 333.8 391.5C342.3 396.7 347.2 406.1 346.6 416C347.2 425.9 342.3 435.3 333.8 440.5C325.3 445.6 314.6 445.6 306.1 440.5C297.6 435.3 292.7 425.9 293.3 416z"/></svg></abbr>
<select class="form-control" id="worldState.galleonOfGhouls" data-default="0">
<select class="form-control" id="worldState.galleonOfGhouls" data-default="">
<option value="0" data-loc="disabled"></option>
<option value="1" data-loc="worldState_we1"></option>
<option value="2" data-loc="worldState_we2"></option>
<option value="3" data-loc="worldState_we3"></option>
</select>
</div>
<div class="form-group mt-2">
<label class="form-label" for="worldState.ghoulEmergenceOverride" data-loc="worldState_ghoulEmergence"></label>
<select class="form-control" id="worldState.ghoulEmergenceOverride" data-default="null">
<option value="null" data-loc="normal"></option>
<option value="true" data-loc="enabled"></option>
<option value="false" data-loc="disabled"></option>
</select>
</div>
<div class="form-group mt-2">
<label class="form-label" for="worldState.plagueStarOverride" data-loc="worldState_plagueStar"></label>
<select class="form-control" id="worldState.plagueStarOverride" data-default="null">
<option value="null" data-loc="normal"></option>
<option value="true" data-loc="enabled"></option>
<option value="false" data-loc="disabled"></option>
</select>
</div>
<div class="form-group mt-2">
<label class="form-label" for="worldState.starDaysOverride" data-loc="worldState_starDays"></label>
<select class="form-control" id="worldState.starDaysOverride" data-default="null">
<option value="null" data-loc="normal"></option>
<option value="true" data-loc="enabled"></option>
<option value="false" data-loc="disabled"></option>
</select>
</div>
<div class="form-group mt-2 d-flex gap-2">
<div class="flex-fill">
<label class="form-label" for="worldState.dogDaysOverride" data-loc="worldState_dogDays"></label>
<abbr data-loc-inc="worldState_hallowedFlame|worldState_hallowedNightmares"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM320 200C333.3 200 344 210.7 344 224L344 336C344 349.3 333.3 360 320 360C306.7 360 296 349.3 296 336L296 224C296 210.7 306.7 200 320 200zM293.3 416C292.7 406.1 297.6 396.7 306.1 391.5C314.6 386.4 325.3 386.4 333.8 391.5C342.3 396.7 347.2 406.1 346.6 416C347.2 425.9 342.3 435.3 333.8 440.5C325.3 445.6 314.6 445.6 306.1 440.5C297.6 435.3 292.7 425.9 293.3 416z"/></svg></abbr>
<select class="form-control" id="worldState.dogDaysOverride" data-default="null">
<option value="null" data-loc="normal"></option>
<option value="true" data-loc="enabled"></option>
<option value="false" data-loc="disabled"></option>
</select>
</div>
<div class="flex-fill">
<label class="form-label" for="worldState.dogDaysRewardsOverride" data-loc="worldState_dogDaysRewards"></label>
<select class="form-control" id="worldState.dogDaysRewardsOverride" data-default="null">
<option value="null" data-loc="normal"></option>
<option value="3" data-loc="worldState_from_year" data-loc-year="2025"></option>
<option value="2" data-loc="worldState_from_year" data-loc-year="2024"></option>
<option value="1" data-loc="worldState_from_year" data-loc-year="2023"></option>
<option value="0" data-loc="worldState_pre_year" data-loc-year="2023"></option>
</select>
</div>
</div>
<div class="form-group mt-2">
<label class="form-label" for="worldState.eidolonOverride" data-loc="worldState_eidolonOverride"></label>
<select class="form-control" id="worldState.eidolonOverride" data-default="">
@ -1105,14 +942,14 @@
<label class="form-label" for="worldState.circuitGameModes" data-loc="worldState_theCircuitOverride"></label>
<div class="input-group">
<input id="worldState.circuitGameModes" type="text" class="form-control tags-input" list="datalist-circuitGameModes" />
<button class="btn btn-secondary" type="submit" data-loc="cheats_save"></button>
<button class="btn btn-primary" type="submit" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2" onsubmit="doSaveConfigFloat('worldState.darvoStockMultiplier'); return false;">
<label class="form-label" for="worldState.darvoStockMultiplier" data-loc="worldState_darvoStockMultiplier"></label>
<div class="input-group">
<input id="worldState.darvoStockMultiplier" class="form-control" type="number" step="0.01" data-default="1" />
<button class="btn btn-secondary" type="submit" data-loc="cheats_save"></button>
<button class="btn btn-primary" type="submit" data-loc="cheats_save"></button>
</div>
</form>
</div>
@ -1178,7 +1015,6 @@
<datalist id="datalist-ModularParts-KUBROW_ANTIGEN"></datalist>
<datalist id="datalist-ModularParts-KUBROW_MUTAGEN"></datalist>
<datalist id="datalist-Boosters"></datalist>
<datalist id="datalist-Abilities"></datalist>
<datalist id="datalist-circuitGameModes">
<option>Survival</option>
<option>VoidFlood</option>

View File

@ -18,7 +18,7 @@ const sendAuth = isRegister => {
window.ws.send(
JSON.stringify({
auth: {
email: localStorage.getItem("email").toLowerCase(),
email: localStorage.getItem("email"),
password: wp.encSync(localStorage.getItem("password")),
isRegister
}
@ -28,8 +28,7 @@ const sendAuth = isRegister => {
};
function openWebSocket() {
const wsProto = location.protocol === "https:" ? "wss://" : "ws://";
window.ws = new WebSocket(wsProto + location.host + "/custom/ws");
window.ws = new WebSocket("/custom/ws");
window.ws.onopen = () => {
ws_is_open = true;
sendAuth(false);
@ -119,16 +118,9 @@ function doLogin() {
window.registerSubmit = false;
}
function revalidateAuthz() {
return new Promise(resolve => {
let interval;
interval = setInterval(() => {
if (ws_is_open && !auth_pending) {
clearInterval(interval);
resolve();
}
}, 10);
});
async function revalidateAuthz() {
await getWebSocket();
// We have a websocket connection, so authz should be good.
}
function logout() {
@ -202,17 +194,6 @@ function updateLocElements() {
document.querySelectorAll("[data-loc-placeholder]").forEach(elm => {
elm.placeholder = loc(elm.getAttribute("data-loc-placeholder"));
});
document.querySelectorAll("[data-loc-inc]").forEach(elm => {
const incWith = elm
.getAttribute("data-loc-inc")
.split("|")
.map(key => loc(key))
.join(", ");
elm.title = `${loc("worldState_incompatibleWith")} ${incWith}`;
});
document.querySelectorAll("[data-loc-year]").forEach(elm => {
elm.innerHTML = elm.innerHTML.replace("|YEAR|", elm.getAttribute("data-loc-year"));
});
}
function setActiveLanguage(lang) {
@ -223,7 +204,7 @@ function setActiveLanguage(lang) {
document.querySelector("[data-lang=" + lang + "]").classList.add("active");
window.dictPromise = new Promise(resolve => {
const webui_lang = ["en", "ru", "fr", "de", "zh", "es", "uk"].indexOf(lang) == -1 ? "en" : lang;
const webui_lang = ["en", "ru", "fr", "de", "zh", "es"].indexOf(lang) == -1 ? "en" : lang;
let script = document.getElementById("translations");
if (script) document.documentElement.removeChild(script);
@ -292,8 +273,6 @@ function fetchItemList() {
window.itemListPromise = new Promise(resolve => {
const req = $.get("/custom/getItemLists?lang=" + window.lang);
req.done(async data => {
window.allQuestKeys = data.QuestKeys;
await dictPromise;
document.querySelectorAll('[id^="datalist-"]').forEach(datalist => {
@ -301,8 +280,7 @@ function fetchItemList() {
});
const syndicateNone = document.createElement("option");
syndicateNone.value = "";
syndicateNone.textContent = loc("general_none");
syndicateNone.textContent = loc("cheats_none");
document.getElementById("changeSyndicate").innerHTML = "";
document.getElementById("changeSyndicate").appendChild(syndicateNone);
@ -323,8 +301,8 @@ function fetchItemList() {
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityDurationMythic": loc("upgrade_WarframeAbilityDuration").split("|VAL|").join("15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityStrength": loc("upgrade_WarframeAbilityStrength").split("|VAL|").join("10"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityStrengthMythic": loc("upgrade_WarframeAbilityStrength").split("|VAL|").join("15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeArmourMax": loc("upgrade_WarframeArmorMax").split("|VAL|").join("150"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeArmourMaxMythic": loc("upgrade_WarframeArmorMax").split("|VAL|").join("225"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeArmourMax": loc("upgrade_WarframeArmourMax").split("|VAL|").join("150"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeArmourMaxMythic": loc("upgrade_WarframeArmourMax").split("|VAL|").join("225"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeBlastProc": loc("upgrade_WarframeBlastProc").split("|VAL|").join("5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeBlastProcMythic": loc("upgrade_WarframeBlastProc").split("|VAL|").join("7.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCastingSpeed": loc("upgrade_WarframeCastingSpeed").split("|VAL|").join("25"),
@ -353,8 +331,8 @@ function fetchItemList() {
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeParkourVelocityMythic": loc("upgrade_WarframeParkourVelocity").split("|VAL|").join("22.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRadiationDamageBoost": loc("upgrade_WarframeRadiationDamageBoost").split("|VAL|").join("10"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRadiationDamageBoostMythic": loc("upgrade_WarframeRadiationDamageBoost").split("|VAL|").join("15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRegen": loc("upgrade_WarframeHealthRegen").split("|VAL|").join("5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRegenMythic": loc("upgrade_WarframeHealthRegen").split("|VAL|").join("7.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRegen": loc("upgrade_WarframeRegen").split("|VAL|").join("5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRegenMythic": loc("upgrade_WarframeRegen").split("|VAL|").join("7.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeShieldMax": loc("upgrade_WarframeShieldMax").split("|VAL|").join("150"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeShieldMaxMythic": loc("upgrade_WarframeShieldMax").split("|VAL|").join("225"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeStartingEnergy": loc("upgrade_WarframeStartingEnergy").split("|VAL|").join("30"),
@ -508,9 +486,6 @@ function fetchItemList() {
uniqueLevelCaps = items;
} else if (type == "Syndicates") {
items.forEach(item => {
if (item.uniqueName === "ConclaveSyndicate") {
return;
}
if (item.uniqueName.startsWith("RadioLegion")) {
item.name += " (" + item.uniqueName + ")";
}
@ -605,8 +580,6 @@ function fetchItemList() {
}
fetchItemList();
const accountCheats = document.querySelectorAll("#account-cheats input[id]");
// Assumes that caller revalidates authz
function updateInventory() {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
@ -755,10 +728,7 @@ function updateInventory() {
td.appendChild(a);
}
if (
["Suits", "LongGuns", "Pistols", "Melee", "SpaceGuns", "SpaceMelee"].includes(category) ||
modularWeapons.includes(item.ItemType)
) {
if (["Suits", "LongGuns", "Pistols", "Melee", "SpaceGuns", "SpaceMelee"].includes(category)) {
const a = document.createElement("a");
a.href = "/webui/detailedView?productCategory=" + category + "&itemId=" + item.ItemId.$oid;
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M278.5 215.6L23 471c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l57-57h68c49.7 0 97.9-14.4 139-41c11.1-7.2 5.5-23-7.8-23c-5.1 0-9.2-4.1-9.2-9.2c0-4.1 2.7-7.6 6.5-8.8l81-24.3c2.5-.8 4.8-2.1 6.7-4l22.4-22.4c10.1-10.1 2.9-27.3-11.3-27.3l-32.2 0c-5.1 0-9.2-4.1-9.2-9.2c0-4.1 2.7-7.6 6.5-8.8l112-33.6c4-1.2 7.4-3.9 9.3-7.7C506.4 207.6 512 184.1 512 160c0-41-16.3-80.3-45.3-109.3l-5.5-5.5C432.3 16.3 393 0 352 0s-80.3 16.3-109.3 45.3L139 149C91 197 64 262.1 64 330v55.3L253.6 195.8c6.2-6.2 16.4-6.2 22.6 0c5.4 5.4 6.1 13.6 2.2 19.8z"/></svg>`;
@ -889,11 +859,15 @@ function updateInventory() {
const datalistEvolutionProgress = document.querySelectorAll("#datalist-EvolutionProgress option");
const formEvolutionProgress = document.querySelector('form[onsubmit*="doAcquireEvolution()"]');
const giveAllQEvolutionProgress = document.querySelector(
'button[onclick*="addMissingEvolutionProgress()"]'
);
if (datalistEvolutionProgress.length === 0) {
formEvolutionProgress.classList.add("disabled");
formEvolutionProgress.querySelector("input").disabled = true;
formEvolutionProgress.querySelector("button").disabled = true;
giveAllQEvolutionProgress.disabled = true;
}
if (data.CrewShipHarnesses?.length) {
@ -905,14 +879,6 @@ function updateInventory() {
// Populate quests route
document.getElementById("QuestKeys-list").innerHTML = "";
window.allQuestKeys.forEach(questKey => {
if (!data.QuestKeys.some(x => x.ItemType == questKey.uniqueName)) {
const datalist = document.getElementById("datalist-QuestKeys");
if (!datalist.querySelector(`option[data-key="${questKey.uniqueName}"]`)) {
readdQuestKey(itemMap, questKey.uniqueName);
}
}
});
data.QuestKeys.forEach(item => {
const tr = document.createElement("tr");
tr.setAttribute("data-item-type", item.ItemType);
@ -1006,7 +972,10 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
readdQuestKey(itemMap, item.ItemType);
const option = document.createElement("option");
option.setAttribute("data-key", item.ItemType);
option.value = itemMap[item.ItemType]?.name ?? item.ItemType;
document.getElementById("datalist-QuestKeys").appendChild(option);
doQuestUpdate("deleteKey", item.ItemType);
};
a.title = loc("code_remove");
@ -1041,19 +1010,13 @@ function updateInventory() {
if (item.ItemType.substr(0, 32) == "/Lotus/Upgrades/Mods/Randomized/") {
const rivenType = item.ItemType.substr(32);
const fingerprint = JSON.parse(item.UpgradeFingerprint);
if ("buffs" in fingerprint) {
if (fingerprint.buffs) {
// Riven has been revealed?
const tr = document.createElement("tr");
{
const td = document.createElement("td");
td.textContent = itemMap[fingerprint.compat]?.name ?? fingerprint.compat;
td.textContent += " ";
try {
td.textContent += RivenParser.parseRiven(rivenType, fingerprint, 1).name;
} catch (e) {
console.warn("malformed riven", { rivenType, fingerprint });
td.textContent += " [Malformed Riven]";
}
td.textContent += " " + RivenParser.parseRiven(rivenType, fingerprint, 1).name;
td.innerHTML +=
" <span title='" +
loc("code_buffsNumber") +
@ -1203,15 +1166,14 @@ function updateInventory() {
const item = data[category].find(x => x.ItemId.$oid == oid);
if (item) {
document.getElementById("detailedView-loading").classList.add("d-none");
if (item.ItemName) {
$("#detailedView-title").text(item.ItemName);
$("#detailedView-route h3").text(item.ItemName);
$("#detailedView-route .text-body-secondary").text(
itemMap[item.ItemType]?.name ?? item.ItemType
);
} else {
$("#detailedView-title").text(itemMap[item.ItemType]?.name ?? item.ItemType);
$("#detailedView-route h3").text(itemMap[item.ItemType]?.name ?? item.ItemType);
$("#detailedView-route .text-body-secondary").text("");
}
if (category == "Suits") {
@ -1251,127 +1213,6 @@ function updateInventory() {
}
document.getElementById("crystals-list").appendChild(tr);
});
document.getElementById("edit-suit-invigorations-card").classList.remove("d-none");
const { OffensiveUpgrade, DefensiveUpgrade, UpgradesExpiry } =
suitInvigorationUpgradeData(item);
document.getElementById("dv-invigoration-offensive").value = OffensiveUpgrade;
document.getElementById("dv-invigoration-defensive").value = DefensiveUpgrade;
document.getElementById("dv-invigoration-expiry").value = UpgradesExpiry;
{
document.getElementById("loadout-card").classList.remove("d-none");
const maxModConfigNum = Math.min(2 + (item.ModSlotPurchases ?? 0), 5);
const configs = item.Configs ?? [];
const loadoutTabs = document.getElementById("loadoutTabs");
const loadoutTabsContent = document.getElementById("loadoutTabsContent");
loadoutTabs.innerHTML = "";
loadoutTabsContent.innerHTML = "";
for (let i = 0; i <= maxModConfigNum; i++) {
const config = configs[i] ?? {};
{
const li = document.createElement("li");
li.classList.add("nav-item");
const button = document.createElement("button");
button.classList.add("nav-link");
if (i === 0) button.classList.add("active");
button.id = `config${i}-tab`;
button.setAttribute("data-bs-toggle", "tab");
button.setAttribute("data-bs-target", `#config${i}`);
button.innerHTML = config.Name?.trim() || String.fromCharCode(65 + i);
li.appendChild(button);
loadoutTabs.appendChild(li);
}
{
const tabDiv = document.createElement("div");
tabDiv.classList = "tab-pane";
if (i === 0) tabDiv.classList.add("show", "active");
tabDiv.id = `config${i}`;
{
const abilityOverrideForm = document.createElement("form");
abilityOverrideForm.classList = "form-group mt-2";
abilityOverrideForm.setAttribute(
"onsubmit",
`handleAbilityOverride(event, ${i});return false;`
);
const abilityOverrideFormLabel = document.createElement("label");
abilityOverrideFormLabel.setAttribute("data-loc", "abilityOverride_label");
abilityOverrideFormLabel.innerHTML = loc("abilityOverride_label");
abilityOverrideFormLabel.classList = "form-label";
abilityOverrideFormLabel.setAttribute(
"for",
`abilityOverride-ability-config-${i}`
);
abilityOverrideForm.appendChild(abilityOverrideFormLabel);
const abilityOverrideInputGroup = document.createElement("div");
abilityOverrideInputGroup.classList = "input-group";
abilityOverrideForm.appendChild(abilityOverrideInputGroup);
const abilityOverrideInput = document.createElement("input");
abilityOverrideInput.id = `abilityOverride-ability-config-${i}`;
abilityOverrideInput.classList = "form-control";
abilityOverrideInput.setAttribute("list", "datalist-Abilities");
if (config.AbilityOverride) {
const datalist = document.getElementById("datalist-Abilities");
const options = Array.from(datalist.options);
abilityOverrideInput.value = options.find(
option =>
config.AbilityOverride.Ability == option.getAttribute("data-key")
).value;
}
abilityOverrideInputGroup.appendChild(abilityOverrideInput);
const abilityOverrideOnSlot = document.createElement("span");
abilityOverrideOnSlot.classList = "input-group-text";
abilityOverrideOnSlot.setAttribute("data-loc", "abilityOverride_onSlot");
abilityOverrideOnSlot.innerHTML = loc("abilityOverride_onSlot");
abilityOverrideInputGroup.appendChild(abilityOverrideOnSlot);
const abilityOverrideSecondInput = document.createElement("input");
abilityOverrideSecondInput.id = `abilityOverride-ability-index-config-${i}`;
abilityOverrideSecondInput.classList = "form-control";
abilityOverrideSecondInput.setAttribute("type", "number");
abilityOverrideSecondInput.setAttribute("min", "0");
abilityOverrideSecondInput.setAttribute("max", "3");
if (config.AbilityOverride)
abilityOverrideSecondInput.value = config.AbilityOverride.Index;
abilityOverrideInputGroup.appendChild(abilityOverrideSecondInput);
const abilityOverrideSetButton = document.createElement("button");
abilityOverrideSetButton.classList = "btn btn-primary";
abilityOverrideSetButton.setAttribute("type", "submit");
abilityOverrideSetButton.setAttribute("value", "set");
abilityOverrideSetButton.setAttribute("data-loc", "general_setButton");
abilityOverrideSetButton.innerHTML = loc("general_setButton");
abilityOverrideInputGroup.appendChild(abilityOverrideSetButton);
const abilityOverrideRemoveButton = document.createElement("button");
abilityOverrideRemoveButton.classList = "btn btn-danger";
abilityOverrideRemoveButton.setAttribute("type", "submit");
abilityOverrideRemoveButton.setAttribute("value", "remove");
abilityOverrideRemoveButton.setAttribute("data-loc", "code_remove");
abilityOverrideRemoveButton.innerHTML = loc("code_remove");
abilityOverrideInputGroup.appendChild(abilityOverrideRemoveButton);
abilityOverrideForm.appendChild(abilityOverrideInputGroup);
tabDiv.appendChild(abilityOverrideForm);
}
loadoutTabsContent.appendChild(tabDiv);
}
}
}
} else if (["LongGuns", "Pistols", "Melee", "SpaceGuns", "SpaceMelee"].includes(category)) {
document.getElementById("valenceBonus-card").classList.remove("d-none");
document.getElementById("valenceBonus-innateDamage").value = "";
@ -1384,35 +1225,6 @@ function updateInventory() {
document.getElementById("valenceBonus-procent").value = Math.round(buffValue * 1000) / 10;
}
}
if (modularWeapons.includes(item.ItemType)) {
document.getElementById("modularParts-card").classList.remove("d-none");
const form = document.getElementById("modularParts-form");
form.innerHTML = "";
const requiredParts = getRequiredParts(category, item.ItemType);
requiredParts.forEach(modularPart => {
const input = document.createElement("input");
input.classList.add("form-control");
input.id = "detailedView-modularPart-" + modularPart;
input.setAttribute("list", "datalist-ModularParts-" + modularPart);
const datalist = document.getElementById("datalist-ModularParts-" + modularPart);
const options = Array.from(datalist.options);
input.value =
options.find(option => item.ModularParts.includes(option.getAttribute("data-key")))
?.value || "";
form.appendChild(input);
});
const changeButton = document.createElement("button");
changeButton.classList.add("btn");
changeButton.classList.add("btn-primary");
changeButton.type = "submit";
changeButton.setAttribute("data-loc", "cheats_changeButton");
changeButton.innerHTML = loc("cheats_changeButton");
form.appendChild(changeButton);
}
} else {
single.loadRoute("/webui/inventory");
}
@ -1475,10 +1287,6 @@ function updateInventory() {
}
document.getElementById("Boosters-list").appendChild(tr);
});
for (const elm of accountCheats) {
elm.checked = !!data[elm.id];
}
});
});
}
@ -1516,41 +1324,47 @@ function doAcquireEquipment(category) {
});
}
function getRequiredParts(category, WeaponType) {
switch (category) {
case "Hoverboards":
return ["HB_DECK", "HB_ENGINE", "HB_FRONT", "HB_JET"];
case "OperatorAmps":
return ["AMP_OCULUS", "AMP_CORE", "AMP_BRACE"];
case "Melee":
return ["BLADE", "HILT", "HILT_WEIGHT"];
case "LongGuns":
return ["GUN_BARREL", "GUN_PRIMARY_HANDLE", "GUN_CLIP"];
case "Pistols":
return ["GUN_BARREL", "GUN_SECONDARY_HANDLE", "GUN_CLIP"];
case "MoaPets":
return WeaponType === "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit"
? ["MOA_ENGINE", "MOA_PAYLOAD", "MOA_HEAD", "MOA_LEG"]
: ["ZANUKA_BODY", "ZANUKA_HEAD", "ZANUKA_LEG", "ZANUKA_TAIL"];
case "KubrowPets": {
return WeaponType.endsWith("InfestedCatbrowPetPowerSuit")
? ["CATBROW_ANTIGEN", "CATBROW_MUTAGEN"]
: ["KUBROW_ANTIGEN", "KUBROW_MUTAGEN"];
}
}
}
function doAcquireModularEquipment(category, WeaponType) {
if (category === "Hoverboards") WeaponType = "/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit";
const requiredParts = getRequiredParts(category, WeaponType);
let requiredParts;
let Parts = [];
switch (category) {
case "HoverBoards":
WeaponType = "/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit";
requiredParts = ["HB_DECK", "HB_ENGINE", "HB_FRONT", "HB_JET"];
break;
case "OperatorAmps":
requiredParts = ["AMP_OCULUS", "AMP_CORE", "AMP_BRACE"];
break;
case "Melee":
requiredParts = ["BLADE", "HILT", "HILT_WEIGHT"];
break;
case "LongGuns":
requiredParts = ["GUN_BARREL", "GUN_PRIMARY_HANDLE", "GUN_CLIP"];
break;
case "Pistols":
requiredParts = ["GUN_BARREL", "GUN_SECONDARY_HANDLE", "GUN_CLIP"];
break;
case "MoaPets":
if (WeaponType == "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit") {
requiredParts = ["MOA_ENGINE", "MOA_PAYLOAD", "MOA_HEAD", "MOA_LEG"];
} else {
requiredParts = ["ZANUKA_BODY", "ZANUKA_HEAD", "ZANUKA_LEG", "ZANUKA_TAIL"];
}
break;
case "KubrowPets":
if (
[
"/Lotus/Types/Friendly/Pets/CreaturePets/VulpineInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/HornedInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/ArmoredInfestedCatbrowPetPowerSuit"
].includes(WeaponType)
) {
requiredParts = ["CATBROW_ANTIGEN", "CATBROW_MUTAGEN"];
} else {
requiredParts = ["KUBROW_ANTIGEN", "KUBROW_MUTAGEN"];
}
break;
}
requiredParts.forEach(part => {
const partName = getKey(document.getElementById("acquire-type-" + category + "-" + part));
if (partName) {
@ -1667,22 +1481,19 @@ function doAcquireEvolution() {
setEvolutionProgress([{ ItemType: uniqueName, Rank: permanentEvolutionWeapons.has(uniqueName) ? 0 : 1 }]);
}
$(document).on("input", "input[list]", function () {
$("input[list]").on("input", function () {
$(this).removeClass("is-invalid");
});
function dispatchAddItemsRequestsBatch(requests) {
return new Promise(resolve => {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/addItems?" + window.authz,
contentType: "application/json",
data: JSON.stringify(requests)
});
req.done(() => {
updateInventory();
resolve();
});
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/addItems?" + window.authz,
contentType: "application/json",
data: JSON.stringify(requests)
});
req.done(() => {
updateInventory();
});
});
}
@ -1703,7 +1514,7 @@ function addMissingEquipment(categories) {
});
});
if (requests.length != 0 && window.confirm(loc("code_addItemsConfirm").split("|COUNT|").join(requests.length))) {
return dispatchAddItemsRequestsBatch(requests);
dispatchAddItemsRequestsBatch(requests);
}
}
@ -1719,7 +1530,7 @@ function addMissingEvolutionProgress() {
requests.push({ ItemType: uniqueName, Rank: permanentEvolutionWeapons.has(uniqueName) ? 0 : 1 });
});
if (requests.length != 0 && window.confirm(loc("code_addItemsConfirm").split("|COUNT|").join(requests.length))) {
return setEvolutionProgress(requests);
setEvolutionProgress(requests);
}
}
@ -1887,7 +1698,7 @@ function disposeOfGear(category, oid) {
];
revalidateAuthz().then(() => {
$.post({
url: "/api/sell.php?" + window.authz + "&wsid=" + wsid,
url: "/api/sell.php?" + window.authz,
contentType: "text/plain",
data: JSON.stringify(data)
});
@ -1909,7 +1720,7 @@ function disposeOfItems(category, type, count) {
];
revalidateAuthz().then(() => {
$.post({
url: "/api/sell.php?" + window.authz + "&wsid=" + wsid,
url: "/api/sell.php?" + window.authz,
contentType: "text/plain",
data: JSON.stringify(data)
});
@ -1946,17 +1757,14 @@ function maturePet(oid, revert) {
}
function setEvolutionProgress(requests) {
return new Promise(resolve => {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/setEvolutionProgress?" + window.authz,
contentType: "application/json",
data: JSON.stringify(requests)
});
req.done(() => {
updateInventory();
resolve();
});
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/setEvolutionProgress?" + window.authz,
contentType: "application/json",
data: JSON.stringify(requests)
});
req.done(() => {
updateInventory();
});
});
}
@ -2115,8 +1923,6 @@ function doAcquireModMax() {
alert("doAcquireModMax: " + uniqueName);
}
// Cheats route
const uiConfigs = [...$(".config-form input[id], .config-form select[id]")].map(x => x.id);
for (const id of uiConfigs) {
@ -2124,13 +1930,7 @@ for (const id of uiConfigs) {
if (elm.tagName == "SELECT") {
elm.onchange = function () {
let value = this.value;
if (value == "true") {
value = true;
} else if (value == "false") {
value = false;
} else if (value == "null") {
value = null;
} else if (!isNaN(parseInt(value))) {
if (!isNaN(parseInt(value))) {
value = parseInt(value);
}
$.post({
@ -2154,19 +1954,6 @@ for (const id of uiConfigs) {
}
}
document.querySelectorAll(".config-form .input-group").forEach(grp => {
const input = grp.querySelector("input");
const btn = grp.querySelector("button");
input.oninput = input.onchange = function () {
btn.classList.remove("btn-secondary");
btn.classList.add("btn-primary");
};
btn.onclick = function () {
btn.classList.remove("btn-primary");
btn.classList.add("btn-secondary");
};
});
function doSaveConfigInt(id) {
$.post({
url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid,
@ -2201,6 +1988,8 @@ function doSaveConfigStringArray(id) {
});
}
// Cheats route
single.getRoute("/webui/cheats").on("beforeload", function () {
let interval;
interval = setInterval(() => {
@ -2221,7 +2010,7 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
if (elm.type == "checkbox") {
elm.checked = value;
} else if (elm.classList.contains("tags-input")) {
elm.value = (value ?? []).join(", ");
elm.value = value.join(", ");
elm.oninput();
} else {
elm.value = value ?? elm.getAttribute("data-default");
@ -2230,10 +2019,6 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
})
.fail(res => {
if (res.responseText == "Log-in expired") {
if (ws_is_open && !auth_pending) {
console.warn("Credentials invalidated but the server didn't let us know");
sendAuth();
}
revalidateAuthz().then(() => {
if (single.getCurrentPath() == "/webui/cheats") {
single.loadRoute("/webui/cheats");
@ -2312,23 +2097,6 @@ function doIntrinsicsUnlockAll() {
});
}
document.querySelectorAll("#account-cheats input[type=checkbox]").forEach(elm => {
elm.onchange = function () {
revalidateAuthz().then(() => {
$.post({
url: "/custom/setAccountCheat?" + window.authz /*+ "&wsid=" + wsid*/,
contentType: "application/json",
data: JSON.stringify({
key: elm.id,
value: elm.checked
})
});
});
};
});
// Mods route
function doAddAllMods() {
let modsAll = new Set();
for (const child of document.getElementById("datalist-mods").children) {
@ -2373,7 +2141,7 @@ function doRemoveUnrankedMods() {
req.done(inventory => {
window.itemListPromise.then(itemMap => {
$.post({
url: "/api/sell.php?" + window.authz + "&wsid=" + wsid,
url: "/api/sell.php?" + window.authz,
contentType: "text/plain",
data: JSON.stringify({
SellCurrency: "SC_RegularCredits",
@ -2403,14 +2171,8 @@ function doAddMissingMaxRankMods() {
// DetailedView Route
single.getRoute("#detailedView-route").on("beforeload", function () {
document.getElementById("detailedView-loading").classList.remove("d-none");
document.getElementById("detailedView-title").textContent = "";
document.querySelector("#detailedView-route .text-body-secondary").textContent = "";
document.getElementById("loadout-card").classList.add("d-none");
this.element.querySelector("h3").textContent = "Loading...";
document.getElementById("archonShards-card").classList.add("d-none");
document.getElementById("edit-suit-invigorations-card").classList.add("d-none");
document.getElementById("modularParts-card").classList.add("d-none");
document.getElementById("modularParts-form").innerHTML = "";
document.getElementById("valenceBonus-card").classList.add("d-none");
if (window.didInitialInventoryUpdate) {
updateInventory();
@ -2492,13 +2254,6 @@ function doAddCurrency(currency) {
});
}
function readdQuestKey(itemMap, itemType) {
const option = document.createElement("option");
option.setAttribute("data-key", itemType);
option.value = itemMap[itemType]?.name ?? itemType;
document.getElementById("datalist-QuestKeys").appendChild(option);
}
function doQuestUpdate(operation, itemType) {
revalidateAuthz().then(() => {
$.post({
@ -2568,10 +2323,22 @@ function handleModularSelection(category) {
modularFieldsZanuka.style.display = "none";
}
} else if (inventoryCategory === "KubrowPets") {
if (key.endsWith("InfestedCatbrowPetPowerSuit")) {
if (
[
"/Lotus/Types/Friendly/Pets/CreaturePets/VulpineInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/HornedInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/ArmoredInfestedCatbrowPetPowerSuit"
].includes(key)
) {
modularFieldsCatbrow.style.display = "";
modularFieldsKubrow.style.display = "none";
} else if (key.endsWith("PredatorKubrowPetPowerSuit")) {
} else if (
[
"/Lotus/Types/Friendly/Pets/CreaturePets/VizierPredatorKubrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/PharaohPredatorKubrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/MedjayPredatorKubrowPetPowerSuit"
].includes(key)
) {
modularFieldsCatbrow.style.display = "none";
modularFieldsKubrow.style.display = "";
} else {
@ -2672,17 +2439,9 @@ function formatDatetime(fmt, date) {
const calls_in_flight = new Set();
async function debounce(func, ...args) {
if (!func.name) {
throw new Error(`cannot debounce anonymous functions`);
}
const callid = JSON.stringify({ func: func.name, args });
if (!calls_in_flight.has(callid)) {
calls_in_flight.add(callid);
await func(...args);
calls_in_flight.delete(callid);
} else {
console.log("debouncing", callid);
}
calls_in_flight.add(func);
await func(...args);
calls_in_flight.delete(func);
}
async function doMaxPlexus() {
@ -3005,146 +2764,3 @@ document.querySelectorAll("#sidebar .nav-link").forEach(function (elm) {
window.scrollTo(0, 0);
});
});
async function markAllAsRead() {
await revalidateAuthz();
const { Inbox } = await fetch("/api/inbox.php?" + window.authz).then(x => x.json());
let any = false;
for (const msg of Inbox) {
if (!msg.r) {
await fetch("/api/inbox.php?" + window.authz + "&messageId=" + msg.messageId.$oid);
any = true;
}
}
toast(loc(any ? "code_succRelog" : "code_nothingToDo"));
}
function handleModularPartsChange(event) {
event.preventDefault();
const urlParams = new URLSearchParams(window.location.search);
const form = document.getElementById("modularParts-form");
const inputs = form.querySelectorAll("input");
const modularParts = [];
inputs.forEach(input => {
const key = getKey(input);
if (!key) {
input.classList.add("is-invalid");
} else {
modularParts.push(key);
}
});
if (inputs.length == modularParts.length) {
revalidateAuthz().then(() => {
$.post({
url: "/custom/changeModularParts?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
category: urlParams.get("productCategory"),
oid: urlParams.get("itemId"),
modularParts
})
}).then(function () {
toast(loc("code_succChange"));
updateInventory();
});
});
}
}
function suitInvigorationUpgradeData(suitData) {
let expiryDate = "";
if (suitData.UpgradesExpiry) {
if (suitData.UpgradesExpiry.$date) {
expiryDate = new Date(parseInt(suitData.UpgradesExpiry.$date.$numberLong));
} else if (typeof suitData.UpgradesExpiry === "number") {
expiryDate = new Date(suitData.UpgradesExpiry);
} else if (suitData.UpgradesExpiry instanceof Date) {
expiryDate = suitData.UpgradesExpiry;
}
if (expiryDate && !isNaN(expiryDate.getTime())) {
const year = expiryDate.getFullYear();
const month = String(expiryDate.getMonth() + 1).padStart(2, "0");
const day = String(expiryDate.getDate()).padStart(2, "0");
const hours = String(expiryDate.getHours()).padStart(2, "0");
const minutes = String(expiryDate.getMinutes()).padStart(2, "0");
expiryDate = `${year}-${month}-${day}T${hours}:${minutes}`;
} else {
expiryDate = "";
}
}
return {
oid: suitData.ItemId.$oid,
OffensiveUpgrade: suitData.OffensiveUpgrade || "",
DefensiveUpgrade: suitData.DefensiveUpgrade || "",
UpgradesExpiry: expiryDate
};
}
function submitSuitInvigorationUpgrade(event) {
event.preventDefault();
const oid = new URLSearchParams(window.location.search).get("itemId");
const offensiveUpgrade = document.getElementById("dv-invigoration-offensive").value;
const defensiveUpgrade = document.getElementById("dv-invigoration-defensive").value;
const expiry = document.getElementById("dv-invigoration-expiry").value;
if (!offensiveUpgrade || !defensiveUpgrade) {
alert(loc("code_requiredInvigorationUpgrade"));
return;
}
const data = {
OffensiveUpgrade: offensiveUpgrade,
DefensiveUpgrade: defensiveUpgrade
};
if (expiry) {
data.UpgradesExpiry = new Date(expiry).getTime();
}
editSuitInvigorationUpgrade(oid, data);
}
function clearSuitInvigorationUpgrades() {
editSuitInvigorationUpgrade(new URLSearchParams(window.location.search).get("itemId"), null);
}
async function editSuitInvigorationUpgrade(oid, data) {
/* data?: {
DefensiveUpgrade: string;
OffensiveUpgrade: string;
UpgradesExpiry?: number;
}*/
$.post({
url: "/custom/editSuitInvigorationUpgrade?" + window.authz,
contentType: "application/json",
data: JSON.stringify({ oid, data })
}).done(function () {
updateInventory();
});
}
function handleAbilityOverride(event, configIndex) {
event.preventDefault();
const urlParams = new URLSearchParams(window.location.search);
const action = event.submitter.value;
const Ability = getKey(document.getElementById(`abilityOverride-ability-config-${configIndex}`));
const Index = document.getElementById(`abilityOverride-ability-index-config-${configIndex}`).value;
revalidateAuthz().then(() => {
$.post({
url: "/custom/abilityOverride?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
category: urlParams.get("productCategory"),
oid: urlParams.get("itemId"),
configIndex,
action,
AbilityOverride: {
Ability,
Index
}
})
}).done(function () {
updateInventory();
});
});
}

View File

@ -11,8 +11,7 @@
margin-left: 7rem;
}
body.logged-in:has([data-lang="de"].active) #main-content,
body.logged-in:has([data-lang="uk"].active) #main-content {
body.logged-in:has([data-lang="de"].active) #main-content {
margin-left: 8rem;
}
@ -29,12 +28,9 @@ body:not(.logged-in) .user-dropdown {
display: none;
}
/* font awesome icons */
svg {
td.text-end > a > svg {
fill: currentColor;
height: 1em;
}
td.text-end > a > svg {
margin-left: 0.5em;
margin-bottom: 4px; /* to centre the icon */
}

View File

@ -1,14 +1,13 @@
// German translation by Animan8000
dict = {
general_inventoryUpdateNote: `Hinweis: Um Änderungen im Spiel zu sehen, musst du dein Inventar neu synchronisieren, z. B. mit dem /sync Befehl des Bootstrappers, durch Besuch eines Dojo/Relais oder durch erneutes Einloggen.`,
general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`,
general_addButton: `Hinzufügen`,
general_setButton: `Festlegen`,
general_none: `Keines`,
general_setButton: `[UNTRANSLATED] Set`,
general_removeButton: `[UNTRANSLATED] Remove`,
general_bulkActions: `Massenaktionen`,
general_loading: `Lädt...`,
code_loginFail: `Anmeldung fehlgeschlagen. Bitte überprüfe deine Angaben.`,
code_regFail: `Registrierung fehlgeschlagen. Account existiert bereits?`,
code_loginFail: `[UNTRANSLATED] Login failed. Double-check the email and password.`,
code_regFail: `[UNTRANSLATED] Registration failed. Account already exists?`,
code_changeNameConfirm: `In welchen Namen möchtest du deinen Account umbenennen?`,
code_deleteAccountConfirm: `Bist du sicher, dass du deinen Account |DISPLAYNAME| (|EMAIL|) löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.`,
code_archgun: `Arch-Gewehr`,
@ -46,8 +45,6 @@ dict = {
code_focusUnlocked: `|COUNT| neue Fokus-Schulen freigeschaltet! Ein Inventar-Update wird benötigt, damit die Änderungen im Spiel sichtbar werden. Die Sternenkarte zu besuchen, sollte der einfachste Weg sein, dies auszulösen.`,
code_addModsConfirm: `Bist du sicher, dass du |COUNT| Mods zu deinem Account hinzufügen möchtest?`,
code_succImport: `Erfolgreich importiert.`,
code_succRelog: `Fertig. Bitte beachte, dass du dich neu einloggen musst, um Änderungen im Spiel zu sehen.`,
code_nothingToDo: `Fertig. Es gab nichts zu tun.`,
code_gild: `Veredeln`,
code_moa: `Moa`,
code_zanuka: `Jagdhund`,
@ -62,8 +59,6 @@ dict = {
code_pigment: `Pigment`,
code_mature: `Für den Kampf auswachsen lassen`,
code_unmature: `Genetisches Altern zurücksetzen`,
code_succChange: `Erfolgreich geändert.`,
code_requiredInvigorationUpgrade: `Du musst sowohl ein offensives & defensives Upgrade auswählen.`,
login_description: `Melde dich mit deinem OpenWF-Account an (denselben Angaben wie im Spiel, wenn du dich mit diesem Server verbindest).`,
login_emailLabel: `E-Mail-Adresse`,
login_passwordLabel: `Passwort`,
@ -93,7 +88,7 @@ dict = {
inventory_moaPets: `Moas`,
inventory_kubrowPets: `Bestien`,
inventory_evolutionProgress: `Incarnon-Entwicklungsfortschritte`,
inventory_Boosters: `Booster`,
inventory_Boosters: `[UNTRANSLATED] Boosters`,
inventory_bulkAddSuits: `Fehlende Warframes hinzufügen`,
inventory_bulkAddWeapons: `Fehlende Waffen hinzufügen`,
inventory_bulkAddSpaceSuits: `Fehlende Archwings hinzufügen`,
@ -108,7 +103,7 @@ dict = {
inventory_bulkRankUpSentinels: `Alle Wächter auf Max. Rang`,
inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`,
inventory_bulkRankUpEvolutionProgress: `Alle Incarnon-Entwicklungsfortschritte auf Max. Rang`,
inventory_maxPlexus: `Plexus auf Max. Rang`,
inventory_maxPlexus: `[UNTRANSLATED] Max Rank Plexus`,
quests_list: `Quests`,
quests_completeAll: `Alle Quests abschließen`,
@ -125,46 +120,14 @@ dict = {
detailedView_archonShardsDescription: `Du kannst diese unbegrenzten Slots nutzen, um eine Vielzahl von Verbesserungen anzuwenden.`,
detailedView_archonShardsDescription2: `Hinweis: Jede Archon-Scherbe benötigt beim Laden etwas Zeit, um angewendet zu werden.`,
detailedView_valenceBonusLabel: `Valenz-Bonus`,
detailedView_valenceBonusDescription: `Du kannst den Valenz-Bonus deiner Waffe festlegen oder entfernen.`,
detailedView_modularPartsLabel: `Modulare Teile ändern`,
detailedView_suitInvigorationLabel: `Warframe-Kräftigung`,
detailedView_loadoutLabel: `Loadouts`,
invigorations_offensive_AbilityStrength: `+200% Fähigkeitsstärke`,
invigorations_offensive_AbilityRange: `+100% Fähigkeitsreichweite`,
invigorations_offensive_AbilityDuration: `+100% Fähigkeitsdauer`,
invigorations_offensive_MeleeDamage: `+250% Nahkampfschaden`,
invigorations_offensive_PrimaryDamage: `+250% Primärwaffen Schaden`,
invigorations_offensive_SecondaryDamage: `+250% Sekundärwaffen Schaden`,
invigorations_offensive_PrimaryCritChance: `+200% Primärwaffen Krit. Chance`,
invigorations_offensive_SecondaryCritChance: `+200% Sekundärwaffen Krit. Chance`,
invigorations_offensive_MeleeCritChance: `+200% Nahkampfwaffen Krit. Chance`,
invigorations_utility_AbilityEfficiency: `+75% Fähigkeitseffizienz`,
invigorations_utility_SprintSpeed: `+75% Sprintgeschwindigkeit`,
invigorations_utility_ParkourVelocity: `+75% Parkourgeschwindigkeit`,
invigorations_utility_HealthMax: `+1000 Gesundheit`,
invigorations_utility_EnergyMax: `+200% Max. Energie`,
invigorations_utility_StatusImmune: `Immun gegen Statuseffekte`,
invigorations_utility_ReloadSpeed: `+75% Nachladegeschwindigkeit`,
invigorations_utility_HealthRegen: `+25 Gesundheitsregeneration pro Sekunde`,
invigorations_utility_ArmorMax: `+1000 Rüstung`,
invigorations_utility_Jumps: `+5 Sprung-Zurücksetzungen`,
invigorations_utility_EnergyRegen: `+2 Energieregeneration pro Sekunde`,
invigorations_offensiveLabel: `Offensives Upgrade`,
invigorations_defensiveLabel: `Defensives Upgrade`,
invigorations_expiryLabel: `Upgrades Ablaufdatum (optional)`,
abilityOverride_label: `Fähigkeitsüberschreibung`,
abilityOverride_onSlot: `auf Slot`,
detailedView_valenceBonusDescription: `[UNTRANSLATED] You can set or remove the Valence Bonus from your weapon.`,
mods_addRiven: `Riven hinzufügen`,
mods_fingerprint: `Fingerabdruck`,
mods_fingerprintHelp: `Benötigst du Hilfe mit dem Fingerabdruck?`,
mods_rivens: `Rivens`,
mods_mods: `Mods`,
mods_addMax: `Max. hinzufügen`,
mods_addMax: `[UNTRANSLATED] Add Maxed`,
mods_addMissingUnrankedMods: `Fehlende Mods ohne Rang hinzufügen`,
mods_removeUnranked: `Mods ohne Rang entfernen`,
mods_addMissingMaxRankMods: `Fehlende Mods mit Max. Rang hinzufügen`,
@ -174,17 +137,17 @@ dict = {
cheats_skipAllDialogue: `Alle Dialoge überspringen`,
cheats_unlockAllScans: `Alle Scans freischalten`,
cheats_unlockAllMissions: `Alle Missionen freischalten`,
cheats_unlockAllMissions_ok: `Erfolgreich. Bitte beachte, dass du ein Dojo/Relais besuchen oder dich neu einloggen musst, damit die Sternenkarte aktualisiert wird.`,
cheats_unlockAllMissions_ok: `[UNTRANSLATED] Success. Please note that you'll need to enter a dojo/relay or relog for the client to refresh the star chart.`,
cheats_infiniteCredits: `Unendlich Credits`,
cheats_infinitePlatinum: `Unendlich Platinum`,
cheats_infiniteEndo: `Unendlich Endo`,
cheats_infiniteRegalAya: `Unendlich Reines Aya`,
cheats_infiniteHelminthMaterials: `Unendlich Helminth-Materialien`,
cheats_claimingBlueprintRefundsIngredients: `Fertige Blaupausen erstatten Ressourcen zurück`,
cheats_dontSubtractPurchaseCreditCost: `Credits beim Kauf nicht verbrauchen`,
cheats_dontSubtractPurchasePlatinumCost: `Platinum beim Kauf nicht verbrauchen`,
cheats_dontSubtractPurchaseItemCost: `Gegenstände beim Kauf nicht verbrauchen`,
cheats_dontSubtractPurchaseStandingCost: `Ansehen beim Kauf nicht verbrauchen`,
cheats_dontSubtractPurchaseCreditCost: `[UNTRANSLATED] Don't Subtract Purchase Credit Cost`,
cheats_dontSubtractPurchasePlatinumCost: `[UNTRANSLATED] Don't Subtract Purchase Platinum Cost`,
cheats_dontSubtractPurchaseItemCost: `[UNTRANSLATED] Don't Subtract Purchase Item Cost`,
cheats_dontSubtractPurchaseStandingCost: `[UNTRANSLATED] Don't Subtract Purchase Standing Cost`,
cheats_dontSubtractVoidTraces: `Void-Spuren nicht verbrauchen`,
cheats_dontSubtractConsumables: `Verbrauchsgegenstände (Ausrüstung) nicht verbrauchen`,
cheats_unlockAllShipFeatures: `Alle Schiffs-Funktionen freischalten`,
@ -204,11 +167,11 @@ dict = {
cheats_noVendorPurchaseLimits: `Keine Kaufbeschränkungen bei Händlern`,
cheats_noDeathMarks: `Keine Todesmarkierungen`,
cheats_noKimCooldowns: `Keine Wartezeit bei KIM`,
cheats_fullyStockedVendors: `Händler haben volles Inventar`,
cheats_baroAlwaysAvailable: `Baro immer im Relais verfügbar`,
cheats_baroFullyStocked: `Baro hat volles Inventar`,
cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`,
cheats_baroAlwaysAvailable: `[UNTRANSLATED] Baro Always Available`,
cheats_baroFullyStocked: `[UNTRANSLATED] Baro Fully Stocked`,
cheats_syndicateMissionsRepeatable: `Syndikat-Missionen wiederholbar`,
cheats_unlockAllProfitTakerStages: `Alle Profiteintreiber-Phasen freischalten`,
cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`,
cheats_instantFinishRivenChallenge: `Riven-Mod Herausforderung sofort abschließen`,
cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`,
@ -219,145 +182,128 @@ dict = {
cheats_noDojoResearchCosts: `Keine Dojo-Forschungskosten`,
cheats_noDojoResearchTime: `Keine Dojo-Forschungszeit`,
cheats_fastClanAscension: `Schneller Clan-Aufstieg`,
cheats_missionsCanGiveAllRelics: `Alle Relikte als Missionsbelohnung möglich`,
cheats_exceptionalRelicsAlwaysGiveBronzeReward: `Überragende Relikte geben immer Bronze-Belohnung`,
cheats_flawlessRelicsAlwaysGiveSilverReward: `Makellose Relikte geben immer Silber-Belohnung`,
cheats_radiantRelicsAlwaysGiveGoldReward: `Strahlende Relikte geben immer Gold-Belohnung`,
cheats_unlockAllSimarisResearchEntries: `Alle Simaris-Forschungseinträge freischalten`,
cheats_disableDailyTribute: `Täglicher Tribut deaktivieren`,
cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`,
cheats_exceptionalRelicsAlwaysGiveBronzeReward: `[UNTRANSLATED] Exceptional Relics Always Give Bronze Reward`,
cheats_flawlessRelicsAlwaysGiveSilverReward: `[UNTRANSLATED] Flawless Relics Always Give Silver Reward`,
cheats_radiantRelicsAlwaysGiveGoldReward: `[UNTRANSLATED] Radiant Relics Always Give Gold Reward`,
cheats_unlockAllSimarisResearchEntries: `[UNTRANSLATED] Unlock All Simaris Research Entries`,
cheats_disableDailyTribute: `[UNTRANSLATED] Disable Daily Tribute`,
cheats_spoofMasteryRank: `Gefälschter Meisterschaftsrang (-1 zum deaktivieren)`,
cheats_relicRewardItemCountMultiplier: `Belohnungsmultiplikator für Relikte`,
cheats_nightwaveStandingMultiplier: `Nightwave Ansehen Multiplikator`,
cheats_save: `Speichern`,
cheats_relicRewardItemCountMultiplier: `[UNTRANSLATED] Relic Reward Item Count Multiplier`,
cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`,
cheats_save: `[UNTRANSLATED] Save`,
cheats_account: `Account`,
cheats_unlockAllFocusSchools: `Alle Fokus-Schulen freischalten`,
cheats_helminthUnlockAll: `Helminth vollständig aufleveln`,
cheats_addMissingSubsumedAbilities: `Fehlende konsumierte Fähigkeiten hinzufügen`,
cheats_addMissingSubsumedAbilities: `[UNTRANSLATED] Add Missing Subsumed Abilities`,
cheats_intrinsicsUnlockAll: `Alle Inhärenzen auf Max. Rang`,
cheats_changeSupportedSyndicate: `Unterstütztes Syndikat`,
cheats_changeButton: `Ändern`,
cheats_markAllAsRead: `Posteingang als gelesen markieren`,
cheats_none: `Keines`,
worldState: `Weltstatus`,
worldState_creditBoost: `Event Booster: Credit`,
worldState_affinityBoost: `Event Booster: Erfahrung`,
worldState_resourceBoost: `Event Booster: Ressourcen`,
worldState_tennoLiveRelay: `TennoLive Relais`,
worldState_baroTennoConRelay: `Baros TennoCon Relais`,
worldState_starDays: `Sternen-Tage`,
worldState_galleonOfGhouls: `Galeone der Ghule`,
worldState_ghoulEmergence: `Ghul Ausrottung`,
worldState_plagueStar: `Plagenstern`,
worldState_dogDays: `Hitzefrei`,
worldState_dogDaysRewards: `[UNTRANSLATED] Dog Days Rewards`,
worldState_wolfHunt: `Wolfsjagd (2025)`,
worldState_longShadow: `Lange Schatten`,
worldState_hallowedFlame: `Geweihte Flamme`,
worldState_hallowedNightmares: `Geweihte Albträume`,
worldState_hallowedNightmaresRewards: `[UNTRANSLATED] Hallowed Nightmares Rewards`,
worldState_proxyRebellion: `Proxy-Rebellion`,
worldState_proxyRebellionRewards: `[UNTRANSLATED] Proxy Rebellion Rewards`,
worldState_from_year: `[UNTRANSLATED] from |YEAR|`,
worldState_pre_year: `[UNTRANSLATED] pre |YEAR|`,
worldState_incompatibleWith: `[UNTRANSLATED] Incompatible with:`,
enabled: `Aktiviert`,
disabled: `Deaktiviert`,
worldState_we1: `Wochenende 1`,
worldState_we2: `Wochenende 2`,
worldState_we3: `Wochenende 3`,
worldState_eidolonOverride: `Eidolon-Überschreibung`,
worldState_day: `Tag`,
worldState_night: `Nacht`,
worldState_vallisOverride: `Orbis-Tal-Überschreibung`,
worldState_warm: `Warm`,
worldState_cold: `Kalt`,
worldState_duviriOverride: `Duviri-Überschreibung`,
worldState_joy: `Freude`,
worldState_anger: `Wut`,
worldState_envy: `Neid`,
worldState_sorrow: `Trauer`,
worldState_fear: `Angst`,
worldState_nightwaveOverride: `Nightwave-Überschreibung`,
worldState_RadioLegionIntermission13Syndicate: `Noras Mix - Vol. 9`,
worldState_RadioLegionIntermission12Syndicate: `Noras Mix - Vol. 8`,
worldState_RadioLegionIntermission11Syndicate: `Noras Mix - Vol. 7`,
worldState_RadioLegionIntermission10Syndicate: `Noras Mix - Vol. 6`,
worldState_RadioLegionIntermission9Syndicate: `Noras Mix - Vol. 5`,
worldState_RadioLegionIntermission8Syndicate: `Noras Mix - Vol. 4`,
worldState_RadioLegionIntermission7Syndicate: `Noras Mix - Vol. 3`,
worldState_RadioLegionIntermission6Syndicate: `Noras Mix - Vol. 2`,
worldState_RadioLegionIntermission5Syndicate: `Noras Mix - Vol. 1`,
worldState_RadioLegionIntermission4Syndicate: `Noras Wahl`,
worldState_RadioLegionIntermission3Syndicate: `Sendepause III`,
worldState_RadioLegion3Syndicate: `Der Glasmacher`,
worldState_RadioLegionIntermission2Syndicate: `Sendepause II`,
worldState_RadioLegion2Syndicate: `Der Botschafter`,
worldState_RadioLegionIntermissionSyndicate: `Sendepause I`,
worldState_RadioLegionSyndicate: `Der Wolf von Saturn Six`,
worldState_fissures: `Void-Risse`,
normal: `Normal`,
worldState_allAtOnceNormal: `Alle gleichzeitig, Normal`,
worldState_allAtOnceSteelPath: `Alle gleichzeitig, Stählerne Pfad`,
worldState_theCircuitOverride: `Der Rundkurs-Überschreibung`,
worldState_darvoStockMultiplier: `Darvo-Vorratsmultiplikator`,
worldState_varziaFullyStocked: `Varzia hat volles Inventar`,
worldState_varziaOverride: `Varzia-Angebotsüberschreibung`,
worldState: `[UNTRANSLATED] World State`,
worldState_creditBoost: `[UNTRANSLATED] Credit Boost`,
worldState_affinityBoost: `[UNTRANSLATED] Affinity Boost`,
worldState_resourceBoost: `[UNTRANSLATED] Resource Boost`,
worldState_starDays: `[UNTRANSLATED] Star Days`,
worldState_galleonOfGhouls: `[UNTRANSLATED] Galleon of Ghouls`,
disabled: `[UNTRANSLATED] Disabled`,
worldState_we1: `[UNTRANSLATED] Weekend 1`,
worldState_we2: `[UNTRANSLATED] Weekend 2`,
worldState_we3: `[UNTRANSLATED] Weekend 3`,
worldState_eidolonOverride: `[UNTRANSLATED] Eidolon Override`,
worldState_day: `[UNTRANSLATED] Day`,
worldState_night: `[UNTRANSLATED] Night`,
worldState_vallisOverride: `[UNTRANSLATED] Orb Vallis Override`,
worldState_warm: `[UNTRANSLATED] Warm`,
worldState_cold: `[UNTRANSLATED] Cold`,
worldState_duviriOverride: `[UNTRANSLATED] Duviri Override`,
worldState_joy: `[UNTRANSLATED] Joy`,
worldState_anger: `[UNTRANSLATED] Anger`,
worldState_envy: `[UNTRANSLATED] Envy`,
worldState_sorrow: `[UNTRANSLATED] Sorrow`,
worldState_fear: `[UNTRANSLATED] Fear`,
worldState_nightwaveOverride: `[UNTRANSLATED] Nightwave Override`,
worldState_RadioLegionIntermission13Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 9`,
worldState_RadioLegionIntermission12Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 8`,
worldState_RadioLegionIntermission11Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 7`,
worldState_RadioLegionIntermission10Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 6`,
worldState_RadioLegionIntermission9Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 5`,
worldState_RadioLegionIntermission8Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 4`,
worldState_RadioLegionIntermission7Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 3`,
worldState_RadioLegionIntermission6Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 2`,
worldState_RadioLegionIntermission5Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 1`,
worldState_RadioLegionIntermission4Syndicate: `[UNTRANSLATED] Nora's Choice`,
worldState_RadioLegionIntermission3Syndicate: `[UNTRANSLATED] Intermission III`,
worldState_RadioLegion3Syndicate: `[UNTRANSLATED] Glassmaker`,
worldState_RadioLegionIntermission2Syndicate: `[UNTRANSLATED] Intermission II`,
worldState_RadioLegion2Syndicate: `[UNTRANSLATED] The Emissary`,
worldState_RadioLegionIntermissionSyndicate: `[UNTRANSLATED] Intermission I`,
worldState_RadioLegionSyndicate: `[UNTRANSLATED] The Wolf of Saturn Six`,
worldState_fissures: `[UNTRANSLATED] Fissures`,
normal: `[UNTRANSLATED] Normal`,
worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`,
worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`,
worldState_theCircuitOverride: `[UNTRANSLATED] The Circuit Override`,
worldState_darvoStockMultiplier: `[UNTRANSLATED] Darvo Stock Multiplier`,
worldState_varziaFullyStocked: `[UNTRANSLATED] Varzia Fully Stocked`,
worldState_varziaOverride: `[UNTRANSLATED] Varzia Rotation Override`,
import_importNote: `Du kannst hier eine vollständige oder teilweise Inventarantwort (Client-Darstellung) einfügen. Alle Felder, die vom Importer unterstützt werden, <b>werden in deinem Account überschrieben</b>.`,
import_submit: `Absenden`,
import_samples: `Beispiele:`,
import_samples_maxFocus: `Alle Fokus-Schulen maximiert`,
import_samples: `[UNTRANSLATED] Samples:`,
import_samples_maxFocus: `[UNTRANSLATED] All Focus Schools Maxed Out`,
upgrade_Equilibrium: `+|VAL|% Energie bei Gesundheitskugeln, +|VAL|% Gesundheit bei Energiekugeln`,
upgrade_MeleeCritDamage: `+|VAL|% Krit. Nahkampfschaden`,
upgrade_PrimaryStatusChance: `+|VAL|% Primärwaffen Statuschance`,
upgrade_SecondaryCritChance: `+|VAL|% Sekundärwaffen Krit. Chance`,
upgrade_WarframeAbilityDuration: `+|VAL|% Fähigkeitsdauer`,
upgrade_WarframeAbilityStrength: `+|VAL|% Fähigkeitsstärke`,
upgrade_WarframeArmorMax: `+|VAL| Rüstung`,
upgrade_WarframeBlastProc: `+|VAL| Schilde beim Töten eines Gegners mit Explosionsschaden`,
upgrade_WarframeCastingSpeed: `+|VAL|% Aktivierungsgeschwindigkeit`,
upgrade_WarframeCorrosiveDamageBoost: `+|VAL|% Fähigkeitsschaden auf Gegner, die von Korrosions-Status betroffen sind`,
upgrade_WarframeCorrosiveStack: `Erhöhe maximale Stapelanzahl von Korrosions-Status um +|VAL|`,
upgrade_WarframeCritDamageBoost: `+|VAL|% Krit. Nahkampfschaden (verdoppelt bei über 500 Energie)`,
upgrade_WarframeElectricDamage: `+|VAL1|% Primärwaffen Elektrizitätsschaden (+|VAL2|% pro zusätzlicher Scherbe)`,
upgrade_WarframeElectricDamageBoost: `+|VAL|% Fähigkeitsschaden auf Gegner, die von Elektrizitäts-Status betroffen sind`,
upgrade_WarframeEnergyMax: `+|VAL| Max. Energie`,
upgrade_WarframeGlobeEffectEnergy: `+|VAL|% Wirksamkeit bei Energiekugeln`,
upgrade_WarframeGlobeEffectHealth: `+|VAL|% Wirksamkeit bei Gesundheitskugeln`,
upgrade_WarframeHealthMax: `+|VAL| Gesundheit`,
upgrade_WarframeHPBoostFromImpact: `+|VAL1| Gesundheit beim Töten eines Gegners mit Explosionsschaden (Max. |VAL2| Gesundheit)`,
upgrade_WarframeParkourVelocity: `+|VAL|% Parkourgeschwindigkeit`,
upgrade_WarframeRadiationDamageBoost: `+|VAL|% Fähigkeitsschaden auf Gegner, die von Strahlungs-Status betroffen sind`,
upgrade_WarframeHealthRegen: `+|VAL| Gesundheitsregeneration pro Sekunde`,
upgrade_WarframeShieldMax: `+|VAL| Schildkapazität`,
upgrade_WarframeStartingEnergy: `+|VAL|% Max. Energie beim Spawnen`,
upgrade_WarframeToxinDamage: `+|VAL|% Schaden durch Gift-Statuseffekte`,
upgrade_WarframeToxinHeal: `+|VAL| Gesundheit beim Schaden an Gegnern mit Gift-Status`,
upgrade_WeaponCritBoostFromHeat: `+|VAL1|% Sekundärwaffen Krit. Chance pro getötetem Gegner mit Hitze-Statuseffekt (Max. |VAL2|%)`,
upgrade_AvatarAbilityRange: `+7.5% Fähigkeitsreichweite`,
upgrade_AvatarAbilityEfficiency: `+5% Fähigkeitseffizienz`,
upgrade_AvatarEnergyRegen: `+0.5 Energieregeneration pro Sekunde`,
upgrade_AvatarEnemyRadar: `+5m Feindradar`,
upgrade_AvatarLootRadar: `+7m Beuteradar`,
upgrade_WeaponAmmoMax: `+15% Max. Munition`,
upgrade_EnemyArmorReductionAura: `-3% Rüstung bei Feinden`,
upgrade_OnExecutionAmmo: `+100% Magazinfüllung für Primär- und Sekundärwaffen bei Gnadenstoß`,
upgrade_OnExecutionHealthDrop: `+100% Gesundheitskugel Chance bei Gnadenstoß`,
upgrade_OnExecutionEnergyDrop: `+50% Energiekugel Chance bei Gnadenstoß`,
upgrade_OnFailHackReset: `+50% Chance, das Hacken bei Fehlschlag zu wiederholen`,
upgrade_DamageReductionOnHack: `+75% Schadensreduktion beim Hacken`,
upgrade_OnExecutionReviveCompanion: `Gnadenstoß-Kills verkürzen die Erholungszeit des Begleiters um 15s`,
upgrade_OnExecutionParkourSpeed: `+60% Parkourgeschwindigkeit für 15s nach Gnadenstoß`,
upgrade_AvatarTimeLimitIncrease: `+8s extra Zeit beim Hacken`,
upgrade_ElectrifyOnHack: `Setze beim Hacken Gegner innerhalb von 20m unter Strom`,
upgrade_OnExecutionTerrify: `+50% Chance bei Gnadenstoß, dass Feinde innerhalb von 15m vor Furcht für 8s kauern`,
upgrade_OnHackLockers: `Schließe nach dem Hacken 5 Spinde innerhalb von 20m auf`,
upgrade_OnExecutionBlind: `Blende bei einem Gnadenstoß Gegner innerhalb von 18m`,
upgrade_OnExecutionDrainPower: `Nächste Fähigkeit erhält +50% Fähigkeitsstärke nach Gnadenstoß`,
upgrade_OnHackSprintSpeed: `+75% Sprintgeschwindigkeit für 15s nach dem Hacken`,
upgrade_SwiftExecute: `+50% Gnadenstoßgeschwindigkeit`,
upgrade_OnHackInvis: `+15s Unsichtbarkeit nach dem Hacken`,
upgrade_Equilibrium: `[UNTRANSLATED] +|VAL|% Energy from Health pickups, +|VAL|% Health from Energy pickups`,
upgrade_MeleeCritDamage: `[UNTRANSLATED] +|VAL|% Melee Critical Damage`,
upgrade_PrimaryStatusChance: `[UNTRANSLATED] +|VAL|% Primary Status Chance`,
upgrade_SecondaryCritChance: `[UNTRANSLATED] +|VAL|% Secondary Critical Chance`,
upgrade_WarframeAbilityDuration: `[UNTRANSLATED] +|VAL|% Ability Duration`,
upgrade_WarframeAbilityStrength: `[UNTRANSLATED] +|VAL|% Ability Strength`,
upgrade_WarframeArmourMax: `[UNTRANSLATED] +|VAL| Armor`,
upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on kill with Blast Damage`,
upgrade_WarframeCastingSpeed: `[UNTRANSLATED] +|VAL|% Casting Speed`,
upgrade_WarframeCorrosiveDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Corrosion Status`,
upgrade_WarframeCorrosiveStack: `[UNTRANSLATED] Increase max stacks of Corrosion Status by +|VAL|`,
upgrade_WarframeCritDamageBoost: `[UNTRANSLATED] +|VAL|% Melee Critical Damage (Doubles over 500 Energy)`,
upgrade_WarframeElectricDamage: `[UNTRANSLATED] +|VAL1|% Primary Electricity Damage (+|VAL2|% per additional Shard)`,
upgrade_WarframeElectricDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Electricity Status`,
upgrade_WarframeEnergyMax: `[UNTRANSLATED] +|VAL| Energy Max`,
upgrade_WarframeGlobeEffectEnergy: `[UNTRANSLATED] +|VAL|% Energy Orb Effectiveness`,
upgrade_WarframeGlobeEffectHealth: `[UNTRANSLATED] +|VAL|% Health Orb Effectiveness`,
upgrade_WarframeHealthMax: `[UNTRANSLATED] +|VAL| Health`,
upgrade_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health on kill with Blast Damage (Max |VAL2| Health)`,
upgrade_WarframeParkourVelocity: `[UNTRANSLATED] +|VAL|% Parkour Velocity`,
upgrade_WarframeRadiationDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Radiation Status`,
upgrade_WarframeRegen: `[UNTRANSLATED] +|VAL| Health Regen/s`,
upgrade_WarframeShieldMax: `[UNTRANSLATED] +|VAL| Shield`,
upgrade_WarframeStartingEnergy: `[UNTRANSLATED] +|VAL|% Energy on Spawn`,
upgrade_WarframeToxinDamage: `[UNTRANSLATED] +|VAL|% Toxin Status Effect Damage`,
upgrade_WarframeToxinHeal: `[UNTRANSLATED] +|VAL| Health on damaging enemies with Toxin Status`,
upgrade_WeaponCritBoostFromHeat: `[UNTRANSLATED] +|VAL1|% Secondary Critical Chance per Heat-affected enemy killed (Max |VAL2|%)`,
upgrade_AvatarAbilityRange: `[UNTRANSLATED] +7.5% Ability Range`,
upgrade_AvatarAbilityEfficiency: `[UNTRANSLATED] +5% Ability Efficiency`,
upgrade_AvatarEnergyRegen: `[UNTRANSLATED] +0.5 Energy Regen/s`,
upgrade_AvatarEnemyRadar: `[UNTRANSLATED] +5m Enemy Radar`,
upgrade_AvatarLootRadar: `[UNTRANSLATED] +7m Loot Radar`,
upgrade_WeaponAmmoMax: `[UNTRANSLATED] +15% Ammo Max`,
upgrade_EnemyArmorReductionAura: `[UNTRANSLATED] -3% Enemy Armor`,
upgrade_OnExecutionAmmo: `[UNTRANSLATED] +100% Primary and Secondary Magazine Refill on Mercy`,
upgrade_OnExecutionHealthDrop: `[UNTRANSLATED] +100% Health Orb Chance on Mercy`,
upgrade_OnExecutionEnergyDrop: `[UNTRANSLATED] +50% Energy Orb Chance on Mercy`,
upgrade_OnFailHackReset: `[UNTRANSLATED] +50% Hacking Retry Chance`,
upgrade_DamageReductionOnHack: `[UNTRANSLATED] +75% Damage Reduction while Hacking`,
upgrade_OnExecutionReviveCompanion: `[UNTRANSLATED] Mercy Kills reduce Companion Recovery by 15s`,
upgrade_OnExecutionParkourSpeed: `[UNTRANSLATED] +60% Parkour Speed after a Mercy for 15s`,
upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] +8s to Hacking`,
upgrade_ElectrifyOnHack: `[UNTRANSLATED] Shock enemies within 20m while Hacking`,
upgrade_OnExecutionTerrify: `[UNTRANSLATED] +50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`,
upgrade_OnHackLockers: `[UNTRANSLATED] Unlock 5 lockers within 20m after Hacking`,
upgrade_OnExecutionBlind: `[UNTRANSLATED] Blind enemies within 18m on Mercy`,
upgrade_OnExecutionDrainPower: `[UNTRANSLATED] +100% chance for next ability cast to gain +50% Ability Strength on Mercy`,
upgrade_OnHackSprintSpeed: `[UNTRANSLATED] +75% Sprint Speed for 15s after Hacking`,
upgrade_SwiftExecute: `[UNTRANSLATED] +50% Mercy Kill Speed`,
upgrade_OnHackInvis: `[UNTRANSLATED] Invisible for 15 seconds after Hacking`,
damageType_Electricity: `Elektrizität`,
damageType_Fire: `Hitze`,
@ -367,8 +313,8 @@ dict = {
damageType_Poison: `Gift`,
damageType_Radiation: `Strahlung`,
theme_dark: `Dunkles Design`,
theme_light: `Helles Design`,
theme_dark: `[UNTRANSLATED] Dark Theme`,
theme_light: `[UNTRANSLATED] Light Theme`,
prettier_sucks_ass: ``
};

View File

@ -2,9 +2,8 @@ dict = {
general_inventoryUpdateNote: `Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`,
general_addButton: `Add`,
general_setButton: `Set`,
general_none: `None`,
general_removeButton: `Remove`,
general_bulkActions: `Bulk Actions`,
general_loading: `Loading...`,
code_loginFail: `Login failed. Double-check the email and password.`,
code_regFail: `Registration failed. Account already exists?`,
@ -45,8 +44,6 @@ dict = {
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.`,
code_succRelog: `Done. Please note that you'll need to relog to see a difference in-game.`,
code_nothingToDo: `Done. There was nothing to do.`,
code_gild: `Gild`,
code_moa: `Moa`,
code_zanuka: `Hound`,
@ -61,8 +58,6 @@ dict = {
code_pigment: `Pigment`,
code_mature: `Mature for combat`,
code_unmature: `Regress genetic aging`,
code_succChange: `Successfully changed.`,
code_requiredInvigorationUpgrade: `You must select both an offensive & defensive upgrade.`,
login_description: `Login using your OpenWF account credentials (same as in-game when connecting to this server).`,
login_emailLabel: `Email address`,
login_passwordLabel: `Password`,
@ -125,38 +120,6 @@ dict = {
detailedView_archonShardsDescription2: `Note that each archon shard takes some time to be applied when loading in.`,
detailedView_valenceBonusLabel: `Valence Bonus`,
detailedView_valenceBonusDescription: `You can set or remove the Valence Bonus from your weapon.`,
detailedView_modularPartsLabel: `Change Modular Parts`,
detailedView_suitInvigorationLabel: `Warframe Invigoration`,
detailedView_loadoutLabel: `Loadouts`,
invigorations_offensive_AbilityStrength: `+200% Ability Strength`,
invigorations_offensive_AbilityRange: `+100% Ability Range`,
invigorations_offensive_AbilityDuration: `+100% Ability Duration`,
invigorations_offensive_MeleeDamage: `+250% Melee Damage`,
invigorations_offensive_PrimaryDamage: `+250% Primary Damage`,
invigorations_offensive_SecondaryDamage: `+250% Secondary Damage`,
invigorations_offensive_PrimaryCritChance: `+200% Primary Critical Chance`,
invigorations_offensive_SecondaryCritChance: `+200% Secondary Critical Chance`,
invigorations_offensive_MeleeCritChance: `+200% Melee Critical Chance`,
invigorations_utility_AbilityEfficiency: `+75% Ability Efficiency`,
invigorations_utility_SprintSpeed: `+75% Sprint Speed`,
invigorations_utility_ParkourVelocity: `+75% Parkour Velocity`,
invigorations_utility_HealthMax: `+1000 Health`,
invigorations_utility_EnergyMax: `+200% Energy Max`,
invigorations_utility_StatusImmune: `Immune to Status Effects`,
invigorations_utility_ReloadSpeed: `+75% Reload Speed`,
invigorations_utility_HealthRegen: `+25 Health Regen/s`,
invigorations_utility_ArmorMax: `+1000 Armor`,
invigorations_utility_Jumps: `+5 Jump Resets`,
invigorations_utility_EnergyRegen: `+2 Energy Regen/s`,
invigorations_offensiveLabel: `Offensive Upgrade`,
invigorations_defensiveLabel: `Defensive Upgrade`,
invigorations_expiryLabel: `Upgrades Expiry (optional)`,
abilityOverride_label: `Ability Override`,
abilityOverride_onSlot: `on slot`,
mods_addRiven: `Add Riven`,
mods_fingerprint: `Fingerprint`,
@ -235,38 +198,21 @@ dict = {
cheats_intrinsicsUnlockAll: `Max Rank All Intrinsics`,
cheats_changeSupportedSyndicate: `Supported syndicate`,
cheats_changeButton: `Change`,
cheats_markAllAsRead: `Mark Inbox As Read`,
cheats_none: `None`,
worldState: `World State`,
worldState_creditBoost: `Credit Boost`,
worldState_affinityBoost: `Affinity Boost`,
worldState_resourceBoost: `Resource Boost`,
worldState_tennoLiveRelay: `TennoLive Relay`,
worldState_baroTennoConRelay: `Baro's TennoCon Relay`,
worldState_starDays: `Star Days`,
worldState_galleonOfGhouls: `Galleon of Ghouls`,
worldState_ghoulEmergence: `Ghoul Purge`,
worldState_plagueStar: `Plague Star`,
worldState_dogDays: `Dog Days`,
worldState_dogDaysRewards: `Dog Days Rewards`,
worldState_wolfHunt: `Wolf Hunt (2025)`,
worldState_longShadow: `Long Shadow`,
worldState_hallowedFlame: `Hallowed Flame`,
worldState_hallowedNightmares: `Hallowed Nightmares`,
worldState_hallowedNightmaresRewards: `Hallowed Nightmares Rewards`,
worldState_proxyRebellion: `Proxy Rebellion`,
worldState_proxyRebellionRewards: `Proxy Rebellion Rewards`,
worldState_from_year: `from |YEAR|`,
worldState_pre_year: `pre |YEAR|`,
worldState_incompatibleWith: `Incompatible with:`,
enabled: `Enabled`,
disabled: `Disabled`,
worldState_we1: `Weekend 1`,
worldState_we2: `Weekend 2`,
worldState_we3: `Weekend 3`,
worldState_eidolonOverride: `Eidolon/Deimos Override`,
worldState_day: `Day/Fass`,
worldState_night: `Night/Vome`,
worldState_eidolonOverride: `Eidolon Override`,
worldState_day: `Day`,
worldState_night: `Night`,
worldState_vallisOverride: `Orb Vallis Override`,
worldState_warm: `Warm`,
worldState_cold: `Cold`,
@ -313,7 +259,7 @@ dict = {
upgrade_SecondaryCritChance: `+|VAL|% Secondary Critical Chance`,
upgrade_WarframeAbilityDuration: `+|VAL|% Ability Duration`,
upgrade_WarframeAbilityStrength: `+|VAL|% Ability Strength`,
upgrade_WarframeArmorMax: `+|VAL| Armor`,
upgrade_WarframeArmourMax: `+|VAL| Armor`,
upgrade_WarframeBlastProc: `+|VAL| Shields on kill with Blast Damage`,
upgrade_WarframeCastingSpeed: `+|VAL|% Casting Speed`,
upgrade_WarframeCorrosiveDamageBoost: `+|VAL|% Ability Damage on enemies affected by Corrosion Status`,
@ -328,7 +274,7 @@ dict = {
upgrade_WarframeHPBoostFromImpact: `+|VAL1| Health on kill with Blast Damage (Max |VAL2| Health)`,
upgrade_WarframeParkourVelocity: `+|VAL|% Parkour Velocity`,
upgrade_WarframeRadiationDamageBoost: `+|VAL|% Ability Damage on enemies affected by Radiation Status`,
upgrade_WarframeHealthRegen: `+|VAL| Health Regen/s`,
upgrade_WarframeRegen: `+|VAL| Health Regen/s`,
upgrade_WarframeShieldMax: `+|VAL| Shield`,
upgrade_WarframeStartingEnergy: `+|VAL|% Energy on Spawn`,
upgrade_WarframeToxinDamage: `+|VAL|% Toxin Status Effect Damage`,
@ -353,7 +299,7 @@ dict = {
upgrade_OnExecutionTerrify: `+50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`,
upgrade_OnHackLockers: `Unlock 5 lockers within 20m after Hacking`,
upgrade_OnExecutionBlind: `Blind enemies within 18m on Mercy`,
upgrade_OnExecutionDrainPower: `Next ability cast gains +50% Ability Strength on Mercy`,
upgrade_OnExecutionDrainPower: `+100% chance for next ability cast to gain +50% Ability Strength on Mercy`,
upgrade_OnHackSprintSpeed: `+75% Sprint Speed for 15s after Hacking`,
upgrade_SwiftExecute: `+50% Mercy Kill Speed`,
upgrade_OnHackInvis: `Invisible for 15 seconds after Hacking`,

View File

@ -3,9 +3,8 @@ dict = {
general_inventoryUpdateNote: `Para ver los cambios en el juego, necesitas volver a sincronizar tu inventario, por ejemplo, usando el comando /sync del bootstrapper, visitando un dojo o repetidor, o volviendo a iniciar sesión.`,
general_addButton: `Agregar`,
general_setButton: `Establecer`,
general_none: `Ninguno`,
general_removeButton: `Quitar`,
general_bulkActions: `Acciones masivas`,
general_loading: `Cargando...`,
code_loginFail: `Error al iniciar sesión. Verifica el correo electrónico y la contraseña.`,
code_regFail: `Error al registrar la cuenta. ¿Ya existe una cuenta con este correo?`,
@ -36,7 +35,7 @@ dict = {
code_succRemoved: `Eliminado exitosamente.`,
code_buffsNumber: `Cantidad de mejoras`,
code_cursesNumber: `Cantidad de maldiciones`,
code_rerollsNumber: `Cantidad de rerolls`,
code_rerollsNumber: `Cantidad de reintentos`,
code_viewStats: `Ver estadísticas`,
code_rank: `Rango`,
code_rankUp: `Subir de rango`,
@ -46,8 +45,6 @@ dict = {
code_focusUnlocked: `¡Desbloqueadas |COUNT| nuevas escuelas de enfoque! Se necesita una actualización del inventario para reflejar los cambios en el juego. Visitar la navegación debería ser la forma más sencilla de activarlo.`,
code_addModsConfirm: `¿Estás seguro de que deseas agregar |COUNT| modificadores a tu cuenta?`,
code_succImport: `Importación exitosa.`,
code_succRelog: `Hecho. Ten en cuenta que deberás volver a iniciar sesión para ver los cambios en el juego.`,
code_nothingToDo: `Hecho. No había nada que hacer.`,
code_gild: `Refinar`,
code_moa: `Moa`,
code_zanuka: `Sabueso`,
@ -62,8 +59,6 @@ dict = {
code_pigment: `Pigmento`,
code_mature: `Listo para el combate`,
code_unmature: `Regresar el envejecimiento genético`,
code_succChange: `Cambiado correctamente`,
code_requiredInvigorationUpgrade: `Debes seleccionar una mejora ofensiva y una defensiva.`,
login_description: `Inicia sesión con las credenciales de tu cuenta OpenWF (las mismas que usas en el juego al conectarte a este servidor).`,
login_emailLabel: `Dirección de correo electrónico`,
login_passwordLabel: `Contraseña`,
@ -121,50 +116,18 @@ dict = {
currency_PrimeTokens: `Aya Real`,
currency_owned: `Tienes |COUNT|.`,
detailedView_archonShardsLabel: `Ranuras de Fragmento de Arconte`,
detailedView_archonShardsLabel: `Ranuras de Fragmento de Archón`,
detailedView_archonShardsDescription: `Puedes usar estas ranuras ilimitadas para aplicar una amplia variedad de mejoras`,
detailedView_archonShardsDescription2: `Ten en cuenta que cada fragmento de arconte tarda un poco en aplicarse al cargar`,
detailedView_valenceBonusLabel: `Bonus de Valéncia`,
detailedView_valenceBonusDescription: `Puedes establecer o quitar el bonus de valencia de tu arma.`,
detailedView_modularPartsLabel: `Cambiar partes modulares`,
detailedView_suitInvigorationLabel: `Vigorización de Warframe`,
detailedView_loadoutLabel: `Equipamientos`,
invigorations_offensive_AbilityStrength: `+200% Fuerza de Habilidad`,
invigorations_offensive_AbilityRange: `+100% Alcance de Habilidad`,
invigorations_offensive_AbilityDuration: `+100% Duración de Habilidad`,
invigorations_offensive_MeleeDamage: `+250% Daño Cuerpo a Cuerpo`,
invigorations_offensive_PrimaryDamage: `+250% Daño de Arma Principal`,
invigorations_offensive_SecondaryDamage: `+250% Daño de Arma Secundaria`,
invigorations_offensive_PrimaryCritChance: `+200% Probabilidad Crítica de Arma Principal`,
invigorations_offensive_SecondaryCritChance: `+200% Probabilidad Crítica de Arma Secundaria`,
invigorations_offensive_MeleeCritChance: `+200% Probabilidad Crítica Cuerpo a Cuerpo`,
invigorations_utility_AbilityEfficiency: `+75% Eficiencia de Habilidad`,
invigorations_utility_SprintSpeed: `+75% Velocidad de Sprint`,
invigorations_utility_ParkourVelocity: `+75% Velocidad de Parkour`,
invigorations_utility_HealthMax: `+1000 Salud Máx.`,
invigorations_utility_EnergyMax: `+200% Energía Máx.`,
invigorations_utility_StatusImmune: `Inmune a Efectos de Estado`,
invigorations_utility_ReloadSpeed: `+75% Velocidad de Recarga`,
invigorations_utility_HealthRegen: `+25 Regeneración de Salud/s`,
invigorations_utility_ArmorMax: `+1000 Armadura Máx.`,
invigorations_utility_Jumps: `+5 Restablecimientos de Salto`,
invigorations_utility_EnergyRegen: `+2 Regeneración de Energía/s`,
invigorations_offensiveLabel: `Mejora Ofensiva`,
invigorations_defensiveLabel: `Mejora Defensiva`,
invigorations_expiryLabel: `Caducidad de Mejoras (opcional)`,
abilityOverride_label: `Intercambio de Habilidad`,
abilityOverride_onSlot: `en el espacio`,
detailedView_archonShardsDescription2: `Ten en cuenta que cada fragmento de archón tarda un poco en aplicarse al cargar`,
detailedView_valenceBonusLabel: `Bônus de Valência`,
detailedView_valenceBonusDescription: `Puedes establecer o quitar el bono de valencia de tu arma.`,
mods_addRiven: `Agregar Agrietado`,
mods_fingerprint: `Huella digital`,
mods_fingerprintHelp: `¿Necesitas ayuda con la huella digital?`,
mods_rivens: `Agrietados`,
mods_mods: `Mods`,
mods_addMax: `Agregar al máximo`,
mods_addMax: `[UNTRANSLATED] Add Maxed`,
mods_addMissingUnrankedMods: `Agregar mods sin rango faltantes`,
mods_removeUnranked: `Quitar mods sin rango`,
mods_addMissingMaxRankMods: `Agregar mods de rango máximo faltantes`,
@ -180,18 +143,18 @@ dict = {
cheats_infiniteEndo: `Endo infinito`,
cheats_infiniteRegalAya: `Aya Real infinita`,
cheats_infiniteHelminthMaterials: `Materiales Helminto infinitos`,
cheats_claimingBlueprintRefundsIngredients: `Reclamar planos devuelve los ingredientes`,
cheats_dontSubtractPurchaseCreditCost: `No restar costo en créditos al comprar`,
cheats_dontSubtractPurchasePlatinumCost: `No restar costo en platino al comprar`,
cheats_dontSubtractPurchaseItemCost: `No restar costo de ítem al comprar`,
cheats_dontSubtractPurchaseStandingCost: `No restar costo en reputación al comprar`,
cheats_claimingBlueprintRefundsIngredients: `Reclamar ingredientes devueltos por planos`,
cheats_dontSubtractPurchaseCreditCost: `No restar costo en créditos de la compra`,
cheats_dontSubtractPurchasePlatinumCost: `No restar costo en platino de la compra`,
cheats_dontSubtractPurchaseItemCost: `No restar costo de ítem en la compra`,
cheats_dontSubtractPurchaseStandingCost: `No restar costo en reputación de la compra`,
cheats_dontSubtractVoidTraces: `No descontar vestigios del Vacío`,
cheats_dontSubtractConsumables: `No restar consumibles`,
cheats_unlockAllShipFeatures: `Desbloquear todas las funciones de nave`,
cheats_unlockAllShipDecorations: `Desbloquear todas las decoraciones de nave`,
cheats_unlockAllFlavourItems: `Desbloquear todos los <abbr title="Conjuntos de animaciones, glifos, paletas, etc.">ítems estéticos</abbr>`,
cheats_unlockAllSkins: `Desbloquear todas las skins`,
cheats_unlockAllCapturaScenes: `Desbloquear todas las escenas de Captura`,
cheats_unlockAllSkins: `Desbloquear todas las apariencias`,
cheats_unlockAllCapturaScenes: `Desbloquear todas las escenas Captura`,
cheats_unlockAllDecoRecipes: `Desbloquear todas las recetas decorativas del dojo`,
cheats_universalPolarityEverywhere: `Polaridad universal en todas partes`,
cheats_unlockDoubleCapacityPotatoesEverywhere: `Patatas en todas partes`,
@ -208,7 +171,7 @@ dict = {
cheats_baroAlwaysAvailable: `Baro siempre disponible`,
cheats_baroFullyStocked: `Baro con stock completo`,
cheats_syndicateMissionsRepeatable: `Misiones de sindicato rejugables`,
cheats_unlockAllProfitTakerStages: `Desbloquea todas las etapas del Roba-ganancias`,
cheats_unlockAllProfitTakerStages: `Deslobquea todas las etapas del Roba-ganancias`,
cheats_instantFinishRivenChallenge: `Terminar desafío de agrietado inmediatamente`,
cheats_instantResourceExtractorDrones: `Drones de extracción de recursos instantáneos`,
cheats_noResourceExtractorDronesDamage: `Sin daño a los drones extractores de recursos`,
@ -220,13 +183,13 @@ dict = {
cheats_noDojoResearchTime: `Sin tiempo de investigación del dojo`,
cheats_fastClanAscension: `Ascenso rápido del clan`,
cheats_missionsCanGiveAllRelics: `Las misiones pueden otorgar todas las reliquias`,
cheats_exceptionalRelicsAlwaysGiveBronzeReward: `Las reliquias excepcionales siempre otorgan recompensa de bronce`,
cheats_flawlessRelicsAlwaysGiveSilverReward: `Las reliquias impecables siempre otorgan recompensa de plata`,
cheats_radiantRelicsAlwaysGiveGoldReward: `Las reliquias radiantes siempre otorgan recompensa de oro`,
cheats_exceptionalRelicsAlwaysGiveBronzeReward: `[UNTRANSLATED] Exceptional Relics Always Give Bronze Reward`,
cheats_flawlessRelicsAlwaysGiveSilverReward: `[UNTRANSLATED] Flawless Relics Always Give Silver Reward`,
cheats_radiantRelicsAlwaysGiveGoldReward: `[UNTRANSLATED] Radiant Relics Always Give Gold Reward`,
cheats_unlockAllSimarisResearchEntries: `Desbloquear todas las entradas de investigación de Simaris`,
cheats_disableDailyTribute: `Desactivar tributo diario`,
cheats_spoofMasteryRank: `Rango de maestría simulado (-1 para desactivar)`,
cheats_relicRewardItemCountMultiplier: `Multiplicador de cantidad de recompensas de reliquia`,
cheats_relicRewardItemCountMultiplier: `[UNTRANSLATED] Relic Reward Item Count Multiplier`,
cheats_nightwaveStandingMultiplier: `Multiplicador de Reputación de Onda Nocturna`,
cheats_save: `Guardar`,
cheats_account: `Cuenta`,
@ -236,31 +199,14 @@ dict = {
cheats_intrinsicsUnlockAll: `Maximizar todos los intrínsecos`,
cheats_changeSupportedSyndicate: `Sindicatos disponibles`,
cheats_changeButton: `Cambiar`,
cheats_markAllAsRead: `Marcar bandeja de entrada como leída`,
cheats_none: `Ninguno`,
worldState: `Estado del mundo`,
worldState_creditBoost: `Potenciador de Créditos`,
worldState_affinityBoost: `Potenciador de Afinidad`,
worldState_resourceBoost: `Potenciador de Recursos`,
worldState_tennoLiveRelay: `Repetidor de TennoLive`,
worldState_baroTennoConRelay: `Repetidor de Baro de la TennoCon`,
worldState_starDays: `Días estelares`,
worldState_galleonOfGhouls: `Galeón de Gules`,
worldState_ghoulEmergence: `Purga de Gules`,
worldState_plagueStar: `Estrella Infestada`,
worldState_dogDays: `Canícula`,
worldState_dogDaysRewards: `Recompensas de Canícula`,
worldState_wolfHunt: `Cacería del Lobo (2025)`,
worldState_longShadow: `Sombra Prolongada`,
worldState_hallowedFlame: `Llama Sagrada`,
worldState_hallowedNightmares: `Pesadillas Sagradas`,
worldState_hallowedNightmaresRewards: `Recompensas de Pesadillas Sagradas`,
worldState_proxyRebellion: `Rebelión Proxy`,
worldState_proxyRebellionRewards: `Recompensas de Rebelión Proxy`,
worldState_from_year: `de |YEAR|`,
worldState_pre_year: `antes de |YEAR|`,
worldState_incompatibleWith: `No compatible con:`,
enabled: `Activado`,
disabled: `Desactivado`,
worldState_we1: `Semana 1`,
worldState_we2: `Semana 2`,
@ -300,8 +246,8 @@ dict = {
worldState_allAtOnceSteelPath: `Todo a la vez, Camino de Acero`,
worldState_theCircuitOverride: `Cambio del Circuito`,
worldState_darvoStockMultiplier: `Multiplicador de stock de Darvo`,
worldState_varziaFullyStocked: `Varzia con stock completo`,
worldState_varziaOverride: `Cambio en rotación de Varzia`,
worldState_varziaFullyStocked: `[UNTRANSLATED] Varzia Fully Stocked`,
worldState_varziaOverride: `[UNTRANSLATED] Varzia Rotation Override`,
import_importNote: `Puedes proporcionar una respuesta de inventario completa o parcial (representación del cliente) aquí. Todos los campos compatibles con el importador <b>serán sobrescritos</b> en tu cuenta.`,
import_submit: `Enviar`,
@ -314,11 +260,11 @@ dict = {
upgrade_SecondaryCritChance: `+|VAL|% de probabilidad crítica en armas secundarias`,
upgrade_WarframeAbilityDuration: `+|VAL|% de duración de habilidades`,
upgrade_WarframeAbilityStrength: `+|VAL|% de fuerza de habilidades`,
upgrade_WarframeArmorMax: `+|VAL| de armadura`,
upgrade_WarframeArmourMax: `+|VAL| de armadura`,
upgrade_WarframeBlastProc: `+|VAL| de escudos al matar con daño de explosión`,
upgrade_WarframeCastingSpeed: `+|VAL|% de velocidad de lanzamiento de habilidades`,
upgrade_WarframeCorrosiveDamageBoost: `+|VAL|% de daño de habilidades a enemigos con estado corrosivo`,
upgrade_WarframeCorrosiveStack: `Aumenta los stacks máximos de estado corrosivo en +|VAL|`,
upgrade_WarframeCorrosiveStack: `Aumenta los acumuladores máximos de estado corrosivo en +|VAL|`,
upgrade_WarframeCritDamageBoost: `+|VAL|% de daño crítico cuerpo a cuerpo (se duplica con más de 500 de energía)`,
upgrade_WarframeElectricDamage: `+|VAL1|% de daño eléctrico en armas primarias (+|VAL2|% por fragmento adicional)`,
upgrade_WarframeElectricDamageBoost: `+|VAL|% de daño de habilidades a enemigos con estado eléctrico`,
@ -329,7 +275,7 @@ dict = {
upgrade_WarframeHPBoostFromImpact: `+|VAL1| de salud al eliminar con daño explosivo (máx. |VAL2| de salud)`,
upgrade_WarframeParkourVelocity: `+|VAL|% de velocidad de parkour`,
upgrade_WarframeRadiationDamageBoost: `+|VAL|% de daño de habilidades a enemigos con estado radiactivo`,
upgrade_WarframeHealthRegen: `+|VAL| de regeneración de salud por segundo`,
upgrade_WarframeRegen: `+|VAL| de regeneración de salud por segundo`,
upgrade_WarframeShieldMax: `+|VAL| de escudo`,
upgrade_WarframeStartingEnergy: `+|VAL|% de energía al reaparecer`,
upgrade_WarframeToxinDamage: `+|VAL|% de daño por efecto de estado tóxico`,
@ -342,30 +288,30 @@ dict = {
upgrade_AvatarLootRadar: `+7m de radar de botín`,
upgrade_WeaponAmmoMax: `+15% de munición máxima`,
upgrade_EnemyArmorReductionAura: `-3% de armadura enemiga`,
upgrade_OnExecutionAmmo: `Recarga al 100% el cargador primario y secundario tras ejecución (Mercy)`,
upgrade_OnExecutionHealthDrop: `100% de probabilidad de soltar un orbe de salud tras ejecución (Mercy)`,
upgrade_OnExecutionEnergyDrop: `50% de probabilidad de soltar un orbe de energía tras ejecución (Mercy)`,
upgrade_OnExecutionAmmo: `Recarga al 100% el cargador primario y secundario tras ejecución (Misericordia)`,
upgrade_OnExecutionHealthDrop: `100% de probabilidad de soltar un orbe de salud tras ejecución (Misericordia)`,
upgrade_OnExecutionEnergyDrop: `50% de probabilidad de soltar un orbe de energía tras ejecución (Misericordia)`,
upgrade_OnFailHackReset: `+50% de probabilidad de reintento al fallar un hackeo`,
upgrade_DamageReductionOnHack: `75% de reducción de daño al hackear`,
upgrade_OnExecutionReviveCompanion: `Las ejecuciones reducen el tiempo de recuperación del compañero en 15s`,
upgrade_OnExecutionParkourSpeed: `+60% de velocidad de parkour durante 15s tras una ejecución (Mercy)`,
upgrade_OnExecutionParkourSpeed: `+60% de velocidad de parkour durante 15s tras una ejecución`,
upgrade_AvatarTimeLimitIncrease: `+8s para hackear`,
upgrade_ElectrifyOnHack: `Electrocuta a los enemigos en un radio de 20m al hackear`,
upgrade_OnExecutionTerrify: `50% de probabilidad de que enemigos en un radio de 15m entren en pánico por 8s tras una ejecución (Mercy)`,
upgrade_OnExecutionTerrify: `50% de probabilidad de que enemigos en un radio de 15m entren en pánico por 8s tras una ejecución`,
upgrade_OnHackLockers: `Desbloquea 5 casilleros en un radio de 20m tras hackear`,
upgrade_OnExecutionBlind: `Ciega a los enemigos en un radio de 18m tras una ejecución (Mercy)`,
upgrade_OnExecutionDrainPower: `La próxima habilidad usada gana +50% de fuerza al realizar tras una ejecución (Mercy)`,
upgrade_OnExecutionBlind: `Ciega a los enemigos en un radio de 18m tras una ejecución`,
upgrade_OnExecutionDrainPower: `100% de probabilidad de que la siguiente habilidad tenga +50% de fuerza tras una ejecución`,
upgrade_OnHackSprintSpeed: `+75% de velocidad de carrera durante 15s después de hackear`,
upgrade_SwiftExecute: `+50% de velocidad al ejecutar remates (Mercy)`,
upgrade_OnHackInvis: `Invisible durante 15 segundos después de hackear`,
upgrade_SwiftExecute: `[UNTRANSLATED] +50% Mercy Kill Speed`,
upgrade_OnHackInvis: `[UNTRANSLATED] Invisible for 15 seconds after Hacking`,
damageType_Electricity: `Eletricidad`,
damageType_Fire: `Calor`,
damageType_Freeze: `Frío`,
damageType_Impact: `Impacto`,
damageType_Electricity: `Eletricidade`,
damageType_Fire: `Ígneo`,
damageType_Freeze: `Glacial`,
damageType_Impact: `Colisivo`,
damageType_Magnetic: `Magnético`,
damageType_Poison: `Tóxico`,
damageType_Radiation: `Radiactivo`,
damageType_Radiation: `Radioativo`,
theme_dark: `Tema Oscuro`,
theme_light: `Tema Claro`,

View File

@ -1,11 +1,10 @@
// French translation by Vitruvio
dict = {
general_inventoryUpdateNote: `Note : Pour voir les changements en jeu, l'inventaire doit être actualisé. Cela se fait en tapant /sync dans le tchat, en visitant un dojo/relais ou en se reconnectant.`,
general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`,
general_addButton: `Ajouter`,
general_setButton: `Définir`,
general_none: `Aucun`,
general_setButton: `[UNTRANSLATED] Set`,
general_removeButton: `[UNTRANSLATED] Remove`,
general_bulkActions: `Action groupée`,
general_loading: `Chargement...`,
code_loginFail: `Connexion échouée. Vérifiez le mot de passe.`,
code_regFail: `Enregistrement impossible. Compte existant?`,
@ -46,8 +45,6 @@ dict = {
code_focusUnlocked: `|COUNT| écoles de Focus déverrouillées ! Synchronisation de l'inventaire nécessaire.`,
code_addModsConfirm: `Ajouter |COUNT| mods à l'inventaire ?`,
code_succImport: `Importé.`,
code_succRelog: `Succès. Un redémarrage du jeu est nécessaire.`,
code_nothingToDo: `Succès.`,
code_gild: `Polir`,
code_moa: `Moa`,
code_zanuka: `Molosse`,
@ -62,8 +59,6 @@ dict = {
code_pigment: `Pigment`,
code_mature: `Maturer pour le combat`,
code_unmature: `Régrésser l'âge génétique`,
code_succChange: `Changement effectué.`,
code_requiredInvigorationUpgrade: `Augmentation offensive et défensive requises.`,
login_description: `Connexion avec les informations de connexion OpenWF.`,
login_emailLabel: `Email`,
login_passwordLabel: `Mot de passe`,
@ -125,46 +120,14 @@ dict = {
detailedView_archonShardsDescription: `Slots illimités pour appliquer plusieurs améliorations`,
detailedView_archonShardsDescription2: `Un délai sera présent entre l'application des éclats et le chargement en jeu.`,
detailedView_valenceBonusLabel: `Bonus de Valence`,
detailedView_valenceBonusDescription: `Définir le Bonus Valence de l'arme.`,
detailedView_modularPartsLabel: `Changer l'équipement modulaire`,
detailedView_suitInvigorationLabel: `Invigoration de Warframe`,
detailedView_loadoutLabel: `Équipements`,
invigorations_offensive_AbilityStrength: `+200% de puissance de pouvoir`,
invigorations_offensive_AbilityRange: `+100% de portée de pouvoir`,
invigorations_offensive_AbilityDuration: `+100% de durée de pouvoir`,
invigorations_offensive_MeleeDamage: `+250% de dégâts de mêlée`,
invigorations_offensive_PrimaryDamage: `+250% de dégâts d'arme primaire`,
invigorations_offensive_SecondaryDamage: `+250% de dégâts d'arme secondaire`,
invigorations_offensive_PrimaryCritChance: `+200% de chances critique sur arme primaire`,
invigorations_offensive_SecondaryCritChance: `+200% de chances critique sur arme secondaire`,
invigorations_offensive_MeleeCritChance: `+200% de chances critique en mêlée`,
invigorations_utility_AbilityEfficiency: `+75% d'efficacité de pouvoir`,
invigorations_utility_SprintSpeed: `+75% de vitesse de course`,
invigorations_utility_ParkourVelocity: `+75% de vélocité de parkour`,
invigorations_utility_HealthMax: `+1000 de vie`,
invigorations_utility_EnergyMax: `+200% d'énergie max`,
invigorations_utility_StatusImmune: `Immunisé contre les effets de statut`,
invigorations_utility_ReloadSpeed: `+75% de vitesse de rechargement`,
invigorations_utility_HealthRegen: `+25 de vie régénérés/s`,
invigorations_utility_ArmorMax: `+1000 d'armure`,
invigorations_utility_Jumps: `+5 réinitialisations de saut`,
invigorations_utility_EnergyRegen: `+2 d'énergie régénérés/s`,
invigorations_offensiveLabel: `Amélioration offensive`,
invigorations_defensiveLabel: `Amélioration défensive`,
invigorations_expiryLabel: `Expiration de l'invigoration (optionnel)`,
abilityOverride_label: `Remplacement de pouvoir`,
abilityOverride_onSlot: `Sur l'emplacement`,
detailedView_valenceBonusDescription: `[UNTRANSLATED] You can set or remove the Valence Bonus from your weapon.`,
mods_addRiven: `Ajouter un riven`,
mods_fingerprint: `Empreinte`,
mods_fingerprintHelp: `Besoin d'aide pour l'empreinte ?`,
mods_rivens: `Rivens`,
mods_mods: `Mods`,
mods_addMax: `Ajouter les mods niveau max`,
mods_addMax: `[UNTRANSLATED] Add Maxed`,
mods_addMissingUnrankedMods: `Ajouter les mods sans rang manquants`,
mods_removeUnranked: `Retirer les mods sans rang`,
mods_addMissingMaxRankMods: `Ajouter les mods niveau max manquants`,
@ -174,7 +137,7 @@ dict = {
cheats_skipAllDialogue: `Passer les dialogues`,
cheats_unlockAllScans: `Débloquer tous les scans`,
cheats_unlockAllMissions: `Débloquer toutes les missions`,
cheats_unlockAllMissions_ok: `Succès. Une actualisation de l'inventaire est nécessaire.`,
cheats_unlockAllMissions_ok: `[UNTRANSLATED] Success. Please note that you'll need to enter a dojo/relay or relog for the client to refresh the star chart.`,
cheats_infiniteCredits: `Crédits infinis`,
cheats_infinitePlatinum: `Platinum infini`,
cheats_infiniteEndo: `Endo infini`,
@ -205,8 +168,8 @@ dict = {
cheats_noDeathMarks: `Aucune marque d'assassin`,
cheats_noKimCooldowns: `Aucun cooldown sur le KIM`,
cheats_fullyStockedVendors: `Les vendeurs ont un stock à 100%`,
cheats_baroAlwaysAvailable: `Baro toujours présent`,
cheats_baroFullyStocked: `Stock de Baro au max`,
cheats_baroAlwaysAvailable: `[UNTRANSLATED] Baro Always Available`,
cheats_baroFullyStocked: `[UNTRANSLATED] Baro Fully Stocked`,
cheats_syndicateMissionsRepeatable: `Mission syndicat répétables`,
cheats_unlockAllProfitTakerStages: `Débloquer toutes les étapes du Preneur de Profit`,
cheats_instantFinishRivenChallenge: `Débloquer le challenge Riven instantanément`,
@ -220,92 +183,75 @@ dict = {
cheats_noDojoResearchTime: `Aucun temps de recherche (Dojo)`,
cheats_fastClanAscension: `Ascension de clan rapide`,
cheats_missionsCanGiveAllRelics: `Les missions donnent toutes les reliques`,
cheats_exceptionalRelicsAlwaysGiveBronzeReward: `Les reliques exceptionnelles donnent toujours une récompense en bronze`,
cheats_flawlessRelicsAlwaysGiveSilverReward: `Les reliques parfaites donnent toujours une récompense en argent`,
cheats_radiantRelicsAlwaysGiveGoldReward: `Les reliques éclatantes donnent toujours une récompense en or`,
cheats_exceptionalRelicsAlwaysGiveBronzeReward: `[UNTRANSLATED] Exceptional Relics Always Give Bronze Reward`,
cheats_flawlessRelicsAlwaysGiveSilverReward: `[UNTRANSLATED] Flawless Relics Always Give Silver Reward`,
cheats_radiantRelicsAlwaysGiveGoldReward: `[UNTRANSLATED] Radiant Relics Always Give Gold Reward`,
cheats_unlockAllSimarisResearchEntries: `Débloquer toute les recherches chez Simaris`,
cheats_disableDailyTribute: `Désactiver la récompense quotidienne de connexion`,
cheats_disableDailyTribute: `[UNTRANSLATED] Disable Daily Tribute`,
cheats_spoofMasteryRank: `Rang de maîtrise personnalisé (-1 pour désactiver)`,
cheats_relicRewardItemCountMultiplier: `Multiplicateur de récompenses de relique`,
cheats_relicRewardItemCountMultiplier: `[UNTRANSLATED] Relic Reward Item Count Multiplier`,
cheats_nightwaveStandingMultiplier: `Multiplicateur de réputation d'Ondes Nocturnes`,
cheats_save: `Sauvegarder`,
cheats_account: `Compte`,
cheats_unlockAllFocusSchools: `Débloquer toutes les écoles de focus`,
cheats_helminthUnlockAll: `Helminth niveau max`,
cheats_addMissingSubsumedAbilities: `Ajouter les capacités subsumées manquantes`,
cheats_addMissingSubsumedAbilities: `[UNTRANSLATED] Add Missing Subsumed Abilities`,
cheats_intrinsicsUnlockAll: `Inhérences niveau max`,
cheats_changeSupportedSyndicate: `Allégeance`,
cheats_changeButton: `Changer`,
cheats_markAllAsRead: `Marquer la boîte de réception comme lue`,
cheats_none: `Aucun`,
worldState: `Carte Solaire`,
worldState_creditBoost: `Booster de Crédit`,
worldState_affinityBoost: `Booster d'Affinité`,
worldState_resourceBoost: `Booster de Ressource`,
worldState_tennoLiveRelay: `Relais TennoLive`,
worldState_baroTennoConRelay: `Relais Baro TennoCon`,
worldState_starDays: `Jours Stellaires`,
worldState_galleonOfGhouls: `Galion des Goules`,
worldState_ghoulEmergence: `Purge des Goules`,
worldState_plagueStar: `Fléau Céleste`,
worldState_dogDays: `Bataille d'Eau`,
worldState_dogDaysRewards: `[UNTRANSLATED] Dog Days Rewards`,
worldState_wolfHunt: `Chasse au Loup (2025)`,
worldState_longShadow: `La Propagation des Ombres`,
worldState_hallowedFlame: `Flamme Hantée`,
worldState_hallowedNightmares: `Cauchemars Hantés`,
worldState_hallowedNightmaresRewards: `[UNTRANSLATED] Hallowed Nightmares Rewards`,
worldState_proxyRebellion: `Rébellion Proxy`,
worldState_proxyRebellionRewards: `[UNTRANSLATED] Proxy Rebellion Rewards`,
worldState_from_year: `[UNTRANSLATED] from |YEAR|`,
worldState_pre_year: `[UNTRANSLATED] pre |YEAR|`,
worldState_incompatibleWith: `[UNTRANSLATED] Incompatible with:`,
enabled: `Activé`,
disabled: `Désactivé`,
worldState_we1: `Weekend 1`,
worldState_we2: `Weekend 2`,
worldState_we3: `Weekend 3`,
worldState_eidolonOverride: `Météo Plaines d'Eidolon`,
worldState_day: `Jour`,
worldState_night: `Nuit`,
worldState_vallisOverride: `Météo Vallée Orbis`,
worldState_warm: `Chaud`,
worldState_cold: `Froid`,
worldState_duviriOverride: `Spirale Duviri`,
worldState_joy: `Joie`,
worldState_anger: `Colère`,
worldState_envy: `Envie `,
worldState_sorrow: `hagrin`,
worldState_fear: `Peur`,
worldState_nightwaveOverride: `Saison d'Ondes Nocturnes`,
worldState_RadioLegionIntermission13Syndicate: `Mix de Nora Vol. 9`,
worldState_RadioLegionIntermission12Syndicate: `Mix de Nora Vol. 8`,
worldState_RadioLegionIntermission11Syndicate: `Mix de Nora Vol. 7`,
worldState_RadioLegionIntermission10Syndicate: `Mix de Nora Vol. 6`,
worldState_RadioLegionIntermission9Syndicate: `Mix de Nora Vol. 5`,
worldState_RadioLegionIntermission8Syndicate: `Mix de Nora Vol. 4`,
worldState_RadioLegionIntermission7Syndicate: `Mix de Nora Vol. 3`,
worldState_RadioLegionIntermission6Syndicate: `Mix de Nora Vol. 2`,
worldState_RadioLegionIntermission5Syndicate: `Mix de Nora Vol. 1`,
worldState_RadioLegionIntermission4Syndicate: `La Sélection de Nora`,
worldState_RadioLegionIntermission3Syndicate: `Intermission III`,
worldState_RadioLegion3Syndicate: `Les Mystères du Verre`,
worldState_RadioLegionIntermission2Syndicate: `Intermission II`,
worldState_RadioLegion2Syndicate: `L'Émissaire`,
worldState_RadioLegionIntermissionSyndicate: `Intermission I`,
worldState_RadioLegionSyndicate: `Le Loup de Saturne Six`,
worldState_fissures: `Fissures`,
normal: `Normal`,
worldState_allAtOnceNormal: `Toutes, Normal`,
worldState_allAtOnceSteelPath: `Toutes, Route de l'Acier`,
worldState_theCircuitOverride: `Remplacement du Circuit`,
worldState_darvoStockMultiplier: `Multiplicateur du stock de Darvo`,
worldState_varziaFullyStocked: `Stock de Varzia au max`,
worldState_varziaOverride: `Rotation de Varzia`,
worldState: `[UNTRANSLATED] World State`,
worldState_creditBoost: `[UNTRANSLATED] Credit Boost`,
worldState_affinityBoost: `[UNTRANSLATED] Affinity Boost`,
worldState_resourceBoost: `[UNTRANSLATED] Resource Boost`,
worldState_starDays: `[UNTRANSLATED] Star Days`,
worldState_galleonOfGhouls: `[UNTRANSLATED] Galleon of Ghouls`,
disabled: `[UNTRANSLATED] Disabled`,
worldState_we1: `[UNTRANSLATED] Weekend 1`,
worldState_we2: `[UNTRANSLATED] Weekend 2`,
worldState_we3: `[UNTRANSLATED] Weekend 3`,
worldState_eidolonOverride: `[UNTRANSLATED] Eidolon Override`,
worldState_day: `[UNTRANSLATED] Day`,
worldState_night: `[UNTRANSLATED] Night`,
worldState_vallisOverride: `[UNTRANSLATED] Orb Vallis Override`,
worldState_warm: `[UNTRANSLATED] Warm`,
worldState_cold: `[UNTRANSLATED] Cold`,
worldState_duviriOverride: `[UNTRANSLATED] Duviri Override`,
worldState_joy: `[UNTRANSLATED] Joy`,
worldState_anger: `[UNTRANSLATED] Anger`,
worldState_envy: `[UNTRANSLATED] Envy`,
worldState_sorrow: `[UNTRANSLATED] Sorrow`,
worldState_fear: `[UNTRANSLATED] Fear`,
worldState_nightwaveOverride: `[UNTRANSLATED] Nightwave Override`,
worldState_RadioLegionIntermission13Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 9`,
worldState_RadioLegionIntermission12Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 8`,
worldState_RadioLegionIntermission11Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 7`,
worldState_RadioLegionIntermission10Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 6`,
worldState_RadioLegionIntermission9Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 5`,
worldState_RadioLegionIntermission8Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 4`,
worldState_RadioLegionIntermission7Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 3`,
worldState_RadioLegionIntermission6Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 2`,
worldState_RadioLegionIntermission5Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 1`,
worldState_RadioLegionIntermission4Syndicate: `[UNTRANSLATED] Nora's Choice`,
worldState_RadioLegionIntermission3Syndicate: `[UNTRANSLATED] Intermission III`,
worldState_RadioLegion3Syndicate: `[UNTRANSLATED] Glassmaker`,
worldState_RadioLegionIntermission2Syndicate: `[UNTRANSLATED] Intermission II`,
worldState_RadioLegion2Syndicate: `[UNTRANSLATED] The Emissary`,
worldState_RadioLegionIntermissionSyndicate: `[UNTRANSLATED] Intermission I`,
worldState_RadioLegionSyndicate: `[UNTRANSLATED] The Wolf of Saturn Six`,
worldState_fissures: `[UNTRANSLATED] Fissures`,
normal: `[UNTRANSLATED] Normal`,
worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`,
worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`,
worldState_theCircuitOverride: `[UNTRANSLATED] The Circuit Override`,
worldState_darvoStockMultiplier: `[UNTRANSLATED] Darvo Stock Multiplier`,
worldState_varziaFullyStocked: `[UNTRANSLATED] Varzia Fully Stocked`,
worldState_varziaOverride: `[UNTRANSLATED] Varzia Rotation Override`,
import_importNote: `Import manuel. Toutes les modifcations supportées par l'inventaire <b>écraseront celles présentes dans la base de données</b>.`,
import_submit: `Soumettre`,
import_samples: `Échantillons :`,
import_samples: `Echantillons :`,
import_samples_maxFocus: `Toutes les écoles de focus au rang max`,
upgrade_Equilibrium: `Ramasser de la santé donne +|VAL|% d'énergie supplémentaire. Ramasser de l'énergie donne +|VAL|% de santé supplémentaire.`,
@ -314,7 +260,7 @@ dict = {
upgrade_SecondaryCritChance: `+|VAL|% de chance critique sur arme secondaire`,
upgrade_WarframeAbilityDuration: `+|VAL|% de durée de pouvoir`,
upgrade_WarframeAbilityStrength: `+|VAL|% de puissance de pouvoir`,
upgrade_WarframeArmorMax: `+|VAL| d'armure`,
upgrade_WarframeArmourMax: `+|VAL| d'armure`,
upgrade_WarframeBlastProc: `+|VAL| de boucliers sur élimination avec des dégats d'explosion`,
upgrade_WarframeCastingSpeed: `+|VAL|% de vitesse de lancement de pouvoir`,
upgrade_WarframeCorrosiveDamageBoost: `+|VAL|% de dégâts de pouvoir sur les ennemis affectés par du statut corrosif`,
@ -326,10 +272,10 @@ dict = {
upgrade_WarframeGlobeEffectEnergy: `+|VAL|% d'efficacité d'orbe d'énergie`,
upgrade_WarframeGlobeEffectHealth: `+|VAL|% d'efficacité d'orbe de santé`,
upgrade_WarframeHealthMax: `+|VAL| de santé`,
upgrade_WarframeHPBoostFromImpact: `+|VAL1| de vie sur élimination avec des dégâts d'explostion (Max |VAL2| Health)`,
upgrade_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health on kill with Blast Damage (Max |VAL2| Health)`,
upgrade_WarframeParkourVelocity: `+|VAL|% de vélocité de parkour`,
upgrade_WarframeRadiationDamageBoost: `+|VAL|% de dégâts de pouvoir sur les ennemis affectés par du statut radiation`,
upgrade_WarframeHealthRegen: `+|VAL| régénération de santé/s`,
upgrade_WarframeRegen: `+|VAL| régénération de santé/s`,
upgrade_WarframeShieldMax: `+|VAL| de boucliers`,
upgrade_WarframeStartingEnergy: `+|VAL|% d'énergie sur apparition`,
upgrade_WarframeToxinDamage: `+|VAL|% de dégâts sur le statut poison`,
@ -345,7 +291,7 @@ dict = {
upgrade_OnExecutionAmmo: `100% de rechargement des armes primaires et secondaires sur une une miséricorde`,
upgrade_OnExecutionHealthDrop: `100% de chance de drop une orbe de santé sur une miséricorde`,
upgrade_OnExecutionEnergyDrop: `50% de chance de drop une orbe d'énergie sur une miséricorde`,
upgrade_OnFailHackReset: `+50% de chance de refaire un piratage`,
upgrade_OnFailHackReset: `[UNTRANSLATED] +50% Hacking Retry Chance`,
upgrade_DamageReductionOnHack: `75% de réduction de dégâts pendant un piratage`,
upgrade_OnExecutionReviveCompanion: `Les miséricordes réduisent le temps de récupération du compagnon de 15s`,
upgrade_OnExecutionParkourSpeed: `+60% de vitesse de parkour pendant 15s après une miséricorde`,
@ -354,10 +300,10 @@ dict = {
upgrade_OnExecutionTerrify: `Les ennemis dans un rayon de 15m ont 50% de chance de s'enfuir après une miséricorde`,
upgrade_OnHackLockers: `5 casiers s'ouvrent dans un rayon de 20m après un piratage`,
upgrade_OnExecutionBlind: `Les ennemis sont aveuglés dans un rayon de 18 après une miséricorde`,
upgrade_OnExecutionDrainPower: `Le prochain pouvoir activé gagne +50% de puissance de pouvoir après une miséricorde`,
upgrade_OnExecutionDrainPower: `100% pour le prochain pouvoir de gagner +50% de puissance de pouvoir sur miséricorde`,
upgrade_OnHackSprintSpeed: `+75% de vitesse de course pendant 15s après un piratage`,
upgrade_SwiftExecute: `+50% de vitesse de d'éxecution en miséricorde`,
upgrade_OnHackInvis: `Invisible pendant 15s après un piratage`,
upgrade_SwiftExecute: `[UNTRANSLATED] +50% Mercy Kill Speed`,
upgrade_OnHackInvis: `[UNTRANSLATED] Invisible for 15 seconds after Hacking`,
damageType_Electricity: `Électrique`,
damageType_Fire: `Feu`,
@ -367,8 +313,8 @@ dict = {
damageType_Poison: `Poison`,
damageType_Radiation: `Radiations`,
theme_dark: `Thème sombre`,
theme_light: `Thème clair`,
theme_dark: `[UNTRANSLATED] Dark Theme`,
theme_light: `[UNTRANSLATED] Light Theme`,
prettier_sucks_ass: ``
};

View File

@ -1,14 +1,13 @@
// Russian translation by AMelonInsideLemon, LoseFace
// Russian translation by AMelonInsideLemon
dict = {
general_inventoryUpdateNote: `Примечание: Чтобы увидеть изменения в игре, вам нужно повторно синхронизировать свой инвентарь, например, используя команду /sync загрузчика, посетив Додзё/Реле или перезагрузив игру.`,
general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`,
general_addButton: `Добавить`,
general_setButton: `Установить`,
general_none: `Отсутствует`,
general_removeButton: `Удалить`,
general_bulkActions: `Массовые действия`,
general_loading: `Загрузка...`,
code_loginFail: `Не удалось войти. Проверьте адрес электронной почты и пароль.`,
code_regFail: `Не удалось зарегистрироваться. Учетная запись уже существует?`,
code_loginFail: `[UNTRANSLATED] Login failed. Double-check the email and password.`,
code_regFail: `[UNTRANSLATED] Registration failed. Account already exists?`,
code_changeNameConfirm: `Какое имя вы хотите установить для своей учетной записи?`,
code_deleteAccountConfirm: `Вы уверены, что хотите удалить аккаунт |DISPLAYNAME| (|EMAIL|)? Это действие нельзя отменить.`,
code_archgun: `Арч-Пушка`,
@ -34,9 +33,9 @@ dict = {
code_noEquipmentToRankUp: `Нет снаряжения для повышения ранга.`,
code_succAdded: `Успешно добавлено.`,
code_succRemoved: `Успешно удалено.`,
code_buffsNumber: `Количество позитивных эффектов`,
code_cursesNumber: `Количество негативных эффектов`,
code_rerollsNumber: `Количество рероллов`,
code_buffsNumber: `Количество усилений`,
code_cursesNumber: `Количество проклятий`,
code_rerollsNumber: `Количество циклов`,
code_viewStats: `Просмотр характеристики`,
code_rank: `Ранг`,
code_rankUp: `Повысить Ранг`,
@ -46,14 +45,12 @@ dict = {
code_focusUnlocked: `Разблокировано |COUNT| новых школ фокуса! Для отображения изменений в игре потребуется обновление инвентаря. Посещение навигации — самый простой способ этого добиться.`,
code_addModsConfirm: `Вы уверены, что хотите добавить |COUNT| модов на ваш аккаунт?`,
code_succImport: `Успешно импортировано.`,
code_succRelog: `Готово. Обратите внимание, что вам нужно будет перезайти, чтобы увидеть изменения в игре.`,
code_nothingToDo: `Готово. Нечего делать.`,
code_gild: `Улучшить`,
code_moa: `МОА`,
code_zanuka: `Гончая`,
code_stage: `Этап`,
code_complete: `Завершить`,
code_nextStage: `Следующий этап`,
code_nextStage: `Cледующий этап`,
code_prevStage: `Предыдущий этап`,
code_reset: `Сбросить`,
code_setInactive: `Сделать квест неактивным`,
@ -62,8 +59,6 @@ dict = {
code_pigment: `Пигмент`,
code_mature: `Подготовить к сражениям`,
code_unmature: `Регрессия генетического старения`,
code_succChange: `Успешно изменено.`,
code_requiredInvigorationUpgrade: `Вы должны выбрать как атакующее, так и вспомогательное улучшение.`,
login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`,
login_emailLabel: `Адрес электронной почты`,
login_passwordLabel: `Пароль`,
@ -93,14 +88,14 @@ dict = {
inventory_moaPets: `МОА`,
inventory_kubrowPets: `Звери`,
inventory_evolutionProgress: `Прогресс эволюции Инкарнонов`,
inventory_Boosters: `Бустеры`,
inventory_Boosters: `[UNTRANSLATED] Boosters`,
inventory_bulkAddSuits: `Добавить отсутствующие варфреймы`,
inventory_bulkAddWeapons: `Добавить отсутствующее оружие`,
inventory_bulkAddSpaceSuits: `Добавить отсутствующие арчвинги`,
inventory_bulkAddSpaceWeapons: `Добавить отсутствующее оружие арчвингов`,
inventory_bulkAddSentinels: `Добавить отсутствующих стражей`,
inventory_bulkAddSentinelWeapons: `Добавить отсутствующее оружие стражей`,
inventory_bulkAddEvolutionProgress: `Добавить отсутствующий прогресс эволюции Инкарнонов`,
inventory_bulkAddEvolutionProgress: `Добавить отсуствующий прогресс эволюции Инкарнонов`,
inventory_bulkRankUpSuits: `Максимальный ранг всех варфреймов`,
inventory_bulkRankUpWeapons: `Максимальный ранг всего оружия`,
inventory_bulkRankUpSpaceSuits: `Максимальный ранг всех арчвингов`,
@ -108,7 +103,7 @@ dict = {
inventory_bulkRankUpSentinels: `Максимальный ранг всех стражей`,
inventory_bulkRankUpSentinelWeapons: `Максимальный ранг всего оружия стражей`,
inventory_bulkRankUpEvolutionProgress: `Максимальный ранг всех эволюций Инкарнонов`,
inventory_maxPlexus: `Максимальный ранг Плексуса`,
inventory_maxPlexus: `[UNTRANSLATED] Max Rank Plexus`,
quests_list: `Квесты`,
quests_completeAll: `Завершить все квесты`,
@ -123,48 +118,16 @@ dict = {
detailedView_archonShardsLabel: `Ячейки осколков архонта`,
detailedView_archonShardsDescription: `Вы можете использовать эти неограниченные ячейки для установки множества улучшений.`,
detailedView_archonShardsDescription2: `Обратите внимание: каждый осколок архонта применяется с задержкой при загрузке.`,
detailedView_archonShardsDescription2: `Обратите внимание: каждый фрагмент архонта применяется с задержкой при загрузке.`,
detailedView_valenceBonusLabel: `Бонус Валентности`,
detailedView_valenceBonusDescription: `Вы можете установить или убрать бонус валентности с вашего оружия.`,
detailedView_modularPartsLabel: `Изменить Модульные Части`,
detailedView_suitInvigorationLabel: `Воодушевление Варфрейма`,
detailedView_loadoutLabel: `Конфигурации`,
invigorations_offensive_AbilityStrength: `+200% Сила Способностей`,
invigorations_offensive_AbilityRange: `+100% Радиус Способностей`,
invigorations_offensive_AbilityDuration: `+100% Длительность Способностей`,
invigorations_offensive_MeleeDamage: `+250% Урон Ближнего Боя`,
invigorations_offensive_PrimaryDamage: `+250% Урон Основного Оружия`,
invigorations_offensive_SecondaryDamage: `+250% Урон Вторичного Оружия`,
invigorations_offensive_PrimaryCritChance: `+200% Шанс Критического Урона Основного Оружия`,
invigorations_offensive_SecondaryCritChance: `+200% Шанс Критического Урона Вторичного Оружия`,
invigorations_offensive_MeleeCritChance: `+200% Шанс Критического Урона Ближнего Боя`,
invigorations_utility_AbilityEfficiency: `+75% Энергоэффективность Способностей`,
invigorations_utility_SprintSpeed: `+75% Скорость Бега`,
invigorations_utility_ParkourVelocity: `+75% Скорость Паркура`,
invigorations_utility_HealthMax: `+1000 Здоровья`,
invigorations_utility_EnergyMax: `+200% Максимум Энергии`,
invigorations_utility_StatusImmune: `Иммунитет к Эффектам Статуса`,
invigorations_utility_ReloadSpeed: `+75% Скорость Перезарядки`,
invigorations_utility_HealthRegen: `+25 Здоровья в секунду`,
invigorations_utility_ArmorMax: `+1000 Брони`,
invigorations_utility_Jumps: `+5 Сбросов Прыжков`,
invigorations_utility_EnergyRegen: `+2 Энергии в секунду`,
invigorations_offensiveLabel: `Атакующее Улучшение`,
invigorations_defensiveLabel: `Вспомогательное Улучшение`,
invigorations_expiryLabel: `Срок действия Воодушевления (необязательно)`,
abilityOverride_label: `Переопределение способности`,
abilityOverride_onSlot: `в ячейке`,
mods_addRiven: `Добавить Мод Разлома`,
mods_fingerprint: `Отпечаток`,
mods_fingerprintHelp: `Нужна помощь с отпечатком?`,
mods_rivens: `Моды Разлома`,
mods_mods: `Моды`,
mods_addMax: `Добавить максимально улучшенный`,
mods_addMax: `[UNTRANSLATED] Add Maxed`,
mods_addMissingUnrankedMods: `Добавить недостающие моды без ранга`,
mods_removeUnranked: `Удалить моды без ранга`,
mods_addMissingMaxRankMods: `Добавить недостающие моды максимального ранга`,
@ -174,19 +137,19 @@ dict = {
cheats_skipAllDialogue: `Пропустить все диалоги`,
cheats_unlockAllScans: `Разблокировать все сканирования`,
cheats_unlockAllMissions: `Разблокировать все миссии`,
cheats_unlockAllMissions_ok: `Успех. Пожалуйста, обратите внимание, что вам нужно будет войти в Додзё/Реле или перезайти, чтобы клиент обновил звездную карту.`,
cheats_unlockAllMissions_ok: `[UNTRANSLATED] Success. Please note that you'll need to enter a dojo/relay or relog for the client to refresh the star chart.`,
cheats_infiniteCredits: `Бесконечные кредиты`,
cheats_infinitePlatinum: `Бесконечная платина`,
cheats_infiniteEndo: `Бесконечное эндо`,
cheats_infiniteRegalAya: `Бесконечная Королевская Айя`,
cheats_infiniteHelminthMaterials: `Бесконечные Секреции Гельминта`,
cheats_claimingBlueprintRefundsIngredients: `Возврат ингредиентов чертежей`,
cheats_dontSubtractPurchaseCreditCost: `Не вычитать стоимость кредитов при покупке`,
cheats_dontSubtractPurchasePlatinumCost: `Не вычитать стоимость платины при покупке`,
cheats_dontSubtractPurchaseItemCost: `Не вычитать стоимость предметов при покупке`,
cheats_dontSubtractPurchaseStandingCost: `Не вычитать стоимость репутации при покупке`,
cheats_dontSubtractVoidTraces: `Не вычитать количество Отголосков Бездны`,
cheats_dontSubtractConsumables: `Не вычитать количество расходников`,
cheats_infiniteHelminthMaterials: `Бесконечные Выделения Гельминта`,
cheats_claimingBlueprintRefundsIngredients: `[UNTRANSLATED] Claiming Blueprint Refunds Ingredients`,
cheats_dontSubtractPurchaseCreditCost: `[UNTRANSLATED] Don't Subtract Purchase Credit Cost`,
cheats_dontSubtractPurchasePlatinumCost: `[UNTRANSLATED] Don't Subtract Purchase Platinum Cost`,
cheats_dontSubtractPurchaseItemCost: `[UNTRANSLATED] Don't Subtract Purchase Item Cost`,
cheats_dontSubtractPurchaseStandingCost: `[UNTRANSLATED] Don't Subtract Purchase Standing Cost`,
cheats_dontSubtractVoidTraces: `[UNTRANSLATED] Don't Subtract Void Traces`,
cheats_dontSubtractConsumables: `Не уменьшать количество расходников`,
cheats_unlockAllShipFeatures: `Разблокировать все функции корабля`,
cheats_unlockAllShipDecorations: `Разблокировать все украшения корабля`,
cheats_unlockAllFlavourItems: `Разблокировать все <abbr title="Наборы анимаций, глифы, палитры и т. д.">уникальные предметы</abbr>`,
@ -194,41 +157,41 @@ dict = {
cheats_unlockAllCapturaScenes: `Разблокировать все сцены Каптуры`,
cheats_unlockAllDecoRecipes: `Разблокировать все рецепты декораций Дoдзё`,
cheats_universalPolarityEverywhere: `Универсальная полярность везде`,
cheats_unlockDoubleCapacityPotatoesEverywhere: `Реакторы/Катализаторы орокин везде`,
cheats_unlockDoubleCapacityPotatoesEverywhere: `Катализаторы везде`,
cheats_unlockExilusEverywhere: `Адаптеры Эксилус везде`,
cheats_unlockArcanesEverywhere: `Адаптеры для мистификаторов везде`,
cheats_noDailyStandingLimits: `Без ежедневных лимитов репутации`,
cheats_noDailyFocusLimit: `Без ежедневных лимитов фокуса`,
cheats_noArgonCrystalDecay: `Без распада аргоновых кристаллов`,
cheats_noMasteryRankUpCooldown: `Повышение ранга мастерства без кулдауна`,
cheats_noVendorPurchaseLimits: `Отсутствие лимитов на покупки у торговцев`,
cheats_noDeathMarks: `Без меток смерти`,
cheats_noVendorPurchaseLimits: `Отсутствие лимитов на покупки у вендоров`,
cheats_noDeathMarks: `Без меток сметри`,
cheats_noKimCooldowns: `Чаты KIM без кулдауна`,
cheats_fullyStockedVendors: `Полностью укомплектованные торговцы`,
cheats_baroAlwaysAvailable: `Баро всегда доступен`,
cheats_baroFullyStocked: `Баро полностью укомплектован`,
cheats_syndicateMissionsRepeatable: `Повторять миссии синдиката`,
cheats_unlockAllProfitTakerStages: `Разблокировать все этапы Сферы извлечения прибыли`,
cheats_instantFinishRivenChallenge: `Мгновенное завершение испытания Мода разлома`,
cheats_instantResourceExtractorDrones: `Мгновенно добывающие дроны-сборщики`,
cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`,
cheats_baroAlwaysAvailable: `[UNTRANSLATED] Baro Always Available`,
cheats_baroFullyStocked: `[UNTRANSLATED] Baro Fully Stocked`,
cheats_syndicateMissionsRepeatable: `[UNTRANSLATED] Syndicate Missions Repeatable`,
cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`,
cheats_instantFinishRivenChallenge: `[UNTRANSLATED] Instant Finish Riven Challenge`,
cheats_instantResourceExtractorDrones: `Мгновенные Экстракторы Ресурсов`,
cheats_noResourceExtractorDronesDamage: `Без урона по дронам-сборщикам`,
cheats_skipClanKeyCrafting: `Пропустить крафт кланового ключа`,
cheats_noDojoRoomBuildStage: `Мгновенное строительство Комнат Додзё`,
cheats_noDojoDecoBuildStage: `Мгновенное строительство Декораций Додзё`,
cheats_noDojoRoomBuildStage: `Мгновенное Строительтво Комнат Додзё`,
cheats_noDojoDecoBuildStage: `Мгновенное Строительтво Декораций Додзё`,
cheats_fastDojoRoomDestruction: `Мгновенные Уничтожение Комнат Додзё`,
cheats_noDojoResearchCosts: `Бесплатные Исследование Додзё`,
cheats_noDojoResearchTime: `Мгновенные Исследование Додзё`,
cheats_fastClanAscension: `Мгновенное Вознесение Клана`,
cheats_missionsCanGiveAllRelics: `Миссии могут давать все реликвии`,
cheats_exceptionalRelicsAlwaysGiveBronzeReward: `Необычные реликвии всегда дают бронзовую награду`,
cheats_flawlessRelicsAlwaysGiveSilverReward: `Бесподобные реликвии всегда дают серебряную награду`,
cheats_radiantRelicsAlwaysGiveGoldReward: `Сияющие реликвии всегда дают золотую награду`,
cheats_unlockAllSimarisResearchEntries: `Разблокировать все записи исследований Симэриса`,
cheats_disableDailyTribute: `Отключить Ежедневные награды`,
cheats_spoofMasteryRank: `Поддельный ранг мастерства (-1 для отключения)`,
cheats_relicRewardItemCountMultiplier: `Мультипликатор количества предметов награды реликвии`,
cheats_nightwaveStandingMultiplier: `Мультипликатор репутации Ночной волны`,
cheats_save: `Сохранить`,
cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`,
cheats_exceptionalRelicsAlwaysGiveBronzeReward: `[UNTRANSLATED] Exceptional Relics Always Give Bronze Reward`,
cheats_flawlessRelicsAlwaysGiveSilverReward: `[UNTRANSLATED] Flawless Relics Always Give Silver Reward`,
cheats_radiantRelicsAlwaysGiveGoldReward: `[UNTRANSLATED] Radiant Relics Always Give Gold Reward`,
cheats_unlockAllSimarisResearchEntries: `[UNTRANSLATED] Unlock All Simaris Research Entries`,
cheats_disableDailyTribute: `[UNTRANSLATED] Disable Daily Tribute`,
cheats_spoofMasteryRank: `Подделанный ранг мастерства (-1 для отключения)`,
cheats_relicRewardItemCountMultiplier: `[UNTRANSLATED] Relic Reward Item Count Multiplier`,
cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`,
cheats_save: `[UNTRANSLATED] Save`,
cheats_account: `Аккаунт`,
cheats_unlockAllFocusSchools: `Разблокировать все школы фокуса`,
cheats_helminthUnlockAll: `Полностью улучшить Гельминта`,
@ -236,128 +199,111 @@ dict = {
cheats_intrinsicsUnlockAll: `Полностью улучшить Модуляры`,
cheats_changeSupportedSyndicate: `Поддерживаемый синдикат`,
cheats_changeButton: `Изменить`,
cheats_markAllAsRead: `Пометить все входящие как прочитанные`,
cheats_none: `Отсутствует`,
worldState: `Состояние мира`,
worldState_creditBoost: `Глобальный бустер кредитов`,
worldState_affinityBoost: `Глобальный бустер синтеза`,
worldState_resourceBoost: `Глобальный бустер ресурсов`,
worldState_tennoLiveRelay: `Реле TennoLive`,
worldState_baroTennoConRelay: `Реле Баро TennoCon`,
worldState_starDays: `Звёздные дни`,
worldState_galleonOfGhouls: `Галеон Гулей`,
worldState_ghoulEmergence: `Избавление от гулей`,
worldState_plagueStar: `Чумная звезда`,
worldState_dogDays: `Знойные дни`,
worldState_dogDaysRewards: `Награды Знойных дней`,
worldState_wolfHunt: `Волчья Охота (2025)`,
worldState_longShadow: `Длинная Тень`,
worldState_hallowedFlame: `Священное пламя`,
worldState_hallowedNightmares: `Священные Кошмары`,
worldState_hallowedNightmaresRewards: `Награды Священных Кошмаров`,
worldState_proxyRebellion: `Восстание Роботов`,
worldState_proxyRebellionRewards: `Награды Восстания Роботов`,
worldState_from_year: `из |YEAR|`,
worldState_pre_year: `до |YEAR|`,
worldState_incompatibleWith: `Несовместимо с:`,
enabled: `Включено`,
disabled: `Отключено`,
worldState_we1: `Выходные 1`,
worldState_we2: `Выходные 2`,
worldState_we3: `Выходные 3`,
worldState_eidolonOverride: `Цикл Равнин Эйдолона/Деймоса`,
worldState_day: `День/Фэз`,
worldState_night: `Ночь/Воум`,
worldState_vallisOverride: `Цикл Долины сфер`,
worldState_warm: `Тепло`,
worldState_cold: `Холод`,
worldState_duviriOverride: `Цикл Дувири`,
worldState_joy: `Радость`,
worldState_anger: `Гнев`,
worldState_envy: `Зависть`,
worldState_sorrow: `Печаль`,
worldState_fear: `Страх`,
worldState_nightwaveOverride: `Сезон Ночной волны`,
worldState_RadioLegionIntermission13Syndicate: `Микс Норы, Диск 9`,
worldState_RadioLegionIntermission12Syndicate: `Микс Норы, Диск 8`,
worldState_RadioLegionIntermission11Syndicate: `Микс Норы, Диск 7`,
worldState_RadioLegionIntermission10Syndicate: `Микс Норы, Диск 6`,
worldState_RadioLegionIntermission9Syndicate: `Микс Норы, Диск 5`,
worldState_RadioLegionIntermission8Syndicate: `Микс Норы, Диск 4`,
worldState_RadioLegionIntermission7Syndicate: `Микс Норы, Диск 3`,
worldState_RadioLegionIntermission6Syndicate: `Микс Норы, Диск 2`,
worldState_RadioLegionIntermission5Syndicate: `Микс Норы, Диск 1`,
worldState_RadioLegionIntermission4Syndicate: `Выбор Норы`,
worldState_RadioLegionIntermission3Syndicate: `Антракт III`,
worldState_RadioLegion3Syndicate: `Стеклодув`,
worldState_RadioLegionIntermission2Syndicate: `Антракт II`,
worldState_RadioLegion2Syndicate: `Эмиссар`,
worldState_RadioLegionIntermissionSyndicate: `Антракт I`,
worldState_RadioLegionSyndicate: `Волк Сатурна Шесть`,
worldState_fissures: `Разрывы бездны`,
normal: `Стандартные`,
worldState_allAtOnceNormal: `Все сразу, в обычном режиме`,
worldState_allAtOnceSteelPath: `Все сразу, в режиме Стального Пути`,
worldState_theCircuitOverride: `Типы миссий в подземелье Дувири`,
worldState_darvoStockMultiplier: `Множитель Запасов Дарво`,
worldState: `[UNTRANSLATED] World State`,
worldState_creditBoost: `[UNTRANSLATED] Credit Boost`,
worldState_affinityBoost: `[UNTRANSLATED] Affinity Boost`,
worldState_resourceBoost: `[UNTRANSLATED] Resource Boost`,
worldState_starDays: `[UNTRANSLATED] Star Days`,
worldState_galleonOfGhouls: `[UNTRANSLATED] Galleon of Ghouls`,
disabled: `[UNTRANSLATED] Disabled`,
worldState_we1: `[UNTRANSLATED] Weekend 1`,
worldState_we2: `[UNTRANSLATED] Weekend 2`,
worldState_we3: `[UNTRANSLATED] Weekend 3`,
worldState_eidolonOverride: `[UNTRANSLATED] Eidolon Override`,
worldState_day: `[UNTRANSLATED] Day`,
worldState_night: `[UNTRANSLATED] Night`,
worldState_vallisOverride: `[UNTRANSLATED] Orb Vallis Override`,
worldState_warm: `[UNTRANSLATED] Warm`,
worldState_cold: `[UNTRANSLATED] Cold`,
worldState_duviriOverride: `[UNTRANSLATED] Duviri Override`,
worldState_joy: `[UNTRANSLATED] Joy`,
worldState_anger: `[UNTRANSLATED] Anger`,
worldState_envy: `[UNTRANSLATED] Envy`,
worldState_sorrow: `[UNTRANSLATED] Sorrow`,
worldState_fear: `[UNTRANSLATED] Fear`,
worldState_nightwaveOverride: `[UNTRANSLATED] Nightwave Override`,
worldState_RadioLegionIntermission13Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 9`,
worldState_RadioLegionIntermission12Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 8`,
worldState_RadioLegionIntermission11Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 7`,
worldState_RadioLegionIntermission10Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 6`,
worldState_RadioLegionIntermission9Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 5`,
worldState_RadioLegionIntermission8Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 4`,
worldState_RadioLegionIntermission7Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 3`,
worldState_RadioLegionIntermission6Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 2`,
worldState_RadioLegionIntermission5Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 1`,
worldState_RadioLegionIntermission4Syndicate: `[UNTRANSLATED] Nora's Choice`,
worldState_RadioLegionIntermission3Syndicate: `[UNTRANSLATED] Intermission III`,
worldState_RadioLegion3Syndicate: `[UNTRANSLATED] Glassmaker`,
worldState_RadioLegionIntermission2Syndicate: `[UNTRANSLATED] Intermission II`,
worldState_RadioLegion2Syndicate: `[UNTRANSLATED] The Emissary`,
worldState_RadioLegionIntermissionSyndicate: `[UNTRANSLATED] Intermission I`,
worldState_RadioLegionSyndicate: `[UNTRANSLATED] The Wolf of Saturn Six`,
worldState_fissures: `[UNTRANSLATED] Fissures`,
normal: `[UNTRANSLATED] Normal`,
worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`,
worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`,
worldState_theCircuitOverride: `[UNTRANSLATED] The Circuit Override`,
worldState_darvoStockMultiplier: `[UNTRANSLATED] Darvo Stock Multiplier`,
worldState_varziaFullyStocked: `Полный Ассортимент Варзии`,
worldState_varziaOverride: `Изменение Ротации Варзии`,
import_importNote: `Вы можете загрузить полный или частичный ответ инвентаря (клиентское представление) здесь. Все поддерживаемые поля <b>будут перезаписаны</b> в вашем аккаунте.`,
import_submit: `Отправить`,
import_samples: `Пример:`,
import_samples_maxFocus: `Все школы Фокуса максимального уровня`,
import_samples: `[UNTRANSLATED] Samples:`,
import_samples_maxFocus: `[UNTRANSLATED] All Focus Schools Maxed Out`,
upgrade_Equilibrium: `+|VAL|% Энергия от подбирания здоровья, +|VAL|% Здоровье от подбирания энергии`,
upgrade_MeleeCritDamage: `+|VAL|% Критический урон ближнего боя`,
upgrade_PrimaryStatusChance: `+|VAL|% Шанс наложения статуса основным оружием`,
upgrade_SecondaryCritChance: `+|VAL|% Шанс критического удара вторичным оружием`,
upgrade_WarframeAbilityDuration: `+|VAL|% Длительность способностей`,
upgrade_WarframeAbilityStrength: `+|VAL|% Сила способностей`,
upgrade_WarframeArmorMax: `+|VAL| Броня`,
upgrade_WarframeBlastProc: `+|VAL| Щиты при убийстве с Взрывным Уроном`,
upgrade_WarframeCastingSpeed: `+|VAL|% Скорость Применения Способностей`,
upgrade_WarframeCorrosiveDamageBoost: `+|VAL|% Урон Способностей по врагам, пораженным Коррозией`,
upgrade_WarframeCorrosiveStack: `Увеличить макс. стаки Коррозии на +|VAL|`,
upgrade_WarframeCritDamageBoost: `+|VAL|% Критический Урон Ближнего Боя (Удваивается при 500 Энергии)`,
upgrade_WarframeElectricDamage: `+|VAL1|% Урон Электричеством Основным Оружием (+|VAL2|% за каждый дополнительный Осколок)`,
upgrade_WarframeElectricDamageBoost: `+|VAL|% Урон Способностей по врагам, пораженным Электричеством`,
upgrade_WarframeEnergyMax: `+|VAL| Макс. Энергия`,
upgrade_WarframeGlobeEffectEnergy: `+|VAL|% Эффективность сфер Энергии`,
upgrade_WarframeGlobeEffectHealth: `+|VAL|% Эффективность сфер Здоровья`,
upgrade_WarframeHealthMax: `+|VAL| Макс. Здоровье`,
upgrade_WarframeHPBoostFromImpact: `+|VAL1| Здоровья при убийстве с Взрывным Уроном (Макс. |VAL2| Здоровья)`,
upgrade_WarframeParkourVelocity: `+|VAL|% Скорость Паркура`,
upgrade_WarframeRadiationDamageBoost: `+|VAL|% Урон Способностей по врагам, пораженным Радиацией`,
upgrade_WarframeHealthRegen: `+|VAL| Здоровья в секунду`,
upgrade_WarframeShieldMax: `+|VAL| Щитов`,
upgrade_WarframeStartingEnergy: `+|VAL|% Энергии при Спавне`,
upgrade_WarframeToxinDamage: `+|VAL|% Урон Токсином`,
upgrade_WarframeToxinHeal: `+|VAL| Здоровья при нанесении урона врагам с Токсином`,
upgrade_WeaponCritBoostFromHeat: `+|VAL1|% Шанс Критического Удара Вторичным Оружием за каждого убитого врага, пораженного Огнем (Макс. |VAL2|%)`,
upgrade_AvatarAbilityRange: `+7.5% Радиус Способностей`,
upgrade_AvatarAbilityEfficiency: `+5% Энергоэффективность Способностей`,
upgrade_AvatarEnergyRegen: `+0.5 Регенерация Энергии в секунду`,
upgrade_AvatarEnemyRadar: `+5m обнаружение врагов`,
upgrade_AvatarLootRadar: `+7m обнаружение добычи`,
upgrade_WeaponAmmoMax: `+15% Макс. Патронов`,
upgrade_EnemyArmorReductionAura: `-3% Броня Врагов`,
upgrade_OnExecutionAmmo: `+100% Заполнение Магазина Основного и Вторичного Оружия при убийстве Милосердием`,
upgrade_OnExecutionHealthDrop: `+100% Шанс Падения сферы Здоровья при убийстве Милосердием`,
upgrade_OnExecutionEnergyDrop: `+50% Шанс Падения сферы Энергии при убийстве Милосердием`,
upgrade_OnFailHackReset: `+50% Шанс Повтора Взлома`,
upgrade_DamageReductionOnHack: `+75% Уменьшение Урона во время Взлома`,
upgrade_OnExecutionReviveCompanion: `Убийства Милосердием уменьшают время восстановления Компаньона на 15 секунд`,
upgrade_OnExecutionParkourSpeed: `+60% Скорость Паркура после убийства Милосердием на 15 секунд`,
upgrade_AvatarTimeLimitIncrease: `+8 секунд к Взлому`,
upgrade_ElectrifyOnHack: `Шокировать врагов в пределах 20 метров во время Взлома`,
upgrade_OnExecutionTerrify: `+50% шанс, что враги в пределах 15 метров будут дрожать от страха в течение 8 секунд после убийства Милосердием`,
upgrade_OnHackLockers: `Открыть 5 шкафчиков в пределах 20 метров после Взлома`,
upgrade_OnExecutionBlind: `Ослепить врагов в пределах 18 метров после убийства Милосердием`,
upgrade_OnExecutionDrainPower: `Следующее использование способности получает +50% Силы Способности после убийства Милосердием`,
upgrade_OnHackSprintSpeed: `+75% Скорость Бега в течение 15 секунд после Взлома`,
upgrade_SwiftExecute: `+50% Скорость Убийства Милосердием`,
upgrade_OnHackInvis: `Невидимость в течение 15 секунд после Взлома`,
upgrade_Equilibrium: `[UNTRANSLATED] +|VAL|% Energy from Health pickups, +|VAL|% Health from Energy pickups`,
upgrade_MeleeCritDamage: `[UNTRANSLATED] +|VAL|% Melee Critical Damage`,
upgrade_PrimaryStatusChance: `[UNTRANSLATED] +|VAL|% Primary Status Chance`,
upgrade_SecondaryCritChance: `[UNTRANSLATED] +|VAL|% Secondary Critical Chance`,
upgrade_WarframeAbilityDuration: `[UNTRANSLATED] +|VAL|% Ability Duration`,
upgrade_WarframeAbilityStrength: `[UNTRANSLATED] +|VAL|% Ability Strength`,
upgrade_WarframeArmourMax: `[UNTRANSLATED] +|VAL| Armor`,
upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on kill with Blast Damage`,
upgrade_WarframeCastingSpeed: `[UNTRANSLATED] +|VAL|% Casting Speed`,
upgrade_WarframeCorrosiveDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Corrosion Status`,
upgrade_WarframeCorrosiveStack: `[UNTRANSLATED] Increase max stacks of Corrosion Status by +|VAL|`,
upgrade_WarframeCritDamageBoost: `[UNTRANSLATED] +|VAL|% Melee Critical Damage (Doubles over 500 Energy)`,
upgrade_WarframeElectricDamage: `[UNTRANSLATED] +|VAL1|% Primary Electricity Damage (+|VAL2|% per additional Shard)`,
upgrade_WarframeElectricDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Electricity Status`,
upgrade_WarframeEnergyMax: `[UNTRANSLATED] +|VAL| Energy Max`,
upgrade_WarframeGlobeEffectEnergy: `[UNTRANSLATED] +|VAL|% Energy Orb Effectiveness`,
upgrade_WarframeGlobeEffectHealth: `[UNTRANSLATED] +|VAL|% Health Orb Effectiveness`,
upgrade_WarframeHealthMax: `[UNTRANSLATED] +|VAL| Health`,
upgrade_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health on kill with Blast Damage (Max |VAL2| Health)`,
upgrade_WarframeParkourVelocity: `[UNTRANSLATED] +|VAL|% Parkour Velocity`,
upgrade_WarframeRadiationDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Radiation Status`,
upgrade_WarframeRegen: `[UNTRANSLATED] +|VAL| Health Regen/s`,
upgrade_WarframeShieldMax: `[UNTRANSLATED] +|VAL| Shield`,
upgrade_WarframeStartingEnergy: `[UNTRANSLATED] +|VAL|% Energy on Spawn`,
upgrade_WarframeToxinDamage: `[UNTRANSLATED] +|VAL|% Toxin Status Effect Damage`,
upgrade_WarframeToxinHeal: `[UNTRANSLATED] +|VAL| Health on damaging enemies with Toxin Status`,
upgrade_WeaponCritBoostFromHeat: `[UNTRANSLATED] +|VAL1|% Secondary Critical Chance per Heat-affected enemy killed (Max |VAL2|%)`,
upgrade_AvatarAbilityRange: `[UNTRANSLATED] +7.5% Ability Range`,
upgrade_AvatarAbilityEfficiency: `[UNTRANSLATED] +5% Ability Efficiency`,
upgrade_AvatarEnergyRegen: `[UNTRANSLATED] +0.5 Energy Regen/s`,
upgrade_AvatarEnemyRadar: `[UNTRANSLATED] +5m Enemy Radar`,
upgrade_AvatarLootRadar: `[UNTRANSLATED] +7m Loot Radar`,
upgrade_WeaponAmmoMax: `[UNTRANSLATED] +15% Ammo Max`,
upgrade_EnemyArmorReductionAura: `[UNTRANSLATED] -3% Enemy Armor`,
upgrade_OnExecutionAmmo: `[UNTRANSLATED] +100% Primary and Secondary Magazine Refill on Mercy`,
upgrade_OnExecutionHealthDrop: `[UNTRANSLATED] +100% Health Orb Chance on Mercy`,
upgrade_OnExecutionEnergyDrop: `[UNTRANSLATED] +50% Energy Orb Chance on Mercy`,
upgrade_OnFailHackReset: `[UNTRANSLATED] +50% Hacking Retry Chance`,
upgrade_DamageReductionOnHack: `[UNTRANSLATED] +75% Damage Reduction while Hacking`,
upgrade_OnExecutionReviveCompanion: `[UNTRANSLATED] Mercy Kills reduce Companion Recovery by 15s`,
upgrade_OnExecutionParkourSpeed: `[UNTRANSLATED] +60% Parkour Speed after a Mercy for 15s`,
upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] +8s to Hacking`,
upgrade_ElectrifyOnHack: `[UNTRANSLATED] Shock enemies within 20m while Hacking`,
upgrade_OnExecutionTerrify: `[UNTRANSLATED] +50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`,
upgrade_OnHackLockers: `[UNTRANSLATED] Unlock 5 lockers within 20m after Hacking`,
upgrade_OnExecutionBlind: `[UNTRANSLATED] Blind enemies within 18m on Mercy`,
upgrade_OnExecutionDrainPower: `[UNTRANSLATED] +100% chance for next ability cast to gain +50% Ability Strength on Mercy`,
upgrade_OnHackSprintSpeed: `[UNTRANSLATED] +75% Sprint Speed for 15s after Hacking`,
upgrade_SwiftExecute: `[UNTRANSLATED] +50% Mercy Kill Speed`,
upgrade_OnHackInvis: `[UNTRANSLATED] Invisible for 15 seconds after Hacking`,
damageType_Electricity: `Электричество`,
damageType_Fire: `Огонь`,
@ -367,8 +313,8 @@ dict = {
damageType_Poison: `Токсин`,
damageType_Radiation: `Радиация`,
theme_dark: `Темная тема`,
theme_light: `Светлая тема`,
theme_dark: `[UNTRANSLATED] Dark Theme`,
theme_light: `[UNTRANSLATED] Light Theme`,
prettier_sucks_ass: ``
};

View File

@ -1,374 +0,0 @@
// Ukrainian translation by LoseFace
dict = {
general_inventoryUpdateNote: `Пам'ятка: Щоб побачити зміни в грі, вам потрібно повторно синхронізувати свій інвентар, наприклад, використовуючи команду /sync завантажувача, відвідавши Доджьо/Реле або перезавантаживши гру.`,
general_addButton: `Добавити`,
general_setButton: `Встановити`,
general_none: `Відсутній`,
general_bulkActions: `Масові дії`,
general_loading: `Завантаження...`,
code_loginFail: `Не вдалося увійти. Перевірте адресу електронної пошти та пароль.`,
code_regFail: `Не вдалося зареєструватися. Обліковий запис вже існує?`,
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_kDrive: `К-Драйв`,
code_legendaryCore: `Легендарне ядро`,
code_traumaticPeculiar: `Травмуюча Странність`,
code_starter: `|MOD| (Пошкоджений)`,
code_badItem: `(Самозванець)`,
code_maxRank: `Максимальний рівень`,
code_rename: `Переіменувати`,
code_renamePrompt: `Введіть нове ім'я:`,
code_remove: `Видалити`,
code_addItemsConfirm: `Ви впевнені, що хочете додати |COUNT| предметів на ваш обліковий запис?`,
code_succRankUp: `Рівень успішно підвищено`,
code_noEquipmentToRankUp: `Немає спорядження для підвищення рівня.`,
code_succAdded: `Успішно додано.`,
code_succRemoved: `Успішно видалено.`,
code_buffsNumber: `Кількість позитивних ефектів`,
code_cursesNumber: `Кількість негативних ефектів`,
code_rerollsNumber: `Кількість рероллів`,
code_viewStats: `Перегляд характеристики`,
code_rank: `Рівень`,
code_rankUp: `Підвищити рівень`,
code_rankDown: `Понизити рівень`,
code_count: `Кількість`,
code_focusAllUnlocked: `Всі школи фокуса вже розблоковані.`,
code_focusUnlocked: `Розблоковано |COUNT| нових шкіл фокуса! Для відображення змін в грі знадобиться оновлення спорядження. Відвідування навігації — найпростіший спосіб цього досягти.`,
code_addModsConfirm: `Ви впевнені, що хочете додати |COUNT| модифікаторів на ваш обліковий запис?`,
code_succImport: `Успішно імпортовано.`,
code_succRelog: `Готово. Зверніть увагу, що вам потрібно буде перезайти, щоб побачити зміни в грі.`,
code_nothingToDo: `Готово. Немає що робити.`,
code_gild: `Покращити`,
code_moa: `МОА`,
code_zanuka: `Гончарка`,
code_stage: `Етап`,
code_complete: `Завершити`,
code_nextStage: `Наступний етап`,
code_prevStage: `Попередній етап`,
code_reset: `Скинути`,
code_setInactive: `Зробити пригоду неактивною`,
code_completed: `Завершено`,
code_active: `Активний`,
code_pigment: `Пігмент`,
code_mature: `Підготувати до бою`,
code_unmature: `Обернути старіння`,
code_succChange: `Успішно змінено.`,
code_requiredInvigorationUpgrade: `Ви повинні вибрати як атакуюче, так і допоміжне покращення.`,
login_description: `Увійдіть, використовуючи облікові дані OpenWF (ті ж, що й у грі при підключенні до цього сервера).`,
login_emailLabel: `Адреса електронної пошти`,
login_passwordLabel: `Пароль`,
login_loginButton: `Увійти`,
login_registerButton: `Зареєструватися`,
navbar_logout: `Вийти`,
navbar_renameAccount: `Переіменувати обліковий запис`,
navbar_deleteAccount: `Видалити обліковий запис`,
navbar_inventory: `Спорядження`,
navbar_mods: `Моди`,
navbar_quests: `Пригоди`,
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_moaPets: `МОА`,
inventory_kubrowPets: `Тварини`,
inventory_evolutionProgress: `Прогрес Еволюції Інкарнонов`,
inventory_Boosters: `Бустери`,
inventory_bulkAddSuits: `Додати відсутні ворфрейми`,
inventory_bulkAddWeapons: `Додати відсутнє озброєння`,
inventory_bulkAddSpaceSuits: `Додати відсутні арквінґи`,
inventory_bulkAddSpaceWeapons: `Додати відсутнє озброєння арквінґів`,
inventory_bulkAddSentinels: `Додати відсутніх вартових`,
inventory_bulkAddSentinelWeapons: `Додати відсутнє озброєння вартових`,
inventory_bulkAddEvolutionProgress: `Додати відсутній прогрес Еволюції Інкарнонов`,
inventory_bulkRankUpSuits: `Максимальний рівень всіх ворфреймів`,
inventory_bulkRankUpWeapons: `Максимальний рівень всього озброєння`,
inventory_bulkRankUpSpaceSuits: `Максимальний рівень всіх арквінґів`,
inventory_bulkRankUpSpaceWeapons: `Максимальний рівень всього озброєння арквінґів`,
inventory_bulkRankUpSentinels: `Максимальний рівень всіх вартових`,
inventory_bulkRankUpSentinelWeapons: `Максимальний рівень всього озброєння вартових`,
inventory_bulkRankUpEvolutionProgress: `Максимальний рівень всіх еволюцій Інкарнонов`,
inventory_maxPlexus: `Максимальний рівень Плексу`,
quests_list: `Пригоди`,
quests_completeAll: `Закінчити всі пригоди`,
quests_resetAll: `Скинути прогрес всіх пригод`,
quests_giveAll: `Видати всі пригоди`,
currency_RegularCredits: `Кредити`,
currency_PremiumCredits: `Платина`,
currency_FusionPoints: `Ендо`,
currency_PrimeTokens: `Королівські Ая`,
currency_owned: `У тебе |COUNT|.`,
detailedView_archonShardsLabel: `Клітинки осколків архонта`,
detailedView_archonShardsDescription: `Ви можете використовувати ці необмежені клітинки для встановлення безлічі вдосконалень.`,
detailedView_archonShardsDescription2: `Зверніть увагу: кожен уламок архонта застосовується з затримкою при завантаженні.`,
detailedView_valenceBonusLabel: `Ознака Валентності`,
detailedView_valenceBonusDescription: `Ви можете встановити або прибрати ознака валентності з вашої зброї.`,
detailedView_modularPartsLabel: `Змінити Модульні Частини`,
detailedView_suitInvigorationLabel: `Зміцнення Ворфрейма`,
detailedView_loadoutLabel: `Конфігурації`,
invigorations_offensive_AbilityStrength: `+200% Потужності Здібностей`,
invigorations_offensive_AbilityRange: `+100% Досяжність Здібностей`,
invigorations_offensive_AbilityDuration: `+100% Тривалість Здібностей`,
invigorations_offensive_MeleeDamage: `+250% Шкода Ближнього Бою`,
invigorations_offensive_PrimaryDamage: `+250% Шкода Основного Озброєння`,
invigorations_offensive_SecondaryDamage: `+250% Шкода Допоміжного Озброєння`,
invigorations_offensive_PrimaryCritChance: `+200% Імовірність Критичної Шкоди Основного Озброєння`,
invigorations_offensive_SecondaryCritChance: `+200% Імовірність Критичної Шкоди Допоміжного Озброєння`,
invigorations_offensive_MeleeCritChance: `+200% Імовірність Критичної Шкоди Ближнього Бою`,
invigorations_utility_AbilityEfficiency: `+75% Ощадливість Здібностей`,
invigorations_utility_SprintSpeed: `+75% Швидкість Бігу`,
invigorations_utility_ParkourVelocity: `+75% Швидкість Паркура`,
invigorations_utility_HealthMax: `+1000 Здоров'я`,
invigorations_utility_EnergyMax: `+200% Максимум Енергії`,
invigorations_utility_StatusImmune: `Імунітет до Ефектів Статусу`,
invigorations_utility_ReloadSpeed: `+75% Швидкість Перезаряджання`,
invigorations_utility_HealthRegen: `+25 Здоров'я в секунду`,
invigorations_utility_ArmorMax: `+1000 Захисту`,
invigorations_utility_Jumps: `+5 Оновлень Стрибків`,
invigorations_utility_EnergyRegen: `+2 Енергії в секунду`,
invigorations_offensiveLabel: `Атакуюче Вдосконалення`,
invigorations_defensiveLabel: `Вспомогательное Вдосконалення`,
invigorations_expiryLabel: `Термін дії Зміцнення (необов'язково)`,
abilityOverride_label: `Перевизначення здібностей`,
abilityOverride_onSlot: `у клітинці`,
mods_addRiven: `Добавити Модифікатор Розколу`,
mods_fingerprint: `Відбиток`,
mods_fingerprintHelp: `Потрібна допомога з відбитком?`,
mods_rivens: `Модифікатори Розколу`,
mods_mods: `Модифікатори`,
mods_addMax: `Добавити максимально вдосконалений`,
mods_addMissingUnrankedMods: `Добавити недостаючі модифікатори без рівня`,
mods_removeUnranked: `Видалити модифікатори без рівня`,
mods_addMissingMaxRankMods: `Добавити недостаючі модифікатори максимального рівня`,
cheats_administratorRequirement: `Ви повинні бути адміністратором для використання цієї функції. Щоб стати адміністратором, додайте <code>\"|DISPLAYNAME|\"</code> в <code>administratorNames</code> в config.json.`,
cheats_server: `Сервер`,
cheats_skipTutorial: `Пропустити навчання`,
cheats_skipAllDialogue: `Пропустити всі діалоги`,
cheats_unlockAllScans: `Розблокувати всі сканування`,
cheats_unlockAllMissions: `Розблокувати всі місії`,
cheats_unlockAllMissions_ok: `Успіх. Будь ласка, зверніть увагу, що вам потрібно буде увійти в Доджьо/Реле або перезайти, щоб клієнт оновив зоряну мапу.`,
cheats_infiniteCredits: `Бескінечні кредити`,
cheats_infinitePlatinum: `Бескінечна платина`,
cheats_infiniteEndo: `Бескінечне ендо`,
cheats_infiniteRegalAya: `Бескінечна Королівська Ая`,
cheats_infiniteHelminthMaterials: `Бескінечні Секреції Гельмінта`,
cheats_claimingBlueprintRefundsIngredients: `Повернення інгредієнтів креслеників`,
cheats_dontSubtractPurchaseCreditCost: `Не вираховувати вартість кредитів при купівлі`,
cheats_dontSubtractPurchasePlatinumCost: `Не вираховувати вартість платини при купівлі`,
cheats_dontSubtractPurchaseItemCost: `Не вираховувати вартість предметів при купівлі`,
cheats_dontSubtractPurchaseStandingCost: `Не вираховувати вартість репутації при купівлі`,
cheats_dontSubtractVoidTraces: `Не вираховувати кількість Відголосків Безодні`,
cheats_dontSubtractConsumables: `Не вираховувати кількість витратних матеріалів`,
cheats_unlockAllShipFeatures: `Розблокувати всі функції судна`,
cheats_unlockAllShipDecorations: `Розблокувати всі прикраси судна`,
cheats_unlockAllFlavourItems: `Розблокувати всі <abbr title="Набори анімацій, гліфи, палітри і т. д.">унікальні предмети</abbr>`,
cheats_unlockAllSkins: `Розблокувати всі скіни`,
cheats_unlockAllCapturaScenes: `Розблокувати всі сцени Світлописця`,
cheats_unlockAllDecoRecipes: `Розблокувати всі рецепти декорацій Доджьо`,
cheats_universalPolarityEverywhere: `Будь-яка полярність скрізь`,
cheats_unlockDoubleCapacityPotatoesEverywhere: `Орокінські Реактори/Каталізатори скрізь`,
cheats_unlockExilusEverywhere: `Ексилотримач скрізь`,
cheats_unlockArcanesEverywhere: `Тримач містифікаторів скрізь`,
cheats_noDailyStandingLimits: `Без щоденних лімітів репутації`,
cheats_noDailyFocusLimit: `Без щоденних лімітів фокуса`,
cheats_noArgonCrystalDecay: `Без розпаду аргонових кристалів`,
cheats_noMasteryRankUpCooldown: `Підвищення ранга майстерності без очікування`,
cheats_noVendorPurchaseLimits: `Відсутність лімітів на купівлю у продавців`,
cheats_noDeathMarks: `Без позначок смерті`,
cheats_noKimCooldowns: `Чати KIM без очікування`,
cheats_fullyStockedVendors: `Повністю укомплектовані продавці`,
cheats_baroAlwaysAvailable: `Баро завжди доступний`,
cheats_baroFullyStocked: `Баро повністю укомплектований`,
cheats_syndicateMissionsRepeatable: `Повторювати місії синдиката`,
cheats_unlockAllProfitTakerStages: `Розблокувати всі етапи Привласнювачки`,
cheats_instantFinishRivenChallenge: `Миттєве завершення випробування Модифікатора Розколу`,
cheats_instantResourceExtractorDrones: `Миттєво добуваючі дрони-видобувачі`,
cheats_noResourceExtractorDronesDamage: `Без шкоди по дронам-видобувачам`,
cheats_skipClanKeyCrafting: `Пропустити створення кланового ключа`,
cheats_noDojoRoomBuildStage: `Миттєве будівництво Кімнат Доджьо`,
cheats_noDojoDecoBuildStage: `Миттєве будівництво Декорацій Доджьо`,
cheats_fastDojoRoomDestruction: `Миттєве знищення Кімнат Доджьо`,
cheats_noDojoResearchCosts: `Безкоштовні Дослідження Доджьо`,
cheats_noDojoResearchTime: `Миттєві Дослідження Доджьо`,
cheats_fastClanAscension: `Миттєве Піднесення Клану`,
cheats_missionsCanGiveAllRelics: `Місії можуть давати всі реліквії`,
cheats_exceptionalRelicsAlwaysGiveBronzeReward: `Вийняткові реліквії завжди дають бронзову нагороду`,
cheats_flawlessRelicsAlwaysGiveSilverReward: `Бездоганні реліквії завжди дають срібну нагороду`,
cheats_radiantRelicsAlwaysGiveGoldReward: `Сяйнисті реліквії завжди дають золоту нагороду`,
cheats_unlockAllSimarisResearchEntries: `Розблокувати всі записи досліджень Симаріса`,
cheats_disableDailyTribute: `Вимкнути щоденні нагороди`,
cheats_spoofMasteryRank: `Підроблений ранг майстерності (-1 для вимкнення)`,
cheats_relicRewardItemCountMultiplier: `Множник кількості предметів нагороди реліквії`,
cheats_nightwaveStandingMultiplier: `Множник репутації Нічної хвилі`,
cheats_save: `Зберегти`,
cheats_account: `Обліковий запис`,
cheats_unlockAllFocusSchools: `Розблокувати всі школи фокуса`,
cheats_helminthUnlockAll: `Повністю покращити Гельмінта`,
cheats_addMissingSubsumedAbilities: `Додати відсутні поглинуті здібності`,
cheats_intrinsicsUnlockAll: `Повністю покращити Кваліфікації`,
cheats_changeSupportedSyndicate: `Підтримуваний синдикат`,
cheats_changeButton: `Змінити`,
cheats_markAllAsRead: `Помітити всі вхідні як прочитані`,
worldState: `Стан світу`,
worldState_creditBoost: `Глобальний бустер кредитів`,
worldState_affinityBoost: `Глобальний бустер синтезу`,
worldState_resourceBoost: `Глобальний бустер ресурсів`,
worldState_tennoLiveRelay: `Реле TennoLive`,
worldState_baroTennoConRelay: `Реле Баро TennoCon`,
worldState_starDays: `Зоряні дні`,
worldState_galleonOfGhouls: `Гульський Галеон`,
worldState_ghoulEmergence: `Зачищення від гулів`,
worldState_plagueStar: `Морова зірка`,
worldState_dogDays: `Спекотні дні`,
worldState_dogDaysRewards: `Нагороди Спекотних днів`,
worldState_wolfHunt: `Полювання на Вовка (2025)`,
worldState_longShadow: `Довга тінь`,
worldState_hallowedFlame: `Священне полум'я`,
worldState_hallowedNightmares: `Священні жахіття`,
worldState_hallowedNightmaresRewards: `Нагороди Священних Жахіть`,
worldState_proxyRebellion: `Повстання роботів`,
worldState_proxyRebellionRewards: `Нагороди Повстання роботів`,
worldState_from_year: `з |YEAR|`,
worldState_pre_year: `до |YEAR|`,
worldState_incompatibleWith: `Несумісне з:`,
enabled: `Увімкнено`,
disabled: `Вимкнено`,
worldState_we1: `Вихідні 1`,
worldState_we2: `Вихідні 2`,
worldState_we3: `Вихідні 3`,
worldState_eidolonOverride: `Цикл Рівнин Ейдолонів/Деймоса`,
worldState_day: `День/Фасс`,
worldState_night: `Ніч/Воум`,
worldState_vallisOverride: `Цикл Долини куль`,
worldState_warm: `Тепло`,
worldState_cold: `Холод`,
worldState_duviriOverride: `Цикл Дувірі`,
worldState_joy: `Радість`,
worldState_anger: `Гнів`,
worldState_envy: `Заздрість`,
worldState_sorrow: `Скорбота`,
worldState_fear: `Страх`,
worldState_nightwaveOverride: `Сезон Нічної хвилі`,
worldState_RadioLegionIntermission13Syndicate: `Вибірка Нори 9`,
worldState_RadioLegionIntermission12Syndicate: `Вибірка Нори 8`,
worldState_RadioLegionIntermission11Syndicate: `Вибірка Нори 7`,
worldState_RadioLegionIntermission10Syndicate: `Вибірка Нори 6`,
worldState_RadioLegionIntermission9Syndicate: `Вибірка Нори 5`,
worldState_RadioLegionIntermission8Syndicate: `Вибірка Нори 4`,
worldState_RadioLegionIntermission7Syndicate: `Вибірка Нори 3`,
worldState_RadioLegionIntermission6Syndicate: `Вибірка Нори 2`,
worldState_RadioLegionIntermission5Syndicate: `Вибірка Нори 1`,
worldState_RadioLegionIntermission4Syndicate: `Вибір Нори`,
worldState_RadioLegionIntermission3Syndicate: `Антракт III`,
worldState_RadioLegion3Syndicate: `Скляр`,
worldState_RadioLegionIntermission2Syndicate: `Антракт II`,
worldState_RadioLegion2Syndicate: `Емісар`,
worldState_RadioLegionIntermissionSyndicate: `Антракт I`,
worldState_RadioLegionSyndicate: `Вовк із Сатурна-6`,
worldState_fissures: `Прориви порожнечі`,
normal: `Стандартні`,
worldState_allAtOnceNormal: `Всі одразу, в звичайному режимі`,
worldState_allAtOnceSteelPath: `Всі одразу, в режимі Шляху Сталі`,
worldState_theCircuitOverride: `Типи місій у підземеллі Дувірі`,
worldState_darvoStockMultiplier: `Множник Запасів Дарво`,
worldState_varziaFullyStocked: `Повний Асортимент Варзії`,
worldState_varziaOverride: `Зміна Ротації Варзії`,
import_importNote: `Ви можете завантажити повну або часткову відповідь спорядження (клієнтське представлення) тут. Всі підтримувані поля <b>будуть перезаписані</b> у вашому акаунті.`,
import_submit: `Відправити`,
import_samples: `Приклад:`,
import_samples_maxFocus: `Всі школи Фокуса максимального рівня`,
upgrade_Equilibrium: `+|VAL|% Енергія від підбирання здоров'я, +|VAL|% Здоров'я від підбирання енергії`,
upgrade_MeleeCritDamage: `+|VAL|% Критична шкода ближнього бою`,
upgrade_PrimaryStatusChance: `+|VAL|% Імовірність накладення ефекту стану основною зброєю`,
upgrade_SecondaryCritChance: `+|VAL|% Імовірність критичної шкоди допоміжною зброєю`,
upgrade_WarframeAbilityDuration: `+|VAL|% Тривалість здібностей`,
upgrade_WarframeAbilityStrength: `+|VAL|% Потужність здібностей`,
upgrade_WarframeArmorMax: `+|VAL| Захист`,
upgrade_WarframeBlastProc: `+|VAL| Щит при вбивстві з Вибуховим Уронoм`,
upgrade_WarframeCastingSpeed: `+|VAL|% Швидкість Застосування Здібностей`,
upgrade_WarframeCorrosiveDamageBoost: `+|VAL|% Урон Здібностей по ворогам, ураженим Корозією`,
upgrade_WarframeCorrosiveStack: `Збільшити макс. стаки Корозії на +|VAL|`,
upgrade_WarframeCritDamageBoost: `+|VAL|% Критична шкода Ближнього Бою (Подвоюється при 500 Енергії)`,
upgrade_WarframeElectricDamage: `+|VAL1|% Урон Електрикою Основним Озброєнням (+|VAL2|% за кожен додатковий Уламок)`,
upgrade_WarframeElectricDamageBoost: `+|VAL|% Шкода Здібностей по ворогам, ураженим Електрикою`,
upgrade_WarframeEnergyMax: `+|VAL| Макс. Енергія`,
upgrade_WarframeGlobeEffectEnergy: `+|VAL|% Ефективність згустків Енергії`,
upgrade_WarframeGlobeEffectHealth: `+|VAL|% Ефективність згустків Здоров'я`,
upgrade_WarframeHealthMax: `+|VAL| Макс. Здоров'я`,
upgrade_WarframeHPBoostFromImpact: `+|VAL1| Здоров'я при вбивстві з Вибуховою шкодою (Макс. |VAL2| Здоров'я)`,
upgrade_WarframeParkourVelocity: `+|VAL|% Швидкість Паркура`,
upgrade_WarframeRadiationDamageBoost: `+|VAL|% Шкода Здібностей по ворогам, ураженим Радіацією`,
upgrade_WarframeHealthRegen: `+|VAL| Здоров'я в секунду`,
upgrade_WarframeShieldMax: `+|VAL| Щиту`,
upgrade_WarframeStartingEnergy: `+|VAL|% Енергії при Спавні`,
upgrade_WarframeToxinDamage: `+|VAL|% Шкода Токсином`,
upgrade_WarframeToxinHeal: `+|VAL| Здоров'я при нанесенні шкоди ворогам з Токсином`,
upgrade_WeaponCritBoostFromHeat: `+|VAL1|% Імовірність Критичної Шкоди Допоміжною Зброєю за кожного вбитого ворога, ураженого Термічною шкодою (Макс. |VAL2|%)`,
upgrade_AvatarAbilityRange: `+7.5% Досяжність Здібностей`,
upgrade_AvatarAbilityEfficiency: `+5% Ощадливість Здібностей`,
upgrade_AvatarEnergyRegen: `+0.5 Відновлення Енергії в секунду`,
upgrade_AvatarEnemyRadar: `+5m Виявлення ворогів`,
upgrade_AvatarLootRadar: `+7m Виявлення здобичі`,
upgrade_WeaponAmmoMax: `+15% Макс. Набоїв`,
upgrade_EnemyArmorReductionAura: `-3% Захист Ворогів`,
upgrade_OnExecutionAmmo: `+100% Заповнення Магазина Основного і Допоміжного Озброєння при вбивстві Милосердям`,
upgrade_OnExecutionHealthDrop: `+100% Імовірність Падіння згустка Здоров'я при вбивстві Милосердям`,
upgrade_OnExecutionEnergyDrop: `+50% Імовірність Падіння згустка Енергії при вбивстві Милосердям`,
upgrade_OnFailHackReset: `+50% Імовірність Повтора Зламу`,
upgrade_DamageReductionOnHack: `+75% Зменшення Шкоди під час Зламу`,
upgrade_OnExecutionReviveCompanion: `Вбивства Милосердям зменшують час відновлення Компаньйона на 15 секунд`,
upgrade_OnExecutionParkourSpeed: `+60% Швидкість Паркура після вбивства Милосердям на 15 секунд`,
upgrade_AvatarTimeLimitIncrease: `+8 секунд до Зламу`,
upgrade_ElectrifyOnHack: `Шокувати ворогів в межах 20 метрів під час Зламу`,
upgrade_OnExecutionTerrify: `+50% Імовірність, що вороги в межах 15 метрів будуть тремтіти від страху протягом 8 секунд після вбивства Милосердям`,
upgrade_OnHackLockers: `Відкрити 5 шафок в межах 20 метрів після Зламу`,
upgrade_OnExecutionBlind: `Засліпити ворогів в межах 18 метрів після вбивства Милосердям`,
upgrade_OnExecutionDrainPower: `Наступне застосування здібності отримує +50% Потужності Здібності після вбивства Милосердям`,
upgrade_OnHackSprintSpeed: `+75% Швидкість Бігу протягом 15 секунд після Зламу`,
upgrade_SwiftExecute: `+50% Швидкість Вбивства Милосердям`,
upgrade_OnHackInvis: `Невидимість протягом 15 секунд після Зламу`,
damageType_Electricity: `Електричний`,
damageType_Fire: `Трммічний`,
damageType_Freeze: `Крижаний`,
damageType_Impact: `Ударний`,
damageType_Magnetic: `Магнетичний`,
damageType_Poison: `Токсичний`,
damageType_Radiation: `Радіаційний`,
theme_dark: `Темна тема`,
theme_light: `Світла тема`,
prettier_sucks_ass: ``
};

Some files were not shown because too many files have changed in this diff Show More