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
|
# Auto detect text files and perform LF normalization
|
||||||
* text=auto
|
* text=auto eol=lf
|
||||||
|
|
||||||
static/webui/libs/ linguist-vendored
|
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 verify
|
||||||
- run: npm run lint:ci
|
- run: npm run lint:ci
|
||||||
- run: npm run prettier
|
- run: npm run prettier
|
||||||
|
- run: npm run update-translations
|
||||||
- name: Fail if there are uncommitted changes
|
- name: Fail if there are uncommitted changes
|
||||||
run: |
|
run: |
|
||||||
if [[ -n "$(git status --porcelain)" ]]; then
|
if [[ -n "$(git status --porcelain)" ]]; then
|
||||||
echo "Uncommitted changes detected:"
|
echo "Uncommitted changes detected:"
|
||||||
git status
|
git status
|
||||||
git diff
|
git --no-pager diff
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
"noArgonCrystalDecay": false,
|
"noArgonCrystalDecay": false,
|
||||||
"noMasteryRankUpCooldown": false,
|
"noMasteryRankUpCooldown": false,
|
||||||
"noVendorPurchaseLimits": true,
|
"noVendorPurchaseLimits": true,
|
||||||
|
"noKimCooldowns": false,
|
||||||
"instantResourceExtractorDrones": false,
|
"instantResourceExtractorDrones": false,
|
||||||
"noDojoRoomBuildStage": false,
|
"noDojoRoomBuildStage": false,
|
||||||
"noDecoBuildStage": false,
|
"noDecoBuildStage": false,
|
||||||
|
8
package-lock.json
generated
8
package-lock.json
generated
@ -18,7 +18,7 @@
|
|||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"ncp": "^2.0.0",
|
"ncp": "^2.0.0",
|
||||||
"typescript": "^5.5",
|
"typescript": "^5.5",
|
||||||
"warframe-public-export-plus": "^0.5.53",
|
"warframe-public-export-plus": "^0.5.54",
|
||||||
"warframe-riven-info": "^0.1.2",
|
"warframe-riven-info": "^0.1.2",
|
||||||
"winston": "^3.17.0",
|
"winston": "^3.17.0",
|
||||||
"winston-daily-rotate-file": "^5.0.0"
|
"winston-daily-rotate-file": "^5.0.0"
|
||||||
@ -3789,9 +3789,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/warframe-public-export-plus": {
|
"node_modules/warframe-public-export-plus": {
|
||||||
"version": "0.5.53",
|
"version": "0.5.54",
|
||||||
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.53.tgz",
|
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.54.tgz",
|
||||||
"integrity": "sha512-FjYeCJ5OxvPWyETnV33YOeX7weVVeMy451RY7uewwSvRbSNFTDhmhvbrLhfwykulUX4RPakfZr8nO0S0a6lGCA=="
|
"integrity": "sha512-27r6qLErr3P8UVDiEzhDAs/BjdAS3vI2CQ58jSI+LClDlj6QL+y1jQe8va/npl3Ft2K8PywLkZ8Yso0j9YzvOA=="
|
||||||
},
|
},
|
||||||
"node_modules/warframe-riven-info": {
|
"node_modules/warframe-riven-info": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"ncp": "^2.0.0",
|
"ncp": "^2.0.0",
|
||||||
"typescript": "^5.5",
|
"typescript": "^5.5",
|
||||||
"warframe-public-export-plus": "^0.5.53",
|
"warframe-public-export-plus": "^0.5.54",
|
||||||
"warframe-riven-info": "^0.1.2",
|
"warframe-riven-info": "^0.1.2",
|
||||||
"winston": "^3.17.0",
|
"winston": "^3.17.0",
|
||||||
"winston-daily-rotate-file": "^5.0.0"
|
"winston-daily-rotate-file": "^5.0.0"
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
|
||||||
function extractStrings(content) {
|
function extractStrings(content) {
|
||||||
const regex = /([a-zA-Z_]+): `([^`]*)`,/g;
|
const regex = /([a-zA-Z0-9_]+): `([^`]*)`,/g;
|
||||||
let matches;
|
let matches;
|
||||||
const strings = {};
|
const strings = {};
|
||||||
while ((matches = regex.exec(content)) !== null) {
|
while ((matches = regex.exec(content)) !== null) {
|
||||||
@ -15,7 +15,7 @@ function extractStrings(content) {
|
|||||||
|
|
||||||
const source = fs.readFileSync("../static/webui/translations/en.js", "utf8");
|
const source = fs.readFileSync("../static/webui/translations/en.js", "utf8");
|
||||||
const sourceStrings = extractStrings(source);
|
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 => {
|
fs.readdirSync("../static/webui/translations").forEach(file => {
|
||||||
if (fs.lstatSync(`../static/webui/translations/${file}`).isFile() && file !== "en.js") {
|
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`);
|
fs.writeSync(fileHandle, ` ${key}: \`[UNTRANSLATED] ${value}\`,\n`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (line.length) {
|
} else {
|
||||||
fs.writeSync(fileHandle, line + "\n");
|
fs.writeSync(fileHandle, line + "\n");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -16,9 +16,9 @@ import { webuiRouter } from "@/src/routes/webui";
|
|||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
app.use((req, _res, next) => {
|
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.
|
// 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;
|
req.headers["content-encoding"] = undefined;
|
||||||
}
|
}
|
||||||
next();
|
next();
|
||||||
|
@ -32,11 +32,11 @@ import { logger } from "@/src/utils/logger";
|
|||||||
export const guildTechController: RequestHandler = async (req, res) => {
|
export const guildTechController: RequestHandler = async (req, res) => {
|
||||||
const accountId = await getAccountIdForRequest(req);
|
const accountId = await getAccountIdForRequest(req);
|
||||||
const inventory = await getInventory(accountId);
|
const inventory = await getInventory(accountId);
|
||||||
const guild = await getGuildForRequestEx(req, inventory);
|
|
||||||
const data = JSON.parse(String(req.body)) as TGuildTechRequest;
|
const data = JSON.parse(String(req.body)) as TGuildTechRequest;
|
||||||
if (data.Action == "Sync") {
|
if (data.Action == "Sync") {
|
||||||
let needSave = false;
|
let needSave = false;
|
||||||
const techProjects: ITechProjectClient[] = [];
|
const techProjects: ITechProjectClient[] = [];
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
if (guild.TechProjects) {
|
if (guild.TechProjects) {
|
||||||
for (const project of guild.TechProjects) {
|
for (const project of guild.TechProjects) {
|
||||||
const techProject: ITechProjectClient = {
|
const techProject: ITechProjectClient = {
|
||||||
@ -59,6 +59,8 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
res.json({ TechProjects: techProjects });
|
res.json({ TechProjects: techProjects });
|
||||||
} else if (data.Action == "Start") {
|
} else if (data.Action == "Start") {
|
||||||
|
if (data.Mode == "Guild") {
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
|
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
|
||||||
res.status(400).send("-1").end();
|
res.status(400).send("-1").end();
|
||||||
return;
|
return;
|
||||||
@ -89,38 +91,94 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
await guild.save();
|
await guild.save();
|
||||||
res.end();
|
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()
|
||||||
|
});
|
||||||
|
}
|
||||||
} else if (data.Action == "Contribute") {
|
} else if (data.Action == "Contribute") {
|
||||||
|
if ((req.query.guildId as string) == "000000000000000000000000") {
|
||||||
|
const techProject = inventory.PersonalTechProjects.id(data.ResearchId)!;
|
||||||
|
|
||||||
|
techProject.ReqCredits -= data.RegularCredits;
|
||||||
|
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, data.RegularCredits, false);
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addMiscItems(inventory, miscItemChanges);
|
||||||
|
inventoryChanges.MiscItems = miscItemChanges;
|
||||||
|
|
||||||
|
techProject.HasContributions = true;
|
||||||
|
|
||||||
|
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)) {
|
if (!hasAccessToDojo(inventory)) {
|
||||||
res.status(400).send("-1").end();
|
res.status(400).send("-1").end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
const guildMember = (await GuildMember.findOne(
|
const guildMember = (await GuildMember.findOne(
|
||||||
{ accountId, guildId: guild._id },
|
{ accountId, guildId: guild._id },
|
||||||
"RegularCreditsContributed MiscItemsContributed"
|
"RegularCreditsContributed MiscItemsContributed"
|
||||||
))!;
|
))!;
|
||||||
|
|
||||||
const contributions = data;
|
const techProject = guild.TechProjects!.find(x => x.ItemType == data.RecipeType)!;
|
||||||
const techProject = guild.TechProjects!.find(x => x.ItemType == contributions.RecipeType)!;
|
|
||||||
|
|
||||||
if (contributions.VaultCredits) {
|
if (data.VaultCredits) {
|
||||||
if (contributions.VaultCredits > techProject.ReqCredits) {
|
if (data.VaultCredits > techProject.ReqCredits) {
|
||||||
contributions.VaultCredits = techProject.ReqCredits;
|
data.VaultCredits = techProject.ReqCredits;
|
||||||
}
|
}
|
||||||
techProject.ReqCredits -= contributions.VaultCredits;
|
techProject.ReqCredits -= data.VaultCredits;
|
||||||
guild.VaultRegularCredits! -= contributions.VaultCredits;
|
guild.VaultRegularCredits! -= data.VaultCredits;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contributions.RegularCredits > techProject.ReqCredits) {
|
if (data.RegularCredits > techProject.ReqCredits) {
|
||||||
contributions.RegularCredits = techProject.ReqCredits;
|
data.RegularCredits = techProject.ReqCredits;
|
||||||
}
|
}
|
||||||
techProject.ReqCredits -= contributions.RegularCredits;
|
techProject.ReqCredits -= data.RegularCredits;
|
||||||
|
|
||||||
guildMember.RegularCreditsContributed ??= 0;
|
guildMember.RegularCreditsContributed ??= 0;
|
||||||
guildMember.RegularCreditsContributed += contributions.RegularCredits;
|
guildMember.RegularCreditsContributed += data.RegularCredits;
|
||||||
|
|
||||||
if (contributions.VaultMiscItems.length) {
|
if (data.VaultMiscItems.length) {
|
||||||
for (const miscItem of contributions.VaultMiscItems) {
|
for (const miscItem of data.VaultMiscItems) {
|
||||||
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
|
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
|
||||||
if (reqItem) {
|
if (reqItem) {
|
||||||
if (miscItem.ItemCount > reqItem.ItemCount) {
|
if (miscItem.ItemCount > reqItem.ItemCount) {
|
||||||
@ -135,7 +193,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const miscItemChanges = [];
|
const miscItemChanges = [];
|
||||||
for (const miscItem of contributions.MiscItems) {
|
for (const miscItem of data.MiscItems) {
|
||||||
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
|
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
|
||||||
if (reqItem) {
|
if (reqItem) {
|
||||||
if (miscItem.ItemCount > reqItem.ItemCount) {
|
if (miscItem.ItemCount > reqItem.ItemCount) {
|
||||||
@ -151,7 +209,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
addMiscItems(inventory, miscItemChanges);
|
addMiscItems(inventory, miscItemChanges);
|
||||||
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, contributions.RegularCredits, false);
|
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, data.RegularCredits, false);
|
||||||
inventoryChanges.MiscItems = miscItemChanges;
|
inventoryChanges.MiscItems = miscItemChanges;
|
||||||
|
|
||||||
// Check if research is fully funded now.
|
// Check if research is fully funded now.
|
||||||
@ -162,7 +220,9 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
|||||||
InventoryChanges: inventoryChanges,
|
InventoryChanges: inventoryChanges,
|
||||||
Vault: getGuildVault(guild)
|
Vault: getGuildVault(guild)
|
||||||
});
|
});
|
||||||
|
}
|
||||||
} else if (data.Action.split(",")[0] == "Buy") {
|
} else if (data.Action.split(",")[0] == "Buy") {
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
|
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
|
||||||
res.status(400).send("-1").end();
|
res.status(400).send("-1").end();
|
||||||
return;
|
return;
|
||||||
@ -190,6 +250,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (data.Action == "Fabricate") {
|
} else if (data.Action == "Fabricate") {
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
|
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
|
||||||
res.status(400).send("-1").end();
|
res.status(400).send("-1").end();
|
||||||
return;
|
return;
|
||||||
@ -206,6 +267,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
|||||||
// Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`.
|
// Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`.
|
||||||
res.json({ inventoryChanges: inventoryChanges });
|
res.json({ inventoryChanges: inventoryChanges });
|
||||||
} else if (data.Action == "Pause") {
|
} else if (data.Action == "Pause") {
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
|
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
|
||||||
res.status(400).send("-1").end();
|
res.status(400).send("-1").end();
|
||||||
return;
|
return;
|
||||||
@ -217,6 +279,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
|||||||
await removePigmentsFromGuildMembers(guild._id);
|
await removePigmentsFromGuildMembers(guild._id);
|
||||||
res.end();
|
res.end();
|
||||||
} else if (data.Action == "Unpause") {
|
} else if (data.Action == "Unpause") {
|
||||||
|
const guild = await getGuildForRequestEx(req, inventory);
|
||||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
|
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
|
||||||
res.status(400).send("-1").end();
|
res.status(400).send("-1").end();
|
||||||
return;
|
return;
|
||||||
@ -239,7 +302,7 @@ type TGuildTechRequest =
|
|||||||
|
|
||||||
interface IGuildTechBasicRequest {
|
interface IGuildTechBasicRequest {
|
||||||
Action: "Start" | "Fabricate" | "Pause" | "Unpause";
|
Action: "Start" | "Fabricate" | "Pause" | "Unpause";
|
||||||
Mode: "Guild";
|
Mode: "Guild" | "Personal";
|
||||||
RecipeType: string;
|
RecipeType: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,7 +314,7 @@ interface IGuildTechBuyRequest {
|
|||||||
|
|
||||||
interface IGuildTechContributeRequest {
|
interface IGuildTechContributeRequest {
|
||||||
Action: "Contribute";
|
Action: "Contribute";
|
||||||
ResearchId: "";
|
ResearchId: string;
|
||||||
RecipeType: string;
|
RecipeType: string;
|
||||||
RegularCredits: number;
|
RegularCredits: number;
|
||||||
MiscItems: IMiscItem[];
|
MiscItems: IMiscItem[];
|
||||||
|
@ -202,7 +202,8 @@ export const getInventoryResponse = async (
|
|||||||
|
|
||||||
if (config.universalPolarityEverywhere) {
|
if (config.universalPolarityEverywhere) {
|
||||||
const Polarity: IPolarity[] = [];
|
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({
|
Polarity.push({
|
||||||
Slot: i,
|
Slot: i,
|
||||||
Value: ArtifactPolarity.Any
|
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 { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||||
|
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
|
||||||
import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
|
import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { SRng } from "@/src/services/rngService";
|
import { SRng } from "@/src/services/rngService";
|
||||||
import { IMongoDate, IOid } from "@/src/types/commonTypes";
|
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 { logger } from "@/src/utils/logger";
|
||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
@ -49,7 +64,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
|||||||
} else if ((req.query.mode as string) == "p") {
|
} else if ((req.query.mode as string) == "p") {
|
||||||
const inventory = await getInventory(accountId, "Nemesis");
|
const inventory = await getInventory(accountId, "Nemesis");
|
||||||
const body = getJSONfromString<INemesisPrespawnCheckRequest>(String(req.body));
|
const body = getJSONfromString<INemesisPrespawnCheckRequest>(String(req.body));
|
||||||
const passcode = getNemesisPasscode(inventory.Nemesis!.fp, inventory.Nemesis!.Faction);
|
const passcode = getNemesisPasscode(inventory.Nemesis!);
|
||||||
let guessResult = 0;
|
let guessResult = 0;
|
||||||
if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
|
if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
|
||||||
for (let i = 0; i != 3; ++i) {
|
for (let i = 0; i != 3; ++i) {
|
||||||
@ -66,6 +81,88 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.json({ GuessResult: guessResult });
|
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") {
|
} else if ((req.query.mode as string) == "s") {
|
||||||
const inventory = await getInventory(accountId, "Nemesis");
|
const inventory = await getInventory(accountId, "Nemesis");
|
||||||
const body = getJSONfromString<INemesisStartRequest>(String(req.body));
|
const body = getJSONfromString<INemesisStartRequest>(String(req.body));
|
||||||
@ -173,6 +270,20 @@ interface INemesisPrespawnCheckRequest {
|
|||||||
potency?: number[];
|
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 = [
|
const kuvaLichVersionSixWeapons = [
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon",
|
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon",
|
||||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak",
|
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak",
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||||
|
import { config } from "@/src/services/configService";
|
||||||
import { addEmailItem, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
import { addEmailItem, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { ICompletedDialogue, IDialogueDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
|
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");
|
throw new Error("bad inventory state");
|
||||||
}
|
}
|
||||||
const inventoryChanges: IInventoryChanges = {};
|
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 ??= [];
|
inventory.DialogueHistory.Dialogues ??= [];
|
||||||
const dialogue = getDialogue(inventory, request.DialogueName);
|
const dialogue = getDialogue(inventory, request.DialogueName);
|
||||||
dialogue.Rank = request.Rank;
|
dialogue.Rank = request.Rank;
|
||||||
|
@ -3,15 +3,9 @@ import { RequestHandler } from "express";
|
|||||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||||
import { ExportNightwave, ExportSyndicates, ISyndicateSacrifice } from "warframe-public-export-plus";
|
import { ExportNightwave, ExportSyndicates, ISyndicateSacrifice } from "warframe-public-export-plus";
|
||||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||||
import {
|
import { addMiscItems, combineInventoryChanges, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||||
addItem,
|
|
||||||
addMiscItems,
|
|
||||||
combineInventoryChanges,
|
|
||||||
getInventory,
|
|
||||||
updateCurrency
|
|
||||||
} from "@/src/services/inventoryService";
|
|
||||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
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) => {
|
export const syndicateSacrificeController: RequestHandler = async (request, response) => {
|
||||||
const accountId = await getAccountIdForRequest(request);
|
const accountId = await getAccountIdForRequest(request);
|
||||||
@ -77,10 +71,13 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp
|
|||||||
res.NewEpisodeReward = true;
|
res.NewEpisodeReward = true;
|
||||||
const reward = ExportNightwave.rewards[index];
|
const reward = ExportNightwave.rewards[index];
|
||||||
let rewardType = reward.uniqueName;
|
let rewardType = reward.uniqueName;
|
||||||
if (isStoreItem(rewardType)) {
|
if (!isStoreItem(rewardType)) {
|
||||||
rewardType = fromStoreItem(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 { ExportRegions } from "warframe-public-export-plus";
|
||||||
import { IInfNode } from "@/src/types/inventoryTypes/inventoryTypes";
|
import { IInfNode } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||||
import { SRng } from "@/src/services/rngService";
|
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[] => {
|
export const getInfNodes = (faction: string, rank: number): IInfNode[] => {
|
||||||
const infNodes = [];
|
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.
|
// 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[] => {
|
export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: string }): number[] => {
|
||||||
const rng = new SRng(fp);
|
const rng = new SRng(nemesis.fp);
|
||||||
const passcode = [rng.randomInt(0, 7)];
|
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));
|
||||||
passcode.push(rng.randomInt(0, 7));
|
passcode.push(rng.randomInt(0, 7));
|
||||||
}
|
}
|
||||||
return passcode;
|
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,
|
IInfNode,
|
||||||
IDiscoveredMarker,
|
IDiscoveredMarker,
|
||||||
IWeeklyMission,
|
IWeeklyMission,
|
||||||
ILockedWeaponGroupDatabase
|
ILockedWeaponGroupDatabase,
|
||||||
|
IPersonalTechProjectDatabase,
|
||||||
|
IPersonalTechProjectClient
|
||||||
} from "../../types/inventoryTypes/inventoryTypes";
|
} from "../../types/inventoryTypes/inventoryTypes";
|
||||||
import { IOid } from "../../types/commonTypes";
|
import { IOid } from "../../types/commonTypes";
|
||||||
import {
|
import {
|
||||||
@ -498,7 +500,34 @@ const seasonChallengeHistorySchema = new Schema<ISeasonChallenge>(
|
|||||||
{ _id: false }
|
{ _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>(
|
const playerSkillsSchema = new Schema<IPlayerSkills>(
|
||||||
{
|
{
|
||||||
LPP_SPACE: { type: Number, default: 0 },
|
LPP_SPACE: { type: Number, default: 0 },
|
||||||
@ -1442,7 +1471,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
|
|||||||
|
|
||||||
//Railjack craft
|
//Railjack craft
|
||||||
//https://warframe.fandom.com/wiki/Rising_Tide
|
//https://warframe.fandom.com/wiki/Rising_Tide
|
||||||
PersonalTechProjects: [Schema.Types.Mixed],
|
PersonalTechProjects: { type: [personalTechProjectSchema], default: [] },
|
||||||
|
|
||||||
//Modulars lvl and exp(Railjack|Duviri)
|
//Modulars lvl and exp(Railjack|Duviri)
|
||||||
//https://warframe.fandom.com/wiki/Intrinsics
|
//https://warframe.fandom.com/wiki/Intrinsics
|
||||||
@ -1471,7 +1500,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
|
|||||||
DuviriInfo: DuviriInfoSchema,
|
DuviriInfo: DuviriInfoSchema,
|
||||||
Mailbox: MailboxSchema,
|
Mailbox: MailboxSchema,
|
||||||
HandlerPoints: Number,
|
HandlerPoints: Number,
|
||||||
ChallengesFixVersion: Number,
|
ChallengesFixVersion: { type: Number, default: 6 },
|
||||||
PlayedParkourTutorial: Boolean,
|
PlayedParkourTutorial: Boolean,
|
||||||
ActiveLandscapeTraps: [Schema.Types.Mixed],
|
ActiveLandscapeTraps: [Schema.Types.Mixed],
|
||||||
RepVotes: [Schema.Types.Mixed],
|
RepVotes: [Schema.Types.Mixed],
|
||||||
@ -1585,6 +1614,7 @@ export type InventoryDocumentProps = {
|
|||||||
Drones: Types.DocumentArray<IDroneDatabase>;
|
Drones: Types.DocumentArray<IDroneDatabase>;
|
||||||
CrewShipWeaponSkins: Types.DocumentArray<IUpgradeDatabase>;
|
CrewShipWeaponSkins: Types.DocumentArray<IUpgradeDatabase>;
|
||||||
CrewShipSalvagedWeaponsSkins: Types.DocumentArray<IUpgradeDatabase>;
|
CrewShipSalvagedWeaponsSkins: Types.DocumentArray<IUpgradeDatabase>;
|
||||||
|
PersonalTechProjects: Types.DocumentArray<IPersonalTechProjectDatabase>;
|
||||||
} & { [K in TEquipmentKey]: Types.DocumentArray<IEquipmentDatabase> };
|
} & { [K in TEquipmentKey]: Types.DocumentArray<IEquipmentDatabase> };
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||||
|
@ -38,6 +38,7 @@ interface IConfig {
|
|||||||
noArgonCrystalDecay?: boolean;
|
noArgonCrystalDecay?: boolean;
|
||||||
noMasteryRankUpCooldown?: boolean;
|
noMasteryRankUpCooldown?: boolean;
|
||||||
noVendorPurchaseLimits?: boolean;
|
noVendorPurchaseLimits?: boolean;
|
||||||
|
noKimCooldowns?: boolean;
|
||||||
instantResourceExtractorDrones?: boolean;
|
instantResourceExtractorDrones?: boolean;
|
||||||
noDojoRoomBuildStage?: boolean;
|
noDojoRoomBuildStage?: boolean;
|
||||||
noDojoDecoBuildStage?: boolean;
|
noDojoDecoBuildStage?: boolean;
|
||||||
|
@ -963,7 +963,7 @@ export const addStanding = (
|
|||||||
const max = getMaxStanding(syndicateMeta, syndicate.Title ?? 0);
|
const max = getMaxStanding(syndicateMeta, syndicate.Title ?? 0);
|
||||||
if (syndicate.Standing + gainedStanding > max) gainedStanding = max - syndicate.Standing;
|
if (syndicate.Standing + gainedStanding > max) gainedStanding = max - syndicate.Standing;
|
||||||
|
|
||||||
if (!isMedallion || (isMedallion && syndicateMeta.medallionsCappedByDailyLimit)) {
|
if (!isMedallion || syndicateMeta.medallionsCappedByDailyLimit) {
|
||||||
if (gainedStanding > getStandingLimit(inventory, syndicateMeta.dailyLimitBin)) {
|
if (gainedStanding > getStandingLimit(inventory, syndicateMeta.dailyLimitBin)) {
|
||||||
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);
|
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,13 +185,14 @@ export const getKeyChainMessage = ({ KeyChain, ChainStage }: IKeyChainRequest):
|
|||||||
throw new Error(`KeyChain ${KeyChain} does not contain chain stages`);
|
throw new Error(`KeyChain ${KeyChain} does not contain chain stages`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyChainStage = chainStages[ChainStage];
|
let i = ChainStage;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
let chainStageMessage = chainStages[i].messageToSendWhenTriggered;
|
||||||
if (!keyChainStage) {
|
while (!chainStageMessage) {
|
||||||
throw new Error(`KeyChainStage ${ChainStage} not found`);
|
if (++i >= chainStages.length) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
chainStageMessage = chainStages[i].messageToSendWhenTriggered;
|
||||||
}
|
}
|
||||||
|
|
||||||
const chainStageMessage = keyChainStage.messageToSendWhenTriggered;
|
|
||||||
|
|
||||||
if (!chainStageMessage) {
|
if (!chainStageMessage) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -699,25 +699,10 @@ export const addMissionRewards = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (inventory.Nemesis.Faction == "FC_INFESTATION") {
|
if (inventory.Nemesis.Faction == "FC_INFESTATION") {
|
||||||
inventoryChanges.Nemesis.HenchmenKilled ??= 0;
|
|
||||||
inventoryChanges.Nemesis.MissionCount ??= 0;
|
|
||||||
|
|
||||||
inventory.Nemesis.HenchmenKilled += 5;
|
|
||||||
inventory.Nemesis.MissionCount += 1;
|
inventory.Nemesis.MissionCount += 1;
|
||||||
|
|
||||||
inventoryChanges.Nemesis.HenchmenKilled += 5;
|
inventoryChanges.Nemesis.MissionCount ??= 0;
|
||||||
inventoryChanges.Nemesis.MissionCount += 1;
|
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;
|
inventoryChanges.Nemesis.InfNodes = inventory.Nemesis.InfNodes;
|
||||||
@ -747,7 +732,7 @@ export const addMissionRewards = async (
|
|||||||
const endlessJob = syndicateEntry.Jobs.find(j => j.endless);
|
const endlessJob = syndicateEntry.Jobs.find(j => j.endless);
|
||||||
if (endlessJob) {
|
if (endlessJob) {
|
||||||
const index = rewardInfo.JobStage % endlessJob.xpAmounts.length;
|
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));
|
medallionAmount = Math.floor(endlessJob.xpAmounts[index] * (1 + 0.15000001 * excess));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -922,15 +907,140 @@ function getRandomMissionDrops(RewardInfo: IRewardInfo, tierOverride: number | u
|
|||||||
|
|
||||||
let rotations: number[] = [];
|
let rotations: number[] = [];
|
||||||
if (RewardInfo.jobId) {
|
if (RewardInfo.jobId) {
|
||||||
if (RewardInfo.JobTier! >= 0) {
|
if (RewardInfo.JobStage! >= 0) {
|
||||||
const id = RewardInfo.jobId.split("_")[3];
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const syndicateInfo = getWorldState().SyndicateMissions.find(x => x._id.$oid == id);
|
const [jobType, tierStr, hubNode, syndicateId, locationTag] = RewardInfo.jobId.split("_");
|
||||||
if (syndicateInfo) {
|
const tier = Number(tierStr);
|
||||||
const jobInfo = syndicateInfo.Jobs![RewardInfo.JobTier!];
|
let isEndlessJob = false;
|
||||||
rewardManifests = [jobInfo.rewards];
|
if (syndicateId) {
|
||||||
rotations = [RewardInfo.JobStage!];
|
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) {
|
} else if (RewardInfo.VaultsCracked) {
|
||||||
// For Spy missions, e.g. 3 vaults cracked = A, B, C
|
// For Spy missions, e.g. 3 vaults cracked = A, B, C
|
||||||
for (let i = 0; i != RewardInfo.VaultsCracked; ++i) {
|
for (let i = 0; i != RewardInfo.VaultsCracked; ++i) {
|
||||||
|
@ -46,6 +46,7 @@ export interface IInventoryDatabase
|
|||||||
| "EntratiVaultCountResetDate"
|
| "EntratiVaultCountResetDate"
|
||||||
| "BrandedSuits"
|
| "BrandedSuits"
|
||||||
| "LockedWeaponGroup"
|
| "LockedWeaponGroup"
|
||||||
|
| "PersonalTechProjects"
|
||||||
| TEquipmentKey
|
| TEquipmentKey
|
||||||
>,
|
>,
|
||||||
InventoryDatabaseEquipment {
|
InventoryDatabaseEquipment {
|
||||||
@ -77,6 +78,7 @@ export interface IInventoryDatabase
|
|||||||
EntratiVaultCountResetDate?: Date;
|
EntratiVaultCountResetDate?: Date;
|
||||||
BrandedSuits?: Types.ObjectId[];
|
BrandedSuits?: Types.ObjectId[];
|
||||||
LockedWeaponGroup?: ILockedWeaponGroupDatabase;
|
LockedWeaponGroup?: ILockedWeaponGroupDatabase;
|
||||||
|
PersonalTechProjects: IPersonalTechProjectDatabase[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IQuestKeyDatabase {
|
export interface IQuestKeyDatabase {
|
||||||
@ -157,6 +159,11 @@ export type TSolarMapRegion =
|
|||||||
|
|
||||||
//TODO: perhaps split response and database into their own files
|
//TODO: perhaps split response and database into their own files
|
||||||
|
|
||||||
|
export enum LoadoutIndex {
|
||||||
|
NORMAL = 0,
|
||||||
|
DATAKNIFE = 7
|
||||||
|
}
|
||||||
|
|
||||||
export interface IDailyAffiliations {
|
export interface IDailyAffiliations {
|
||||||
DailyAffiliation: number;
|
DailyAffiliation: number;
|
||||||
DailyAffiliationPvp: number;
|
DailyAffiliationPvp: number;
|
||||||
@ -220,7 +227,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
|
|||||||
ActiveQuest: string;
|
ActiveQuest: string;
|
||||||
FlavourItems: IFlavourItem[];
|
FlavourItems: IFlavourItem[];
|
||||||
LoadOutPresets: ILoadOutPresets;
|
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[];
|
Missions: IMission[];
|
||||||
RandomUpgradesIdentified?: number;
|
RandomUpgradesIdentified?: number;
|
||||||
LastRegionPlayed: TSolarMapRegion;
|
LastRegionPlayed: TSolarMapRegion;
|
||||||
@ -301,7 +308,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
|
|||||||
NemesisHistory: INemesisBaseClient[];
|
NemesisHistory: INemesisBaseClient[];
|
||||||
LastNemesisAllySpawnTime?: IMongoDate;
|
LastNemesisAllySpawnTime?: IMongoDate;
|
||||||
Settings?: ISettings;
|
Settings?: ISettings;
|
||||||
PersonalTechProjects: IPersonalTechProject[];
|
PersonalTechProjects: IPersonalTechProjectClient[];
|
||||||
PlayerSkills: IPlayerSkills;
|
PlayerSkills: IPlayerSkills;
|
||||||
CrewShipAmmo: ITypeCount[];
|
CrewShipAmmo: ITypeCount[];
|
||||||
CrewShipWeaponSkins: IUpgradeClient[];
|
CrewShipWeaponSkins: IUpgradeClient[];
|
||||||
@ -936,16 +943,20 @@ export interface IPersonalGoalProgress {
|
|||||||
ReceivedClanReward1?: boolean;
|
ReceivedClanReward1?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPersonalTechProject {
|
export interface IPersonalTechProjectDatabase {
|
||||||
State: number;
|
State: number;
|
||||||
ReqCredits: number;
|
ReqCredits: number;
|
||||||
ItemType: string;
|
ItemType: string;
|
||||||
ReqItems: ITypeCount[];
|
ReqItems: ITypeCount[];
|
||||||
|
HasContributions?: boolean;
|
||||||
|
CompletionDate?: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPersonalTechProjectClient extends Omit<IPersonalTechProjectDatabase, "CompletionDate"> {
|
||||||
CompletionDate?: IMongoDate;
|
CompletionDate?: IMongoDate;
|
||||||
ItemId: IOid;
|
|
||||||
ProductCategory?: string;
|
ProductCategory?: string;
|
||||||
CategoryItemId?: IOid;
|
CategoryItemId?: IOid;
|
||||||
HasContributions?: boolean;
|
ItemId: IOid;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPlayerSkills {
|
export interface IPlayerSkills {
|
||||||
|
@ -406,7 +406,10 @@
|
|||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<h5 class="card-header" data-loc="powersuit_archonShardsLabel"></h5>
|
<h5 class="card-header" data-loc="powersuit_archonShardsLabel"></h5>
|
||||||
<div class="card-body">
|
<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;">
|
<form class="input-group mb-3" onsubmit="doPushArchonCrystalUpgrade();return false;">
|
||||||
<input type="number" id="archon-crystal-add-count" min="1" max="10000" value="1" class="form-control" style="max-width:100px" />
|
<input type="number" id="archon-crystal-add-count" min="1" max="10000" value="1" class="form-control" style="max-width:100px" />
|
||||||
<span class="input-group-text">x</span>
|
<span class="input-group-text">x</span>
|
||||||
@ -604,6 +607,10 @@
|
|||||||
<input class="form-check-input" type="checkbox" id="noVendorPurchaseLimits" />
|
<input class="form-check-input" type="checkbox" id="noVendorPurchaseLimits" />
|
||||||
<label class="form-check-label" for="noVendorPurchaseLimits" data-loc="cheats_noVendorPurchaseLimits"></label>
|
<label class="form-check-label" for="noVendorPurchaseLimits" data-loc="cheats_noVendorPurchaseLimits"></label>
|
||||||
</div>
|
</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">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="instantResourceExtractorDrones" />
|
<input class="form-check-input" type="checkbox" id="instantResourceExtractorDrones" />
|
||||||
<label class="form-check-label" for="instantResourceExtractorDrones" data-loc="cheats_instantResourceExtractorDrones"></label>
|
<label class="form-check-label" for="instantResourceExtractorDrones" data-loc="cheats_instantResourceExtractorDrones"></label>
|
||||||
|
@ -277,9 +277,7 @@ function fetchItemList() {
|
|||||||
} else {
|
} else {
|
||||||
const nameSet = new Set();
|
const nameSet = new Set();
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
if (item.name.includes("<ARCHWING> ")) {
|
item.name = item.name.replace(/<.+>/g, "").trim();
|
||||||
item.name = item.name.replace("<ARCHWING> ", "");
|
|
||||||
}
|
|
||||||
if ("badReason" in item) {
|
if ("badReason" in item) {
|
||||||
if (item.badReason == "starter") {
|
if (item.badReason == "starter") {
|
||||||
item.name = loc("code_starter").split("|MOD|").join(item.name);
|
item.name = loc("code_starter").split("|MOD|").join(item.name);
|
||||||
|
@ -105,6 +105,7 @@ dict = {
|
|||||||
currency_owned: `Du hast |COUNT|.`,
|
currency_owned: `Du hast |COUNT|.`,
|
||||||
powersuit_archonShardsLabel: `Archon-Scherben-Slots`,
|
powersuit_archonShardsLabel: `Archon-Scherben-Slots`,
|
||||||
powersuit_archonShardsDescription: `Du kannst diese unbegrenzten Slots nutzen, um eine Vielzahl von Verbesserungen anzuwenden.`,
|
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_addRiven: `Riven hinzufügen`,
|
||||||
mods_fingerprint: `Fingerabdruck`,
|
mods_fingerprint: `Fingerabdruck`,
|
||||||
mods_fingerprintHelp: `Benötigst du Hilfe mit dem Fingerabdruck?`,
|
mods_fingerprintHelp: `Benötigst du Hilfe mit dem Fingerabdruck?`,
|
||||||
@ -136,6 +137,7 @@ dict = {
|
|||||||
cheats_noArgonCrystalDecay: `Argon-Kristalle verschwinden niemals`,
|
cheats_noArgonCrystalDecay: `Argon-Kristalle verschwinden niemals`,
|
||||||
cheats_noMasteryRankUpCooldown: `Keine Wartezeit beim Meisterschaftsrangaufstieg`,
|
cheats_noMasteryRankUpCooldown: `Keine Wartezeit beim Meisterschaftsrangaufstieg`,
|
||||||
cheats_noVendorPurchaseLimits: `Keine Kaufbeschränkungen bei Händlern`,
|
cheats_noVendorPurchaseLimits: `Keine Kaufbeschränkungen bei Händlern`,
|
||||||
|
cheats_noKimCooldowns: `[UNTRANSLATED] No KIM Cooldowns`,
|
||||||
cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
|
cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
|
||||||
cheats_noDojoRoomBuildStage: `Kein Dojo-Raum-Bauvorgang`,
|
cheats_noDojoRoomBuildStage: `Kein Dojo-Raum-Bauvorgang`,
|
||||||
cheats_noDojoDecoBuildStage: `Kein Dojo-Deko-Bauvorgang`,
|
cheats_noDojoDecoBuildStage: `Kein Dojo-Deko-Bauvorgang`,
|
||||||
|
@ -103,7 +103,8 @@ dict = {
|
|||||||
currency_PrimeTokens: `Regal Aya`,
|
currency_PrimeTokens: `Regal Aya`,
|
||||||
currency_owned: `You have |COUNT|.`,
|
currency_owned: `You have |COUNT|.`,
|
||||||
powersuit_archonShardsLabel: `Archon Shard Slots`,
|
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_addRiven: `Add Riven`,
|
||||||
mods_fingerprint: `Fingerprint`,
|
mods_fingerprint: `Fingerprint`,
|
||||||
mods_fingerprintHelp: `Need help with the fingerprint?`,
|
mods_fingerprintHelp: `Need help with the fingerprint?`,
|
||||||
@ -135,6 +136,7 @@ dict = {
|
|||||||
cheats_noArgonCrystalDecay: `No Argon Crystal Decay`,
|
cheats_noArgonCrystalDecay: `No Argon Crystal Decay`,
|
||||||
cheats_noMasteryRankUpCooldown: `No Mastery Rank Up Cooldown`,
|
cheats_noMasteryRankUpCooldown: `No Mastery Rank Up Cooldown`,
|
||||||
cheats_noVendorPurchaseLimits: `No Vendor Purchase Limits`,
|
cheats_noVendorPurchaseLimits: `No Vendor Purchase Limits`,
|
||||||
|
cheats_noKimCooldowns: `No KIM Cooldowns`,
|
||||||
cheats_instantResourceExtractorDrones: `Instant Resource Extractor Drones`,
|
cheats_instantResourceExtractorDrones: `Instant Resource Extractor Drones`,
|
||||||
cheats_noDojoRoomBuildStage: `No Dojo Room Build Stage`,
|
cheats_noDojoRoomBuildStage: `No Dojo Room Build Stage`,
|
||||||
cheats_noDojoDecoBuildStage: `No Dojo Deco 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.`,
|
currency_owned: `|COUNT| possédés.`,
|
||||||
powersuit_archonShardsLabel: `Emplacements de fragments d'Archonte`,
|
powersuit_archonShardsLabel: `Emplacements de fragments d'Archonte`,
|
||||||
powersuit_archonShardsDescription: `Slots illimités pour appliquer plusieurs améliorations.`,
|
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_addRiven: `Ajouter un riven`,
|
||||||
mods_fingerprint: `Empreinte`,
|
mods_fingerprint: `Empreinte`,
|
||||||
mods_fingerprintHelp: `Besoin d'aide pour l'empreinte ?`,
|
mods_fingerprintHelp: `Besoin d'aide pour l'empreinte ?`,
|
||||||
@ -136,6 +137,7 @@ dict = {
|
|||||||
cheats_noArgonCrystalDecay: `[UNTRANSLATED] No Argon Crystal Decay`,
|
cheats_noArgonCrystalDecay: `[UNTRANSLATED] No Argon Crystal Decay`,
|
||||||
cheats_noMasteryRankUpCooldown: `[UNTRANSLATED] No Mastery Rank Up Cooldown`,
|
cheats_noMasteryRankUpCooldown: `[UNTRANSLATED] No Mastery Rank Up Cooldown`,
|
||||||
cheats_noVendorPurchaseLimits: `[UNTRANSLATED] No Vendor Purchase Limits`,
|
cheats_noVendorPurchaseLimits: `[UNTRANSLATED] No Vendor Purchase Limits`,
|
||||||
|
cheats_noKimCooldowns: `[UNTRANSLATED] No KIM Cooldowns`,
|
||||||
cheats_instantResourceExtractorDrones: `Ressources de drone d'extraction instantannées`,
|
cheats_instantResourceExtractorDrones: `Ressources de drone d'extraction instantannées`,
|
||||||
cheats_noDojoRoomBuildStage: `No Dojo Room Build Stage`,
|
cheats_noDojoRoomBuildStage: `No Dojo Room Build Stage`,
|
||||||
cheats_noDojoDecoBuildStage: `[UNTRANSLATED] No Dojo Deco Build Stage`,
|
cheats_noDojoDecoBuildStage: `[UNTRANSLATED] No Dojo Deco Build Stage`,
|
||||||
|
@ -105,6 +105,7 @@ dict = {
|
|||||||
currency_owned: `У тебя |COUNT|.`,
|
currency_owned: `У тебя |COUNT|.`,
|
||||||
powersuit_archonShardsLabel: `Ячейки осколков архонта`,
|
powersuit_archonShardsLabel: `Ячейки осколков архонта`,
|
||||||
powersuit_archonShardsDescription: `Вы можете использовать эти неограниченные ячейки для установки множества улучшений.`,
|
powersuit_archonShardsDescription: `Вы можете использовать эти неограниченные ячейки для установки множества улучшений.`,
|
||||||
|
powersuit_archonShardsDescription2: `[UNTRANSLATED] Note that each archon shard takes some time to be applied when loading in.`,
|
||||||
mods_addRiven: `Добавить Мод Разлома`,
|
mods_addRiven: `Добавить Мод Разлома`,
|
||||||
mods_fingerprint: `Отпечаток`,
|
mods_fingerprint: `Отпечаток`,
|
||||||
mods_fingerprintHelp: `Нужна помощь с отпечатком?`,
|
mods_fingerprintHelp: `Нужна помощь с отпечатком?`,
|
||||||
@ -134,11 +135,12 @@ dict = {
|
|||||||
cheats_unlockArcanesEverywhere: `Адаптеры для мистификаторов везде`,
|
cheats_unlockArcanesEverywhere: `Адаптеры для мистификаторов везде`,
|
||||||
cheats_noDailyStandingLimits: `Без ежедневных ограничений репутации`,
|
cheats_noDailyStandingLimits: `Без ежедневных ограничений репутации`,
|
||||||
cheats_noArgonCrystalDecay: `Без распада аргоновых кристаллов`,
|
cheats_noArgonCrystalDecay: `Без распада аргоновых кристаллов`,
|
||||||
cheats_noMasteryRankUpCooldown: `[UNTRANSLATED] No Mastery Rank Up Cooldown`,
|
cheats_noMasteryRankUpCooldown: `Повышение ранга мастерства без кулдауна`,
|
||||||
cheats_noVendorPurchaseLimits: `Отсутствие лимитов на покупки у вендоров`,
|
cheats_noVendorPurchaseLimits: `Отсутствие лимитов на покупки у вендоров`,
|
||||||
|
cheats_noKimCooldowns: `[UNTRANSLATED] No KIM Cooldowns`,
|
||||||
cheats_instantResourceExtractorDrones: `Мгновенные Экстракторы Ресурсов`,
|
cheats_instantResourceExtractorDrones: `Мгновенные Экстракторы Ресурсов`,
|
||||||
cheats_noDojoRoomBuildStage: `Мгновенное Строительтво Комнат Додзё`,
|
cheats_noDojoRoomBuildStage: `Мгновенное Строительтво Комнат Додзё`,
|
||||||
cheats_noDojoDecoBuildStage: `[UNTRANSLATED] No Dojo Deco Build Stage`,
|
cheats_noDojoDecoBuildStage: `Мгновенное Строительтво Декораций Додзё`,
|
||||||
cheats_fastDojoRoomDestruction: `Мгновенные Уничтожение Комнат Додзё`,
|
cheats_fastDojoRoomDestruction: `Мгновенные Уничтожение Комнат Додзё`,
|
||||||
cheats_noDojoResearchCosts: `Бесплатные Исследование Додзё`,
|
cheats_noDojoResearchCosts: `Бесплатные Исследование Додзё`,
|
||||||
cheats_noDojoResearchTime: `Мгновенные Исследование Додзё`,
|
cheats_noDojoResearchTime: `Мгновенные Исследование Додзё`,
|
||||||
|
@ -105,6 +105,7 @@ dict = {
|
|||||||
currency_owned: `当前拥有 |COUNT|。`,
|
currency_owned: `当前拥有 |COUNT|。`,
|
||||||
powersuit_archonShardsLabel: `执刑官源力石槽位`,
|
powersuit_archonShardsLabel: `执刑官源力石槽位`,
|
||||||
powersuit_archonShardsDescription: `您可以使用这些无限插槽应用各种强化效果`,
|
powersuit_archonShardsDescription: `您可以使用这些无限插槽应用各种强化效果`,
|
||||||
|
powersuit_archonShardsDescription2: `[UNTRANSLATED] Note that each archon shard takes some time to be applied when loading in.`,
|
||||||
mods_addRiven: `添加裂罅MOD`,
|
mods_addRiven: `添加裂罅MOD`,
|
||||||
mods_fingerprint: `印记`,
|
mods_fingerprint: `印记`,
|
||||||
mods_fingerprintHelp: `需要印记相关的帮助?`,
|
mods_fingerprintHelp: `需要印记相关的帮助?`,
|
||||||
@ -136,6 +137,7 @@ dict = {
|
|||||||
cheats_noArgonCrystalDecay: `[UNTRANSLATED] No Argon Crystal Decay`,
|
cheats_noArgonCrystalDecay: `[UNTRANSLATED] No Argon Crystal Decay`,
|
||||||
cheats_noMasteryRankUpCooldown: `[UNTRANSLATED] No Mastery Rank Up Cooldown`,
|
cheats_noMasteryRankUpCooldown: `[UNTRANSLATED] No Mastery Rank Up Cooldown`,
|
||||||
cheats_noVendorPurchaseLimits: `[UNTRANSLATED] No Vendor Purchase Limits`,
|
cheats_noVendorPurchaseLimits: `[UNTRANSLATED] No Vendor Purchase Limits`,
|
||||||
|
cheats_noKimCooldowns: `[UNTRANSLATED] No KIM Cooldowns`,
|
||||||
cheats_instantResourceExtractorDrones: `即时资源采集无人机`,
|
cheats_instantResourceExtractorDrones: `即时资源采集无人机`,
|
||||||
cheats_noDojoRoomBuildStage: `无视道场房间建造阶段`,
|
cheats_noDojoRoomBuildStage: `无视道场房间建造阶段`,
|
||||||
cheats_noDojoDecoBuildStage: `[UNTRANSLATED] No Dojo Deco Build Stage`,
|
cheats_noDojoDecoBuildStage: `[UNTRANSLATED] No Dojo Deco Build Stage`,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user