merge upstream
This commit is contained in:
commit
0acca4fe31
2
.gitattributes
vendored
2
.gitattributes
vendored
@ -1,4 +1,4 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
* text=auto eol=lf
|
||||
|
||||
static/webui/libs/ linguist-vendored
|
||||
|
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@ -15,11 +15,12 @@ jobs:
|
||||
- run: npm run verify
|
||||
- run: npm run lint:ci
|
||||
- run: npm run prettier
|
||||
- run: npm run update-translations
|
||||
- name: Fail if there are uncommitted changes
|
||||
run: |
|
||||
if [[ -n "$(git status --porcelain)" ]]; then
|
||||
echo "Uncommitted changes detected:"
|
||||
git status
|
||||
git diff
|
||||
git --no-pager diff
|
||||
exit 1
|
||||
fi
|
||||
|
@ -32,6 +32,7 @@
|
||||
"noArgonCrystalDecay": false,
|
||||
"noMasteryRankUpCooldown": false,
|
||||
"noVendorPurchaseLimits": true,
|
||||
"noKimCooldowns": false,
|
||||
"instantResourceExtractorDrones": false,
|
||||
"noDojoRoomBuildStage": false,
|
||||
"noDecoBuildStage": false,
|
||||
|
8
package-lock.json
generated
8
package-lock.json
generated
@ -18,7 +18,7 @@
|
||||
"morgan": "^1.10.0",
|
||||
"ncp": "^2.0.0",
|
||||
"typescript": "^5.5",
|
||||
"warframe-public-export-plus": "^0.5.53",
|
||||
"warframe-public-export-plus": "^0.5.54",
|
||||
"warframe-riven-info": "^0.1.2",
|
||||
"winston": "^3.17.0",
|
||||
"winston-daily-rotate-file": "^5.0.0"
|
||||
@ -3789,9 +3789,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/warframe-public-export-plus": {
|
||||
"version": "0.5.53",
|
||||
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.53.tgz",
|
||||
"integrity": "sha512-FjYeCJ5OxvPWyETnV33YOeX7weVVeMy451RY7uewwSvRbSNFTDhmhvbrLhfwykulUX4RPakfZr8nO0S0a6lGCA=="
|
||||
"version": "0.5.54",
|
||||
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.54.tgz",
|
||||
"integrity": "sha512-27r6qLErr3P8UVDiEzhDAs/BjdAS3vI2CQ58jSI+LClDlj6QL+y1jQe8va/npl3Ft2K8PywLkZ8Yso0j9YzvOA=="
|
||||
},
|
||||
"node_modules/warframe-riven-info": {
|
||||
"version": "0.1.2",
|
||||
|
@ -25,7 +25,7 @@
|
||||
"morgan": "^1.10.0",
|
||||
"ncp": "^2.0.0",
|
||||
"typescript": "^5.5",
|
||||
"warframe-public-export-plus": "^0.5.53",
|
||||
"warframe-public-export-plus": "^0.5.54",
|
||||
"warframe-riven-info": "^0.1.2",
|
||||
"winston": "^3.17.0",
|
||||
"winston-daily-rotate-file": "^5.0.0"
|
||||
|
@ -4,7 +4,7 @@
|
||||
const fs = require("fs");
|
||||
|
||||
function extractStrings(content) {
|
||||
const regex = /([a-zA-Z_]+): `([^`]*)`,/g;
|
||||
const regex = /([a-zA-Z0-9_]+): `([^`]*)`,/g;
|
||||
let matches;
|
||||
const strings = {};
|
||||
while ((matches = regex.exec(content)) !== null) {
|
||||
@ -15,7 +15,7 @@ function extractStrings(content) {
|
||||
|
||||
const source = fs.readFileSync("../static/webui/translations/en.js", "utf8");
|
||||
const sourceStrings = extractStrings(source);
|
||||
const sourceLines = source.split("\n");
|
||||
const sourceLines = source.substring(0, source.length - 1).split("\n");
|
||||
|
||||
fs.readdirSync("../static/webui/translations").forEach(file => {
|
||||
if (fs.lstatSync(`../static/webui/translations/${file}`).isFile() && file !== "en.js") {
|
||||
@ -36,7 +36,7 @@ fs.readdirSync("../static/webui/translations").forEach(file => {
|
||||
fs.writeSync(fileHandle, ` ${key}: \`[UNTRANSLATED] ${value}\`,\n`);
|
||||
}
|
||||
});
|
||||
} else if (line.length) {
|
||||
} else {
|
||||
fs.writeSync(fileHandle, line + "\n");
|
||||
}
|
||||
});
|
||||
|
@ -16,9 +16,9 @@ import { webuiRouter } from "@/src/routes/webui";
|
||||
const app = express();
|
||||
|
||||
app.use((req, _res, next) => {
|
||||
// 38.5.0 introduced "ezip" for encrypted body blobs.
|
||||
// 38.5.0 introduced "ezip" for encrypted body blobs and "e" for request verification only (encrypted body blobs with no application data).
|
||||
// The bootstrapper decrypts it for us but having an unsupported Content-Encoding here would still be an issue for Express, so removing it.
|
||||
if (req.headers["content-encoding"] == "ezip") {
|
||||
if (req.headers["content-encoding"] == "ezip" || req.headers["content-encoding"] == "e") {
|
||||
req.headers["content-encoding"] = undefined;
|
||||
}
|
||||
next();
|
||||
|
@ -32,11 +32,11 @@ import { logger } from "@/src/utils/logger";
|
||||
export const guildTechController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId);
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
const data = JSON.parse(String(req.body)) as TGuildTechRequest;
|
||||
if (data.Action == "Sync") {
|
||||
let needSave = false;
|
||||
const techProjects: ITechProjectClient[] = [];
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
if (guild.TechProjects) {
|
||||
for (const project of guild.TechProjects) {
|
||||
const techProject: ITechProjectClient = {
|
||||
@ -59,110 +59,170 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
res.json({ TechProjects: techProjects });
|
||||
} else if (data.Action == "Start") {
|
||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
|
||||
res.status(400).send("-1").end();
|
||||
return;
|
||||
}
|
||||
const recipe = ExportDojoRecipes.research[data.RecipeType];
|
||||
guild.TechProjects ??= [];
|
||||
if (!guild.TechProjects.find(x => x.ItemType == data.RecipeType)) {
|
||||
const techProject =
|
||||
guild.TechProjects[
|
||||
guild.TechProjects.push({
|
||||
ItemType: data.RecipeType,
|
||||
ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, recipe.price),
|
||||
ReqItems: recipe.ingredients.map(x => ({
|
||||
ItemType: x.ItemType,
|
||||
ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, x.ItemCount)
|
||||
})),
|
||||
State: 0
|
||||
}) - 1
|
||||
];
|
||||
setGuildTechLogState(guild, techProject.ItemType, 5);
|
||||
if (config.noDojoResearchCosts) {
|
||||
processFundedGuildTechProject(guild, techProject, recipe);
|
||||
} else {
|
||||
if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
|
||||
guild.ActiveDojoColorResearch = data.RecipeType;
|
||||
if (data.Mode == "Guild") {
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
|
||||
res.status(400).send("-1").end();
|
||||
return;
|
||||
}
|
||||
const recipe = ExportDojoRecipes.research[data.RecipeType];
|
||||
guild.TechProjects ??= [];
|
||||
if (!guild.TechProjects.find(x => x.ItemType == data.RecipeType)) {
|
||||
const techProject =
|
||||
guild.TechProjects[
|
||||
guild.TechProjects.push({
|
||||
ItemType: data.RecipeType,
|
||||
ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, recipe.price),
|
||||
ReqItems: recipe.ingredients.map(x => ({
|
||||
ItemType: x.ItemType,
|
||||
ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, x.ItemCount)
|
||||
})),
|
||||
State: 0
|
||||
}) - 1
|
||||
];
|
||||
setGuildTechLogState(guild, techProject.ItemType, 5);
|
||||
if (config.noDojoResearchCosts) {
|
||||
processFundedGuildTechProject(guild, techProject, recipe);
|
||||
} else {
|
||||
if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
|
||||
guild.ActiveDojoColorResearch = data.RecipeType;
|
||||
}
|
||||
}
|
||||
}
|
||||
await guild.save();
|
||||
res.end();
|
||||
} else {
|
||||
const recipe = ExportDojoRecipes.research[data.RecipeType];
|
||||
const techProject =
|
||||
inventory.PersonalTechProjects[
|
||||
inventory.PersonalTechProjects.push({
|
||||
State: 0,
|
||||
ReqCredits: recipe.price,
|
||||
ItemType: data.RecipeType,
|
||||
ReqItems: recipe.ingredients
|
||||
}) - 1
|
||||
];
|
||||
await inventory.save();
|
||||
res.json({
|
||||
isPersonal: true,
|
||||
action: "Start",
|
||||
personalTech: techProject.toJSON()
|
||||
});
|
||||
}
|
||||
await guild.save();
|
||||
res.end();
|
||||
} else if (data.Action == "Contribute") {
|
||||
if (!hasAccessToDojo(inventory)) {
|
||||
res.status(400).send("-1").end();
|
||||
return;
|
||||
}
|
||||
if ((req.query.guildId as string) == "000000000000000000000000") {
|
||||
const techProject = inventory.PersonalTechProjects.id(data.ResearchId)!;
|
||||
|
||||
const guildMember = (await GuildMember.findOne(
|
||||
{ accountId, guildId: guild._id },
|
||||
"RegularCreditsContributed MiscItemsContributed"
|
||||
))!;
|
||||
techProject.ReqCredits -= data.RegularCredits;
|
||||
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, data.RegularCredits, false);
|
||||
|
||||
const contributions = data;
|
||||
const techProject = guild.TechProjects!.find(x => x.ItemType == contributions.RecipeType)!;
|
||||
|
||||
if (contributions.VaultCredits) {
|
||||
if (contributions.VaultCredits > techProject.ReqCredits) {
|
||||
contributions.VaultCredits = techProject.ReqCredits;
|
||||
}
|
||||
techProject.ReqCredits -= contributions.VaultCredits;
|
||||
guild.VaultRegularCredits! -= contributions.VaultCredits;
|
||||
}
|
||||
|
||||
if (contributions.RegularCredits > techProject.ReqCredits) {
|
||||
contributions.RegularCredits = techProject.ReqCredits;
|
||||
}
|
||||
techProject.ReqCredits -= contributions.RegularCredits;
|
||||
|
||||
guildMember.RegularCreditsContributed ??= 0;
|
||||
guildMember.RegularCreditsContributed += contributions.RegularCredits;
|
||||
|
||||
if (contributions.VaultMiscItems.length) {
|
||||
for (const miscItem of contributions.VaultMiscItems) {
|
||||
const miscItemChanges = [];
|
||||
for (const miscItem of data.MiscItems) {
|
||||
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
|
||||
if (reqItem) {
|
||||
if (miscItem.ItemCount > reqItem.ItemCount) {
|
||||
miscItem.ItemCount = reqItem.ItemCount;
|
||||
}
|
||||
reqItem.ItemCount -= miscItem.ItemCount;
|
||||
|
||||
const vaultMiscItem = guild.VaultMiscItems!.find(x => x.ItemType == miscItem.ItemType)!;
|
||||
vaultMiscItem.ItemCount -= miscItem.ItemCount;
|
||||
miscItemChanges.push({
|
||||
ItemType: miscItem.ItemType,
|
||||
ItemCount: miscItem.ItemCount * -1
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
addMiscItems(inventory, miscItemChanges);
|
||||
inventoryChanges.MiscItems = miscItemChanges;
|
||||
|
||||
const miscItemChanges = [];
|
||||
for (const miscItem of contributions.MiscItems) {
|
||||
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
|
||||
if (reqItem) {
|
||||
if (miscItem.ItemCount > reqItem.ItemCount) {
|
||||
miscItem.ItemCount = reqItem.ItemCount;
|
||||
}
|
||||
reqItem.ItemCount -= miscItem.ItemCount;
|
||||
miscItemChanges.push({
|
||||
ItemType: miscItem.ItemType,
|
||||
ItemCount: miscItem.ItemCount * -1
|
||||
});
|
||||
techProject.HasContributions = true;
|
||||
|
||||
addGuildMemberMiscItemContribution(guildMember, miscItem);
|
||||
if (techProject.ReqCredits == 0 && !techProject.ReqItems.find(x => x.ItemCount > 0)) {
|
||||
techProject.State = 1;
|
||||
const recipe = ExportDojoRecipes.research[techProject.ItemType];
|
||||
techProject.CompletionDate = new Date(Date.now() + recipe.time * 1000);
|
||||
}
|
||||
|
||||
await inventory.save();
|
||||
res.json({
|
||||
InventoryChanges: inventoryChanges,
|
||||
PersonalResearch: { $oid: data.ResearchId },
|
||||
PersonalResearchDate: techProject.CompletionDate ? toMongoDate(techProject.CompletionDate) : undefined
|
||||
});
|
||||
} else {
|
||||
if (!hasAccessToDojo(inventory)) {
|
||||
res.status(400).send("-1").end();
|
||||
return;
|
||||
}
|
||||
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
const guildMember = (await GuildMember.findOne(
|
||||
{ accountId, guildId: guild._id },
|
||||
"RegularCreditsContributed MiscItemsContributed"
|
||||
))!;
|
||||
|
||||
const techProject = guild.TechProjects!.find(x => x.ItemType == data.RecipeType)!;
|
||||
|
||||
if (data.VaultCredits) {
|
||||
if (data.VaultCredits > techProject.ReqCredits) {
|
||||
data.VaultCredits = techProject.ReqCredits;
|
||||
}
|
||||
techProject.ReqCredits -= data.VaultCredits;
|
||||
guild.VaultRegularCredits! -= data.VaultCredits;
|
||||
}
|
||||
|
||||
if (data.RegularCredits > techProject.ReqCredits) {
|
||||
data.RegularCredits = techProject.ReqCredits;
|
||||
}
|
||||
techProject.ReqCredits -= data.RegularCredits;
|
||||
|
||||
guildMember.RegularCreditsContributed ??= 0;
|
||||
guildMember.RegularCreditsContributed += data.RegularCredits;
|
||||
|
||||
if (data.VaultMiscItems.length) {
|
||||
for (const miscItem of data.VaultMiscItems) {
|
||||
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
|
||||
if (reqItem) {
|
||||
if (miscItem.ItemCount > reqItem.ItemCount) {
|
||||
miscItem.ItemCount = reqItem.ItemCount;
|
||||
}
|
||||
reqItem.ItemCount -= miscItem.ItemCount;
|
||||
|
||||
const vaultMiscItem = guild.VaultMiscItems!.find(x => x.ItemType == miscItem.ItemType)!;
|
||||
vaultMiscItem.ItemCount -= miscItem.ItemCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const miscItemChanges = [];
|
||||
for (const miscItem of data.MiscItems) {
|
||||
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
|
||||
if (reqItem) {
|
||||
if (miscItem.ItemCount > reqItem.ItemCount) {
|
||||
miscItem.ItemCount = reqItem.ItemCount;
|
||||
}
|
||||
reqItem.ItemCount -= miscItem.ItemCount;
|
||||
miscItemChanges.push({
|
||||
ItemType: miscItem.ItemType,
|
||||
ItemCount: miscItem.ItemCount * -1
|
||||
});
|
||||
|
||||
addGuildMemberMiscItemContribution(guildMember, miscItem);
|
||||
}
|
||||
}
|
||||
addMiscItems(inventory, miscItemChanges);
|
||||
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, data.RegularCredits, false);
|
||||
inventoryChanges.MiscItems = miscItemChanges;
|
||||
|
||||
// Check if research is fully funded now.
|
||||
await processGuildTechProjectContributionsUpdate(guild, techProject);
|
||||
|
||||
await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
|
||||
res.json({
|
||||
InventoryChanges: inventoryChanges,
|
||||
Vault: getGuildVault(guild)
|
||||
});
|
||||
}
|
||||
addMiscItems(inventory, miscItemChanges);
|
||||
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, contributions.RegularCredits, false);
|
||||
inventoryChanges.MiscItems = miscItemChanges;
|
||||
|
||||
// Check if research is fully funded now.
|
||||
await processGuildTechProjectContributionsUpdate(guild, techProject);
|
||||
|
||||
await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
|
||||
res.json({
|
||||
InventoryChanges: inventoryChanges,
|
||||
Vault: getGuildVault(guild)
|
||||
});
|
||||
} else if (data.Action.split(",")[0] == "Buy") {
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
|
||||
res.status(400).send("-1").end();
|
||||
return;
|
||||
@ -190,6 +250,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
});
|
||||
} else if (data.Action == "Fabricate") {
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
|
||||
res.status(400).send("-1").end();
|
||||
return;
|
||||
@ -206,6 +267,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
||||
// Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`.
|
||||
res.json({ inventoryChanges: inventoryChanges });
|
||||
} else if (data.Action == "Pause") {
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
|
||||
res.status(400).send("-1").end();
|
||||
return;
|
||||
@ -217,6 +279,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
||||
await removePigmentsFromGuildMembers(guild._id);
|
||||
res.end();
|
||||
} else if (data.Action == "Unpause") {
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
|
||||
res.status(400).send("-1").end();
|
||||
return;
|
||||
@ -239,7 +302,7 @@ type TGuildTechRequest =
|
||||
|
||||
interface IGuildTechBasicRequest {
|
||||
Action: "Start" | "Fabricate" | "Pause" | "Unpause";
|
||||
Mode: "Guild";
|
||||
Mode: "Guild" | "Personal";
|
||||
RecipeType: string;
|
||||
}
|
||||
|
||||
@ -251,7 +314,7 @@ interface IGuildTechBuyRequest {
|
||||
|
||||
interface IGuildTechContributeRequest {
|
||||
Action: "Contribute";
|
||||
ResearchId: "";
|
||||
ResearchId: string;
|
||||
RecipeType: string;
|
||||
RegularCredits: number;
|
||||
MiscItems: IMiscItem[];
|
||||
|
@ -202,7 +202,8 @@ export const getInventoryResponse = async (
|
||||
|
||||
if (config.universalPolarityEverywhere) {
|
||||
const Polarity: IPolarity[] = [];
|
||||
for (let i = 0; i != 12; ++i) {
|
||||
// 12 is needed for necramechs. 14 is needed for plexus/crewshipharness.
|
||||
for (let i = 0; i != 14; ++i) {
|
||||
Polarity.push({
|
||||
Slot: i,
|
||||
Value: ArtifactPolarity.Any
|
||||
|
@ -1,10 +1,25 @@
|
||||
import { getInfNodes, getNemesisPasscode } from "@/src/helpers/nemesisHelpers";
|
||||
import {
|
||||
consumeModCharge,
|
||||
encodeNemesisGuess,
|
||||
getInfNodes,
|
||||
getNemesisPasscode,
|
||||
IKnifeResponse
|
||||
} from "@/src/helpers/nemesisHelpers";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
|
||||
import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { SRng } from "@/src/services/rngService";
|
||||
import { IMongoDate, IOid } from "@/src/types/commonTypes";
|
||||
import { IInnateDamageFingerprint, InventorySlot, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import {
|
||||
IInnateDamageFingerprint,
|
||||
InventorySlot,
|
||||
IUpgradeClient,
|
||||
IWeaponSkinClient,
|
||||
LoadoutIndex,
|
||||
TEquipmentKey
|
||||
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
@ -49,7 +64,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
||||
} else if ((req.query.mode as string) == "p") {
|
||||
const inventory = await getInventory(accountId, "Nemesis");
|
||||
const body = getJSONfromString<INemesisPrespawnCheckRequest>(String(req.body));
|
||||
const passcode = getNemesisPasscode(inventory.Nemesis!.fp, inventory.Nemesis!.Faction);
|
||||
const passcode = getNemesisPasscode(inventory.Nemesis!);
|
||||
let guessResult = 0;
|
||||
if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
|
||||
for (let i = 0; i != 3; ++i) {
|
||||
@ -66,6 +81,88 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
}
|
||||
res.json({ GuessResult: guessResult });
|
||||
} else if (req.query.mode == "r") {
|
||||
const inventory = await getInventory(
|
||||
accountId,
|
||||
"Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
|
||||
);
|
||||
const body = getJSONfromString<INemesisRequiemRequest>(String(req.body));
|
||||
if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
|
||||
const guess: number[] = [body.guess & 0xf, (body.guess >> 4) & 0xf, (body.guess >> 8) & 0xf];
|
||||
const passcode = getNemesisPasscode(inventory.Nemesis!)[0];
|
||||
|
||||
// Add to GuessHistory
|
||||
const result1 = passcode == guess[0] ? 0 : 1;
|
||||
const result2 = passcode == guess[1] ? 0 : 1;
|
||||
const result3 = passcode == guess[2] ? 0 : 1;
|
||||
inventory.Nemesis!.GuessHistory.push(
|
||||
encodeNemesisGuess(guess[0], result1, guess[1], result2, guess[2], result3)
|
||||
);
|
||||
|
||||
// Increase antivirus
|
||||
let antivirusGain = 5;
|
||||
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
|
||||
const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid);
|
||||
const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0;
|
||||
const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!;
|
||||
const response: IKnifeResponse = {};
|
||||
for (const upgrade of body.knife!.AttachedUpgrades) {
|
||||
switch (upgrade.ItemType) {
|
||||
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod":
|
||||
antivirusGain += 10;
|
||||
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
|
||||
break;
|
||||
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndWeaponDamageOnUseMod":
|
||||
antivirusGain += 10;
|
||||
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
|
||||
break;
|
||||
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusLargeOnSingleUseMod": // Instant Secure
|
||||
antivirusGain += 15;
|
||||
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
|
||||
break;
|
||||
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusOnUseMod": // Immuno Shield
|
||||
antivirusGain += 15;
|
||||
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
|
||||
break;
|
||||
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod":
|
||||
antivirusGain += 10;
|
||||
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
|
||||
break;
|
||||
}
|
||||
}
|
||||
inventory.Nemesis!.HenchmenKilled += antivirusGain;
|
||||
if (inventory.Nemesis!.HenchmenKilled >= 100) {
|
||||
inventory.Nemesis!.HenchmenKilled = 100;
|
||||
inventory.Nemesis!.InfNodes = [
|
||||
{
|
||||
Node: "CrewBattleNode559",
|
||||
Influence: 1
|
||||
}
|
||||
];
|
||||
inventory.Nemesis!.Weakened = true;
|
||||
} else {
|
||||
inventory.Nemesis!.InfNodes = getInfNodes("FC_INFESTATION", 0);
|
||||
}
|
||||
|
||||
await inventory.save();
|
||||
res.json(response);
|
||||
} else {
|
||||
const passcode = getNemesisPasscode(inventory.Nemesis!);
|
||||
if (passcode[body.position] != body.guess) {
|
||||
res.end();
|
||||
} else {
|
||||
inventory.Nemesis!.Rank += 1;
|
||||
inventory.Nemesis!.InfNodes = getInfNodes(inventory.Nemesis!.Faction, inventory.Nemesis!.Rank);
|
||||
await inventory.save();
|
||||
res.json({ RankIncrease: 1 });
|
||||
}
|
||||
}
|
||||
} else if ((req.query.mode as string) == "rs") {
|
||||
// report spawn; POST but no application data in body
|
||||
const inventory = await getInventory(accountId, "Nemesis");
|
||||
inventory.Nemesis!.LastEnc = inventory.Nemesis!.MissionCount;
|
||||
await inventory.save();
|
||||
res.json({ LastEnc: inventory.Nemesis!.LastEnc });
|
||||
} else if ((req.query.mode as string) == "s") {
|
||||
const inventory = await getInventory(accountId, "Nemesis");
|
||||
const body = getJSONfromString<INemesisStartRequest>(String(req.body));
|
||||
@ -173,6 +270,20 @@ interface INemesisPrespawnCheckRequest {
|
||||
potency?: number[];
|
||||
}
|
||||
|
||||
interface INemesisRequiemRequest {
|
||||
guess: number; // grn/crp: 4 bits | coda: 3x 4 bits
|
||||
position: number; // grn/crp: 0-2 | coda: 0
|
||||
// knife field provided for coda only
|
||||
knife?: {
|
||||
Item: IEquipmentClient;
|
||||
Skins: IWeaponSkinClient[];
|
||||
ModSlot: number;
|
||||
CustSlot: number;
|
||||
AttachedUpgrades: IUpgradeClient[];
|
||||
HiddenWhenHolstered: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
const kuvaLichVersionSixWeapons = [
|
||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon",
|
||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak",
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||
import { config } from "@/src/services/configService";
|
||||
import { addEmailItem, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { ICompletedDialogue, IDialogueDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
@ -24,7 +25,9 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
|
||||
throw new Error("bad inventory state");
|
||||
}
|
||||
const inventoryChanges: IInventoryChanges = {};
|
||||
const tomorrowAt0Utc = (Math.trunc(Date.now() / 86400_000) + 1) * 86400_000;
|
||||
const tomorrowAt0Utc = config.noKimCooldowns
|
||||
? Date.now()
|
||||
: (Math.trunc(Date.now() / 86400_000) + 1) * 86400_000;
|
||||
inventory.DialogueHistory.Dialogues ??= [];
|
||||
const dialogue = getDialogue(inventory, request.DialogueName);
|
||||
dialogue.Rank = request.Rank;
|
||||
|
@ -3,15 +3,9 @@ import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { ExportNightwave, ExportSyndicates, ISyndicateSacrifice } from "warframe-public-export-plus";
|
||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||
import {
|
||||
addItem,
|
||||
addMiscItems,
|
||||
combineInventoryChanges,
|
||||
getInventory,
|
||||
updateCurrency
|
||||
} from "@/src/services/inventoryService";
|
||||
import { addMiscItems, combineInventoryChanges, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { fromStoreItem, isStoreItem } from "@/src/services/itemDataService";
|
||||
import { isStoreItem, toStoreItem } from "@/src/services/itemDataService";
|
||||
|
||||
export const syndicateSacrificeController: RequestHandler = async (request, response) => {
|
||||
const accountId = await getAccountIdForRequest(request);
|
||||
@ -77,10 +71,13 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp
|
||||
res.NewEpisodeReward = true;
|
||||
const reward = ExportNightwave.rewards[index];
|
||||
let rewardType = reward.uniqueName;
|
||||
if (isStoreItem(rewardType)) {
|
||||
rewardType = fromStoreItem(rewardType);
|
||||
if (!isStoreItem(rewardType)) {
|
||||
rewardType = toStoreItem(rewardType);
|
||||
}
|
||||
combineInventoryChanges(res.InventoryChanges, await addItem(inventory, rewardType, reward.itemCount ?? 1));
|
||||
combineInventoryChanges(
|
||||
res.InventoryChanges,
|
||||
(await handleStoreItemAcquisition(rewardType, inventory, reward.itemCount)).InventoryChanges
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,11 @@
|
||||
import { ExportRegions } from "warframe-public-export-plus";
|
||||
import { IInfNode } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { SRng } from "@/src/services/rngService";
|
||||
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
|
||||
import { logger } from "../utils/logger";
|
||||
import { IOid } from "../types/commonTypes";
|
||||
import { Types } from "mongoose";
|
||||
import { addMods } from "../services/inventoryService";
|
||||
|
||||
export const getInfNodes = (faction: string, rank: number): IInfNode[] => {
|
||||
const infNodes = [];
|
||||
@ -33,12 +38,93 @@ const systemIndexes: Record<string, number[]> = {
|
||||
};
|
||||
|
||||
// Get a parazon 'passcode' based on the nemesis fingerprint so it's always the same for the same nemesis.
|
||||
export const getNemesisPasscode = (fp: bigint, faction: string): number[] => {
|
||||
const rng = new SRng(fp);
|
||||
export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: string }): number[] => {
|
||||
const rng = new SRng(nemesis.fp);
|
||||
const passcode = [rng.randomInt(0, 7)];
|
||||
if (faction != "FC_INFESTATION") {
|
||||
if (nemesis.Faction != "FC_INFESTATION") {
|
||||
passcode.push(rng.randomInt(0, 7));
|
||||
passcode.push(rng.randomInt(0, 7));
|
||||
}
|
||||
return passcode;
|
||||
};
|
||||
|
||||
export const encodeNemesisGuess = (
|
||||
symbol1: number,
|
||||
result1: number,
|
||||
symbol2: number,
|
||||
result2: number,
|
||||
symbol3: number,
|
||||
result3: number
|
||||
): number => {
|
||||
return (
|
||||
(symbol1 & 0xf) |
|
||||
((result1 & 3) << 12) |
|
||||
((symbol2 << 4) & 0xff) |
|
||||
((result2 << 14) & 0xffff) |
|
||||
((symbol3 & 0xf) << 8) |
|
||||
((result3 & 3) << 16)
|
||||
);
|
||||
};
|
||||
|
||||
export const decodeNemesisGuess = (val: number): number[] => {
|
||||
return [val & 0xf, (val >> 12) & 3, (val & 0xff) >> 4, (val & 0xffff) >> 14, (val >> 8) & 0xf, (val >> 16) & 3];
|
||||
};
|
||||
|
||||
export interface IKnifeResponse {
|
||||
UpgradeIds?: string[];
|
||||
UpgradeTypes?: string[];
|
||||
UpgradeFingerprints?: { lvl: number }[];
|
||||
UpgradeNew?: boolean[];
|
||||
HasKnife?: boolean;
|
||||
}
|
||||
|
||||
export const consumeModCharge = (
|
||||
response: IKnifeResponse,
|
||||
inventory: TInventoryDatabaseDocument,
|
||||
upgrade: { ItemId: IOid; ItemType: string },
|
||||
dataknifeUpgrades: string[]
|
||||
): void => {
|
||||
response.UpgradeIds ??= [];
|
||||
response.UpgradeTypes ??= [];
|
||||
response.UpgradeFingerprints ??= [];
|
||||
response.UpgradeNew ??= [];
|
||||
response.HasKnife = true;
|
||||
|
||||
if (upgrade.ItemId.$oid != "000000000000000000000000") {
|
||||
const dbUpgrade = inventory.Upgrades.id(upgrade.ItemId.$oid)!;
|
||||
const fingerprint = JSON.parse(dbUpgrade.UpgradeFingerprint!) as { lvl: number };
|
||||
fingerprint.lvl += 1;
|
||||
dbUpgrade.UpgradeFingerprint = JSON.stringify(fingerprint);
|
||||
|
||||
response.UpgradeIds.push(upgrade.ItemId.$oid);
|
||||
response.UpgradeTypes.push(upgrade.ItemType);
|
||||
response.UpgradeFingerprints.push(fingerprint);
|
||||
response.UpgradeNew.push(false);
|
||||
} else {
|
||||
const id = new Types.ObjectId();
|
||||
inventory.Upgrades.push({
|
||||
_id: id,
|
||||
ItemType: upgrade.ItemType,
|
||||
UpgradeFingerprint: `{"lvl":1}`
|
||||
});
|
||||
|
||||
addMods(inventory, [
|
||||
{
|
||||
ItemType: upgrade.ItemType,
|
||||
ItemCount: -1
|
||||
}
|
||||
]);
|
||||
|
||||
const dataknifeRawUpgradeIndex = dataknifeUpgrades.indexOf(upgrade.ItemType);
|
||||
if (dataknifeRawUpgradeIndex != -1) {
|
||||
dataknifeUpgrades[dataknifeRawUpgradeIndex] = id.toString();
|
||||
} else {
|
||||
logger.warn(`${upgrade.ItemType} not found in dataknife config`);
|
||||
}
|
||||
|
||||
response.UpgradeIds.push(id.toString());
|
||||
response.UpgradeTypes.push(upgrade.ItemType);
|
||||
response.UpgradeFingerprints.push({ lvl: 1 });
|
||||
response.UpgradeNew.push(true);
|
||||
}
|
||||
};
|
||||
|
@ -84,7 +84,9 @@ import {
|
||||
IInfNode,
|
||||
IDiscoveredMarker,
|
||||
IWeeklyMission,
|
||||
ILockedWeaponGroupDatabase
|
||||
ILockedWeaponGroupDatabase,
|
||||
IPersonalTechProjectDatabase,
|
||||
IPersonalTechProjectClient
|
||||
} from "../../types/inventoryTypes/inventoryTypes";
|
||||
import { IOid } from "../../types/commonTypes";
|
||||
import {
|
||||
@ -498,7 +500,34 @@ const seasonChallengeHistorySchema = new Schema<ISeasonChallenge>(
|
||||
{ _id: false }
|
||||
);
|
||||
|
||||
//TODO: check whether this is complete
|
||||
const personalTechProjectSchema = new Schema<IPersonalTechProjectDatabase>({
|
||||
State: Number,
|
||||
ReqCredits: Number,
|
||||
ItemType: String,
|
||||
ReqItems: { type: [typeCountSchema], default: undefined },
|
||||
HasContributions: Boolean,
|
||||
CompletionDate: Date
|
||||
});
|
||||
|
||||
personalTechProjectSchema.virtual("ItemId").get(function () {
|
||||
return { $oid: this._id.toString() };
|
||||
});
|
||||
|
||||
personalTechProjectSchema.set("toJSON", {
|
||||
virtuals: true,
|
||||
transform(_doc, ret, _options) {
|
||||
delete ret._id;
|
||||
delete ret.__v;
|
||||
|
||||
const db = ret as IPersonalTechProjectDatabase;
|
||||
const client = ret as IPersonalTechProjectClient;
|
||||
|
||||
if (db.CompletionDate) {
|
||||
client.CompletionDate = toMongoDate(db.CompletionDate);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const playerSkillsSchema = new Schema<IPlayerSkills>(
|
||||
{
|
||||
LPP_SPACE: { type: Number, default: 0 },
|
||||
@ -1442,7 +1471,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
|
||||
|
||||
//Railjack craft
|
||||
//https://warframe.fandom.com/wiki/Rising_Tide
|
||||
PersonalTechProjects: [Schema.Types.Mixed],
|
||||
PersonalTechProjects: { type: [personalTechProjectSchema], default: [] },
|
||||
|
||||
//Modulars lvl and exp(Railjack|Duviri)
|
||||
//https://warframe.fandom.com/wiki/Intrinsics
|
||||
@ -1471,7 +1500,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
|
||||
DuviriInfo: DuviriInfoSchema,
|
||||
Mailbox: MailboxSchema,
|
||||
HandlerPoints: Number,
|
||||
ChallengesFixVersion: Number,
|
||||
ChallengesFixVersion: { type: Number, default: 6 },
|
||||
PlayedParkourTutorial: Boolean,
|
||||
ActiveLandscapeTraps: [Schema.Types.Mixed],
|
||||
RepVotes: [Schema.Types.Mixed],
|
||||
@ -1585,6 +1614,7 @@ export type InventoryDocumentProps = {
|
||||
Drones: Types.DocumentArray<IDroneDatabase>;
|
||||
CrewShipWeaponSkins: Types.DocumentArray<IUpgradeDatabase>;
|
||||
CrewShipSalvagedWeaponsSkins: Types.DocumentArray<IUpgradeDatabase>;
|
||||
PersonalTechProjects: Types.DocumentArray<IPersonalTechProjectDatabase>;
|
||||
} & { [K in TEquipmentKey]: Types.DocumentArray<IEquipmentDatabase> };
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
|
@ -38,6 +38,7 @@ interface IConfig {
|
||||
noArgonCrystalDecay?: boolean;
|
||||
noMasteryRankUpCooldown?: boolean;
|
||||
noVendorPurchaseLimits?: boolean;
|
||||
noKimCooldowns?: boolean;
|
||||
instantResourceExtractorDrones?: boolean;
|
||||
noDojoRoomBuildStage?: boolean;
|
||||
noDojoDecoBuildStage?: boolean;
|
||||
|
@ -963,7 +963,7 @@ export const addStanding = (
|
||||
const max = getMaxStanding(syndicateMeta, syndicate.Title ?? 0);
|
||||
if (syndicate.Standing + gainedStanding > max) gainedStanding = max - syndicate.Standing;
|
||||
|
||||
if (!isMedallion || (isMedallion && syndicateMeta.medallionsCappedByDailyLimit)) {
|
||||
if (!isMedallion || syndicateMeta.medallionsCappedByDailyLimit) {
|
||||
if (gainedStanding > getStandingLimit(inventory, syndicateMeta.dailyLimitBin)) {
|
||||
gainedStanding = getStandingLimit(inventory, syndicateMeta.dailyLimitBin);
|
||||
}
|
||||
@ -996,6 +996,10 @@ export const updateGeneric = async (data: IGenericUpdate, accountId: string): Pr
|
||||
}
|
||||
];
|
||||
addMiscItems(inventory, inventoryChanges.MiscItems);
|
||||
} else if (node == "BeatCaliberChicks") {
|
||||
await addEmailItem(inventory, "/Lotus/Types/Items/EmailItems/BeatCaliberChicksEmailItem", inventoryChanges);
|
||||
} else if (node == "ClearedFiveLoops") {
|
||||
await addEmailItem(inventory, "/Lotus/Types/Items/EmailItems/ClearedFiveLoopsEmailItem", inventoryChanges);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,14 +185,15 @@ export const getKeyChainMessage = ({ KeyChain, ChainStage }: IKeyChainRequest):
|
||||
throw new Error(`KeyChain ${KeyChain} does not contain chain stages`);
|
||||
}
|
||||
|
||||
const keyChainStage = chainStages[ChainStage];
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (!keyChainStage) {
|
||||
throw new Error(`KeyChainStage ${ChainStage} not found`);
|
||||
let i = ChainStage;
|
||||
let chainStageMessage = chainStages[i].messageToSendWhenTriggered;
|
||||
while (!chainStageMessage) {
|
||||
if (++i >= chainStages.length) {
|
||||
break;
|
||||
}
|
||||
chainStageMessage = chainStages[i].messageToSendWhenTriggered;
|
||||
}
|
||||
|
||||
const chainStageMessage = keyChainStage.messageToSendWhenTriggered;
|
||||
|
||||
if (!chainStageMessage) {
|
||||
throw new Error(
|
||||
`client requested key chain message in keychain ${KeyChain} at stage ${ChainStage} but they did not exist`
|
||||
|
@ -699,25 +699,10 @@ export const addMissionRewards = async (
|
||||
}
|
||||
|
||||
if (inventory.Nemesis.Faction == "FC_INFESTATION") {
|
||||
inventoryChanges.Nemesis.HenchmenKilled ??= 0;
|
||||
inventoryChanges.Nemesis.MissionCount ??= 0;
|
||||
|
||||
inventory.Nemesis.HenchmenKilled += 5;
|
||||
inventory.Nemesis.MissionCount += 1;
|
||||
|
||||
inventoryChanges.Nemesis.HenchmenKilled += 5;
|
||||
inventoryChanges.Nemesis.MissionCount ??= 0;
|
||||
inventoryChanges.Nemesis.MissionCount += 1;
|
||||
|
||||
if (inventory.Nemesis.HenchmenKilled >= 100) {
|
||||
inventory.Nemesis.InfNodes = [
|
||||
{
|
||||
Node: "CrewBattleNode559",
|
||||
Influence: 1
|
||||
}
|
||||
];
|
||||
inventory.Nemesis.Weakened = true;
|
||||
inventoryChanges.Nemesis.Weakened = true;
|
||||
}
|
||||
}
|
||||
|
||||
inventoryChanges.Nemesis.InfNodes = inventory.Nemesis.InfNodes;
|
||||
@ -747,7 +732,7 @@ export const addMissionRewards = async (
|
||||
const endlessJob = syndicateEntry.Jobs.find(j => j.endless);
|
||||
if (endlessJob) {
|
||||
const index = rewardInfo.JobStage % endlessJob.xpAmounts.length;
|
||||
const excess = Math.floor(rewardInfo.JobStage / endlessJob.xpAmounts.length);
|
||||
const excess = Math.floor(rewardInfo.JobStage / (endlessJob.xpAmounts.length - 1));
|
||||
medallionAmount = Math.floor(endlessJob.xpAmounts[index] * (1 + 0.15000001 * excess));
|
||||
}
|
||||
}
|
||||
@ -922,15 +907,140 @@ function getRandomMissionDrops(RewardInfo: IRewardInfo, tierOverride: number | u
|
||||
|
||||
let rotations: number[] = [];
|
||||
if (RewardInfo.jobId) {
|
||||
if (RewardInfo.JobTier! >= 0) {
|
||||
const id = RewardInfo.jobId.split("_")[3];
|
||||
const syndicateInfo = getWorldState().SyndicateMissions.find(x => x._id.$oid == id);
|
||||
if (syndicateInfo) {
|
||||
const jobInfo = syndicateInfo.Jobs![RewardInfo.JobTier!];
|
||||
rewardManifests = [jobInfo.rewards];
|
||||
rotations = [RewardInfo.JobStage!];
|
||||
if (RewardInfo.JobStage! >= 0) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [jobType, tierStr, hubNode, syndicateId, locationTag] = RewardInfo.jobId.split("_");
|
||||
const tier = Number(tierStr);
|
||||
let isEndlessJob = false;
|
||||
if (syndicateId) {
|
||||
const worldState = getWorldState();
|
||||
let syndicateEntry = worldState.SyndicateMissions.find(m => m._id.$oid === syndicateId);
|
||||
if (!syndicateEntry) syndicateEntry = worldState.SyndicateMissions.find(m => m.Tag === syndicateId);
|
||||
|
||||
if (syndicateEntry && syndicateEntry.Jobs) {
|
||||
let job = syndicateEntry.Jobs[tier];
|
||||
|
||||
if (syndicateEntry.Tag === "EntratiSyndicate") {
|
||||
const vault = syndicateEntry.Jobs.find(j => j.locationTag === locationTag);
|
||||
if (vault) job = vault;
|
||||
// if (
|
||||
// [
|
||||
// "DeimosRuinsExterminateBounty",
|
||||
// "DeimosRuinsEscortBounty",
|
||||
// "DeimosRuinsMistBounty",
|
||||
// "DeimosRuinsPurifyBounty",
|
||||
// "DeimosRuinsSacBounty"
|
||||
// ].some(ending => jobType.endsWith(ending))
|
||||
// ) {
|
||||
// job.rewards = "TODO"; // Droptable for Arcana Isolation Vault
|
||||
// }
|
||||
if (
|
||||
[
|
||||
"DeimosEndlessAreaDefenseBounty",
|
||||
"DeimosEndlessExcavateBounty",
|
||||
"DeimosEndlessPurifyBounty"
|
||||
].some(ending => jobType.endsWith(ending))
|
||||
) {
|
||||
const endlessJob = syndicateEntry.Jobs.find(j => j.endless);
|
||||
if (endlessJob) {
|
||||
isEndlessJob = true;
|
||||
job = endlessJob;
|
||||
const excess = Math.floor(RewardInfo.JobStage! / (job.xpAmounts.length - 1));
|
||||
|
||||
const rotationIndexes = [0, 0, 1, 2];
|
||||
const rotationIndex = rotationIndexes[excess % rotationIndexes.length];
|
||||
const dropTable = [
|
||||
"/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierBTableARewards",
|
||||
"/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierBTableBRewards",
|
||||
"/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierBTableCRewards"
|
||||
];
|
||||
job.rewards = dropTable[rotationIndex];
|
||||
}
|
||||
}
|
||||
} else if (syndicateEntry.Tag === "SolarisSyndicate") {
|
||||
if (jobType.endsWith("Heists/HeistProfitTakerBountyOne") && RewardInfo.JobStage == 2) {
|
||||
job = {
|
||||
rewards:
|
||||
"/Lotus/Types/Game/MissionDecks/HeistJobMissionRewards/HeistTierATableARewards",
|
||||
masteryReq: 0,
|
||||
minEnemyLevel: 40,
|
||||
maxEnemyLevel: 60,
|
||||
xpAmounts: [1000]
|
||||
};
|
||||
RewardInfo.Q = false; // Just in case
|
||||
} else {
|
||||
const tierMap = {
|
||||
Two: "B",
|
||||
Three: "C",
|
||||
Four: "D"
|
||||
};
|
||||
|
||||
for (const [key, tier] of Object.entries(tierMap)) {
|
||||
if (jobType.endsWith(`Heists/HeistProfitTakerBounty${key}`)) {
|
||||
job = {
|
||||
rewards: `/Lotus/Types/Game/MissionDecks/HeistJobMissionRewards/HeistTier${tier}TableARewards`,
|
||||
masteryReq: 0,
|
||||
minEnemyLevel: 40,
|
||||
maxEnemyLevel: 60,
|
||||
xpAmounts: [1000]
|
||||
};
|
||||
RewardInfo.Q = false; // Just in case
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
rewardManifests = [job.rewards];
|
||||
rotations = [RewardInfo.JobStage! % (job.xpAmounts.length - 1)];
|
||||
if (
|
||||
RewardInfo.Q &&
|
||||
(RewardInfo.JobStage === job.xpAmounts.length - 1 || job.isVault) &&
|
||||
!isEndlessJob
|
||||
) {
|
||||
rewardManifests.push(job.rewards);
|
||||
rotations.push(ExportRewards[job.rewards].length - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (RewardInfo.challengeMissionId) {
|
||||
const rewardTables: Record<string, string[]> = {
|
||||
EntratiLabSyndicate: [
|
||||
"/Lotus/Types/Game/MissionDecks/EntratiLabJobMissionReward/TierATableRewards",
|
||||
"/Lotus/Types/Game/MissionDecks/EntratiLabJobMissionReward/TierBTableRewards",
|
||||
"/Lotus/Types/Game/MissionDecks/EntratiLabJobMissionReward/TierCTableRewards",
|
||||
"/Lotus/Types/Game/MissionDecks/EntratiLabJobMissionReward/TierDTableRewards",
|
||||
"/Lotus/Types/Game/MissionDecks/EntratiLabJobMissionReward/TierETableRewards"
|
||||
],
|
||||
ZarimanSyndicate: [
|
||||
"/Lotus/Types/Game/MissionDecks/ZarimanJobMissionRewards/TierATableRewards",
|
||||
"/Lotus/Types/Game/MissionDecks/ZarimanJobMissionRewards/TierBTableRewards",
|
||||
"/Lotus/Types/Game/MissionDecks/ZarimanJobMissionRewards/TierCTableRewards",
|
||||
"/Lotus/Types/Game/MissionDecks/ZarimanJobMissionRewards/TierDTableRewards",
|
||||
"/Lotus/Types/Game/MissionDecks/ZarimanJobMissionRewards/TierETableRewards"
|
||||
],
|
||||
HexSyndicate: [
|
||||
"/Lotus/Types/Game/MissionDecks/1999MissionRewards/TierABountyRewards",
|
||||
"/Lotus/Types/Game/MissionDecks/1999MissionRewards/TierBBountyRewards",
|
||||
"/Lotus/Types/Game/MissionDecks/1999MissionRewards/TierCBountyRewards",
|
||||
"/Lotus/Types/Game/MissionDecks/1999MissionRewards/TierDBountyRewards",
|
||||
"/Lotus/Types/Game/MissionDecks/1999MissionRewards/TierEBountyRewards",
|
||||
"/Lotus/Types/Game/MissionDecks/1999MissionRewards/TierFBountyRewards",
|
||||
"/Lotus/Types/Game/MissionDecks/1999MissionRewards/InfestedLichBountyRewards"
|
||||
]
|
||||
};
|
||||
|
||||
const [syndicateTag, tierStr] = RewardInfo.challengeMissionId.split("_");
|
||||
const tier = Number(tierStr);
|
||||
|
||||
const rewardTable = rewardTables[syndicateTag][tier];
|
||||
|
||||
if (rewardTable) {
|
||||
rewardManifests = [rewardTable];
|
||||
rotations = [0];
|
||||
} else {
|
||||
logger.error(`Unknown syndicate or tier: ${RewardInfo.challengeMissionId}`);
|
||||
}
|
||||
} else if (RewardInfo.VaultsCracked) {
|
||||
// For Spy missions, e.g. 3 vaults cracked = A, B, C
|
||||
for (let i = 0; i != RewardInfo.VaultsCracked; ++i) {
|
||||
|
@ -46,6 +46,7 @@ export interface IInventoryDatabase
|
||||
| "EntratiVaultCountResetDate"
|
||||
| "BrandedSuits"
|
||||
| "LockedWeaponGroup"
|
||||
| "PersonalTechProjects"
|
||||
| TEquipmentKey
|
||||
>,
|
||||
InventoryDatabaseEquipment {
|
||||
@ -77,6 +78,7 @@ export interface IInventoryDatabase
|
||||
EntratiVaultCountResetDate?: Date;
|
||||
BrandedSuits?: Types.ObjectId[];
|
||||
LockedWeaponGroup?: ILockedWeaponGroupDatabase;
|
||||
PersonalTechProjects: IPersonalTechProjectDatabase[];
|
||||
}
|
||||
|
||||
export interface IQuestKeyDatabase {
|
||||
@ -157,6 +159,11 @@ export type TSolarMapRegion =
|
||||
|
||||
//TODO: perhaps split response and database into their own files
|
||||
|
||||
export enum LoadoutIndex {
|
||||
NORMAL = 0,
|
||||
DATAKNIFE = 7
|
||||
}
|
||||
|
||||
export interface IDailyAffiliations {
|
||||
DailyAffiliation: number;
|
||||
DailyAffiliationPvp: number;
|
||||
@ -220,7 +227,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
|
||||
ActiveQuest: string;
|
||||
FlavourItems: IFlavourItem[];
|
||||
LoadOutPresets: ILoadOutPresets;
|
||||
CurrentLoadOutIds: IOid[]; //TODO: we store it in the database using this representation as well :/
|
||||
CurrentLoadOutIds: IOid[]; // we store it in the database using this representation as well :/
|
||||
Missions: IMission[];
|
||||
RandomUpgradesIdentified?: number;
|
||||
LastRegionPlayed: TSolarMapRegion;
|
||||
@ -301,7 +308,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
|
||||
NemesisHistory: INemesisBaseClient[];
|
||||
LastNemesisAllySpawnTime?: IMongoDate;
|
||||
Settings?: ISettings;
|
||||
PersonalTechProjects: IPersonalTechProject[];
|
||||
PersonalTechProjects: IPersonalTechProjectClient[];
|
||||
PlayerSkills: IPlayerSkills;
|
||||
CrewShipAmmo: ITypeCount[];
|
||||
CrewShipWeaponSkins: IUpgradeClient[];
|
||||
@ -936,16 +943,20 @@ export interface IPersonalGoalProgress {
|
||||
ReceivedClanReward1?: boolean;
|
||||
}
|
||||
|
||||
export interface IPersonalTechProject {
|
||||
export interface IPersonalTechProjectDatabase {
|
||||
State: number;
|
||||
ReqCredits: number;
|
||||
ItemType: string;
|
||||
ReqItems: ITypeCount[];
|
||||
HasContributions?: boolean;
|
||||
CompletionDate?: Date;
|
||||
}
|
||||
|
||||
export interface IPersonalTechProjectClient extends Omit<IPersonalTechProjectDatabase, "CompletionDate"> {
|
||||
CompletionDate?: IMongoDate;
|
||||
ItemId: IOid;
|
||||
ProductCategory?: string;
|
||||
CategoryItemId?: IOid;
|
||||
HasContributions?: boolean;
|
||||
ItemId: IOid;
|
||||
}
|
||||
|
||||
export interface IPlayerSkills {
|
||||
|
@ -406,7 +406,10 @@
|
||||
<div class="card mb-3">
|
||||
<h5 class="card-header" data-loc="powersuit_archonShardsLabel"></h5>
|
||||
<div class="card-body">
|
||||
<p data-loc="powersuit_archonShardsDescription"></p>
|
||||
<p>
|
||||
<span data-loc="powersuit_archonShardsDescription"></span>
|
||||
<span data-loc="powersuit_archonShardsDescription2"></span>
|
||||
</p>
|
||||
<form class="input-group mb-3" onsubmit="doPushArchonCrystalUpgrade();return false;">
|
||||
<input type="number" id="archon-crystal-add-count" min="1" max="10000" value="1" class="form-control" style="max-width:100px" />
|
||||
<span class="input-group-text">x</span>
|
||||
@ -604,6 +607,10 @@
|
||||
<input class="form-check-input" type="checkbox" id="noVendorPurchaseLimits" />
|
||||
<label class="form-check-label" for="noVendorPurchaseLimits" data-loc="cheats_noVendorPurchaseLimits"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="noKimCooldowns" />
|
||||
<label class="form-check-label" for="noKimCooldowns" data-loc="cheats_noKimCooldowns"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="instantResourceExtractorDrones" />
|
||||
<label class="form-check-label" for="instantResourceExtractorDrones" data-loc="cheats_instantResourceExtractorDrones"></label>
|
||||
|
@ -277,9 +277,7 @@ function fetchItemList() {
|
||||
} else {
|
||||
const nameSet = new Set();
|
||||
items.forEach(item => {
|
||||
if (item.name.includes("<ARCHWING> ")) {
|
||||
item.name = item.name.replace("<ARCHWING> ", "");
|
||||
}
|
||||
item.name = item.name.replace(/<.+>/g, "").trim();
|
||||
if ("badReason" in item) {
|
||||
if (item.badReason == "starter") {
|
||||
item.name = loc("code_starter").split("|MOD|").join(item.name);
|
||||
|
@ -105,6 +105,7 @@ dict = {
|
||||
currency_owned: `Du hast |COUNT|.`,
|
||||
powersuit_archonShardsLabel: `Archon-Scherben-Slots`,
|
||||
powersuit_archonShardsDescription: `Du kannst diese unbegrenzten Slots nutzen, um eine Vielzahl von Verbesserungen anzuwenden.`,
|
||||
powersuit_archonShardsDescription2: `Hinweis: Jede Archon-Scherbe benötigt beim Laden etwas Zeit, um angewendet zu werden.`,
|
||||
mods_addRiven: `Riven hinzufügen`,
|
||||
mods_fingerprint: `Fingerabdruck`,
|
||||
mods_fingerprintHelp: `Benötigst du Hilfe mit dem Fingerabdruck?`,
|
||||
@ -136,6 +137,7 @@ dict = {
|
||||
cheats_noArgonCrystalDecay: `Argon-Kristalle verschwinden niemals`,
|
||||
cheats_noMasteryRankUpCooldown: `Keine Wartezeit beim Meisterschaftsrangaufstieg`,
|
||||
cheats_noVendorPurchaseLimits: `Keine Kaufbeschränkungen bei Händlern`,
|
||||
cheats_noKimCooldowns: `[UNTRANSLATED] No KIM Cooldowns`,
|
||||
cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
|
||||
cheats_noDojoRoomBuildStage: `Kein Dojo-Raum-Bauvorgang`,
|
||||
cheats_noDojoDecoBuildStage: `Kein Dojo-Deko-Bauvorgang`,
|
||||
|
@ -103,7 +103,8 @@ dict = {
|
||||
currency_PrimeTokens: `Regal Aya`,
|
||||
currency_owned: `You have |COUNT|.`,
|
||||
powersuit_archonShardsLabel: `Archon Shard Slots`,
|
||||
powersuit_archonShardsDescription: `You can use these unlimited slots to apply a wide range of upgrades`,
|
||||
powersuit_archonShardsDescription: `You can use these unlimited slots to apply a wide range of upgrades.`,
|
||||
powersuit_archonShardsDescription2: `Note that each archon shard takes some time to be applied when loading in.`,
|
||||
mods_addRiven: `Add Riven`,
|
||||
mods_fingerprint: `Fingerprint`,
|
||||
mods_fingerprintHelp: `Need help with the fingerprint?`,
|
||||
@ -135,6 +136,7 @@ dict = {
|
||||
cheats_noArgonCrystalDecay: `No Argon Crystal Decay`,
|
||||
cheats_noMasteryRankUpCooldown: `No Mastery Rank Up Cooldown`,
|
||||
cheats_noVendorPurchaseLimits: `No Vendor Purchase Limits`,
|
||||
cheats_noKimCooldowns: `No KIM Cooldowns`,
|
||||
cheats_instantResourceExtractorDrones: `Instant Resource Extractor Drones`,
|
||||
cheats_noDojoRoomBuildStage: `No Dojo Room Build Stage`,
|
||||
cheats_noDojoDecoBuildStage: `No Dojo Deco Build Stage`,
|
||||
|
160
static/webui/translations/es.js
Normal file
160
static/webui/translations/es.js
Normal file
@ -0,0 +1,160 @@
|
||||
// Spanish translation by hxedcl
|
||||
dict = {
|
||||
general_inventoryUpdateNote: `Nota: Los cambios realizados aquí se reflejarán en el juego cuando este sincronice el inventario. Usar la navegación debería ser la forma más sencilla de activar esto.`,
|
||||
general_addButton: `Agregar`,
|
||||
general_bulkActions: `Acciones masivas`,
|
||||
code_nonValidAuthz: `Tus credenciales no son válidas.`,
|
||||
code_changeNameConfirm: `¿Qué nombre te gustaría ponerle a tu cuenta?`,
|
||||
code_deleteAccountConfirm: `¿Estás seguro de que deseas eliminar tu cuenta |DISPLAYNAME| (|EMAIL|)? Esta acción es permanente.`,
|
||||
code_archgun: `Archcañón`,
|
||||
code_melee: `Cuerpo a cuerpo`,
|
||||
code_pistol: `Pistola`,
|
||||
code_rifle: `Rifle`,
|
||||
code_shotgun: `Escopeta`,
|
||||
code_kitgun: `Kitgun`,
|
||||
code_zaw: `Zaw`,
|
||||
code_moteAmp: `Amp Mota`,
|
||||
code_amp: `Amp`,
|
||||
code_kDrive: `K-Drive`,
|
||||
code_legendaryCore: `Núcleo legendario`,
|
||||
code_traumaticPeculiar: `Traumatismo peculiar`,
|
||||
code_starter: `|MOD| (Defectuoso)`,
|
||||
code_badItem: `(Impostor)`,
|
||||
code_maxRank: `Rango máximo`,
|
||||
code_rename: `Renombrar`,
|
||||
code_renamePrompt: `Escribe tu nuevo nombre personalizado:`,
|
||||
code_remove: `Quitar`,
|
||||
code_addItemsConfirm: `¿Estás seguro de que deseas agregar |COUNT| objetos a tu cuenta?`,
|
||||
code_succRankUp: `Ascenso exitoso.`,
|
||||
code_noEquipmentToRankUp: `No hay equipo para ascender.`,
|
||||
code_succAdded: `Agregado exitosamente.`,
|
||||
code_succRemoved: `Eliminado exitosamente.`,
|
||||
code_buffsNumber: `Cantidad de mejoras`,
|
||||
code_cursesNumber: `Cantidad de maldiciones`,
|
||||
code_rerollsNumber: `Cantidad de reintentos`,
|
||||
code_viewStats: `Ver estadísticas`,
|
||||
code_rank: `Rango`,
|
||||
code_count: `Cantidad`,
|
||||
code_focusAllUnlocked: `Todas las escuelas de enfoque ya están desbloqueadas.`,
|
||||
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_gild: `Refinar`,
|
||||
code_moa: `Moa`,
|
||||
code_zanuka: `Sabueso`,
|
||||
code_zanukaA: `Sabueso Dorma`,
|
||||
code_zanukaB: `Sabueso Bhaira`,
|
||||
code_zanukaC: `Sabueso Hec`,
|
||||
code_stage: `Etapa`,
|
||||
code_complete: `Completa`,
|
||||
code_nextStage: `Siguiente etapa`,
|
||||
code_prevStage: `Etapa anterior`,
|
||||
code_reset: `Reiniciar`,
|
||||
code_setInactive: `Marcar la misión como inactiva`,
|
||||
code_completed: `Completada`,
|
||||
code_active: `Activa`,
|
||||
code_pigment: `Pigmento`,
|
||||
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`,
|
||||
login_loginButton: `Iniciar sesión`,
|
||||
navbar_logout: `Cerrar sesión`,
|
||||
navbar_renameAccount: `Renombrar cuenta`,
|
||||
navbar_deleteAccount: `Eliminar cuenta`,
|
||||
navbar_inventory: `Inventario`,
|
||||
navbar_mods: `Mods`,
|
||||
navbar_quests: `Misiones`,
|
||||
navbar_cheats: `Trucos`,
|
||||
navbar_import: `Importar`,
|
||||
inventory_addItems: `Agregar objetos`,
|
||||
inventory_suits: `Warframes`,
|
||||
inventory_longGuns: `Armas primarias`,
|
||||
inventory_pistols: `Armas secundarias`,
|
||||
inventory_melee: `Armas cuerpo a cuerpo`,
|
||||
inventory_spaceSuits: `Archwings`,
|
||||
inventory_spaceGuns: `Armas primarias Archwing`,
|
||||
inventory_spaceMelee: `Armas cuerpo a cuerpo Archwing`,
|
||||
inventory_mechSuits: `Necramechs`,
|
||||
inventory_sentinels: `Centinelas`,
|
||||
inventory_sentinelWeapons: `Armas de centinela`,
|
||||
inventory_operatorAmps: `Amps`,
|
||||
inventory_hoverboards: `K-Drives`,
|
||||
inventory_moaPets: `Moa`,
|
||||
inventory_bulkAddSuits: `Agregar Warframes faltantes`,
|
||||
inventory_bulkAddWeapons: `Agregar armas faltantes`,
|
||||
inventory_bulkAddSpaceSuits: `Agregar Archwings faltantes`,
|
||||
inventory_bulkAddSpaceWeapons: `Agregar armas Archwing faltantes`,
|
||||
inventory_bulkAddSentinels: `Agregar centinelas faltantes`,
|
||||
inventory_bulkAddSentinelWeapons: `Agregar armas de centinela faltantes`,
|
||||
inventory_bulkRankUpSuits: `Maximizar rango de todos los Warframes`,
|
||||
inventory_bulkRankUpWeapons: `Maximizar rango de todas las armas`,
|
||||
inventory_bulkRankUpSpaceSuits: `Maximizar rango de todos los Archwings`,
|
||||
inventory_bulkRankUpSpaceWeapons: `Maximizar rango de todas las armas Archwing`,
|
||||
inventory_bulkRankUpSentinels: `Maximizar rango de todos los centinelas`,
|
||||
inventory_bulkRankUpSentinelWeapons: `Maximizar rango de todas las armas de centinela`,
|
||||
|
||||
quests_list: `Misiones`,
|
||||
quests_completeAll: `Completar todas las misiones`,
|
||||
quests_resetAll: `Reiniciar todas las misiones`,
|
||||
quests_giveAll: `Otorgar todas las misiones`,
|
||||
|
||||
currency_RegularCredits: `Créditos`,
|
||||
currency_PremiumCredits: `Platino`,
|
||||
currency_FusionPoints: `Endo`,
|
||||
currency_PrimeTokens: `Aya Real`,
|
||||
currency_owned: `Tienes |COUNT|.`,
|
||||
powersuit_archonShardsLabel: `Ranuras de Fragmento de Archón`,
|
||||
powersuit_archonShardsDescription: `Puedes usar estas ranuras ilimitadas para aplicar una amplia variedad de mejoras`,
|
||||
powersuit_archonShardsDescription2: `[UNTRANSLATED] Note that each archon shard takes some time to be applied when loading in.`,
|
||||
mods_addRiven: `Agregar Agrietado`,
|
||||
mods_fingerprint: `Huella digital`,
|
||||
mods_fingerprintHelp: `¿Necesitas ayuda con la huella digital?`,
|
||||
mods_rivens: `Agrietados`,
|
||||
mods_mods: `Mods`,
|
||||
mods_bulkAddMods: `Agregar mods faltantes`,
|
||||
cheats_administratorRequirement: `Debes ser administrador para usar esta función. Para convertirte en administrador, agrega <code>|DISPLAYNAME|</code> a <code>administratorNames</code> en el archivo config.json.`,
|
||||
cheats_server: `Servidor`,
|
||||
cheats_skipTutorial: `Omitir tutorial`,
|
||||
cheats_skipAllDialogue: `Omitir todos los diálogos`,
|
||||
cheats_unlockAllScans: `Desbloquear todos los escaneos`,
|
||||
cheats_unlockAllMissions: `Desbloquear todas las misiones`,
|
||||
cheats_infiniteCredits: `Créditos infinitos`,
|
||||
cheats_infinitePlatinum: `Platino infinito`,
|
||||
cheats_infiniteEndo: `Endo infinito`,
|
||||
cheats_infiniteRegalAya: `Aya Real infinita`,
|
||||
cheats_infiniteHelminthMaterials: `Materiales Helminto infinitos`,
|
||||
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 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`,
|
||||
cheats_unlockExilusEverywhere: `Adaptadores Exilus en todas partes`,
|
||||
cheats_unlockArcanesEverywhere: `Adaptadores de Arcanos en todas partes`,
|
||||
cheats_noDailyStandingLimits: `Sin límite diario de reputación`,
|
||||
cheats_noArgonCrystalDecay: `Sin descomposición de cristal de Argón`,
|
||||
cheats_noMasteryRankUpCooldown: `Sin tiempo de espera para rango de maestría`,
|
||||
cheats_noVendorPurchaseLimits: `Sin límite de compras de vendedores`,
|
||||
cheats_noKimCooldowns: `[UNTRANSLATED] No KIM Cooldowns`,
|
||||
cheats_instantResourceExtractorDrones: `Drones de extracción de recursos instantáneos`,
|
||||
cheats_noDojoRoomBuildStage: `Sin etapa de construcción de sala del dojo`,
|
||||
cheats_noDojoDecoBuildStage: `Sin etapa de construcción de decoraciones del dojo`,
|
||||
cheats_fastDojoRoomDestruction: `Destrucción rápida de salas del dojo`,
|
||||
cheats_noDojoResearchCosts: `Sin costo de investigación del dojo`,
|
||||
cheats_noDojoResearchTime: `Sin tiempo de investigación del dojo`,
|
||||
cheats_fastClanAscension: `Ascenso rápido del clan`,
|
||||
cheats_spoofMasteryRank: `Rango de maestría simulado (-1 para desactivar)`,
|
||||
cheats_saveSettings: `Guardar configuración`,
|
||||
cheats_account: `Cuenta`,
|
||||
cheats_unlockAllFocusSchools: `Desbloquear todas las escuelas de enfoque`,
|
||||
cheats_helminthUnlockAll: `Subir al máximo el Helminto`,
|
||||
cheats_intrinsicsUnlockAll: `Maximizar todos los intrínsecos`,
|
||||
cheats_changeSupportedSyndicate: `Sindicatos disponibles`,
|
||||
cheats_changeButton: `Cambiar`,
|
||||
cheats_none: `Ninguno`,
|
||||
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`,
|
||||
prettier_sucks_ass: ``
|
||||
};
|
@ -105,6 +105,7 @@ dict = {
|
||||
currency_owned: `|COUNT| possédés.`,
|
||||
powersuit_archonShardsLabel: `Emplacements de fragments d'Archonte`,
|
||||
powersuit_archonShardsDescription: `Slots illimités pour appliquer plusieurs améliorations.`,
|
||||
powersuit_archonShardsDescription2: `[UNTRANSLATED] Note that each archon shard takes some time to be applied when loading in.`,
|
||||
mods_addRiven: `Ajouter un riven`,
|
||||
mods_fingerprint: `Empreinte`,
|
||||
mods_fingerprintHelp: `Besoin d'aide pour l'empreinte ?`,
|
||||
@ -136,6 +137,7 @@ dict = {
|
||||
cheats_noArgonCrystalDecay: `[UNTRANSLATED] No Argon Crystal Decay`,
|
||||
cheats_noMasteryRankUpCooldown: `[UNTRANSLATED] No Mastery Rank Up Cooldown`,
|
||||
cheats_noVendorPurchaseLimits: `[UNTRANSLATED] No Vendor Purchase Limits`,
|
||||
cheats_noKimCooldowns: `[UNTRANSLATED] No KIM Cooldowns`,
|
||||
cheats_instantResourceExtractorDrones: `Ressources de drone d'extraction instantannées`,
|
||||
cheats_noDojoRoomBuildStage: `No Dojo Room Build Stage`,
|
||||
cheats_noDojoDecoBuildStage: `[UNTRANSLATED] No Dojo Deco Build Stage`,
|
||||
|
@ -105,6 +105,7 @@ dict = {
|
||||
currency_owned: `У тебя |COUNT|.`,
|
||||
powersuit_archonShardsLabel: `Ячейки осколков архонта`,
|
||||
powersuit_archonShardsDescription: `Вы можете использовать эти неограниченные ячейки для установки множества улучшений.`,
|
||||
powersuit_archonShardsDescription2: `[UNTRANSLATED] Note that each archon shard takes some time to be applied when loading in.`,
|
||||
mods_addRiven: `Добавить Мод Разлома`,
|
||||
mods_fingerprint: `Отпечаток`,
|
||||
mods_fingerprintHelp: `Нужна помощь с отпечатком?`,
|
||||
@ -134,11 +135,12 @@ dict = {
|
||||
cheats_unlockArcanesEverywhere: `Адаптеры для мистификаторов везде`,
|
||||
cheats_noDailyStandingLimits: `Без ежедневных ограничений репутации`,
|
||||
cheats_noArgonCrystalDecay: `Без распада аргоновых кристаллов`,
|
||||
cheats_noMasteryRankUpCooldown: `[UNTRANSLATED] No Mastery Rank Up Cooldown`,
|
||||
cheats_noMasteryRankUpCooldown: `Повышение ранга мастерства без кулдауна`,
|
||||
cheats_noVendorPurchaseLimits: `Отсутствие лимитов на покупки у вендоров`,
|
||||
cheats_noKimCooldowns: `[UNTRANSLATED] No KIM Cooldowns`,
|
||||
cheats_instantResourceExtractorDrones: `Мгновенные Экстракторы Ресурсов`,
|
||||
cheats_noDojoRoomBuildStage: `Мгновенное Строительтво Комнат Додзё`,
|
||||
cheats_noDojoDecoBuildStage: `[UNTRANSLATED] No Dojo Deco Build Stage`,
|
||||
cheats_noDojoDecoBuildStage: `Мгновенное Строительтво Декораций Додзё`,
|
||||
cheats_fastDojoRoomDestruction: `Мгновенные Уничтожение Комнат Додзё`,
|
||||
cheats_noDojoResearchCosts: `Бесплатные Исследование Додзё`,
|
||||
cheats_noDojoResearchTime: `Мгновенные Исследование Додзё`,
|
||||
|
@ -105,6 +105,7 @@ dict = {
|
||||
currency_owned: `当前拥有 |COUNT|。`,
|
||||
powersuit_archonShardsLabel: `执刑官源力石槽位`,
|
||||
powersuit_archonShardsDescription: `您可以使用这些无限插槽应用各种强化效果`,
|
||||
powersuit_archonShardsDescription2: `[UNTRANSLATED] Note that each archon shard takes some time to be applied when loading in.`,
|
||||
mods_addRiven: `添加裂罅MOD`,
|
||||
mods_fingerprint: `印记`,
|
||||
mods_fingerprintHelp: `需要印记相关的帮助?`,
|
||||
@ -136,6 +137,7 @@ dict = {
|
||||
cheats_noArgonCrystalDecay: `[UNTRANSLATED] No Argon Crystal Decay`,
|
||||
cheats_noMasteryRankUpCooldown: `[UNTRANSLATED] No Mastery Rank Up Cooldown`,
|
||||
cheats_noVendorPurchaseLimits: `[UNTRANSLATED] No Vendor Purchase Limits`,
|
||||
cheats_noKimCooldowns: `[UNTRANSLATED] No KIM Cooldowns`,
|
||||
cheats_instantResourceExtractorDrones: `即时资源采集无人机`,
|
||||
cheats_noDojoRoomBuildStage: `无视道场房间建造阶段`,
|
||||
cheats_noDojoDecoBuildStage: `[UNTRANSLATED] No Dojo Deco Build Stage`,
|
||||
|
Loading…
x
Reference in New Issue
Block a user