forked from OpenWF/SpaceNinjaServer
		
	Compare commits
	
		
			35 Commits
		
	
	
		
			1066b4a983
			...
			71c4835a69
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 71c4835a69 | |||
| 86a63ace41 | |||
| 32c95b6715 | |||
| 6f8b14fb2d | |||
| 3d8aa60838 | |||
| 87da94658d | |||
| 05fbefa7f4 | |||
| a2abf6db8f | |||
| 64a1c8b276 | |||
| 4fa07a1319 | |||
| a3cc7d9f92 | |||
| c47c60fdcc | |||
| 367455baaa | |||
| 6c2b7a61e2 | |||
| 6a6683fb25 | |||
| e3b6accb5d | |||
| 7e437d75bf | |||
| 62570177b6 | |||
| d2aff211c6 | |||
| 791ae389d8 | |||
| d027e7f26e | |||
| cd6ce61b80 | |||
| a5be29159f | |||
| f099b64ef4 | |||
| c4f348c252 | |||
| 0d388b4b0f | |||
| d64531f4b2 | |||
| 01b8f7acf3 | |||
| 8a7db2cd85 | |||
| 5a9415ae0c | |||
| 39f898cd30 | |||
| 9c55a8a4aa | |||
| 253ae09f24 | |||
| 703e9007b0 | |||
| 3e555b1753 | 
@ -31,7 +31,8 @@
 | 
				
			|||||||
        "no-mixed-spaces-and-tabs": "error",
 | 
					        "no-mixed-spaces-and-tabs": "error",
 | 
				
			||||||
        "@typescript-eslint/require-await": "error",
 | 
					        "@typescript-eslint/require-await": "error",
 | 
				
			||||||
        "import/no-named-as-default-member": "off",
 | 
					        "import/no-named-as-default-member": "off",
 | 
				
			||||||
        "import/no-cycle": "warn"
 | 
					        "import/no-cycle": "warn",
 | 
				
			||||||
 | 
					        "@typescript-eslint/no-deprecated": "warn"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "parser": "@typescript-eslint/parser",
 | 
					    "parser": "@typescript-eslint/parser",
 | 
				
			||||||
    "parserOptions": {
 | 
					    "parserOptions": {
 | 
				
			||||||
 | 
				
			|||||||
@ -5,22 +5,15 @@
 | 
				
			|||||||
    "level": "trace"
 | 
					    "level": "trace"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "myAddress": "localhost",
 | 
					  "myAddress": "localhost",
 | 
				
			||||||
 | 
					  "bindAddress": "0.0.0.0",
 | 
				
			||||||
  "httpPort": 80,
 | 
					  "httpPort": 80,
 | 
				
			||||||
  "httpsPort": 443,
 | 
					  "httpsPort": 443,
 | 
				
			||||||
  "administratorNames": [],
 | 
					  "administratorNames": [],
 | 
				
			||||||
  "autoCreateAccount": true,
 | 
					  "autoCreateAccount": true,
 | 
				
			||||||
  "skipTutorial": false,
 | 
					  "skipTutorial": false,
 | 
				
			||||||
  "unlockAllShipDecorations": false,
 | 
					 | 
				
			||||||
  "unlockAllFlavourItems": false,
 | 
					 | 
				
			||||||
  "unlockAllSkins": false,
 | 
					  "unlockAllSkins": false,
 | 
				
			||||||
  "fullyStockedVendors": false,
 | 
					  "fullyStockedVendors": false,
 | 
				
			||||||
  "skipClanKeyCrafting": false,
 | 
					  "skipClanKeyCrafting": false,
 | 
				
			||||||
  "noDojoRoomBuildStage": false,
 | 
					 | 
				
			||||||
  "noDojoDecoBuildStage": false,
 | 
					 | 
				
			||||||
  "fastDojoRoomDestruction": false,
 | 
					 | 
				
			||||||
  "noDojoResearchCosts": false,
 | 
					 | 
				
			||||||
  "noDojoResearchTime": false,
 | 
					 | 
				
			||||||
  "fastClanAscension": false,
 | 
					 | 
				
			||||||
  "spoofMasteryRank": -1,
 | 
					  "spoofMasteryRank": -1,
 | 
				
			||||||
  "relicRewardItemCountMultiplier": 1,
 | 
					  "relicRewardItemCountMultiplier": 1,
 | 
				
			||||||
  "nightwaveStandingMultiplier": 1,
 | 
					  "nightwaveStandingMultiplier": 1,
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										8
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -17,7 +17,7 @@
 | 
				
			|||||||
        "morgan": "^1.10.0",
 | 
					        "morgan": "^1.10.0",
 | 
				
			||||||
        "ncp": "^2.0.0",
 | 
					        "ncp": "^2.0.0",
 | 
				
			||||||
        "undici": "^7.10.0",
 | 
					        "undici": "^7.10.0",
 | 
				
			||||||
        "warframe-public-export-plus": "^0.5.83",
 | 
					        "warframe-public-export-plus": "^0.5.89",
 | 
				
			||||||
        "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",
 | 
				
			||||||
@ -5532,9 +5532,9 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/warframe-public-export-plus": {
 | 
					    "node_modules/warframe-public-export-plus": {
 | 
				
			||||||
      "version": "0.5.84",
 | 
					      "version": "0.5.89",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.84.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.89.tgz",
 | 
				
			||||||
      "integrity": "sha512-ZpI1Y5CgWDmCwM4/oQpv9u0GD6KFvsJ9f1vJVXYhm5VD9DdOJcFzXgXgg98HXJ5JHbO16ZGIj83117qdpd0RQA=="
 | 
					      "integrity": "sha512-a6dM1MirzofSsuv3LlRQHFLSSIGKPVSN93dcXSDmA3njsWqOGjJJdWyXqcyxxYw8rEB8CNowSHst/MUmKvKlRg=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/warframe-riven-info": {
 | 
					    "node_modules/warframe-riven-info": {
 | 
				
			||||||
      "version": "0.1.2",
 | 
					      "version": "0.1.2",
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										10
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								package.json
									
									
									
									
									
								
							@ -5,10 +5,10 @@
 | 
				
			|||||||
  "main": "index.ts",
 | 
					  "main": "index.ts",
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
    "start": "node --enable-source-maps build/src/index.js",
 | 
					    "start": "node --enable-source-maps build/src/index.js",
 | 
				
			||||||
    "build": "tsgo --sourceMap && ncp static/webui build/static/webui",
 | 
					    "build": "tsgo --inlineSourceMap && ncp static/webui build/static/webui",
 | 
				
			||||||
    "build:tsc": "tsc --incremental --sourceMap && ncp static/webui build/static/webui",
 | 
					    "build:tsc": "tsc --incremental --inlineSourceMap && ncp static/webui build/static/webui",
 | 
				
			||||||
    "build:dev": "tsgo --sourceMap",
 | 
					    "build:dev": "tsgo --inlineSourceMap",
 | 
				
			||||||
    "build:dev:tsc": "tsc --incremental --sourceMap",
 | 
					    "build:dev:tsc": "tsc --incremental --inlineSourceMap",
 | 
				
			||||||
    "build-and-start": "npm run build && npm run start",
 | 
					    "build-and-start": "npm run build && npm run start",
 | 
				
			||||||
    "build-and-start:bun": "npm run verify && npm run bun-run",
 | 
					    "build-and-start:bun": "npm run verify && npm run bun-run",
 | 
				
			||||||
    "dev": "node scripts/dev.cjs",
 | 
					    "dev": "node scripts/dev.cjs",
 | 
				
			||||||
@ -35,7 +35,7 @@
 | 
				
			|||||||
    "morgan": "^1.10.0",
 | 
					    "morgan": "^1.10.0",
 | 
				
			||||||
    "ncp": "^2.0.0",
 | 
					    "ncp": "^2.0.0",
 | 
				
			||||||
    "undici": "^7.10.0",
 | 
					    "undici": "^7.10.0",
 | 
				
			||||||
    "warframe-public-export-plus": "^0.5.83",
 | 
					    "warframe-public-export-plus": "^0.5.89",
 | 
				
			||||||
    "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",
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@ import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			|||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
import type { IInventoryClient, IUpgradeClient } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
					import type { IInventoryClient, IUpgradeClient } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
				
			||||||
import { addMods, getInventory } from "../../services/inventoryService.ts";
 | 
					import { addMods, getInventory } from "../../services/inventoryService.ts";
 | 
				
			||||||
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const artifactsController: RequestHandler = async (req, res) => {
 | 
					export const artifactsController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -57,6 +58,7 @@ export const artifactsController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.send(itemId);
 | 
					    res.send(itemId);
 | 
				
			||||||
 | 
					    broadcastInventoryUpdate(req);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IArtifactsRequest {
 | 
					interface IArtifactsRequest {
 | 
				
			||||||
 | 
				
			|||||||
@ -95,10 +95,7 @@ export const confirmGuildInvitationPostController: RequestHandler = async (req,
 | 
				
			|||||||
        await GuildMember.deleteMany({ accountId: guildMember.accountId, status: 1 });
 | 
					        await GuildMember.deleteMany({ accountId: guildMember.accountId, status: 1 });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Update inventory of new member
 | 
					        // Update inventory of new member
 | 
				
			||||||
        const inventory = await getInventory(
 | 
					        const inventory = await getInventory(guildMember.accountId.toString(), "GuildId LevelKeys Recipes");
 | 
				
			||||||
            guildMember.accountId.toString(),
 | 
					 | 
				
			||||||
            "GuildId LevelKeys Recipes skipClanKeyCrafting"
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        inventory.GuildId = new Types.ObjectId(req.query.clanId as string);
 | 
					        inventory.GuildId = new Types.ObjectId(req.query.clanId as string);
 | 
				
			||||||
        giveClanKey(inventory);
 | 
					        giveClanKey(inventory);
 | 
				
			||||||
        await inventory.save();
 | 
					        await inventory.save();
 | 
				
			||||||
 | 
				
			|||||||
@ -27,7 +27,7 @@ export const createGuildController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        rank: 0
 | 
					        rank: 0
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes skipClanKeyCrafting");
 | 
					    const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes");
 | 
				
			||||||
    inventory.GuildId = guild._id;
 | 
					    inventory.GuildId = guild._id;
 | 
				
			||||||
    const inventoryChanges: IInventoryChanges = {};
 | 
					    const inventoryChanges: IInventoryChanges = {};
 | 
				
			||||||
    giveClanKey(inventory, inventoryChanges);
 | 
					    giveClanKey(inventory, inventoryChanges);
 | 
				
			||||||
 | 
				
			|||||||
@ -231,7 +231,7 @@ interface ILensInstallRequest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Works for ways & upgrades
 | 
					// Works for ways & upgrades
 | 
				
			||||||
const focusTypeToPolarity = (type: string): TFocusPolarity => {
 | 
					const focusTypeToPolarity = (type: string): TFocusPolarity => {
 | 
				
			||||||
    return ("AP_" + type.substr(1).split("/")[3].toUpperCase()) as TFocusPolarity;
 | 
					    return ("AP_" + type.substring(1).split("/")[3].toUpperCase()) as TFocusPolarity;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const shardValues = {
 | 
					const shardValues = {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
import { sendWsBroadcastTo } from "../../services/wsService.ts";
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
					import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
				
			||||||
import { addMiscItems, getInventory } from "../../services/inventoryService.ts";
 | 
					import { addMiscItems, getInventory } from "../../services/inventoryService.ts";
 | 
				
			||||||
import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
					import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
				
			||||||
@ -75,5 +75,5 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        InventoryChanges: inventoryChanges,
 | 
					        InventoryChanges: inventoryChanges,
 | 
				
			||||||
        AffiliationMods: affiliationMods
 | 
					        AffiliationMods: affiliationMods
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    sendWsBroadcastTo(accountId, { update_inventory: true });
 | 
					    broadcastInventoryUpdate(req);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,8 @@ export const giveKeyChainTriggeredItemsController: RequestHandler = async (req,
 | 
				
			|||||||
    const keyChainInfo = getJSONfromString<IKeyChainRequest>((req.body as string).toString());
 | 
					    const keyChainInfo = getJSONfromString<IKeyChainRequest>((req.body as string).toString());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const inventory = await getInventory(accountId);
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
    const inventoryChanges = await giveKeyChainItem(inventory, keyChainInfo);
 | 
					    const questKey = inventory.QuestKeys.find(qk => qk.ItemType === keyChainInfo.KeyChain)!;
 | 
				
			||||||
 | 
					    const inventoryChanges = await giveKeyChainItem(inventory, keyChainInfo, questKey);
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.send(inventoryChanges);
 | 
					    res.send(inventoryChanges);
 | 
				
			||||||
 | 
				
			|||||||
@ -8,8 +8,9 @@ export const giveKeyChainTriggeredMessageController: RequestHandler = async (req
 | 
				
			|||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const keyChainInfo = JSON.parse((req.body as Buffer).toString()) as IKeyChainRequest;
 | 
					    const keyChainInfo = JSON.parse((req.body as Buffer).toString()) as IKeyChainRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const inventory = await getInventory(accountId, "QuestKeys");
 | 
					    const inventory = await getInventory(accountId, "QuestKeys accountOwnerId");
 | 
				
			||||||
    await giveKeyChainMessage(inventory, accountId, keyChainInfo);
 | 
					    const questKey = inventory.QuestKeys.find(qk => qk.ItemType === keyChainInfo.KeyChain)!;
 | 
				
			||||||
 | 
					    await giveKeyChainMessage(inventory, keyChainInfo, questKey);
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.send(1);
 | 
					    res.send(1);
 | 
				
			||||||
 | 
				
			|||||||
@ -28,7 +28,6 @@ import {
 | 
				
			|||||||
import type { IMiscItem } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
					import type { IMiscItem } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
				
			||||||
import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
					import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
				
			||||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
 | 
					import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
 | 
				
			||||||
import { config } from "../../services/configService.ts";
 | 
					 | 
				
			||||||
import type { ITechProjectClient } from "../../types/guildTypes.ts";
 | 
					import type { ITechProjectClient } from "../../types/guildTypes.ts";
 | 
				
			||||||
import { GuildPermission } from "../../types/guildTypes.ts";
 | 
					import { GuildPermission } from "../../types/guildTypes.ts";
 | 
				
			||||||
import { GuildMember } from "../../models/guildModel.ts";
 | 
					import { GuildMember } from "../../models/guildModel.ts";
 | 
				
			||||||
@ -83,16 +82,16 @@ export const guildTechController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                    guild.TechProjects[
 | 
					                    guild.TechProjects[
 | 
				
			||||||
                        guild.TechProjects.push({
 | 
					                        guild.TechProjects.push({
 | 
				
			||||||
                            ItemType: data.RecipeType,
 | 
					                            ItemType: data.RecipeType,
 | 
				
			||||||
                            ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, recipe.price),
 | 
					                            ReqCredits: guild.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, recipe.price),
 | 
				
			||||||
                            ReqItems: recipe.ingredients.map(x => ({
 | 
					                            ReqItems: recipe.ingredients.map(x => ({
 | 
				
			||||||
                                ItemType: x.ItemType,
 | 
					                                ItemType: x.ItemType,
 | 
				
			||||||
                                ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, x.ItemCount)
 | 
					                                ItemCount: guild.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, x.ItemCount)
 | 
				
			||||||
                            })),
 | 
					                            })),
 | 
				
			||||||
                            State: 0
 | 
					                            State: 0
 | 
				
			||||||
                        }) - 1
 | 
					                        }) - 1
 | 
				
			||||||
                    ];
 | 
					                    ];
 | 
				
			||||||
                setGuildTechLogState(guild, techProject.ItemType, 5);
 | 
					                setGuildTechLogState(guild, techProject.ItemType, 5);
 | 
				
			||||||
                if (config.noDojoResearchCosts) {
 | 
					                if (guild.noDojoResearchCosts) {
 | 
				
			||||||
                    processFundedGuildTechProject(guild, techProject, recipe);
 | 
					                    processFundedGuildTechProject(guild, techProject, recipe);
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
 | 
					                    if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,7 @@ import type {
 | 
				
			|||||||
    IMiscItem
 | 
					    IMiscItem
 | 
				
			||||||
} from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
					} from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
				
			||||||
import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
					import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
				
			||||||
import { ExportMisc } from "warframe-public-export-plus";
 | 
					import { ExportResources } from "warframe-public-export-plus";
 | 
				
			||||||
import { getRecipe } from "../../services/itemDataService.ts";
 | 
					import { getRecipe } from "../../services/itemDataService.ts";
 | 
				
			||||||
import { toMongoDate, version_compare } from "../../helpers/inventoryHelpers.ts";
 | 
					import { toMongoDate, version_compare } from "../../helpers/inventoryHelpers.ts";
 | 
				
			||||||
import { logger } from "../../utils/logger.ts";
 | 
					import { logger } from "../../utils/logger.ts";
 | 
				
			||||||
@ -20,6 +20,7 @@ import {
 | 
				
			|||||||
    applyCheatsToInfestedFoundry,
 | 
					    applyCheatsToInfestedFoundry,
 | 
				
			||||||
    handleSubsumeCompletion
 | 
					    handleSubsumeCompletion
 | 
				
			||||||
} from "../../services/infestedFoundryService.ts";
 | 
					} from "../../services/infestedFoundryService.ts";
 | 
				
			||||||
 | 
					import { sendWsBroadcastToGame } from "../../services/wsService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const infestedFoundryController: RequestHandler = async (req, res) => {
 | 
					export const infestedFoundryController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const account = await getAccountForRequest(req);
 | 
					    const account = await getAccountForRequest(req);
 | 
				
			||||||
@ -145,7 +146,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            const currentUnixSeconds = Math.trunc(Date.now() / 1000);
 | 
					            const currentUnixSeconds = Math.trunc(Date.now() / 1000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            for (const contribution of request.ResourceContributions) {
 | 
					            for (const contribution of request.ResourceContributions) {
 | 
				
			||||||
                const snack = ExportMisc.helminthSnacks[contribution.ItemType];
 | 
					                const snack = ExportResources[contribution.ItemType].helminthSnack!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // tally items for removal
 | 
					                // tally items for removal
 | 
				
			||||||
                const change = miscItemChanges.find(x => x.ItemType == contribution.ItemType);
 | 
					                const change = miscItemChanges.find(x => x.ItemType == contribution.ItemType);
 | 
				
			||||||
@ -363,6 +364,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                );
 | 
					                );
 | 
				
			||||||
                addRecipes(inventory, recipeChanges);
 | 
					                addRecipes(inventory, recipeChanges);
 | 
				
			||||||
                await inventory.save();
 | 
					                await inventory.save();
 | 
				
			||||||
 | 
					                sendWsBroadcastToGame(account._id.toString(), { sync_inventory: true });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            res.end();
 | 
					            res.end();
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,7 @@ import { equipmentKeys } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
				
			|||||||
import type { IPolarity } from "../../types/inventoryTypes/commonInventoryTypes.ts";
 | 
					import type { IPolarity } from "../../types/inventoryTypes/commonInventoryTypes.ts";
 | 
				
			||||||
import { ArtifactPolarity } from "../../types/inventoryTypes/commonInventoryTypes.ts";
 | 
					import { ArtifactPolarity } from "../../types/inventoryTypes/commonInventoryTypes.ts";
 | 
				
			||||||
import type { ICountedItem } from "warframe-public-export-plus";
 | 
					import type { ICountedItem } from "warframe-public-export-plus";
 | 
				
			||||||
import { eFaction, ExportCustoms, ExportFlavour, ExportResources } from "warframe-public-export-plus";
 | 
					import { ExportCustoms } from "warframe-public-export-plus";
 | 
				
			||||||
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "../../services/infestedFoundryService.ts";
 | 
					import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "../../services/infestedFoundryService.ts";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    addEmailItem,
 | 
					    addEmailItem,
 | 
				
			||||||
@ -220,7 +220,10 @@ export const inventoryController: RequestHandler = async (request, response) =>
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    await createMessage(account._id, [
 | 
					                    await createMessage(account._id, [
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
                            sndr: eFaction.find(x => x.tag == factionSidedWith)?.name ?? factionSidedWith, // TOVERIFY
 | 
					                            sndr:
 | 
				
			||||||
 | 
					                                factionSidedWith == "FC_GRINEER"
 | 
				
			||||||
 | 
					                                    ? "/Lotus/Language/Menu/GrineerInvasionLeader"
 | 
				
			||||||
 | 
					                                    : "/Lotus/Language/Menu/CorpusInvasionLeader",
 | 
				
			||||||
                            msg: `/Lotus/Language/G1Quests/${factionSidedWith}_InvasionThankyouMessageBody`,
 | 
					                            msg: `/Lotus/Language/G1Quests/${factionSidedWith}_InvasionThankyouMessageBody`,
 | 
				
			||||||
                            sub: `/Lotus/Language/G1Quests/${factionSidedWith}_InvasionThankyouMessageSubject`,
 | 
					                            sub: `/Lotus/Language/G1Quests/${factionSidedWith}_InvasionThankyouMessageSubject`,
 | 
				
			||||||
                            countedAtt: battlePay,
 | 
					                            countedAtt: battlePay,
 | 
				
			||||||
@ -318,21 +321,7 @@ export const getInventoryResponse = async (
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (config.unlockAllShipDecorations) {
 | 
					    if (config.worldState?.baroTennoConRelay) {
 | 
				
			||||||
        inventoryResponse.ShipDecorations = [];
 | 
					 | 
				
			||||||
        for (const [uniqueName, item] of Object.entries(ExportResources)) {
 | 
					 | 
				
			||||||
            if (item.productCategory == "ShipDecorations") {
 | 
					 | 
				
			||||||
                inventoryResponse.ShipDecorations.push({ ItemType: uniqueName, ItemCount: 999_999 });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (config.unlockAllFlavourItems) {
 | 
					 | 
				
			||||||
        inventoryResponse.FlavourItems = [];
 | 
					 | 
				
			||||||
        for (const uniqueName in ExportFlavour) {
 | 
					 | 
				
			||||||
            inventoryResponse.FlavourItems.push({ ItemType: uniqueName });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    } else if (config.worldState?.baroTennoConRelay) {
 | 
					 | 
				
			||||||
        [
 | 
					        [
 | 
				
			||||||
            "/Lotus/Types/Items/Events/TennoConRelay2022EarlyAccess",
 | 
					            "/Lotus/Types/Items/Events/TennoConRelay2022EarlyAccess",
 | 
				
			||||||
            "/Lotus/Types/Items/Events/TennoConRelay2023EarlyAccess",
 | 
					            "/Lotus/Types/Items/Events/TennoConRelay2023EarlyAccess",
 | 
				
			||||||
 | 
				
			|||||||
@ -8,7 +8,7 @@ import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } f
 | 
				
			|||||||
import type { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "../../types/loginTypes.ts";
 | 
					import type { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "../../types/loginTypes.ts";
 | 
				
			||||||
import { logger } from "../../utils/logger.ts";
 | 
					import { logger } from "../../utils/logger.ts";
 | 
				
			||||||
import { version_compare } from "../../helpers/inventoryHelpers.ts";
 | 
					import { version_compare } from "../../helpers/inventoryHelpers.ts";
 | 
				
			||||||
import { sendWsBroadcastTo } from "../../services/wsService.ts";
 | 
					import { handleNonceInvalidation } from "../../services/wsService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const loginController: RequestHandler = async (request, response) => {
 | 
					export const loginController: RequestHandler = async (request, response) => {
 | 
				
			||||||
    const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
 | 
					    const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
 | 
				
			||||||
@ -74,7 +74,7 @@ export const loginController: RequestHandler = async (request, response) => {
 | 
				
			|||||||
    account.LastLogin = new Date();
 | 
					    account.LastLogin = new Date();
 | 
				
			||||||
    await account.save();
 | 
					    await account.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sendWsBroadcastTo(account._id.toString(), { nonce_updated: true });
 | 
					    handleNonceInvalidation(account._id.toString());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    response.json(createLoginResponse(myAddress, myUrlBase, account.toJSON(), buildLabel));
 | 
					    response.json(createLoginResponse(myAddress, myUrlBase, account.toJSON(), buildLabel));
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
import { Account } from "../../models/loginModel.ts";
 | 
					import { Account } from "../../models/loginModel.ts";
 | 
				
			||||||
import { sendWsBroadcastTo } from "../../services/wsService.ts";
 | 
					import { handleNonceInvalidation } from "../../services/wsService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const logoutController: RequestHandler = async (req, res) => {
 | 
					export const logoutController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    if (!req.query.accountId) {
 | 
					    if (!req.query.accountId) {
 | 
				
			||||||
@ -21,7 +21,7 @@ export const logoutController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    if (stat.modifiedCount) {
 | 
					    if (stat.modifiedCount) {
 | 
				
			||||||
        sendWsBroadcastTo(req.query.accountId as string, { nonce_updated: true });
 | 
					        handleNonceInvalidation(req.query.accountId as string);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.writeHead(200, {
 | 
					    res.writeHead(200, {
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@ import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
				
			|||||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
					import { getInventory } from "../../services/inventoryService.ts";
 | 
				
			||||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const maturePetController: RequestHandler = async (req, res) => {
 | 
					export const maturePetController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -19,6 +20,7 @@ export const maturePetController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
            : [details.DominantTraits.FurPattern, details.DominantTraits.FurPattern, details.DominantTraits.FurPattern],
 | 
					            : [details.DominantTraits.FurPattern, details.DominantTraits.FurPattern, details.DominantTraits.FurPattern],
 | 
				
			||||||
        unmature: data.revert
 | 
					        unmature: data.revert
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    broadcastInventoryUpdate(req);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IMaturePetRequest {
 | 
					interface IMaturePetRequest {
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,11 @@ import type { RequestHandler } from "express";
 | 
				
			|||||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
					import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
				
			||||||
import { getAccountForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
import type { IMissionInventoryUpdateRequest } from "../../types/requestTypes.ts";
 | 
					import type { IMissionInventoryUpdateRequest } from "../../types/requestTypes.ts";
 | 
				
			||||||
import { addMissionInventoryUpdates, addMissionRewards } from "../../services/missionInventoryUpdateService.ts";
 | 
					import {
 | 
				
			||||||
 | 
					    addMissionInventoryUpdates,
 | 
				
			||||||
 | 
					    addMissionRewards,
 | 
				
			||||||
 | 
					    handleConservation
 | 
				
			||||||
 | 
					} from "../../services/missionInventoryUpdateService.ts";
 | 
				
			||||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
					import { getInventory } from "../../services/inventoryService.ts";
 | 
				
			||||||
import { getInventoryResponse } from "./inventoryController.ts";
 | 
					import { getInventoryResponse } from "./inventoryController.ts";
 | 
				
			||||||
import { logger } from "../../utils/logger.ts";
 | 
					import { logger } from "../../utils/logger.ts";
 | 
				
			||||||
@ -94,6 +98,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
 | 
				
			|||||||
        SyndicateXPItemReward,
 | 
					        SyndicateXPItemReward,
 | 
				
			||||||
        ConquestCompletedMissionsCount
 | 
					        ConquestCompletedMissionsCount
 | 
				
			||||||
    } = await addMissionRewards(account, inventory, missionReport, firstCompletion);
 | 
					    } = await addMissionRewards(account, inventory, missionReport, firstCompletion);
 | 
				
			||||||
 | 
					    handleConservation(inventory, missionReport, AffiliationMods); // Conservation reports have GS_SUCCESS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (missionReport.EndOfMatchUpload) {
 | 
					    if (missionReport.EndOfMatchUpload) {
 | 
				
			||||||
        inventory.RewardSeed = generateRewardSeed();
 | 
					        inventory.RewardSeed = generateRewardSeed();
 | 
				
			||||||
@ -111,8 +116,16 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
 | 
				
			|||||||
        AffiliationMods,
 | 
					        AffiliationMods,
 | 
				
			||||||
        ConquestCompletedMissionsCount
 | 
					        ConquestCompletedMissionsCount
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    if (missionReport.RJ) {
 | 
					    if (
 | 
				
			||||||
        logger.debug(`railjack interstitial request, sending only deltas`, deltas);
 | 
					        missionReport.BMI ||
 | 
				
			||||||
 | 
					        missionReport.TNT ||
 | 
				
			||||||
 | 
					        missionReport.SSC ||
 | 
				
			||||||
 | 
					        missionReport.RJ ||
 | 
				
			||||||
 | 
					        missionReport.SS ||
 | 
				
			||||||
 | 
					        missionReport.CMI ||
 | 
				
			||||||
 | 
					        missionReport.EJC
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        logger.debug(`interstitial request, sending only deltas`, deltas);
 | 
				
			||||||
        res.json(deltas);
 | 
					        res.json(deltas);
 | 
				
			||||||
    } else if (missionReport.RewardInfo) {
 | 
					    } else if (missionReport.RewardInfo) {
 | 
				
			||||||
        logger.debug(`classic mission completion, sending everything`);
 | 
					        logger.debug(`classic mission completion, sending everything`);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
import { sendWsBroadcastTo } from "../../services/wsService.ts";
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
					import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    getInventory,
 | 
					    getInventory,
 | 
				
			||||||
@ -197,5 +197,5 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
 | 
				
			|||||||
            MiscItems: miscItemChanges
 | 
					            MiscItems: miscItemChanges
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    sendWsBroadcastTo(accountId, { update_inventory: true });
 | 
					    broadcastInventoryUpdate(req);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,7 @@ import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			|||||||
import { getInventory, updateCurrency } from "../../services/inventoryService.ts";
 | 
					import { getInventory, updateCurrency } from "../../services/inventoryService.ts";
 | 
				
			||||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
					import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
				
			||||||
import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
					import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
				
			||||||
import { sendWsBroadcastTo } from "../../services/wsService.ts";
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface INameWeaponRequest {
 | 
					interface INameWeaponRequest {
 | 
				
			||||||
    ItemName: string;
 | 
					    ItemName: string;
 | 
				
			||||||
@ -28,5 +28,5 @@ export const nameWeaponController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    res.json({
 | 
					    res.json({
 | 
				
			||||||
        InventoryChanges: currencyChanges
 | 
					        InventoryChanges: currencyChanges
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    sendWsBroadcastTo(accountId, { update_inventory: true });
 | 
					    broadcastInventoryUpdate(req);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,6 @@ import { GuildPermission } from "../../types/guildTypes.ts";
 | 
				
			|||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
import { Types } from "mongoose";
 | 
					import { Types } from "mongoose";
 | 
				
			||||||
import { ExportDojoRecipes, ExportResources } from "warframe-public-export-plus";
 | 
					import { ExportDojoRecipes, ExportResources } from "warframe-public-export-plus";
 | 
				
			||||||
import { config } from "../../services/configService.ts";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const placeDecoInComponentController: RequestHandler = async (req, res) => {
 | 
					export const placeDecoInComponentController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -74,7 +73,7 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (deco.Type != "/Lotus/Objects/Tenno/Props/TnoPaintBotDojoDeco") {
 | 
					        if (deco.Type != "/Lotus/Objects/Tenno/Props/TnoPaintBotDojoDeco") {
 | 
				
			||||||
            if (!meta || (meta.price == 0 && meta.ingredients.length == 0) || config.noDojoDecoBuildStage) {
 | 
					            if (!meta || (meta.price == 0 && meta.ingredients.length == 0) || guild.noDojoDecoBuildStage) {
 | 
				
			||||||
                deco.CompletionTime = new Date();
 | 
					                deco.CompletionTime = new Date();
 | 
				
			||||||
                if (meta) {
 | 
					                if (meta) {
 | 
				
			||||||
                    processDojoBuildMaterialsGathered(guild, meta);
 | 
					                    processDojoBuildMaterialsGathered(guild, meta);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,3 @@
 | 
				
			|||||||
import { config } from "../../services/configService.ts";
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    getDojoClient,
 | 
					    getDojoClient,
 | 
				
			||||||
    getGuildForRequestEx,
 | 
					    getGuildForRequestEx,
 | 
				
			||||||
@ -21,7 +20,7 @@ export const queueDojoComponentDestructionController: RequestHandler = async (re
 | 
				
			|||||||
    const componentId = req.query.componentId as string;
 | 
					    const componentId = req.query.componentId as string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    guild.DojoComponents.id(componentId)!.DestructionTime = new Date(
 | 
					    guild.DojoComponents.id(componentId)!.DestructionTime = new Date(
 | 
				
			||||||
        (Math.trunc(Date.now() / 1000) + (config.fastDojoRoomDestruction ? 5 : 2 * 3600)) * 1000
 | 
					        (Math.trunc(Date.now() / 1000) + (guild.fastDojoRoomDestruction ? 5 : 2 * 3600)) * 1000
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await guild.save();
 | 
					    await guild.save();
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
					import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
				
			||||||
import { getInventory, updateCurrency } from "../../services/inventoryService.ts";
 | 
					import { getInventory, updateCurrency } from "../../services/inventoryService.ts";
 | 
				
			||||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
import { sendWsBroadcastTo } from "../../services/wsService.ts";
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const releasePetController: RequestHandler = async (req, res) => {
 | 
					export const releasePetController: RequestHandler = async (req, res) => {
 | 
				
			||||||
@ -20,7 +20,7 @@ export const releasePetController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.json({ inventoryChanges }); // Not a mistake; it's "inventoryChanges" here.
 | 
					    res.json({ inventoryChanges }); // Not a mistake; it's "inventoryChanges" here.
 | 
				
			||||||
    sendWsBroadcastTo(accountId, { update_inventory: true });
 | 
					    broadcastInventoryUpdate(req);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IReleasePetRequest {
 | 
					interface IReleasePetRequest {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
					import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
				
			||||||
import { getInventory, updateCurrency } from "../../services/inventoryService.ts";
 | 
					import { getInventory, updateCurrency } from "../../services/inventoryService.ts";
 | 
				
			||||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
import { sendWsBroadcastTo } from "../../services/wsService.ts";
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
 | 
					import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
 | 
				
			||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -23,7 +23,7 @@ export const renamePetController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        ...data,
 | 
					        ...data,
 | 
				
			||||||
        inventoryChanges: inventoryChanges
 | 
					        inventoryChanges: inventoryChanges
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    sendWsBroadcastTo(accountId, { update_inventory: true });
 | 
					    broadcastInventoryUpdate(req);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IRenamePetRequest {
 | 
					interface IRenamePetRequest {
 | 
				
			||||||
 | 
				
			|||||||
@ -17,7 +17,7 @@ import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
				
			|||||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
					import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
				
			||||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
 | 
					import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
 | 
				
			||||||
import type { TInventoryDatabaseDocument } from "../../models/inventoryModels/inventoryModel.ts";
 | 
					import type { TInventoryDatabaseDocument } from "../../models/inventoryModels/inventoryModel.ts";
 | 
				
			||||||
import { sendWsBroadcastEx } from "../../services/wsService.ts";
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
import { parseFusionTreasure } from "../../helpers/inventoryHelpers.ts";
 | 
					import { parseFusionTreasure } from "../../helpers/inventoryHelpers.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const sellController: RequestHandler = async (req, res) => {
 | 
					export const sellController: RequestHandler = async (req, res) => {
 | 
				
			||||||
@ -307,7 +307,7 @@ export const sellController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    res.json({
 | 
					    res.json({
 | 
				
			||||||
        inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges"
 | 
					        inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges"
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    sendWsBroadcastEx({ update_inventory: true }, accountId, parseInt(String(req.query.wsid)));
 | 
					    broadcastInventoryUpdate(req);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ISellRequest {
 | 
					interface ISellRequest {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
import { Inventory } from "../../models/inventoryModels/inventoryModel.ts";
 | 
					import { Inventory } from "../../models/inventoryModels/inventoryModel.ts";
 | 
				
			||||||
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const setSupportedSyndicateController: RequestHandler = async (req, res) => {
 | 
					export const setSupportedSyndicateController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -15,4 +16,5 @@ export const setSupportedSyndicateController: RequestHandler = async (req, res)
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
 | 
					    broadcastInventoryUpdate(req);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -17,7 +17,7 @@ export const startCollectibleEntryController: RequestHandler = async (req, res)
 | 
				
			|||||||
        IncentiveStates: request.other
 | 
					        IncentiveStates: request.other
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.status(200).end();
 | 
					    res.send(`target = ${request.target}key = 0key = 1{"Target":"${request.target}"}`);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IStartCollectibleEntryRequest {
 | 
					interface IStartCollectibleEntryRequest {
 | 
				
			||||||
 | 
				
			|||||||
@ -11,7 +11,6 @@ import {
 | 
				
			|||||||
} from "../../services/guildService.ts";
 | 
					} from "../../services/guildService.ts";
 | 
				
			||||||
import { Types } from "mongoose";
 | 
					import { Types } from "mongoose";
 | 
				
			||||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
					import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
				
			||||||
import { config } from "../../services/configService.ts";
 | 
					 | 
				
			||||||
import { getAccountForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
					import { getInventory } from "../../services/inventoryService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -57,7 +56,7 @@ export const startDojoRecipeController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                DecoCapacity: room?.decoCapacity
 | 
					                DecoCapacity: room?.decoCapacity
 | 
				
			||||||
            }) - 1
 | 
					            }) - 1
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
    if (config.noDojoRoomBuildStage) {
 | 
					    if (guild.noDojoRoomBuildStage) {
 | 
				
			||||||
        component.CompletionTime = new Date(Date.now());
 | 
					        component.CompletionTime = new Date(Date.now());
 | 
				
			||||||
        if (room) {
 | 
					        if (room) {
 | 
				
			||||||
            processDojoBuildMaterialsGathered(guild, room);
 | 
					            processDojoBuildMaterialsGathered(guild, room);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
					import { getInventory } from "../../services/inventoryService.ts";
 | 
				
			||||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
					import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
				
			||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -19,6 +20,7 @@ export const abilityOverrideController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
 | 
					    broadcastInventoryUpdate(req);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IAbilityOverrideRequest {
 | 
					interface IAbilityOverrideRequest {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,21 +1,42 @@
 | 
				
			|||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
import { addFusionPoints, getInventory } from "../../services/inventoryService.ts";
 | 
					import { addFusionPoints, getInventory } from "../../services/inventoryService.ts";
 | 
				
			||||||
 | 
					import { getGuildForRequestEx, hasGuildPermission } from "../../services/guildService.ts";
 | 
				
			||||||
 | 
					import { GuildPermission } from "../../types/guildTypes.ts";
 | 
				
			||||||
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const addCurrencyController: RequestHandler = async (req, res) => {
 | 
					export const addCurrencyController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const request = req.body as IAddCurrencyRequest;
 | 
					    const request = req.body as IAddCurrencyRequest;
 | 
				
			||||||
    const inventory = await getInventory(accountId, request.currency);
 | 
					    let projection = request.currency as string;
 | 
				
			||||||
 | 
					    if (request.currency.startsWith("Vault")) projection = "GuildId";
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, projection);
 | 
				
			||||||
    if (request.currency == "FusionPoints") {
 | 
					    if (request.currency == "FusionPoints") {
 | 
				
			||||||
        addFusionPoints(inventory, request.delta);
 | 
					        addFusionPoints(inventory, request.delta);
 | 
				
			||||||
 | 
					    } else if (request.currency == "VaultRegularCredits" || request.currency == "VaultPremiumCredits") {
 | 
				
			||||||
 | 
					        const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
 | 
					        if (await hasGuildPermission(guild, accountId, GuildPermission.Treasurer)) {
 | 
				
			||||||
 | 
					            guild[request.currency] ??= 0;
 | 
				
			||||||
 | 
					            guild[request.currency]! += request.delta;
 | 
				
			||||||
 | 
					            await guild.save();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        inventory[request.currency] += request.delta;
 | 
					        inventory[request.currency] += request.delta;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    if (!request.currency.startsWith("Vault")) {
 | 
				
			||||||
        await inventory.save();
 | 
					        await inventory.save();
 | 
				
			||||||
 | 
					        broadcastInventoryUpdate(req);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IAddCurrencyRequest {
 | 
					interface IAddCurrencyRequest {
 | 
				
			||||||
    currency: "RegularCredits" | "PremiumCredits" | "FusionPoints" | "PrimeTokens";
 | 
					    currency:
 | 
				
			||||||
 | 
					        | "RegularCredits"
 | 
				
			||||||
 | 
					        | "PremiumCredits"
 | 
				
			||||||
 | 
					        | "FusionPoints"
 | 
				
			||||||
 | 
					        | "PrimeTokens"
 | 
				
			||||||
 | 
					        | "VaultRegularCredits"
 | 
				
			||||||
 | 
					        | "VaultPremiumCredits";
 | 
				
			||||||
    delta: number;
 | 
					    delta: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
import { getInventory, addItem } from "../../services/inventoryService.ts";
 | 
					import { getInventory, addItem } from "../../services/inventoryService.ts";
 | 
				
			||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const addItemsController: RequestHandler = async (req, res) => {
 | 
					export const addItemsController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -11,6 +12,7 @@ export const addItemsController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
 | 
					    broadcastInventoryUpdate(req);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IAddItemRequest {
 | 
					interface IAddItemRequest {
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@ import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			|||||||
import { getInventory, addRecipes } from "../../services/inventoryService.ts";
 | 
					import { getInventory, addRecipes } from "../../services/inventoryService.ts";
 | 
				
			||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
import { ExportRecipes } from "warframe-public-export-plus";
 | 
					import { ExportRecipes } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const addMissingHelminthBlueprintsController: RequestHandler = async (req, res) => {
 | 
					export const addMissingHelminthBlueprintsController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -21,4 +22,5 @@ export const addMissingHelminthBlueprintsController: RequestHandler = async (req
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
 | 
					    broadcastInventoryUpdate(req);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@ import { getInventory } from "../../services/inventoryService.ts";
 | 
				
			|||||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
import { ExportArcanes, ExportUpgrades } from "warframe-public-export-plus";
 | 
					import { ExportArcanes, ExportUpgrades } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const addMissingMaxRankModsController: RequestHandler = async (req, res) => {
 | 
					export const addMissingMaxRankModsController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -41,4 +42,5 @@ export const addMissingMaxRankModsController: RequestHandler = async (req, res)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
 | 
					    broadcastInventoryUpdate(req);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										36
									
								
								src/controllers/custom/addVaultDecoRecipeController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/controllers/custom/addVaultDecoRecipeController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
 | 
					import { getInventory } from "../../services/inventoryService.ts";
 | 
				
			||||||
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { getGuildForRequestEx, hasGuildPermission } from "../../services/guildService.ts";
 | 
				
			||||||
 | 
					import { GuildPermission } from "../../types/guildTypes.ts";
 | 
				
			||||||
 | 
					import type { ITypeCount } from "../../types/commonTypes.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const addVaultDecoRecipeController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const requests = req.body as ITypeCount[];
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "GuildId");
 | 
				
			||||||
 | 
					    const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
 | 
					    if (!(await hasGuildPermission(guild, accountId, GuildPermission.Architect))) {
 | 
				
			||||||
 | 
					        res.status(400).send("-1").end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    guild.VaultDecoRecipes ??= [];
 | 
				
			||||||
 | 
					    for (const request of requests) {
 | 
				
			||||||
 | 
					        const index = guild.VaultDecoRecipes.findIndex(x => x.ItemType === request.ItemType);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (index == -1) {
 | 
				
			||||||
 | 
					            guild.VaultDecoRecipes.push({
 | 
				
			||||||
 | 
					                ItemType: request.ItemType,
 | 
				
			||||||
 | 
					                ItemCount: request.ItemCount
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            guild.VaultDecoRecipes[index].ItemCount += request.ItemCount;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (guild.VaultDecoRecipes[index].ItemCount < 1) {
 | 
				
			||||||
 | 
					                guild.VaultDecoRecipes.splice(index, 1);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await guild.save();
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -1,10 +1,11 @@
 | 
				
			|||||||
import { applyClientEquipmentUpdates, getInventory } from "../../services/inventoryService.ts";
 | 
					import { applyClientEquipmentUpdates, getInventory } from "../../services/inventoryService.ts";
 | 
				
			||||||
 | 
					import { getMaxLevelCap } from "../../services/itemDataService.ts";
 | 
				
			||||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
import type { IOid } from "../../types/commonTypes.ts";
 | 
					import type { IOid } from "../../types/commonTypes.ts";
 | 
				
			||||||
import type { IEquipmentClient } from "../../types/equipmentTypes.ts";
 | 
					import type { IEquipmentClient } from "../../types/equipmentTypes.ts";
 | 
				
			||||||
import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
					import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
				
			||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
import { ExportMisc } from "warframe-public-export-plus";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const addXpController: RequestHandler = async (req, res) => {
 | 
					export const addXpController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -14,7 +15,7 @@ export const addXpController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        for (const clientItem of gear) {
 | 
					        for (const clientItem of gear) {
 | 
				
			||||||
            const dbItem = inventory[category as TEquipmentKey].id((clientItem.ItemId as IOid).$oid);
 | 
					            const dbItem = inventory[category as TEquipmentKey].id((clientItem.ItemId as IOid).$oid);
 | 
				
			||||||
            if (dbItem) {
 | 
					            if (dbItem) {
 | 
				
			||||||
                if (dbItem.ItemType in ExportMisc.uniqueLevelCaps) {
 | 
					                if (getMaxLevelCap(dbItem.ItemType) > 30) {
 | 
				
			||||||
                    if ((dbItem.Polarized ?? 0) < 5) {
 | 
					                    if ((dbItem.Polarized ?? 0) < 5) {
 | 
				
			||||||
                        dbItem.Polarized = 5;
 | 
					                        dbItem.Polarized = 5;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
@ -25,6 +26,7 @@ export const addXpController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
 | 
					    broadcastInventoryUpdate(req);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type IAddXpRequest = {
 | 
					type IAddXpRequest = {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
					import { getInventory } from "../../services/inventoryService.ts";
 | 
				
			||||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
					import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
				
			||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -20,6 +21,7 @@ export const changeModularPartsController: RequestHandler = async (req, res) =>
 | 
				
			|||||||
        await inventory.save();
 | 
					        await inventory.save();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
 | 
					    broadcastInventoryUpdate(req);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IUpdateFingerPrintRequest {
 | 
					interface IUpdateFingerPrintRequest {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
					import { getInventory } from "../../services/inventoryService.ts";
 | 
				
			||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DEFAULT_UPGRADE_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
 | 
					const DEFAULT_UPGRADE_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -31,4 +32,5 @@ export const editSuitInvigorationUpgradeController: RequestHandler = async (req,
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
 | 
					    broadcastInventoryUpdate(req);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										16
									
								
								src/controllers/custom/getAllianceController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/controllers/custom/getAllianceController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					import { Alliance, Guild } from "../../models/guildModel.ts";
 | 
				
			||||||
 | 
					import { getAllianceClient } from "../../services/guildService.ts";
 | 
				
			||||||
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getAllianceController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const guildId = req.query.guildId;
 | 
				
			||||||
 | 
					    if (guildId) {
 | 
				
			||||||
 | 
					        const guild = await Guild.findById(guildId, "Name Tier AllianceId");
 | 
				
			||||||
 | 
					        if (guild && guild.AllianceId) {
 | 
				
			||||||
 | 
					            const alliance = (await Alliance.findById(guild.AllianceId))!;
 | 
				
			||||||
 | 
					            res.json(await getAllianceClient(alliance, guild));
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										40
									
								
								src/controllers/custom/getGuildController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/controllers/custom/getGuildController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { Guild, GuildMember } from "../../models/guildModel.ts";
 | 
				
			||||||
 | 
					import { toMongoDate, toOid2 } from "../../helpers/inventoryHelpers.ts";
 | 
				
			||||||
 | 
					import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "../../services/friendService.ts";
 | 
				
			||||||
 | 
					import type { IGuildMemberClient } from "../../types/guildTypes.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getGuildController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const guildId = req.query.guildId;
 | 
				
			||||||
 | 
					    if (guildId) {
 | 
				
			||||||
 | 
					        const guild = await Guild.findById(guildId);
 | 
				
			||||||
 | 
					        if (guild) {
 | 
				
			||||||
 | 
					            const guildMembers = await GuildMember.find({ guildId: guild._id });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const members: IGuildMemberClient[] = [];
 | 
				
			||||||
 | 
					            const dataFillInPromises: Promise<void>[] = [];
 | 
				
			||||||
 | 
					            for (const guildMember of guildMembers) {
 | 
				
			||||||
 | 
					                const member: IGuildMemberClient = {
 | 
				
			||||||
 | 
					                    _id: toOid2(guildMember.accountId, undefined),
 | 
				
			||||||
 | 
					                    Rank: guildMember.rank,
 | 
				
			||||||
 | 
					                    Status: guildMember.status,
 | 
				
			||||||
 | 
					                    Note: guildMember.RequestMsg,
 | 
				
			||||||
 | 
					                    RequestExpiry: guildMember.RequestExpiry ? toMongoDate(guildMember.RequestExpiry) : undefined
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                dataFillInPromises.push(addAccountDataToFriendInfo(member));
 | 
				
			||||||
 | 
					                dataFillInPromises.push(addInventoryDataToFriendInfo(member));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                members.push(member);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await Promise.all(dataFillInPromises);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            res.json({
 | 
				
			||||||
 | 
					                ...guild.toObject(),
 | 
				
			||||||
 | 
					                Members: members
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            res.status(400).end();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -7,10 +7,12 @@ import {
 | 
				
			|||||||
    ExportAvionics,
 | 
					    ExportAvionics,
 | 
				
			||||||
    ExportBoosters,
 | 
					    ExportBoosters,
 | 
				
			||||||
    ExportCustoms,
 | 
					    ExportCustoms,
 | 
				
			||||||
 | 
					    ExportDojoRecipes,
 | 
				
			||||||
    ExportDrones,
 | 
					    ExportDrones,
 | 
				
			||||||
 | 
					    ExportFactions,
 | 
				
			||||||
 | 
					    ExportFlavour,
 | 
				
			||||||
    ExportGear,
 | 
					    ExportGear,
 | 
				
			||||||
    ExportKeys,
 | 
					    ExportKeys,
 | 
				
			||||||
    ExportMisc,
 | 
					 | 
				
			||||||
    ExportRailjackWeapons,
 | 
					    ExportRailjackWeapons,
 | 
				
			||||||
    ExportRecipes,
 | 
					    ExportRecipes,
 | 
				
			||||||
    ExportRelics,
 | 
					    ExportRelics,
 | 
				
			||||||
@ -34,10 +36,11 @@ interface ListedItem {
 | 
				
			|||||||
    partType?: string;
 | 
					    partType?: string;
 | 
				
			||||||
    chainLength?: number;
 | 
					    chainLength?: number;
 | 
				
			||||||
    parazon?: boolean;
 | 
					    parazon?: boolean;
 | 
				
			||||||
 | 
					    alwaysAvailable?: boolean;
 | 
				
			||||||
 | 
					    maxLevelCap?: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ItemLists {
 | 
					interface ItemLists {
 | 
				
			||||||
    uniqueLevelCaps: Record<string, number>;
 | 
					 | 
				
			||||||
    Suits: ListedItem[];
 | 
					    Suits: ListedItem[];
 | 
				
			||||||
    LongGuns: ListedItem[];
 | 
					    LongGuns: ListedItem[];
 | 
				
			||||||
    Melee: ListedItem[];
 | 
					    Melee: ListedItem[];
 | 
				
			||||||
@ -59,24 +62,27 @@ interface ItemLists {
 | 
				
			|||||||
    Boosters: ListedItem[];
 | 
					    Boosters: ListedItem[];
 | 
				
			||||||
    VarziaOffers: ListedItem[];
 | 
					    VarziaOffers: ListedItem[];
 | 
				
			||||||
    Abilities: ListedItem[];
 | 
					    Abilities: ListedItem[];
 | 
				
			||||||
 | 
					    TechProjects: ListedItem[];
 | 
				
			||||||
 | 
					    VaultDecoRecipes: ListedItem[];
 | 
				
			||||||
 | 
					    FlavourItems: ListedItem[];
 | 
				
			||||||
 | 
					    ShipDecorations: ListedItem[];
 | 
				
			||||||
    //circuitGameModes: ListedItem[];
 | 
					    //circuitGameModes: ListedItem[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const relicQualitySuffixes: Record<TRelicQuality, string> = {
 | 
					const relicQualitySuffixes: Record<TRelicQuality, string> = {
 | 
				
			||||||
    VPQ_BRONZE: "",
 | 
					    VPQ_BRONZE: "",
 | 
				
			||||||
    VPQ_SILVER: " [Exceptional]",
 | 
					    VPQ_SILVER: "/Lotus/Language/Relics/VoidProjectionQuality_Silver",
 | 
				
			||||||
    VPQ_GOLD: " [Flawless]",
 | 
					    VPQ_GOLD: "/Lotus/Language/Relics/VoidProjectionQuality_Gold",
 | 
				
			||||||
    VPQ_PLATINUM: " [Radiant]"
 | 
					    VPQ_PLATINUM: "/Lotus/Language/Relics/VoidProjectionQuality_Platinum"
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*const toTitleCase = (str: string): string => {
 | 
					const toTitleCase = (str: string): string => {
 | 
				
			||||||
    return str.replace(/[^\s-]+/g, word => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase());
 | 
					    return str.replace(/[^\s-]+/g, word => word.charAt(0).toUpperCase() + word.substring(1).toLowerCase());
 | 
				
			||||||
};*/
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getItemListsController: RequestHandler = (req, response) => {
 | 
					const getItemListsController: RequestHandler = (req, response) => {
 | 
				
			||||||
    const lang = getDict(typeof req.query.lang == "string" ? req.query.lang : "en");
 | 
					    const lang = getDict(typeof req.query.lang == "string" ? req.query.lang : "en");
 | 
				
			||||||
    const res: ItemLists = {
 | 
					    const res: ItemLists = {
 | 
				
			||||||
        uniqueLevelCaps: ExportMisc.uniqueLevelCaps,
 | 
					 | 
				
			||||||
        Suits: [],
 | 
					        Suits: [],
 | 
				
			||||||
        LongGuns: [],
 | 
					        LongGuns: [],
 | 
				
			||||||
        Melee: [],
 | 
					        Melee: [],
 | 
				
			||||||
@ -97,7 +103,11 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
				
			|||||||
        mods: [],
 | 
					        mods: [],
 | 
				
			||||||
        Boosters: [],
 | 
					        Boosters: [],
 | 
				
			||||||
        VarziaOffers: [],
 | 
					        VarziaOffers: [],
 | 
				
			||||||
        Abilities: []
 | 
					        Abilities: [],
 | 
				
			||||||
 | 
					        TechProjects: [],
 | 
				
			||||||
 | 
					        VaultDecoRecipes: [],
 | 
				
			||||||
 | 
					        FlavourItems: [],
 | 
				
			||||||
 | 
					        ShipDecorations: []
 | 
				
			||||||
        /*circuitGameModes: [
 | 
					        /*circuitGameModes: [
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                uniqueName: "Survival",
 | 
					                uniqueName: "Survival",
 | 
				
			||||||
@ -133,7 +143,8 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
				
			|||||||
        res[item.productCategory].push({
 | 
					        res[item.productCategory].push({
 | 
				
			||||||
            uniqueName,
 | 
					            uniqueName,
 | 
				
			||||||
            name: getString(item.name, lang),
 | 
					            name: getString(item.name, lang),
 | 
				
			||||||
            exalted: item.exalted
 | 
					            exalted: item.exalted,
 | 
				
			||||||
 | 
					            maxLevelCap: item.maxLevelCap
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        item.abilities.forEach(ability => {
 | 
					        item.abilities.forEach(ability => {
 | 
				
			||||||
            res.Abilities.push({
 | 
					            res.Abilities.push({
 | 
				
			||||||
@ -181,7 +192,8 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
				
			|||||||
            ) {
 | 
					            ) {
 | 
				
			||||||
                res[item.productCategory].push({
 | 
					                res[item.productCategory].push({
 | 
				
			||||||
                    uniqueName,
 | 
					                    uniqueName,
 | 
				
			||||||
                    name: getString(item.name, lang)
 | 
					                    name: getString(item.name, lang),
 | 
				
			||||||
 | 
					                    maxLevelCap: item.maxLevelCap
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else if (!item.excludeFromCodex) {
 | 
					        } else if (!item.excludeFromCodex) {
 | 
				
			||||||
@ -219,9 +231,14 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (
 | 
					        if (item.productCategory == "ShipDecorations") {
 | 
				
			||||||
 | 
					            res.ShipDecorations.push({
 | 
				
			||||||
 | 
					                uniqueName: uniqueName,
 | 
				
			||||||
 | 
					                name: name
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        } else if (
 | 
				
			||||||
            name &&
 | 
					            name &&
 | 
				
			||||||
            uniqueName.substr(0, 30) != "/Lotus/Types/Game/Projections/" &&
 | 
					            uniqueName.substring(0, 30) != "/Lotus/Types/Game/Projections/" &&
 | 
				
			||||||
            uniqueName != "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLanthornBundle"
 | 
					            uniqueName != "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLanthornBundle"
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
            res.miscitems.push({
 | 
					            res.miscitems.push({
 | 
				
			||||||
@ -232,14 +249,19 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    for (const [uniqueName, item] of Object.entries(ExportRelics)) {
 | 
					    for (const [uniqueName, item] of Object.entries(ExportRelics)) {
 | 
				
			||||||
 | 
					        const qualitySuffix =
 | 
				
			||||||
 | 
					            item.quality !== "VPQ_BRONZE"
 | 
				
			||||||
 | 
					                ? ` [${toTitleCase(getString(relicQualitySuffixes[item.quality], lang))}]`
 | 
				
			||||||
 | 
					                : "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        res.miscitems.push({
 | 
					        res.miscitems.push({
 | 
				
			||||||
            uniqueName: uniqueName,
 | 
					            uniqueName: uniqueName,
 | 
				
			||||||
            name:
 | 
					            name:
 | 
				
			||||||
                getString("/Lotus/Language/Relics/VoidProjectionName", lang)
 | 
					                getString("/Lotus/Language/Relics/VoidProjectionName", lang)
 | 
				
			||||||
                    .split("|ERA|")
 | 
					                    .split("|ERA|")
 | 
				
			||||||
                    .join(item.era)
 | 
					                    .join(getString(`/Lotus/Language/Relics/Era_${item.era.toUpperCase()}`, lang))
 | 
				
			||||||
                    .split("|CATEGORY|")
 | 
					                    .split("|CATEGORY|")
 | 
				
			||||||
                    .join(item.category) + relicQualitySuffixes[item.quality]
 | 
					                    .join(item.category) + qualitySuffix
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    for (const [uniqueName, item] of Object.entries(ExportGear)) {
 | 
					    for (const [uniqueName, item] of Object.entries(ExportGear)) {
 | 
				
			||||||
@ -313,7 +335,7 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
				
			|||||||
                uniqueName,
 | 
					                uniqueName,
 | 
				
			||||||
                name: getString(arcane.name, lang)
 | 
					                name: getString(arcane.name, lang)
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
            if (arcane.isFrivolous) {
 | 
					            if (arcane.excludeFromCodex) {
 | 
				
			||||||
                mod.badReason = "frivolous";
 | 
					                mod.badReason = "frivolous";
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            res.mods.push(mod);
 | 
					            res.mods.push(mod);
 | 
				
			||||||
@ -367,6 +389,74 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const uniqueName of Object.keys(ExportDojoRecipes.research)) {
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					            !["Zekti", "Vidar", "Lavan"].some(house => uniqueName.includes(house)) &&
 | 
				
			||||||
 | 
					            !uniqueName.startsWith("/Lotus/Types/Items/ShipFeatureItems/Railjack/")
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            let resultType;
 | 
				
			||||||
 | 
					            if (uniqueName in ExportRecipes) {
 | 
				
			||||||
 | 
					                resultType = ExportRecipes[uniqueName].resultType;
 | 
				
			||||||
 | 
					            } else if (uniqueName in ExportDojoRecipes.fabrications) {
 | 
				
			||||||
 | 
					                resultType = ExportDojoRecipes.fabrications[uniqueName].resultType;
 | 
				
			||||||
 | 
					            } else if (uniqueName.startsWith("/Lotus/Types/Game/")) {
 | 
				
			||||||
 | 
					                resultType = uniqueName.replace("Blueprint", "");
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                resultType = uniqueName;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let name = getString(getItemName(resultType) || resultType, lang);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (uniqueName in ExportRecipes) {
 | 
				
			||||||
 | 
					                const recipeNum = ExportRecipes[uniqueName].num;
 | 
				
			||||||
 | 
					                if (recipeNum > 1) {
 | 
				
			||||||
 | 
					                    name = `${name} X ${recipeNum}`;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            res.TechProjects.push({
 | 
				
			||||||
 | 
					                uniqueName,
 | 
				
			||||||
 | 
					                name
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const uniqueName of [
 | 
				
			||||||
 | 
					        ...Object.entries(ExportDojoRecipes.decos)
 | 
				
			||||||
 | 
					            .filter(([_, data]) => data.requiredInVault)
 | 
				
			||||||
 | 
					            .map(([uniqueName]) => uniqueName),
 | 
				
			||||||
 | 
					        // not requiredInVault:
 | 
				
			||||||
 | 
					        "/Lotus/Levels/ClanDojo/ComponentPropRecipes/PlagueStarEventTrophyBronzeRecipe",
 | 
				
			||||||
 | 
					        "/Lotus/Levels/ClanDojo/ComponentPropRecipes/PlagueStarEventTrophyGoldRecipe",
 | 
				
			||||||
 | 
					        "/Lotus/Levels/ClanDojo/ComponentPropRecipes/PlagueStarEventTrophySilverRecipe",
 | 
				
			||||||
 | 
					        "/Lotus/Levels/ClanDojo/ComponentPropRecipes/PlagueStarEventTrophyTerracottaRecipe"
 | 
				
			||||||
 | 
					        // removed in 38.6.0:
 | 
				
			||||||
 | 
					        // "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophyBronzeRecipe",
 | 
				
			||||||
 | 
					        // "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophyCrystalRecipe",
 | 
				
			||||||
 | 
					        // "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophyGoldRecipe",
 | 
				
			||||||
 | 
					        // "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophySilverRecipe",
 | 
				
			||||||
 | 
					        // "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NaturalPlaceables/CoralChunkARecipe"
 | 
				
			||||||
 | 
					    ]) {
 | 
				
			||||||
 | 
					        let name = getString(getItemName(uniqueName) || uniqueName, lang);
 | 
				
			||||||
 | 
					        if (uniqueName.startsWith("/Lotus/Levels/ClanDojo/ComponentPropRecipes/GradivusDilemma")) {
 | 
				
			||||||
 | 
					            const factionTag = uniqueName.includes("Corpus") ? "FC_CORPUS" : "FC_GRINEER";
 | 
				
			||||||
 | 
					            const faction = ExportFactions[factionTag].name;
 | 
				
			||||||
 | 
					            name += ` [${getString(faction, lang)}]`;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        res.VaultDecoRecipes.push({
 | 
				
			||||||
 | 
					            uniqueName,
 | 
				
			||||||
 | 
					            name
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const [uniqueName, item] of Object.entries(ExportFlavour)) {
 | 
				
			||||||
 | 
					        res.FlavourItems.push({
 | 
				
			||||||
 | 
					            uniqueName,
 | 
				
			||||||
 | 
					            name: getString(item.name, lang),
 | 
				
			||||||
 | 
					            alwaysAvailable: item.alwaysAvailable
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    response.json(res);
 | 
					    response.json(res);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@ import { getInventory } from "../../services/inventoryService.ts";
 | 
				
			|||||||
import { getLoadout } from "../../services/loadoutService.ts";
 | 
					import { getLoadout } from "../../services/loadoutService.ts";
 | 
				
			||||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
import { getPersonalRooms } from "../../services/personalRoomsService.ts";
 | 
					import { getPersonalRooms } from "../../services/personalRoomsService.ts";
 | 
				
			||||||
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
import type { IInventoryClient } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
					import type { IInventoryClient } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
				
			||||||
import type { IGetShipResponse } from "../../types/personalRoomsTypes.ts";
 | 
					import type { IGetShipResponse } from "../../types/personalRoomsTypes.ts";
 | 
				
			||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
@ -32,6 +33,7 @@ export const importController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
 | 
					    broadcastInventoryUpdate(req);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IImportRequest {
 | 
					interface IImportRequest {
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,7 @@ import {
 | 
				
			|||||||
import { logger } from "../../utils/logger.ts";
 | 
					import { logger } from "../../utils/logger.ts";
 | 
				
			||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
import { ExportKeys } from "warframe-public-export-plus";
 | 
					import { ExportKeys } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const manageQuestsController: RequestHandler = async (req, res) => {
 | 
					export const manageQuestsController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -101,8 +102,16 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                    questKey.Completed = false;
 | 
					                    questKey.Completed = false;
 | 
				
			||||||
                    questKey.CompletionDate = undefined;
 | 
					                    questKey.CompletionDate = undefined;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const run = questKey.Progress[0]?.c ?? 0;
 | 
				
			||||||
 | 
					                const stage = questKey.Progress.map(p => p.c).lastIndexOf(run);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (run > 0) {
 | 
				
			||||||
 | 
					                    questKey.Progress[stage].c = run - 1;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
                    questKey.Progress.pop();
 | 
					                    questKey.Progress.pop();
 | 
				
			||||||
                const stage = questKey.Progress.length - 1;
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (stage > 0) {
 | 
					                if (stage > 0) {
 | 
				
			||||||
                    await giveKeyChainStageTriggered(inventory, {
 | 
					                    await giveKeyChainStageTriggered(inventory, {
 | 
				
			||||||
                        KeyChain: questKey.ItemType,
 | 
					                        KeyChain: questKey.ItemType,
 | 
				
			||||||
@ -122,28 +131,28 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
                if (!questKey.Progress) break;
 | 
					                if (!questKey.Progress) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const currentStage = questKey.Progress.length;
 | 
					                const run = questKey.Progress[0]?.c ?? 0;
 | 
				
			||||||
 | 
					                const currentStage = questKey.Progress.map(p => p.c).lastIndexOf(run);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (currentStage + 1 == questManifest.chainStages?.length) {
 | 
					                if (currentStage + 1 == questManifest.chainStages?.length) {
 | 
				
			||||||
                    logger.debug(`Trying to complete last stage with nextStage, calling completeQuest instead`);
 | 
					                    logger.debug(`Trying to complete last stage with nextStage, calling completeQuest instead`);
 | 
				
			||||||
                    await completeQuest(inventory, questKey.ItemType);
 | 
					                    await completeQuest(inventory, questKey.ItemType);
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    const progress = {
 | 
					                    if (run > 0) {
 | 
				
			||||||
                        c: 0,
 | 
					                        questKey.Progress[currentStage + 1].c = run;
 | 
				
			||||||
                        i: false,
 | 
					                    } else {
 | 
				
			||||||
                        m: false,
 | 
					                        questKey.Progress.push({ c: run, i: false, m: false, b: [] });
 | 
				
			||||||
                        b: []
 | 
					                    }
 | 
				
			||||||
                    };
 | 
					 | 
				
			||||||
                    questKey.Progress.push(progress);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    await giveKeyChainStageTriggered(inventory, {
 | 
					                    await giveKeyChainStageTriggered(inventory, {
 | 
				
			||||||
                        KeyChain: questKey.ItemType,
 | 
					                        KeyChain: questKey.ItemType,
 | 
				
			||||||
                        ChainStage: currentStage
 | 
					                        ChainStage: currentStage + 1
 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if (currentStage > 0) {
 | 
					                    if (currentStage > 0) {
 | 
				
			||||||
                        await giveKeyChainMissionReward(inventory, {
 | 
					                        await giveKeyChainMissionReward(inventory, {
 | 
				
			||||||
                            KeyChain: questKey.ItemType,
 | 
					                            KeyChain: questKey.ItemType,
 | 
				
			||||||
                            ChainStage: currentStage - 1
 | 
					                            ChainStage: currentStage
 | 
				
			||||||
                        });
 | 
					                        });
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@ -157,4 +166,5 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.status(200).end();
 | 
					    res.status(200).end();
 | 
				
			||||||
 | 
					    broadcastInventoryUpdate(req);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
					import { getInventory } from "../../services/inventoryService.ts";
 | 
				
			||||||
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const popArchonCrystalUpgradeController: RequestHandler = async (req, res) => {
 | 
					export const popArchonCrystalUpgradeController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -12,6 +13,7 @@ export const popArchonCrystalUpgradeController: RequestHandler = async (req, res
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
        await inventory.save();
 | 
					        await inventory.save();
 | 
				
			||||||
        res.end();
 | 
					        res.end();
 | 
				
			||||||
 | 
					        broadcastInventoryUpdate(req);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    res.status(400).end();
 | 
					    res.status(400).end();
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
					import { getInventory } from "../../services/inventoryService.ts";
 | 
				
			||||||
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const pushArchonCrystalUpgradeController: RequestHandler = async (req, res) => {
 | 
					export const pushArchonCrystalUpgradeController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -15,6 +16,7 @@ export const pushArchonCrystalUpgradeController: RequestHandler = async (req, re
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            await inventory.save();
 | 
					            await inventory.save();
 | 
				
			||||||
            res.end();
 | 
					            res.end();
 | 
				
			||||||
 | 
					            broadcastInventoryUpdate(req);
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										14
									
								
								src/controllers/custom/removeCustomizationController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/controllers/custom/removeCustomizationController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
 | 
					import { getInventory } from "../../services/inventoryService.ts";
 | 
				
			||||||
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const removeCustomizationController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const ItemType = req.query.itemType as string;
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "FlavourItems");
 | 
				
			||||||
 | 
					    inventory.FlavourItems.pull({ ItemType });
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					    broadcastInventoryUpdate(req);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
					import { getInventory } from "../../services/inventoryService.ts";
 | 
				
			||||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
 | 
					import { sendWsBroadcastTo } from "../../services/wsService.ts";
 | 
				
			||||||
import type { IAccountCheats } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
					import type { IAccountCheats } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
				
			||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -10,6 +11,9 @@ export const setAccountCheatController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    inventory[payload.key] = payload.value;
 | 
					    inventory[payload.key] = payload.value;
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
 | 
					    if (["infiniteCredits", "infinitePlatinum", "infiniteEndo", "infiniteRegalAya"].indexOf(payload.key) != -1) {
 | 
				
			||||||
 | 
					        sendWsBroadcastTo(accountId, { update_inventory: true, sync_inventory: true });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ISetAccountCheatRequest {
 | 
					interface ISetAccountCheatRequest {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,45 +1,22 @@
 | 
				
			|||||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
					import { getInventory } from "../../services/inventoryService.ts";
 | 
				
			||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
import { ExportBoosters } from "warframe-public-export-plus";
 | 
					import type { IBooster } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
				
			||||||
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
const I32_MAX = 0x7fffffff;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const setBoosterController: RequestHandler = async (req, res) => {
 | 
					export const setBoosterController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const requests = req.body as { ItemType: string; ExpiryDate: number }[];
 | 
					    const requests = req.body as IBooster[];
 | 
				
			||||||
    const inventory = await getInventory(accountId, "Boosters");
 | 
					    const inventory = await getInventory(accountId, "Boosters");
 | 
				
			||||||
    const boosters = inventory.Boosters;
 | 
					    for (const request of requests) {
 | 
				
			||||||
    if (
 | 
					        const index = inventory.Boosters.findIndex(item => item.ItemType === request.ItemType);
 | 
				
			||||||
        requests.some(request => {
 | 
					 | 
				
			||||||
            if (typeof request.ItemType !== "string") return true;
 | 
					 | 
				
			||||||
            if (Object.entries(ExportBoosters).find(([_, item]) => item.typeName === request.ItemType) === undefined)
 | 
					 | 
				
			||||||
                return true;
 | 
					 | 
				
			||||||
            if (typeof request.ExpiryDate !== "number") return true;
 | 
					 | 
				
			||||||
            if (request.ExpiryDate < 0 || request.ExpiryDate > I32_MAX) return true;
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
        res.status(400).send("Invalid ItemType provided.");
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    const now = Math.trunc(Date.now() / 1000);
 | 
					 | 
				
			||||||
    for (const { ItemType, ExpiryDate } of requests) {
 | 
					 | 
				
			||||||
        if (ExpiryDate <= now) {
 | 
					 | 
				
			||||||
            // remove expired boosters
 | 
					 | 
				
			||||||
            const index = boosters.findIndex(item => item.ItemType === ItemType);
 | 
					 | 
				
			||||||
        if (index !== -1) {
 | 
					        if (index !== -1) {
 | 
				
			||||||
                boosters.splice(index, 1);
 | 
					            inventory.Boosters[index].ExpiryDate = request.ExpiryDate;
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            const boosterItem = boosters.find(item => item.ItemType === ItemType);
 | 
					            inventory.Boosters.push(request);
 | 
				
			||||||
            if (boosterItem) {
 | 
					 | 
				
			||||||
                boosterItem.ExpiryDate = ExpiryDate;
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                boosters.push({ ItemType, ExpiryDate });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
 | 
					    broadcastInventoryUpdate(req);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
					import { getInventory } from "../../services/inventoryService.ts";
 | 
				
			||||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const setEvolutionProgressController: RequestHandler = async (req, res) => {
 | 
					export const setEvolutionProgressController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -25,6 +26,7 @@ export const setEvolutionProgressController: RequestHandler = async (req, res) =
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
 | 
					    broadcastInventoryUpdate(req);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ISetEvolutionProgressRequest = {
 | 
					type ISetEvolutionProgressRequest = {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										29
									
								
								src/controllers/custom/setGuildCheatController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/controllers/custom/setGuildCheatController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					import { GuildMember } from "../../models/guildModel.ts";
 | 
				
			||||||
 | 
					import { getGuildForRequestEx } from "../../services/guildService.ts";
 | 
				
			||||||
 | 
					import { getInventory } from "../../services/inventoryService.ts";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
 | 
					import type { IGuildCheats } from "../../types/guildTypes.ts";
 | 
				
			||||||
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const setGuildCheatController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const payload = req.body as ISetGuildCheatRequest;
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, `GuildId`);
 | 
				
			||||||
 | 
					    const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
 | 
					    const member = await GuildMember.findOne({ accountId: accountId, guildId: guild._id });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (member) {
 | 
				
			||||||
 | 
					        if (member.rank > 1) {
 | 
				
			||||||
 | 
					            res.end();
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        guild[payload.key] = payload.value;
 | 
				
			||||||
 | 
					        await guild.save();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ISetGuildCheatRequest {
 | 
				
			||||||
 | 
					    key: keyof IGuildCheats;
 | 
				
			||||||
 | 
					    value: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										127
									
								
								src/controllers/custom/techProjectController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								src/controllers/custom/techProjectController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,127 @@
 | 
				
			|||||||
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
 | 
					import { getInventory } from "../../services/inventoryService.ts";
 | 
				
			||||||
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    getGuildForRequestEx,
 | 
				
			||||||
 | 
					    setGuildTechLogState,
 | 
				
			||||||
 | 
					    processFundedGuildTechProject,
 | 
				
			||||||
 | 
					    scaleRequiredCount,
 | 
				
			||||||
 | 
					    hasGuildPermission,
 | 
				
			||||||
 | 
					    addGuildMemberMiscItemContribution,
 | 
				
			||||||
 | 
					    processGuildTechProjectContributionsUpdate,
 | 
				
			||||||
 | 
					    processCompletedGuildTechProject
 | 
				
			||||||
 | 
					} from "../../services/guildService.ts";
 | 
				
			||||||
 | 
					import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					import { GuildPermission } from "../../types/guildTypes.ts";
 | 
				
			||||||
 | 
					import { GuildMember } from "../../models/guildModel.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const addTechProjectController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const requests = req.body as ITechProjectRequest[];
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "GuildId");
 | 
				
			||||||
 | 
					    const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
 | 
					    if (!(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
 | 
				
			||||||
 | 
					        res.status(400).send("-1").end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    guild.TechProjects ??= [];
 | 
				
			||||||
 | 
					    for (const request of requests) {
 | 
				
			||||||
 | 
					        const recipe = ExportDojoRecipes.research[request.ItemType];
 | 
				
			||||||
 | 
					        if (!guild.TechProjects.find(x => x.ItemType == request.ItemType)) {
 | 
				
			||||||
 | 
					            const techProject =
 | 
				
			||||||
 | 
					                guild.TechProjects[
 | 
				
			||||||
 | 
					                    guild.TechProjects.push({
 | 
				
			||||||
 | 
					                        ItemType: request.ItemType,
 | 
				
			||||||
 | 
					                        ReqCredits: guild.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, recipe.price),
 | 
				
			||||||
 | 
					                        ReqItems: recipe.ingredients.map(x => ({
 | 
				
			||||||
 | 
					                            ItemType: x.ItemType,
 | 
				
			||||||
 | 
					                            ItemCount: guild.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, x.ItemCount)
 | 
				
			||||||
 | 
					                        })),
 | 
				
			||||||
 | 
					                        State: 0
 | 
				
			||||||
 | 
					                    }) - 1
 | 
				
			||||||
 | 
					                ];
 | 
				
			||||||
 | 
					            setGuildTechLogState(guild, techProject.ItemType, 5);
 | 
				
			||||||
 | 
					            if (guild.noDojoResearchCosts) {
 | 
				
			||||||
 | 
					                processFundedGuildTechProject(guild, techProject, recipe);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await guild.save();
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const removeTechProjectController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const requests = req.body as ITechProjectRequest[];
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "GuildId");
 | 
				
			||||||
 | 
					    const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
 | 
					    if (!(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
 | 
				
			||||||
 | 
					        res.status(400).send("-1").end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    guild.TechProjects ??= [];
 | 
				
			||||||
 | 
					    for (const request of requests) {
 | 
				
			||||||
 | 
					        const index = guild.TechProjects.findIndex(x => x.ItemType === request.ItemType);
 | 
				
			||||||
 | 
					        if (index !== -1) {
 | 
				
			||||||
 | 
					            guild.TechProjects.splice(index, 1);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await guild.save();
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const fundTechProjectController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const requests = req.body as ITechProjectRequest[];
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "GuildId");
 | 
				
			||||||
 | 
					    const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
 | 
					    const guildMember = (await GuildMember.findOne(
 | 
				
			||||||
 | 
					        { accountId, guildId: guild._id },
 | 
				
			||||||
 | 
					        "RegularCreditsContributed MiscItemsContributed"
 | 
				
			||||||
 | 
					    ))!;
 | 
				
			||||||
 | 
					    if (!(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
 | 
				
			||||||
 | 
					        res.status(400).send("-1").end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for (const request of requests) {
 | 
				
			||||||
 | 
					        const techProject = guild.TechProjects!.find(x => x.ItemType == request.ItemType)!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        guildMember.RegularCreditsContributed ??= 0;
 | 
				
			||||||
 | 
					        guildMember.RegularCreditsContributed += techProject.ReqCredits;
 | 
				
			||||||
 | 
					        techProject.ReqCredits = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const reqItem of techProject.ReqItems) {
 | 
				
			||||||
 | 
					            addGuildMemberMiscItemContribution(guildMember, reqItem);
 | 
				
			||||||
 | 
					            reqItem.ItemCount = 0;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await processGuildTechProjectContributionsUpdate(guild, techProject);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await Promise.all([guild.save(), guildMember.save()]);
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const completeTechProjectsController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const requests = req.body as ITechProjectRequest[];
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, "GuildId");
 | 
				
			||||||
 | 
					    const guild = await getGuildForRequestEx(req, inventory);
 | 
				
			||||||
 | 
					    if (!(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
 | 
				
			||||||
 | 
					        res.status(400).send("-1").end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for (const request of requests) {
 | 
				
			||||||
 | 
					        const techProject = guild.TechProjects!.find(x => x.ItemType == request.ItemType)!;
 | 
				
			||||||
 | 
					        techProject.CompletionDate = new Date();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (setGuildTechLogState(guild, techProject.ItemType, 4, techProject.CompletionDate)) {
 | 
				
			||||||
 | 
					            processCompletedGuildTechProject(guild, techProject.ItemType);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await guild.save();
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ITechProjectRequest {
 | 
				
			||||||
 | 
					    ItemType: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -2,19 +2,25 @@ import type { RequestHandler } from "express";
 | 
				
			|||||||
import { ExportResources, ExportVirtuals } from "warframe-public-export-plus";
 | 
					import { ExportResources, ExportVirtuals } from "warframe-public-export-plus";
 | 
				
			||||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
import { addItem, getInventory } from "../../services/inventoryService.ts";
 | 
					import { addItem, getInventory } from "../../services/inventoryService.ts";
 | 
				
			||||||
 | 
					import { sendWsBroadcastToGame } from "../../services/wsService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const unlockAllCapturaScenesController: RequestHandler = async (req, res) => {
 | 
					export const unlockAllCapturaScenesController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
    const inventory = await getInventory(accountId);
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let needSync = false;
 | 
				
			||||||
    for (const uniqueName of Object.keys(ExportResources)) {
 | 
					    for (const uniqueName of Object.keys(ExportResources)) {
 | 
				
			||||||
        if (resourceInheritsFrom(uniqueName, "/Lotus/Types/Items/MiscItems/PhotoboothTile")) {
 | 
					        if (resourceInheritsFrom(uniqueName, "/Lotus/Types/Items/MiscItems/PhotoboothTile")) {
 | 
				
			||||||
            await addItem(inventory, uniqueName, 1);
 | 
					            await addItem(inventory, uniqueName, 1);
 | 
				
			||||||
 | 
					            needSync = true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
 | 
					    if (needSync) {
 | 
				
			||||||
 | 
					        sendWsBroadcastToGame(accountId, { sync_inventory: true });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const resourceInheritsFrom = (resourceName: string, targetName: string): boolean => {
 | 
					const resourceInheritsFrom = (resourceName: string, targetName: string): boolean => {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
					import { getInventory } from "../../services/inventoryService.ts";
 | 
				
			||||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { sendWsBroadcastToGame } from "../../services/wsService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const unlockAllIntrinsicsController: RequestHandler = async (req, res) => {
 | 
					export const unlockAllIntrinsicsController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -16,4 +17,5 @@ export const unlockAllIntrinsicsController: RequestHandler = async (req, res) =>
 | 
				
			|||||||
    inventory.PlayerSkills.LPS_DRIFT_ENDURANCE = 10;
 | 
					    inventory.PlayerSkills.LPS_DRIFT_ENDURANCE = 10;
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
 | 
					    sendWsBroadcastToGame(accountId, { sync_inventory: true });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
					import { getInventory } from "../../services/inventoryService.ts";
 | 
				
			||||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { sendWsBroadcastToGame } from "../../services/wsService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const allEudicoHeistJobs = [
 | 
					const allEudicoHeistJobs = [
 | 
				
			||||||
    "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyOne",
 | 
					    "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyOne",
 | 
				
			||||||
@ -21,4 +22,5 @@ export const unlockAllProfitTakerStagesController: RequestHandler = async (req,
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
 | 
					    sendWsBroadcastToGame(accountId, { sync_inventory: true });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
					import { getInventory } from "../../services/inventoryService.ts";
 | 
				
			||||||
 | 
					import { sendWsBroadcastToGame } from "../../services/wsService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const unlockAllSimarisResearchEntriesController: RequestHandler = async (req, res) => {
 | 
					export const unlockAllSimarisResearchEntriesController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -17,4 +18,5 @@ export const unlockAllSimarisResearchEntriesController: RequestHandler = async (
 | 
				
			|||||||
    ].map(type => ({ TargetType: type, Scans: 10, Completed: true }));
 | 
					    ].map(type => ({ TargetType: type, Scans: 10, Completed: true }));
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
 | 
					    sendWsBroadcastToGame(accountId, { sync_inventory: true });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										25
									
								
								src/controllers/custom/unlockLevelCapController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/controllers/custom/unlockLevelCapController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
 | 
					import { getInventory } from "../../services/inventoryService.ts";
 | 
				
			||||||
 | 
					import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const unlockLevelCapController: RequestHandler = async (req, res) => {
 | 
				
			||||||
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
 | 
					    const data = req.body as IunlockLevelCapRequest;
 | 
				
			||||||
 | 
					    const inventory = await getInventory(accountId, data.Category);
 | 
				
			||||||
 | 
					    const equipment = inventory[data.Category].id(data.ItemId)!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    equipment.Polarized ??= 0;
 | 
				
			||||||
 | 
					    equipment.Polarized = data.Polarized;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await inventory.save();
 | 
				
			||||||
 | 
					    res.end();
 | 
				
			||||||
 | 
					    broadcastInventoryUpdate(req);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IunlockLevelCapRequest {
 | 
				
			||||||
 | 
					    Category: TEquipmentKey;
 | 
				
			||||||
 | 
					    ItemId: string;
 | 
				
			||||||
 | 
					    Polarized: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -2,6 +2,7 @@ import { getInventory } from "../../services/inventoryService.ts";
 | 
				
			|||||||
import type { WeaponTypeInternal } from "../../services/itemDataService.ts";
 | 
					import type { WeaponTypeInternal } from "../../services/itemDataService.ts";
 | 
				
			||||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
					import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
				
			||||||
import type { RequestHandler } from "express";
 | 
					import type { RequestHandler } from "express";
 | 
				
			||||||
 | 
					import { sendWsBroadcastToGame } from "../../services/wsService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const updateFingerprintController: RequestHandler = async (req, res) => {
 | 
					export const updateFingerprintController: RequestHandler = async (req, res) => {
 | 
				
			||||||
    const accountId = await getAccountIdForRequest(req);
 | 
					    const accountId = await getAccountIdForRequest(req);
 | 
				
			||||||
@ -22,6 +23,7 @@ export const updateFingerprintController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
        await inventory.save();
 | 
					        await inventory.save();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
 | 
					    sendWsBroadcastToGame(accountId, { sync_inventory: true });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IUpdateFingerPrintRequest {
 | 
					interface IUpdateFingerPrintRequest {
 | 
				
			||||||
 | 
				
			|||||||
@ -212,12 +212,12 @@ export const getInfNodes = (manifest: INemesisManifest, rank: number): IInfNode[
 | 
				
			|||||||
            value.systemIndex === systemIndex &&
 | 
					            value.systemIndex === systemIndex &&
 | 
				
			||||||
            value.nodeType != 3 && // not hub
 | 
					            value.nodeType != 3 && // not hub
 | 
				
			||||||
            value.nodeType != 7 && // not junction
 | 
					            value.nodeType != 7 && // not junction
 | 
				
			||||||
            value.missionIndex && // must have a mission type and not assassination
 | 
					            value.missionType != "MT_ASSASSINATION" &&
 | 
				
			||||||
            value.missionIndex != 28 && // not open world
 | 
					            value.missionType != "MT_LANDSCAPE" &&
 | 
				
			||||||
            value.missionIndex != 32 && // not railjack
 | 
					            value.missionType != "MT_RAILJACK" &&
 | 
				
			||||||
            value.missionIndex != 41 && // not saya's visions
 | 
					            value.missionType != "MT_OFFERING" &&
 | 
				
			||||||
            value.missionIndex != 42 && // not face off
 | 
					            value.missionType != "MT_PVPVE" &&
 | 
				
			||||||
            value.name.indexOf("1999NodeI") == -1 && // not stage defence
 | 
					            value.name.indexOf("1999NodeI") == -1 && // not stage defense
 | 
				
			||||||
            value.name.indexOf("1999NodeJ") == -1 && // not lich bounty
 | 
					            value.name.indexOf("1999NodeJ") == -1 && // not lich bounty
 | 
				
			||||||
            !isArchwingMission(value)
 | 
					            !isArchwingMission(value)
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,23 +0,0 @@
 | 
				
			|||||||
import type { SlotPurchase, SlotPurchaseName } from "../types/purchaseTypes.ts";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const slotPurchaseNameToSlotName: SlotPurchase = {
 | 
					 | 
				
			||||||
    SuitSlotItem: { name: "SuitBin", purchaseQuantity: 1 },
 | 
					 | 
				
			||||||
    TwoSentinelSlotItem: { name: "SentinelBin", purchaseQuantity: 2 },
 | 
					 | 
				
			||||||
    TwoWeaponSlotItem: { name: "WeaponBin", purchaseQuantity: 2 },
 | 
					 | 
				
			||||||
    SpaceSuitSlotItem: { name: "SpaceSuitBin", purchaseQuantity: 1 },
 | 
					 | 
				
			||||||
    TwoSpaceWeaponSlotItem: { name: "SpaceWeaponBin", purchaseQuantity: 2 },
 | 
					 | 
				
			||||||
    MechSlotItem: { name: "MechBin", purchaseQuantity: 1 },
 | 
					 | 
				
			||||||
    TwoOperatorWeaponSlotItem: { name: "OperatorAmpBin", purchaseQuantity: 2 },
 | 
					 | 
				
			||||||
    RandomModSlotItem: { name: "RandomModBin", purchaseQuantity: 3 },
 | 
					 | 
				
			||||||
    TwoCrewShipSalvageSlotItem: { name: "CrewShipSalvageBin", purchaseQuantity: 2 },
 | 
					 | 
				
			||||||
    CrewMemberSlotItem: { name: "CrewMemberBin", purchaseQuantity: 1 }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const isSlotPurchaseName = (slotPurchaseName: string): slotPurchaseName is SlotPurchaseName => {
 | 
					 | 
				
			||||||
    return slotPurchaseName in slotPurchaseNameToSlotName;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const parseSlotPurchaseName = (slotPurchaseName: string): SlotPurchaseName => {
 | 
					 | 
				
			||||||
    if (!isSlotPurchaseName(slotPurchaseName)) throw new Error(`invalid slot name ${slotPurchaseName}`);
 | 
					 | 
				
			||||||
    return slotPurchaseName;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@ -104,11 +104,11 @@ const guildRankSchema = new Schema<IGuildRank>(
 | 
				
			|||||||
const defaultRanks: IGuildRank[] = [
 | 
					const defaultRanks: IGuildRank[] = [
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        Name: "/Lotus/Language/Game/Rank_Creator",
 | 
					        Name: "/Lotus/Language/Game/Rank_Creator",
 | 
				
			||||||
        Permissions: 16351
 | 
					        Permissions: GuildPermission.Host | 16351
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        Name: "/Lotus/Language/Game/Rank_Warlord",
 | 
					        Name: "/Lotus/Language/Game/Rank_Warlord",
 | 
				
			||||||
        Permissions: 16351
 | 
					        Permissions: GuildPermission.Host | 16351
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        Name: "/Lotus/Language/Game/Rank_General",
 | 
					        Name: "/Lotus/Language/Game/Rank_General",
 | 
				
			||||||
@ -201,6 +201,14 @@ goalProgressSchema.set("toJSON", {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const guildSchema = new Schema<IGuildDatabase>(
 | 
					const guildSchema = new Schema<IGuildDatabase>(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					        // SNS guild cheats
 | 
				
			||||||
 | 
					        noDojoRoomBuildStage: Boolean,
 | 
				
			||||||
 | 
					        noDojoDecoBuildStage: Boolean,
 | 
				
			||||||
 | 
					        fastDojoRoomDestruction: Boolean,
 | 
				
			||||||
 | 
					        noDojoResearchCosts: Boolean,
 | 
				
			||||||
 | 
					        noDojoResearchTime: Boolean,
 | 
				
			||||||
 | 
					        fastClanAscension: Boolean,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Name: { type: String, required: true, unique: true },
 | 
					        Name: { type: String, required: true, unique: true },
 | 
				
			||||||
        MOTD: { type: String, default: "" },
 | 
					        MOTD: { type: String, default: "" },
 | 
				
			||||||
        LongMOTD: { type: longMOTDSchema, default: undefined },
 | 
					        LongMOTD: { type: longMOTDSchema, default: undefined },
 | 
				
			||||||
 | 
				
			|||||||
@ -14,8 +14,8 @@ cacheRouter.get(/^\/origin\/[a-zA-Z0-9]+\/[0-9]+\/H\.Cache\.bin.*$/, (req, res)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
cacheRouter.get(/^\/0\/.+!.+$/, async (req, res) => {
 | 
					cacheRouter.get(/^\/0\/.+!.+$/, async (req, res) => {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        const dir = req.path.substr(0, req.path.lastIndexOf("/"));
 | 
					        const dir = req.path.substring(0, req.path.lastIndexOf("/"));
 | 
				
			||||||
        const file = req.path.substr(dir.length + 1);
 | 
					        const file = req.path.substring(dir.length + 1);
 | 
				
			||||||
        const filePath = `static/data${dir}/${file}`;
 | 
					        const filePath = `static/data${dir}/${file}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Return file if we have it
 | 
					        // Return file if we have it
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,8 @@ import { popArchonCrystalUpgradeController } from "../controllers/custom/popArch
 | 
				
			|||||||
import { deleteAccountController } from "../controllers/custom/deleteAccountController.ts";
 | 
					import { deleteAccountController } from "../controllers/custom/deleteAccountController.ts";
 | 
				
			||||||
import { getNameController } from "../controllers/custom/getNameController.ts";
 | 
					import { getNameController } from "../controllers/custom/getNameController.ts";
 | 
				
			||||||
import { getAccountInfoController } from "../controllers/custom/getAccountInfoController.ts";
 | 
					import { getAccountInfoController } from "../controllers/custom/getAccountInfoController.ts";
 | 
				
			||||||
 | 
					import { getGuildController } from "../controllers/custom/getGuildController.ts";
 | 
				
			||||||
 | 
					import { getAllianceController } from "../controllers/custom/getAllianceController.ts";
 | 
				
			||||||
import { renameAccountController } from "../controllers/custom/renameAccountController.ts";
 | 
					import { renameAccountController } from "../controllers/custom/renameAccountController.ts";
 | 
				
			||||||
import { ircDroppedController } from "../controllers/custom/ircDroppedController.ts";
 | 
					import { ircDroppedController } from "../controllers/custom/ircDroppedController.ts";
 | 
				
			||||||
import { unlockAllIntrinsicsController } from "../controllers/custom/unlockAllIntrinsicsController.ts";
 | 
					import { unlockAllIntrinsicsController } from "../controllers/custom/unlockAllIntrinsicsController.ts";
 | 
				
			||||||
@ -19,21 +21,31 @@ import { unlockAllSimarisResearchEntriesController } from "../controllers/custom
 | 
				
			|||||||
import { unlockAllScansController } from "../controllers/custom/unlockAllScansController.ts";
 | 
					import { unlockAllScansController } from "../controllers/custom/unlockAllScansController.ts";
 | 
				
			||||||
import { unlockAllShipFeaturesController } from "../controllers/custom/unlockAllShipFeaturesController.ts";
 | 
					import { unlockAllShipFeaturesController } from "../controllers/custom/unlockAllShipFeaturesController.ts";
 | 
				
			||||||
import { unlockAllCapturaScenesController } from "../controllers/custom/unlockAllCapturaScenesController.ts";
 | 
					import { unlockAllCapturaScenesController } from "../controllers/custom/unlockAllCapturaScenesController.ts";
 | 
				
			||||||
 | 
					import { removeCustomizationController } from "../controllers/custom/removeCustomizationController.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { abilityOverrideController } from "../controllers/custom/abilityOverrideController.ts";
 | 
					import { abilityOverrideController } from "../controllers/custom/abilityOverrideController.ts";
 | 
				
			||||||
import { createAccountController } from "../controllers/custom/createAccountController.ts";
 | 
					import { createAccountController } from "../controllers/custom/createAccountController.ts";
 | 
				
			||||||
import { createMessageController } from "../controllers/custom/createMessageController.ts";
 | 
					import { createMessageController } from "../controllers/custom/createMessageController.ts";
 | 
				
			||||||
import { addCurrencyController } from "../controllers/custom/addCurrencyController.ts";
 | 
					import { addCurrencyController } from "../controllers/custom/addCurrencyController.ts";
 | 
				
			||||||
import { addItemsController } from "../controllers/custom/addItemsController.ts";
 | 
					import { addItemsController } from "../controllers/custom/addItemsController.ts";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    addTechProjectController,
 | 
				
			||||||
 | 
					    completeTechProjectsController,
 | 
				
			||||||
 | 
					    fundTechProjectController,
 | 
				
			||||||
 | 
					    removeTechProjectController
 | 
				
			||||||
 | 
					} from "../controllers/custom/techProjectController.ts";
 | 
				
			||||||
 | 
					import { addVaultDecoRecipeController } from "../controllers/custom/addVaultDecoRecipeController.ts";
 | 
				
			||||||
import { addXpController } from "../controllers/custom/addXpController.ts";
 | 
					import { addXpController } from "../controllers/custom/addXpController.ts";
 | 
				
			||||||
import { importController } from "../controllers/custom/importController.ts";
 | 
					import { importController } from "../controllers/custom/importController.ts";
 | 
				
			||||||
import { manageQuestsController } from "../controllers/custom/manageQuestsController.ts";
 | 
					import { manageQuestsController } from "../controllers/custom/manageQuestsController.ts";
 | 
				
			||||||
import { setEvolutionProgressController } from "../controllers/custom/setEvolutionProgressController.ts";
 | 
					import { setEvolutionProgressController } from "../controllers/custom/setEvolutionProgressController.ts";
 | 
				
			||||||
import { setBoosterController } from "../controllers/custom/setBoosterController.ts";
 | 
					import { setBoosterController } from "../controllers/custom/setBoosterController.ts";
 | 
				
			||||||
import { updateFingerprintController } from "../controllers/custom/updateFingerprintController.ts";
 | 
					import { updateFingerprintController } from "../controllers/custom/updateFingerprintController.ts";
 | 
				
			||||||
 | 
					import { unlockLevelCapController } from "../controllers/custom/unlockLevelCapController.ts";
 | 
				
			||||||
import { changeModularPartsController } from "../controllers/custom/changeModularPartsController.ts";
 | 
					import { changeModularPartsController } from "../controllers/custom/changeModularPartsController.ts";
 | 
				
			||||||
import { editSuitInvigorationUpgradeController } from "../controllers/custom/editSuitInvigorationUpgradeController.ts";
 | 
					import { editSuitInvigorationUpgradeController } from "../controllers/custom/editSuitInvigorationUpgradeController.ts";
 | 
				
			||||||
import { setAccountCheatController } from "../controllers/custom/setAccountCheatController.ts";
 | 
					import { setAccountCheatController } from "../controllers/custom/setAccountCheatController.ts";
 | 
				
			||||||
 | 
					import { setGuildCheatController } from "../controllers/custom/setGuildCheatController.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { getConfigController, setConfigController } from "../controllers/custom/configController.ts";
 | 
					import { getConfigController, setConfigController } from "../controllers/custom/configController.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -46,6 +58,8 @@ customRouter.get("/popArchonCrystalUpgrade", popArchonCrystalUpgradeController);
 | 
				
			|||||||
customRouter.get("/deleteAccount", deleteAccountController);
 | 
					customRouter.get("/deleteAccount", deleteAccountController);
 | 
				
			||||||
customRouter.get("/getName", getNameController);
 | 
					customRouter.get("/getName", getNameController);
 | 
				
			||||||
customRouter.get("/getAccountInfo", getAccountInfoController);
 | 
					customRouter.get("/getAccountInfo", getAccountInfoController);
 | 
				
			||||||
 | 
					customRouter.get("/getGuild", getGuildController);
 | 
				
			||||||
 | 
					customRouter.get("/getAlliance", getAllianceController);
 | 
				
			||||||
customRouter.get("/renameAccount", renameAccountController);
 | 
					customRouter.get("/renameAccount", renameAccountController);
 | 
				
			||||||
customRouter.get("/ircDropped", ircDroppedController);
 | 
					customRouter.get("/ircDropped", ircDroppedController);
 | 
				
			||||||
customRouter.get("/unlockAllIntrinsics", unlockAllIntrinsicsController);
 | 
					customRouter.get("/unlockAllIntrinsics", unlockAllIntrinsicsController);
 | 
				
			||||||
@ -58,21 +72,29 @@ customRouter.get("/unlockAllSimarisResearchEntries", unlockAllSimarisResearchEnt
 | 
				
			|||||||
customRouter.get("/unlockAllScans", unlockAllScansController);
 | 
					customRouter.get("/unlockAllScans", unlockAllScansController);
 | 
				
			||||||
customRouter.get("/unlockAllShipFeatures", unlockAllShipFeaturesController);
 | 
					customRouter.get("/unlockAllShipFeatures", unlockAllShipFeaturesController);
 | 
				
			||||||
customRouter.get("/unlockAllCapturaScenes", unlockAllCapturaScenesController);
 | 
					customRouter.get("/unlockAllCapturaScenes", unlockAllCapturaScenesController);
 | 
				
			||||||
 | 
					customRouter.get("/removeCustomization", removeCustomizationController);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
customRouter.post("/abilityOverride", abilityOverrideController);
 | 
					customRouter.post("/abilityOverride", abilityOverrideController);
 | 
				
			||||||
customRouter.post("/createAccount", createAccountController);
 | 
					customRouter.post("/createAccount", createAccountController);
 | 
				
			||||||
customRouter.post("/createMessage", createMessageController);
 | 
					customRouter.post("/createMessage", createMessageController);
 | 
				
			||||||
customRouter.post("/addCurrency", addCurrencyController);
 | 
					customRouter.post("/addCurrency", addCurrencyController);
 | 
				
			||||||
customRouter.post("/addItems", addItemsController);
 | 
					customRouter.post("/addItems", addItemsController);
 | 
				
			||||||
 | 
					customRouter.post("/addTechProject", addTechProjectController);
 | 
				
			||||||
 | 
					customRouter.post("/removeTechProject", removeTechProjectController);
 | 
				
			||||||
 | 
					customRouter.post("/addVaultDecoRecipe", addVaultDecoRecipeController);
 | 
				
			||||||
 | 
					customRouter.post("/fundTechProject", fundTechProjectController);
 | 
				
			||||||
 | 
					customRouter.post("/completeTechProject", completeTechProjectsController);
 | 
				
			||||||
customRouter.post("/addXp", addXpController);
 | 
					customRouter.post("/addXp", addXpController);
 | 
				
			||||||
customRouter.post("/import", importController);
 | 
					customRouter.post("/import", importController);
 | 
				
			||||||
customRouter.post("/manageQuests", manageQuestsController);
 | 
					customRouter.post("/manageQuests", manageQuestsController);
 | 
				
			||||||
customRouter.post("/setEvolutionProgress", setEvolutionProgressController);
 | 
					customRouter.post("/setEvolutionProgress", setEvolutionProgressController);
 | 
				
			||||||
customRouter.post("/setBooster", setBoosterController);
 | 
					customRouter.post("/setBooster", setBoosterController);
 | 
				
			||||||
customRouter.post("/updateFingerprint", updateFingerprintController);
 | 
					customRouter.post("/updateFingerprint", updateFingerprintController);
 | 
				
			||||||
 | 
					customRouter.post("/unlockLevelCap", unlockLevelCapController);
 | 
				
			||||||
customRouter.post("/changeModularParts", changeModularPartsController);
 | 
					customRouter.post("/changeModularParts", changeModularPartsController);
 | 
				
			||||||
customRouter.post("/editSuitInvigorationUpgrade", editSuitInvigorationUpgradeController);
 | 
					customRouter.post("/editSuitInvigorationUpgrade", editSuitInvigorationUpgradeController);
 | 
				
			||||||
customRouter.post("/setAccountCheat", setAccountCheatController);
 | 
					customRouter.post("/setAccountCheat", setAccountCheatController);
 | 
				
			||||||
 | 
					customRouter.post("/setGuildCheat", setGuildCheatController);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
customRouter.post("/getConfig", getConfigController);
 | 
					customRouter.post("/getConfig", getConfigController);
 | 
				
			||||||
customRouter.post("/setConfig", setConfigController);
 | 
					customRouter.post("/setConfig", setConfigController);
 | 
				
			||||||
 | 
				
			|||||||
@ -42,6 +42,9 @@ webuiRouter.get("/webui/cheats", (_req, res) => {
 | 
				
			|||||||
webuiRouter.get("/webui/import", (_req, res) => {
 | 
					webuiRouter.get("/webui/import", (_req, res) => {
 | 
				
			||||||
    res.sendFile(path.join(baseDir, "static/webui/index.html"));
 | 
					    res.sendFile(path.join(baseDir, "static/webui/index.html"));
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					webuiRouter.get("/webui/guildView", (_req, res) => {
 | 
				
			||||||
 | 
					    res.sendFile(path.join(baseDir, "static/webui/index.html"));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Serve static files
 | 
					// Serve static files
 | 
				
			||||||
webuiRouter.use("/webui", express.static(path.join(baseDir, "static/webui")));
 | 
					webuiRouter.use("/webui", express.static(path.join(baseDir, "static/webui")));
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,7 @@ export interface IConfig {
 | 
				
			|||||||
        level: string; // "fatal" | "error" | "warn" | "info" | "http" | "debug" | "trace";
 | 
					        level: string; // "fatal" | "error" | "warn" | "info" | "http" | "debug" | "trace";
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    myAddress: string;
 | 
					    myAddress: string;
 | 
				
			||||||
 | 
					    bindAddress?: string;
 | 
				
			||||||
    httpPort?: number;
 | 
					    httpPort?: number;
 | 
				
			||||||
    httpsPort?: number;
 | 
					    httpsPort?: number;
 | 
				
			||||||
    ircAddress?: string;
 | 
					    ircAddress?: string;
 | 
				
			||||||
@ -20,18 +21,9 @@ export interface IConfig {
 | 
				
			|||||||
    administratorNames?: string[];
 | 
					    administratorNames?: string[];
 | 
				
			||||||
    autoCreateAccount?: boolean;
 | 
					    autoCreateAccount?: boolean;
 | 
				
			||||||
    skipTutorial?: boolean;
 | 
					    skipTutorial?: boolean;
 | 
				
			||||||
    unlockAllShipDecorations?: boolean;
 | 
					 | 
				
			||||||
    unlockAllFlavourItems?: boolean;
 | 
					 | 
				
			||||||
    unlockAllSkins?: boolean;
 | 
					    unlockAllSkins?: boolean;
 | 
				
			||||||
    unlockAllDecoRecipes?: boolean;
 | 
					 | 
				
			||||||
    fullyStockedVendors?: boolean;
 | 
					    fullyStockedVendors?: boolean;
 | 
				
			||||||
    skipClanKeyCrafting?: boolean;
 | 
					    skipClanKeyCrafting?: boolean;
 | 
				
			||||||
    noDojoRoomBuildStage?: boolean;
 | 
					 | 
				
			||||||
    noDojoDecoBuildStage?: boolean;
 | 
					 | 
				
			||||||
    fastDojoRoomDestruction?: boolean;
 | 
					 | 
				
			||||||
    noDojoResearchCosts?: boolean;
 | 
					 | 
				
			||||||
    noDojoResearchTime?: boolean;
 | 
					 | 
				
			||||||
    fastClanAscension?: boolean;
 | 
					 | 
				
			||||||
    spoofMasteryRank?: number;
 | 
					    spoofMasteryRank?: number;
 | 
				
			||||||
    relicRewardItemCountMultiplier?: number;
 | 
					    relicRewardItemCountMultiplier?: number;
 | 
				
			||||||
    nightwaveStandingMultiplier?: number;
 | 
					    nightwaveStandingMultiplier?: number;
 | 
				
			||||||
@ -127,7 +119,16 @@ export const configRemovedOptionsKeys = [
 | 
				
			|||||||
    "exceptionalRelicsAlwaysGiveBronzeReward",
 | 
					    "exceptionalRelicsAlwaysGiveBronzeReward",
 | 
				
			||||||
    "flawlessRelicsAlwaysGiveSilverReward",
 | 
					    "flawlessRelicsAlwaysGiveSilverReward",
 | 
				
			||||||
    "radiantRelicsAlwaysGiveGoldReward",
 | 
					    "radiantRelicsAlwaysGiveGoldReward",
 | 
				
			||||||
    "disableDailyTribute"
 | 
					    "disableDailyTribute",
 | 
				
			||||||
 | 
					    "noDojoRoomBuildStage",
 | 
				
			||||||
 | 
					    "noDojoDecoBuildStage",
 | 
				
			||||||
 | 
					    "fastDojoRoomDestruction",
 | 
				
			||||||
 | 
					    "noDojoResearchCosts",
 | 
				
			||||||
 | 
					    "noDojoResearchTime",
 | 
				
			||||||
 | 
					    "fastClanAscension",
 | 
				
			||||||
 | 
					    "unlockAllFlavourItems",
 | 
				
			||||||
 | 
					    "unlockAllShipDecorations",
 | 
				
			||||||
 | 
					    "unlockAllDecoRecipes"
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const configPath = path.join(repoDir, args.configPath ?? "config.json");
 | 
					export const configPath = path.join(repoDir, args.configPath ?? "config.json");
 | 
				
			||||||
@ -188,3 +189,17 @@ export const getReflexiveAddress = (request: Request): { myAddress: string; myUr
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    return { myAddress, myUrlBase };
 | 
					    return { myAddress, myUrlBase };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IBindings {
 | 
				
			||||||
 | 
					    address: string;
 | 
				
			||||||
 | 
					    httpPort: number;
 | 
				
			||||||
 | 
					    httpsPort: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const configGetWebBindings = (): IBindings => {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        address: config.bindAddress || "0.0.0.0",
 | 
				
			||||||
 | 
					        httpPort: config.httpPort || 80,
 | 
				
			||||||
 | 
					        httpsPort: config.httpsPort || 443
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@ import chokidar from "chokidar";
 | 
				
			|||||||
import { logger } from "../utils/logger.ts";
 | 
					import { logger } from "../utils/logger.ts";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    config,
 | 
					    config,
 | 
				
			||||||
 | 
					    configGetWebBindings,
 | 
				
			||||||
    configPath,
 | 
					    configPath,
 | 
				
			||||||
    configRemovedOptionsKeys,
 | 
					    configRemovedOptionsKeys,
 | 
				
			||||||
    loadConfig,
 | 
					    loadConfig,
 | 
				
			||||||
@ -9,7 +10,7 @@ import {
 | 
				
			|||||||
    type IConfig
 | 
					    type IConfig
 | 
				
			||||||
} from "./configService.ts";
 | 
					} from "./configService.ts";
 | 
				
			||||||
import { saveConfig, shouldReloadConfig } from "./configWriterService.ts";
 | 
					import { saveConfig, shouldReloadConfig } from "./configWriterService.ts";
 | 
				
			||||||
import { getWebPorts, startWebServer, stopWebServer } from "./webService.ts";
 | 
					import { getWebBindings, startWebServer, stopWebServer } from "./webService.ts";
 | 
				
			||||||
import { sendWsBroadcast } from "./wsService.ts";
 | 
					import { sendWsBroadcast } from "./wsService.ts";
 | 
				
			||||||
import varzia from "../../static/fixed_responses/worldState/varzia.json" with { type: "json" };
 | 
					import varzia from "../../static/fixed_responses/worldState/varzia.json" with { type: "json" };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -25,9 +26,14 @@ chokidar.watch(configPath).on("change", () => {
 | 
				
			|||||||
        validateConfig();
 | 
					        validateConfig();
 | 
				
			||||||
        syncConfigWithDatabase();
 | 
					        syncConfigWithDatabase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const webPorts = getWebPorts();
 | 
					        const configBindings = configGetWebBindings();
 | 
				
			||||||
        if (config.httpPort != webPorts.http || config.httpsPort != webPorts.https) {
 | 
					        const bindings = getWebBindings();
 | 
				
			||||||
            logger.info(`Restarting web server to apply port changes.`);
 | 
					        if (
 | 
				
			||||||
 | 
					            configBindings.address != bindings.address ||
 | 
				
			||||||
 | 
					            configBindings.httpPort != bindings.httpPort ||
 | 
				
			||||||
 | 
					            configBindings.httpsPort != bindings.httpsPort
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            logger.info(`Restarting web server to apply binding changes.`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Tell webui clients to reload with new port
 | 
					            // Tell webui clients to reload with new port
 | 
				
			||||||
            sendWsBroadcast({ ports: { http: config.httpPort, https: config.httpsPort } });
 | 
					            sendWsBroadcast({ ports: { http: config.httpPort, https: config.httpsPort } });
 | 
				
			||||||
 | 
				
			|||||||
@ -33,7 +33,6 @@ import { Inbox } from "../models/inboxModel.ts";
 | 
				
			|||||||
import type { IFusionTreasure } from "../types/inventoryTypes/inventoryTypes.ts";
 | 
					import type { IFusionTreasure } from "../types/inventoryTypes/inventoryTypes.ts";
 | 
				
			||||||
import type { IInventoryChanges } from "../types/purchaseTypes.ts";
 | 
					import type { IInventoryChanges } from "../types/purchaseTypes.ts";
 | 
				
			||||||
import { parallelForeach } from "../utils/async-utils.ts";
 | 
					import { parallelForeach } from "../utils/async-utils.ts";
 | 
				
			||||||
import allDecoRecipes from "../../static/fixed_responses/allDecoRecipes.json" with { type: "json" };
 | 
					 | 
				
			||||||
import { createMessage } from "./inboxService.ts";
 | 
					import { createMessage } from "./inboxService.ts";
 | 
				
			||||||
import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "./friendService.ts";
 | 
					import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "./friendService.ts";
 | 
				
			||||||
import type { ITypeCount } from "../types/commonTypes.ts";
 | 
					import type { ITypeCount } from "../types/commonTypes.ts";
 | 
				
			||||||
@ -136,9 +135,7 @@ export const getGuildVault = (guild: TGuildDatabaseDocument): IGuildVault => {
 | 
				
			|||||||
        DojoRefundPremiumCredits: guild.VaultPremiumCredits,
 | 
					        DojoRefundPremiumCredits: guild.VaultPremiumCredits,
 | 
				
			||||||
        ShipDecorations: guild.VaultShipDecorations,
 | 
					        ShipDecorations: guild.VaultShipDecorations,
 | 
				
			||||||
        FusionTreasures: guild.VaultFusionTreasures,
 | 
					        FusionTreasures: guild.VaultFusionTreasures,
 | 
				
			||||||
        DecoRecipes: config.unlockAllDecoRecipes
 | 
					        DecoRecipes: guild.VaultDecoRecipes
 | 
				
			||||||
            ? allDecoRecipes.map(recipe => ({ ItemType: recipe, ItemCount: 1 }))
 | 
					 | 
				
			||||||
            : guild.VaultDecoRecipes
 | 
					 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -565,12 +562,12 @@ export const processFundedGuildTechProject = (
 | 
				
			|||||||
    recipe: IDojoResearch
 | 
					    recipe: IDojoResearch
 | 
				
			||||||
): void => {
 | 
					): void => {
 | 
				
			||||||
    techProject.State = 1;
 | 
					    techProject.State = 1;
 | 
				
			||||||
    techProject.CompletionDate = new Date(Date.now() + (config.noDojoResearchTime ? 0 : recipe.time) * 1000);
 | 
					    techProject.CompletionDate = new Date(Date.now() + (guild.noDojoResearchTime ? 0 : recipe.time) * 1000);
 | 
				
			||||||
    if (recipe.guildXpValue) {
 | 
					    if (recipe.guildXpValue) {
 | 
				
			||||||
        guild.XP += recipe.guildXpValue;
 | 
					        guild.XP += recipe.guildXpValue;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    setGuildTechLogState(guild, techProject.ItemType, config.noDojoResearchTime ? 4 : 3, techProject.CompletionDate);
 | 
					    setGuildTechLogState(guild, techProject.ItemType, guild.noDojoResearchTime ? 4 : 3, techProject.CompletionDate);
 | 
				
			||||||
    if (config.noDojoResearchTime) {
 | 
					    if (guild.noDojoResearchTime) {
 | 
				
			||||||
        processCompletedGuildTechProject(guild, techProject.ItemType);
 | 
					        processCompletedGuildTechProject(guild, techProject.ItemType);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@ -657,8 +654,8 @@ export const checkClanAscensionHasRequiredContributors = async (guild: TGuildDat
 | 
				
			|||||||
    if (guild.CeremonyContributors!.length >= requiredContributors) {
 | 
					    if (guild.CeremonyContributors!.length >= requiredContributors) {
 | 
				
			||||||
        guild.Class = guild.CeremonyClass!;
 | 
					        guild.Class = guild.CeremonyClass!;
 | 
				
			||||||
        guild.CeremonyClass = undefined;
 | 
					        guild.CeremonyClass = undefined;
 | 
				
			||||||
        guild.CeremonyResetDate = new Date(Date.now() + (config.fastClanAscension ? 5_000 : 72 * 3600_000));
 | 
					        guild.CeremonyResetDate = new Date(Date.now() + (guild.fastClanAscension ? 5_000 : 72 * 3600_000));
 | 
				
			||||||
        if (!config.fastClanAscension) {
 | 
					        if (!guild.fastClanAscension) {
 | 
				
			||||||
            // Send message to all active guild members
 | 
					            // Send message to all active guild members
 | 
				
			||||||
            const members = await GuildMember.find({ guildId: guild._id, status: 0 }, "accountId");
 | 
					            const members = await GuildMember.find({ guildId: guild._id, status: 0 }, "accountId");
 | 
				
			||||||
            await parallelForeach(members, async member => {
 | 
					            await parallelForeach(members, async member => {
 | 
				
			||||||
 | 
				
			|||||||
@ -40,6 +40,7 @@ import {
 | 
				
			|||||||
    ExportBoosters,
 | 
					    ExportBoosters,
 | 
				
			||||||
    ExportBundles,
 | 
					    ExportBundles,
 | 
				
			||||||
    ExportChallenges,
 | 
					    ExportChallenges,
 | 
				
			||||||
 | 
					    ExportCreditBundles,
 | 
				
			||||||
    ExportCustoms,
 | 
					    ExportCustoms,
 | 
				
			||||||
    ExportDrones,
 | 
					    ExportDrones,
 | 
				
			||||||
    ExportEmailItems,
 | 
					    ExportEmailItems,
 | 
				
			||||||
@ -48,7 +49,6 @@ import {
 | 
				
			|||||||
    ExportFusionBundles,
 | 
					    ExportFusionBundles,
 | 
				
			||||||
    ExportGear,
 | 
					    ExportGear,
 | 
				
			||||||
    ExportKeys,
 | 
					    ExportKeys,
 | 
				
			||||||
    ExportMisc,
 | 
					 | 
				
			||||||
    ExportRailjackWeapons,
 | 
					    ExportRailjackWeapons,
 | 
				
			||||||
    ExportRecipes,
 | 
					    ExportRecipes,
 | 
				
			||||||
    ExportResources,
 | 
					    ExportResources,
 | 
				
			||||||
@ -631,8 +631,8 @@ export const addItem = async (
 | 
				
			|||||||
            };
 | 
					            };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (typeName in ExportMisc.creditBundles) {
 | 
					    if (typeName in ExportCreditBundles) {
 | 
				
			||||||
        const creditsTotal = ExportMisc.creditBundles[typeName] * quantity;
 | 
					        const creditsTotal = ExportCreditBundles[typeName].credits * quantity;
 | 
				
			||||||
        inventory.RegularCredits += creditsTotal;
 | 
					        inventory.RegularCredits += creditsTotal;
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            RegularCredits: creditsTotal
 | 
					            RegularCredits: creditsTotal
 | 
				
			||||||
@ -687,10 +687,10 @@ export const addItem = async (
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Path-based duck typing
 | 
					    // Path-based duck typing
 | 
				
			||||||
    switch (typeName.substr(1).split("/")[1]) {
 | 
					    switch (typeName.substring(1).split("/")[1]) {
 | 
				
			||||||
        case "Powersuits":
 | 
					        case "Powersuits":
 | 
				
			||||||
            if (typeName.endsWith("AugmentCard")) break;
 | 
					            if (typeName.endsWith("AugmentCard")) break;
 | 
				
			||||||
            switch (typeName.substr(1).split("/")[2]) {
 | 
					            switch (typeName.substring(1).split("/")[2]) {
 | 
				
			||||||
                default: {
 | 
					                default: {
 | 
				
			||||||
                    return {
 | 
					                    return {
 | 
				
			||||||
                        ...(await addPowerSuit(inventory, typeName, {
 | 
					                        ...(await addPowerSuit(inventory, typeName, {
 | 
				
			||||||
@ -725,7 +725,7 @@ export const addItem = async (
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        case "Upgrades": {
 | 
					        case "Upgrades": {
 | 
				
			||||||
            switch (typeName.substr(1).split("/")[2]) {
 | 
					            switch (typeName.substring(1).split("/")[2]) {
 | 
				
			||||||
                case "Mods": // Legendary Core
 | 
					                case "Mods": // Legendary Core
 | 
				
			||||||
                case "CosmeticEnhancers": // Traumatic Peculiar
 | 
					                case "CosmeticEnhancers": // Traumatic Peculiar
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
@ -782,12 +782,12 @@ export const addItem = async (
 | 
				
			|||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        case "Types":
 | 
					        case "Types":
 | 
				
			||||||
            switch (typeName.substr(1).split("/")[2]) {
 | 
					            switch (typeName.substring(1).split("/")[2]) {
 | 
				
			||||||
                case "Sentinels": {
 | 
					                case "Sentinels": {
 | 
				
			||||||
                    return addSentinel(inventory, typeName, premiumPurchase);
 | 
					                    return addSentinel(inventory, typeName, premiumPurchase);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                case "Game": {
 | 
					                case "Game": {
 | 
				
			||||||
                    if (typeName.substr(1).split("/")[3] == "Projections") {
 | 
					                    if (typeName.substring(1).split("/")[3] == "Projections") {
 | 
				
			||||||
                        // Void Relics, e.g. /Lotus/Types/Game/Projections/T2VoidProjectionGaussPrimeDBronze
 | 
					                        // Void Relics, e.g. /Lotus/Types/Game/Projections/T2VoidProjectionGaussPrimeDBronze
 | 
				
			||||||
                        const miscItemChanges = [
 | 
					                        const miscItemChanges = [
 | 
				
			||||||
                            {
 | 
					                            {
 | 
				
			||||||
@ -801,8 +801,8 @@ export const addItem = async (
 | 
				
			|||||||
                            MiscItems: miscItemChanges
 | 
					                            MiscItems: miscItemChanges
 | 
				
			||||||
                        };
 | 
					                        };
 | 
				
			||||||
                    } else if (
 | 
					                    } else if (
 | 
				
			||||||
                        typeName.substr(1).split("/")[3] == "CatbrowPet" ||
 | 
					                        typeName.substring(1).split("/")[3] == "CatbrowPet" ||
 | 
				
			||||||
                        typeName.substr(1).split("/")[3] == "KubrowPet"
 | 
					                        typeName.substring(1).split("/")[3] == "KubrowPet"
 | 
				
			||||||
                    ) {
 | 
					                    ) {
 | 
				
			||||||
                        if (
 | 
					                        if (
 | 
				
			||||||
                            typeName != "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem" &&
 | 
					                            typeName != "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem" &&
 | 
				
			||||||
@ -826,7 +826,7 @@ export const addItem = async (
 | 
				
			|||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                case "Items": {
 | 
					                case "Items": {
 | 
				
			||||||
                    if (typeName.substr(1).split("/")[3] == "Emotes") {
 | 
					                    if (typeName.substring(1).split("/")[3] == "Emotes") {
 | 
				
			||||||
                        return addCustomization(inventory, typeName);
 | 
					                        return addCustomization(inventory, typeName);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
@ -875,8 +875,8 @@ export const addItem = async (
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        case "Weapons": {
 | 
					        case "Weapons": {
 | 
				
			||||||
            if (typeName.substr(1).split("/")[4] == "MeleeTrees") break;
 | 
					            if (typeName.substring(1).split("/")[4] == "MeleeTrees") break;
 | 
				
			||||||
            const productCategory = typeName.substr(1).split("/")[3];
 | 
					            const productCategory = typeName.substring(1).split("/")[3];
 | 
				
			||||||
            switch (productCategory) {
 | 
					            switch (productCategory) {
 | 
				
			||||||
                case "Pistols":
 | 
					                case "Pistols":
 | 
				
			||||||
                case "LongGuns":
 | 
					                case "LongGuns":
 | 
				
			||||||
@ -2160,7 +2160,7 @@ export const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag,
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const addBooster = (ItemType: string, time: number, inventory: TInventoryDatabaseDocument): void => {
 | 
					export const addBooster = (ItemType: string, timeSecs: number, inventory: TInventoryDatabaseDocument): void => {
 | 
				
			||||||
    const currentTime = Math.floor(Date.now() / 1000);
 | 
					    const currentTime = Math.floor(Date.now() / 1000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { Boosters } = inventory;
 | 
					    const { Boosters } = inventory;
 | 
				
			||||||
@ -2169,9 +2169,9 @@ export const addBooster = (ItemType: string, time: number, inventory: TInventory
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if (itemIndex !== -1) {
 | 
					    if (itemIndex !== -1) {
 | 
				
			||||||
        const existingBooster = Boosters[itemIndex];
 | 
					        const existingBooster = Boosters[itemIndex];
 | 
				
			||||||
        existingBooster.ExpiryDate = Math.max(existingBooster.ExpiryDate, currentTime) + time;
 | 
					        existingBooster.ExpiryDate = Math.max(existingBooster.ExpiryDate, currentTime) + timeSecs;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        Boosters.push({ ItemType, ExpiryDate: currentTime + time });
 | 
					        Boosters.push({ ItemType, ExpiryDate: currentTime + timeSecs });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -27,9 +27,11 @@ import {
 | 
				
			|||||||
    ExportBoosters,
 | 
					    ExportBoosters,
 | 
				
			||||||
    ExportBundles,
 | 
					    ExportBundles,
 | 
				
			||||||
    ExportCustoms,
 | 
					    ExportCustoms,
 | 
				
			||||||
 | 
					    ExportDojoRecipes,
 | 
				
			||||||
    ExportDrones,
 | 
					    ExportDrones,
 | 
				
			||||||
    ExportGear,
 | 
					    ExportGear,
 | 
				
			||||||
    ExportKeys,
 | 
					    ExportKeys,
 | 
				
			||||||
 | 
					    ExportRailjackWeapons,
 | 
				
			||||||
    ExportRecipes,
 | 
					    ExportRecipes,
 | 
				
			||||||
    ExportResources,
 | 
					    ExportResources,
 | 
				
			||||||
    ExportSentinels,
 | 
					    ExportSentinels,
 | 
				
			||||||
@ -149,6 +151,18 @@ export const getItemName = (uniqueName: string): string | undefined => {
 | 
				
			|||||||
    if (uniqueName in ExportWeapons) {
 | 
					    if (uniqueName in ExportWeapons) {
 | 
				
			||||||
        return ExportWeapons[uniqueName].name;
 | 
					        return ExportWeapons[uniqueName].name;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    if (uniqueName in ExportRailjackWeapons) {
 | 
				
			||||||
 | 
					        return ExportRailjackWeapons[uniqueName].name;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (uniqueName in ExportDojoRecipes.colours) {
 | 
				
			||||||
 | 
					        return ExportDojoRecipes.colours[uniqueName].name;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (uniqueName in ExportDojoRecipes.backdrops) {
 | 
				
			||||||
 | 
					        return ExportDojoRecipes.backdrops[uniqueName].name;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (uniqueName in ExportDojoRecipes.decos) {
 | 
				
			||||||
 | 
					        return ExportDojoRecipes.decos[uniqueName].name;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    return undefined;
 | 
					    return undefined;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -309,3 +323,13 @@ export const getDefaultUpgrades = (parts: string[]): IDefaultUpgrade[] | undefin
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    return allDefaultUpgrades.length == 0 ? undefined : allDefaultUpgrades;
 | 
					    return allDefaultUpgrades.length == 0 ? undefined : allDefaultUpgrades;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getMaxLevelCap = (type: string): number => {
 | 
				
			||||||
 | 
					    if (type in ExportWarframes) {
 | 
				
			||||||
 | 
					        return ExportWarframes[type].maxLevelCap ?? 30;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (type in ExportWeapons) {
 | 
				
			||||||
 | 
					        return ExportWeapons[type].maxLevelCap ?? 30;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return 30;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,11 @@
 | 
				
			|||||||
import type { IMissionReward as IMissionRewardExternal, IRegion, IReward } from "warframe-public-export-plus";
 | 
					import type {
 | 
				
			||||||
 | 
					    IMissionReward as IMissionRewardExternal,
 | 
				
			||||||
 | 
					    IRegion,
 | 
				
			||||||
 | 
					    IReward,
 | 
				
			||||||
 | 
					    TMissionType
 | 
				
			||||||
 | 
					} from "warframe-public-export-plus";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					    ExportAnimals,
 | 
				
			||||||
    ExportEnemies,
 | 
					    ExportEnemies,
 | 
				
			||||||
    ExportFusionBundles,
 | 
					    ExportFusionBundles,
 | 
				
			||||||
    ExportRegions,
 | 
					    ExportRegions,
 | 
				
			||||||
@ -56,7 +62,6 @@ import { createMessage } from "./inboxService.ts";
 | 
				
			|||||||
import kuriaMessage50 from "../../static/fixed_responses/kuriaMessages/fiftyPercent.json" with { type: "json" };
 | 
					import kuriaMessage50 from "../../static/fixed_responses/kuriaMessages/fiftyPercent.json" with { type: "json" };
 | 
				
			||||||
import kuriaMessage75 from "../../static/fixed_responses/kuriaMessages/seventyFivePercent.json" with { type: "json" };
 | 
					import kuriaMessage75 from "../../static/fixed_responses/kuriaMessages/seventyFivePercent.json" with { type: "json" };
 | 
				
			||||||
import kuriaMessage100 from "../../static/fixed_responses/kuriaMessages/oneHundredPercent.json" with { type: "json" };
 | 
					import kuriaMessage100 from "../../static/fixed_responses/kuriaMessages/oneHundredPercent.json" with { type: "json" };
 | 
				
			||||||
import conservationAnimals from "../../static/fixed_responses/conservationAnimals.json" with { type: "json" };
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    generateNemesisProfile,
 | 
					    generateNemesisProfile,
 | 
				
			||||||
    getInfestedLichItemRewards,
 | 
					    getInfestedLichItemRewards,
 | 
				
			||||||
@ -87,11 +92,6 @@ import { handleGuildGoalProgress } from "./guildService.ts";
 | 
				
			|||||||
import { importLoadOutConfig } from "./importService.ts";
 | 
					import { importLoadOutConfig } from "./importService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] => {
 | 
					const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] => {
 | 
				
			||||||
    // Disruption missions just tell us (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2599)
 | 
					 | 
				
			||||||
    if (rewardInfo.rewardTierOverrides) {
 | 
					 | 
				
			||||||
        return rewardInfo.rewardTierOverrides;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // For Spy missions, e.g. 3 vaults cracked = A, B, C
 | 
					    // For Spy missions, e.g. 3 vaults cracked = A, B, C
 | 
				
			||||||
    if (rewardInfo.VaultsCracked) {
 | 
					    if (rewardInfo.VaultsCracked) {
 | 
				
			||||||
        const rotations: number[] = [];
 | 
					        const rotations: number[] = [];
 | 
				
			||||||
@ -102,19 +102,28 @@ const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[]
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const region = ExportRegions[rewardInfo.node] as IRegion | undefined;
 | 
					    const region = ExportRegions[rewardInfo.node] as IRegion | undefined;
 | 
				
			||||||
    const missionIndex: number | undefined = region?.missionIndex;
 | 
					    const missionType: TMissionType | undefined = region?.missionType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // For Rescue missions
 | 
					    // Disruption uses 'rewardTierOverrides' to tell us (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2599)
 | 
				
			||||||
    if (missionIndex == 3 && rewardInfo.rewardTier) {
 | 
					    // Note that this may stick in lab conquest so we need to filter by mission type (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2768)
 | 
				
			||||||
 | 
					    if (missionType == "MT_ARTIFACT") {
 | 
				
			||||||
 | 
					        return rewardInfo.rewardTierOverrides ?? [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (missionType == "MT_RESCUE" && rewardInfo.rewardTier !== undefined) {
 | 
				
			||||||
        return [rewardInfo.rewardTier];
 | 
					        return [rewardInfo.rewardTier];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 'rewardQualifications' is unreliable for non-endless railjack missions (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2586, https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2612)
 | 
					    // 'rewardQualifications' may stick from previous missions for non-endless missions done after them
 | 
				
			||||||
 | 
					    // - via railjack (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2586, https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2612)
 | 
				
			||||||
 | 
					    // - via lab conquest (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2768)
 | 
				
			||||||
    switch (region?.missionName) {
 | 
					    switch (region?.missionName) {
 | 
				
			||||||
        case "/Lotus/Language/Missions/MissionName_Railjack":
 | 
					        case "/Lotus/Language/Missions/MissionName_Railjack":
 | 
				
			||||||
        case "/Lotus/Language/Missions/MissionName_RailjackVolatile":
 | 
					        case "/Lotus/Language/Missions/MissionName_RailjackVolatile":
 | 
				
			||||||
        case "/Lotus/Language/Missions/MissionName_RailjackExterminate":
 | 
					        case "/Lotus/Language/Missions/MissionName_RailjackExterminate":
 | 
				
			||||||
        case "/Lotus/Language/Missions/MissionName_RailjackAssassinate":
 | 
					        case "/Lotus/Language/Missions/MissionName_RailjackAssassinate":
 | 
				
			||||||
 | 
					        case "/Lotus/Language/Missions/MissionName_Assassination":
 | 
				
			||||||
 | 
					        case "/Lotus/Language/Missions/MissionName_Exterminate":
 | 
				
			||||||
            return [0];
 | 
					            return [0];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -123,7 +132,7 @@ const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[]
 | 
				
			|||||||
    // Empty or absent rewardQualifications should not give rewards when:
 | 
					    // Empty or absent rewardQualifications should not give rewards when:
 | 
				
			||||||
    // - Completing only 1 zone of (E)SO (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1823)
 | 
					    // - Completing only 1 zone of (E)SO (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1823)
 | 
				
			||||||
    // - Aborting a railjack mission (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1741)
 | 
					    // - Aborting a railjack mission (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1741)
 | 
				
			||||||
    if (rotationCount == 0 && missionIndex != 30 && missionIndex != 32) {
 | 
					    if (rotationCount == 0 && missionType != "MT_ENDLESS_EXTERMINATION" && missionType != "MT_RAILJACK") {
 | 
				
			||||||
        return [0];
 | 
					        return [0];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -508,39 +517,6 @@ export const addMissionInventoryUpdates = async (
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            case "CapturedAnimals": {
 | 
					 | 
				
			||||||
                for (const capturedAnimal of value) {
 | 
					 | 
				
			||||||
                    const meta = conservationAnimals[capturedAnimal.AnimalType as keyof typeof conservationAnimals];
 | 
					 | 
				
			||||||
                    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
					 | 
				
			||||||
                    if (meta) {
 | 
					 | 
				
			||||||
                        if (capturedAnimal.NumTags) {
 | 
					 | 
				
			||||||
                            addMiscItems(inventory, [
 | 
					 | 
				
			||||||
                                {
 | 
					 | 
				
			||||||
                                    ItemType: meta.tag,
 | 
					 | 
				
			||||||
                                    ItemCount: capturedAnimal.NumTags
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
                            ]);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        if (capturedAnimal.NumExtraRewards) {
 | 
					 | 
				
			||||||
                            if ("extraReward" in meta) {
 | 
					 | 
				
			||||||
                                addMiscItems(inventory, [
 | 
					 | 
				
			||||||
                                    {
 | 
					 | 
				
			||||||
                                        ItemType: meta.extraReward,
 | 
					 | 
				
			||||||
                                        ItemCount: capturedAnimal.NumExtraRewards
 | 
					 | 
				
			||||||
                                    }
 | 
					 | 
				
			||||||
                                ]);
 | 
					 | 
				
			||||||
                            } else {
 | 
					 | 
				
			||||||
                                logger.warn(
 | 
					 | 
				
			||||||
                                    `client attempted to claim unknown extra rewards for conservation of ${capturedAnimal.AnimalType}`
 | 
					 | 
				
			||||||
                                );
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    } else {
 | 
					 | 
				
			||||||
                        logger.warn(`ignoring conservation of unknown AnimalType: ${capturedAnimal.AnimalType}`);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            case "KubrowPetEggs": {
 | 
					            case "KubrowPetEggs": {
 | 
				
			||||||
                for (const egg of value) {
 | 
					                for (const egg of value) {
 | 
				
			||||||
                    inventory.KubrowPetEggs.push({
 | 
					                    inventory.KubrowPetEggs.push({
 | 
				
			||||||
@ -945,7 +921,7 @@ interface AddMissionRewardsReturnType {
 | 
				
			|||||||
    MissionRewards: IMissionReward[];
 | 
					    MissionRewards: IMissionReward[];
 | 
				
			||||||
    inventoryChanges?: IInventoryChanges;
 | 
					    inventoryChanges?: IInventoryChanges;
 | 
				
			||||||
    credits?: IMissionCredits;
 | 
					    credits?: IMissionCredits;
 | 
				
			||||||
    AffiliationMods?: IAffiliationMods[];
 | 
					    AffiliationMods: IAffiliationMods[];
 | 
				
			||||||
    SyndicateXPItemReward?: number;
 | 
					    SyndicateXPItemReward?: number;
 | 
				
			||||||
    ConquestCompletedMissionsCount?: number;
 | 
					    ConquestCompletedMissionsCount?: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -1095,8 +1071,8 @@ const isEligibleForCreditReward = (rewardInfo: IRewardInfo, missions: IMission,
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    // The rest here might not be needed anymore, but just to be sure we don't give undue credits...
 | 
					    // The rest here might not be needed anymore, but just to be sure we don't give undue credits...
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        node.missionIndex != 23 && // junction
 | 
					        node.missionType != "MT_JUNCTION" &&
 | 
				
			||||||
        node.missionIndex != 28 && // open world
 | 
					        node.missionType != "MT_LANDSCAPE" &&
 | 
				
			||||||
        missions.Tag != "SolNode761" && // the index
 | 
					        missions.Tag != "SolNode761" && // the index
 | 
				
			||||||
        missions.Tag != "SolNode762" && // the index
 | 
					        missions.Tag != "SolNode762" && // the index
 | 
				
			||||||
        missions.Tag != "SolNode763" && // the index
 | 
					        missions.Tag != "SolNode763" && // the index
 | 
				
			||||||
@ -1121,10 +1097,12 @@ export const addMissionRewards = async (
 | 
				
			|||||||
    }: IMissionInventoryUpdateRequest,
 | 
					    }: IMissionInventoryUpdateRequest,
 | 
				
			||||||
    firstCompletion: boolean
 | 
					    firstCompletion: boolean
 | 
				
			||||||
): Promise<AddMissionRewardsReturnType> => {
 | 
					): Promise<AddMissionRewardsReturnType> => {
 | 
				
			||||||
 | 
					    AffiliationMods ??= [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!rewardInfo) {
 | 
					    if (!rewardInfo) {
 | 
				
			||||||
        //TODO: if there is a case where you can have credits collected during a mission but no rewardInfo, add credits needs to be handled earlier
 | 
					        //TODO: if there is a case where you can have credits collected during a mission but no rewardInfo, add credits needs to be handled earlier
 | 
				
			||||||
        logger.debug(`Mission ${missions!.Tag} did not have Reward Info `);
 | 
					        logger.debug(`Mission ${missions!.Tag} did not have Reward Info `);
 | 
				
			||||||
        return { MissionRewards: [] };
 | 
					        return { MissionRewards: [], AffiliationMods };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //TODO: check double reward merging
 | 
					    //TODO: check double reward merging
 | 
				
			||||||
@ -1434,8 +1412,6 @@ export const addMissionRewards = async (
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    AffiliationMods ??= [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (rewardInfo.JobStage != undefined && rewardInfo.jobId) {
 | 
					    if (rewardInfo.JobStage != undefined && rewardInfo.jobId) {
 | 
				
			||||||
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					        // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
				
			||||||
        const [jobType, unkIndex, hubNode, syndicateMissionId] = rewardInfo.jobId.split("_");
 | 
					        const [jobType, unkIndex, hubNode, syndicateMissionId] = rewardInfo.jobId.split("_");
 | 
				
			||||||
@ -1792,14 +1768,14 @@ function getRandomMissionDrops(
 | 
				
			|||||||
        let rewardManifests: string[];
 | 
					        let rewardManifests: string[];
 | 
				
			||||||
        if (RewardInfo.periodicMissionTag == "EliteAlert" || RewardInfo.periodicMissionTag == "EliteAlertB") {
 | 
					        if (RewardInfo.periodicMissionTag == "EliteAlert" || RewardInfo.periodicMissionTag == "EliteAlertB") {
 | 
				
			||||||
            rewardManifests = ["/Lotus/Types/Game/MissionDecks/EliteAlertMissionRewards/EliteAlertMissionRewards"];
 | 
					            rewardManifests = ["/Lotus/Types/Game/MissionDecks/EliteAlertMissionRewards/EliteAlertMissionRewards"];
 | 
				
			||||||
        } else if (RewardInfo.invasionId && region.missionIndex == 0) {
 | 
					        } else if (RewardInfo.invasionId && region.missionType == "MT_ASSASSINATION") {
 | 
				
			||||||
            // Invasion assassination has Phorid has the boss who should drop Nyx parts
 | 
					            // Invasion assassination has Phorid has the boss who should drop Nyx parts
 | 
				
			||||||
            // TODO: Check that the invasion faction is indeed FC_INFESTATION once the Invasions in worldState are more dynamic
 | 
					            // TODO: Check that the invasion faction is indeed FC_INFESTATION once the Invasions in worldState are more dynamic
 | 
				
			||||||
            rewardManifests = ["/Lotus/Types/Game/MissionDecks/BossMissionRewards/NyxRewards"];
 | 
					            rewardManifests = ["/Lotus/Types/Game/MissionDecks/BossMissionRewards/NyxRewards"];
 | 
				
			||||||
        } else if (RewardInfo.sortieId) {
 | 
					        } else if (RewardInfo.sortieId) {
 | 
				
			||||||
            // Sortie mission types differ from the underlying node and hence also don't give rewards from the underlying nodes.
 | 
					            // Sortie mission types differ from the underlying node and hence also don't give rewards from the underlying nodes.
 | 
				
			||||||
            // Assassinations in non-lite sorties are an exception to this.
 | 
					            // Assassinations in non-lite sorties are an exception to this.
 | 
				
			||||||
            if (region.missionIndex == 0) {
 | 
					            if (region.missionType == "MT_ASSASSINATION") {
 | 
				
			||||||
                const arr = RewardInfo.sortieId.split("_");
 | 
					                const arr = RewardInfo.sortieId.split("_");
 | 
				
			||||||
                let giveNodeReward = false;
 | 
					                let giveNodeReward = false;
 | 
				
			||||||
                if (arr[1] != "Lite") {
 | 
					                if (arr[1] != "Lite") {
 | 
				
			||||||
@ -2165,7 +2141,7 @@ function getRandomMissionDrops(
 | 
				
			|||||||
            const deck = ExportRewards["/Lotus/Types/Game/MissionDecks/NightmareModeRewards"];
 | 
					            const deck = ExportRewards["/Lotus/Types/Game/MissionDecks/NightmareModeRewards"];
 | 
				
			||||||
            let rotation = 0;
 | 
					            let rotation = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (region.missionIndex === 3 && RewardInfo.rewardTier) {
 | 
					            if (region.missionType == "MT_RESCUE" && RewardInfo.rewardTier) {
 | 
				
			||||||
                rotation = RewardInfo.rewardTier;
 | 
					                rotation = RewardInfo.rewardTier;
 | 
				
			||||||
            } else if ([6, 7, 8, 10, 11].includes(region.systemIndex)) {
 | 
					            } else if ([6, 7, 8, 10, 11].includes(region.systemIndex)) {
 | 
				
			||||||
                rotation = 2;
 | 
					                rotation = 2;
 | 
				
			||||||
@ -2236,6 +2212,54 @@ function getRandomMissionDrops(
 | 
				
			|||||||
    return drops;
 | 
					    return drops;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const handleConservation = (
 | 
				
			||||||
 | 
					    inventory: TInventoryDatabaseDocument,
 | 
				
			||||||
 | 
					    missionReport: IMissionInventoryUpdateRequest,
 | 
				
			||||||
 | 
					    AffiliationMods: IAffiliationMods[]
 | 
				
			||||||
 | 
					): void => {
 | 
				
			||||||
 | 
					    if (missionReport.CapturedAnimals) {
 | 
				
			||||||
 | 
					        for (const capturedAnimal of missionReport.CapturedAnimals) {
 | 
				
			||||||
 | 
					            // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
				
			||||||
 | 
					            const meta = ExportAnimals[capturedAnimal.AnimalType]?.conservation;
 | 
				
			||||||
 | 
					            // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
				
			||||||
 | 
					            if (meta) {
 | 
				
			||||||
 | 
					                if (capturedAnimal.NumTags) {
 | 
				
			||||||
 | 
					                    addMiscItems(inventory, [
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            ItemType: meta.itemReward,
 | 
				
			||||||
 | 
					                            ItemCount: capturedAnimal.NumTags * capturedAnimal.Count
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    ]);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (capturedAnimal.NumExtraRewards) {
 | 
				
			||||||
 | 
					                    if (meta.woundedAnimalReward) {
 | 
				
			||||||
 | 
					                        addMiscItems(inventory, [
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
 | 
					                                ItemType: meta.woundedAnimalReward,
 | 
				
			||||||
 | 
					                                ItemCount: capturedAnimal.NumExtraRewards * capturedAnimal.Count
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        ]);
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        logger.warn(
 | 
				
			||||||
 | 
					                            `client attempted to claim unknown extra rewards for conservation of ${capturedAnimal.AnimalType}`
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (meta.standingReward) {
 | 
				
			||||||
 | 
					                    addStanding(
 | 
				
			||||||
 | 
					                        inventory,
 | 
				
			||||||
 | 
					                        missionReport.Missions!.Tag == "SolNode129" ? "SolarisSyndicate" : "CetusSyndicate",
 | 
				
			||||||
 | 
					                        [2, 1.5, 1][capturedAnimal.CaptureRating] * meta.standingReward * capturedAnimal.Count,
 | 
				
			||||||
 | 
					                        AffiliationMods
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                logger.warn(`ignoring conservation of unknown AnimalType: ${capturedAnimal.AnimalType}`);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const corruptedMods = [
 | 
					const corruptedMods = [
 | 
				
			||||||
    "/Lotus/StoreItems/Upgrades/Mods/Melee/DualStat/CorruptedHeavyDamageChargeSpeedMod", // Corrupt Charge
 | 
					    "/Lotus/StoreItems/Upgrades/Mods/Melee/DualStat/CorruptedHeavyDamageChargeSpeedMod", // Corrupt Charge
 | 
				
			||||||
    "/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/CorruptedCritDamagePistol", // Hollow Point
 | 
					    "/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/CorruptedCritDamagePistol", // Hollow Point
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,3 @@
 | 
				
			|||||||
import { parseSlotPurchaseName, slotPurchaseNameToSlotName } from "../helpers/purchaseHelpers.ts";
 | 
					 | 
				
			||||||
import { getSubstringFromKeyword } from "../helpers/stringHelpers.ts";
 | 
					import { getSubstringFromKeyword } from "../helpers/stringHelpers.ts";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    addBooster,
 | 
					    addBooster,
 | 
				
			||||||
@ -15,18 +14,18 @@ import type {
 | 
				
			|||||||
    IPurchaseRequest,
 | 
					    IPurchaseRequest,
 | 
				
			||||||
    IPurchaseResponse,
 | 
					    IPurchaseResponse,
 | 
				
			||||||
    IInventoryChanges,
 | 
					    IInventoryChanges,
 | 
				
			||||||
    IPurchaseParams
 | 
					    IPurchaseParams,
 | 
				
			||||||
 | 
					    SlotNames
 | 
				
			||||||
} from "../types/purchaseTypes.ts";
 | 
					} from "../types/purchaseTypes.ts";
 | 
				
			||||||
import { PurchaseSource } from "../types/purchaseTypes.ts";
 | 
					import { PurchaseSource } from "../types/purchaseTypes.ts";
 | 
				
			||||||
import { logger } from "../utils/logger.ts";
 | 
					import { logger } from "../utils/logger.ts";
 | 
				
			||||||
import { getWorldState } from "./worldStateService.ts";
 | 
					import { getWorldState } from "./worldStateService.ts";
 | 
				
			||||||
import type { TRarity } from "warframe-public-export-plus";
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    ExportBoosterPacks,
 | 
					    ExportBoosterPacks,
 | 
				
			||||||
    ExportBoosters,
 | 
					    ExportBoosters,
 | 
				
			||||||
    ExportBundles,
 | 
					    ExportBundles,
 | 
				
			||||||
 | 
					    ExportCreditBundles,
 | 
				
			||||||
    ExportGear,
 | 
					    ExportGear,
 | 
				
			||||||
    ExportMisc,
 | 
					 | 
				
			||||||
    ExportResources,
 | 
					    ExportResources,
 | 
				
			||||||
    ExportSyndicates,
 | 
					    ExportSyndicates,
 | 
				
			||||||
    ExportVendors
 | 
					    ExportVendors
 | 
				
			||||||
@ -419,7 +418,7 @@ export const handleBundleAcqusition = async (
 | 
				
			|||||||
                    component.typeName,
 | 
					                    component.typeName,
 | 
				
			||||||
                    inventory,
 | 
					                    inventory,
 | 
				
			||||||
                    component.purchaseQuantity * quantity,
 | 
					                    component.purchaseQuantity * quantity,
 | 
				
			||||||
                    component.durability,
 | 
					                    component.durabilityDays,
 | 
				
			||||||
                    true
 | 
					                    true
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            ).InventoryChanges
 | 
					            ).InventoryChanges
 | 
				
			||||||
@ -432,7 +431,7 @@ export const handleStoreItemAcquisition = async (
 | 
				
			|||||||
    storeItemName: string,
 | 
					    storeItemName: string,
 | 
				
			||||||
    inventory: TInventoryDatabaseDocument,
 | 
					    inventory: TInventoryDatabaseDocument,
 | 
				
			||||||
    quantity: number = 1,
 | 
					    quantity: number = 1,
 | 
				
			||||||
    durability: TRarity = "COMMON",
 | 
					    durabilityDays: number = 3,
 | 
				
			||||||
    ignorePurchaseQuantity: boolean = false,
 | 
					    ignorePurchaseQuantity: boolean = false,
 | 
				
			||||||
    premiumPurchase: boolean = true,
 | 
					    premiumPurchase: boolean = true,
 | 
				
			||||||
    seed?: bigint
 | 
					    seed?: bigint
 | 
				
			||||||
@ -482,13 +481,27 @@ export const handleStoreItemAcquisition = async (
 | 
				
			|||||||
                );
 | 
					                );
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            case "Boosters":
 | 
					            case "Boosters":
 | 
				
			||||||
                purchaseResponse = handleBoostersPurchase(storeItemName, inventory, durability);
 | 
					                purchaseResponse = handleBoostersPurchase(storeItemName, inventory, durabilityDays);
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return purchaseResponse;
 | 
					    return purchaseResponse;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const slotPurchaseNameToSlotName: Record<string, { name: SlotNames; purchaseQuantity: number }> = {
 | 
				
			||||||
 | 
					    SuitSlotItem: { name: "SuitBin", purchaseQuantity: 1 },
 | 
				
			||||||
 | 
					    TwoSentinelSlotItem: { name: "SentinelBin", purchaseQuantity: 2 },
 | 
				
			||||||
 | 
					    TwoWeaponSlotItem: { name: "WeaponBin", purchaseQuantity: 2 },
 | 
				
			||||||
 | 
					    SpaceSuitSlotItem: { name: "SpaceSuitBin", purchaseQuantity: 1 },
 | 
				
			||||||
 | 
					    TwoSpaceWeaponSlotItem: { name: "SpaceWeaponBin", purchaseQuantity: 2 },
 | 
				
			||||||
 | 
					    MechSlotItem: { name: "MechBin", purchaseQuantity: 1 },
 | 
				
			||||||
 | 
					    TwoOperatorWeaponSlotItem: { name: "OperatorAmpBin", purchaseQuantity: 2 },
 | 
				
			||||||
 | 
					    RandomModSlotItem: { name: "RandomModBin", purchaseQuantity: 3 },
 | 
				
			||||||
 | 
					    TwoCrewShipSalvageSlotItem: { name: "CrewShipSalvageBin", purchaseQuantity: 2 },
 | 
				
			||||||
 | 
					    CrewMemberSlotItem: { name: "CrewMemberBin", purchaseQuantity: 1 },
 | 
				
			||||||
 | 
					    PvPLoadoutSlotItem: { name: "PvpBonusLoadoutBin", purchaseQuantity: 1 }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// // extra = everything above the base +2 slots (depending on slot type)
 | 
					// // extra = everything above the base +2 slots (depending on slot type)
 | 
				
			||||||
// // new slot above base = extra + 1 and slots +1
 | 
					// // new slot above base = extra + 1 and slots +1
 | 
				
			||||||
// // new frame = slots -1
 | 
					// // new frame = slots -1
 | 
				
			||||||
@ -500,9 +513,8 @@ const handleSlotPurchase = (
 | 
				
			|||||||
    ignorePurchaseQuantity: boolean
 | 
					    ignorePurchaseQuantity: boolean
 | 
				
			||||||
): IPurchaseResponse => {
 | 
					): IPurchaseResponse => {
 | 
				
			||||||
    logger.debug(`slot name ${slotPurchaseNameFull}`);
 | 
					    logger.debug(`slot name ${slotPurchaseNameFull}`);
 | 
				
			||||||
    const slotPurchaseName = parseSlotPurchaseName(
 | 
					    const slotPurchaseName = slotPurchaseNameFull.substring(slotPurchaseNameFull.lastIndexOf("/") + 1);
 | 
				
			||||||
        slotPurchaseNameFull.substring(slotPurchaseNameFull.lastIndexOf("/") + 1)
 | 
					    if (!(slotPurchaseName in slotPurchaseNameToSlotName)) throw new Error(`invalid slot name ${slotPurchaseName}`);
 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    logger.debug(`slot purchase name ${slotPurchaseName}`);
 | 
					    logger.debug(`slot purchase name ${slotPurchaseName}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const slotName = slotPurchaseNameToSlotName[slotPurchaseName].name;
 | 
					    const slotName = slotPurchaseNameToSlotName[slotPurchaseName].name;
 | 
				
			||||||
@ -619,8 +631,8 @@ const handleCreditBundlePurchase = async (
 | 
				
			|||||||
    typeName: string,
 | 
					    typeName: string,
 | 
				
			||||||
    inventory: TInventoryDatabaseDocument
 | 
					    inventory: TInventoryDatabaseDocument
 | 
				
			||||||
): Promise<IPurchaseResponse> => {
 | 
					): Promise<IPurchaseResponse> => {
 | 
				
			||||||
    if (typeName && typeName in ExportMisc.creditBundles) {
 | 
					    if (typeName && typeName in ExportCreditBundles) {
 | 
				
			||||||
        const creditsAmount = ExportMisc.creditBundles[typeName];
 | 
					        const creditsAmount = ExportCreditBundles[typeName].credits;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        inventory.RegularCredits += creditsAmount;
 | 
					        inventory.RegularCredits += creditsAmount;
 | 
				
			||||||
        await inventory.save();
 | 
					        await inventory.save();
 | 
				
			||||||
@ -659,7 +671,7 @@ const handleTypesPurchase = async (
 | 
				
			|||||||
const handleBoostersPurchase = (
 | 
					const handleBoostersPurchase = (
 | 
				
			||||||
    boosterStoreName: string,
 | 
					    boosterStoreName: string,
 | 
				
			||||||
    inventory: TInventoryDatabaseDocument,
 | 
					    inventory: TInventoryDatabaseDocument,
 | 
				
			||||||
    durability: TRarity
 | 
					    durabilityDays: number
 | 
				
			||||||
): { InventoryChanges: IInventoryChanges } => {
 | 
					): { InventoryChanges: IInventoryChanges } => {
 | 
				
			||||||
    if (!(boosterStoreName in ExportBoosters)) {
 | 
					    if (!(boosterStoreName in ExportBoosters)) {
 | 
				
			||||||
        logger.error(`unknown booster type: ${boosterStoreName}`);
 | 
					        logger.error(`unknown booster type: ${boosterStoreName}`);
 | 
				
			||||||
@ -667,7 +679,7 @@ const handleBoostersPurchase = (
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const ItemType = ExportBoosters[boosterStoreName].typeName;
 | 
					    const ItemType = ExportBoosters[boosterStoreName].typeName;
 | 
				
			||||||
    const ExpiryDate = ExportMisc.boosterDurations[durability];
 | 
					    const ExpiryDate = durabilityDays * 86400;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    addBooster(ItemType, ExpiryDate, inventory);
 | 
					    addBooster(ItemType, ExpiryDate, inventory);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -6,7 +6,6 @@ import { addItem, addItems, addKeyChainItems, setupKahlSyndicate } from "./inven
 | 
				
			|||||||
import { fromStoreItem, getKeyChainMessage, getLevelKeyRewards } from "./itemDataService.ts";
 | 
					import { fromStoreItem, getKeyChainMessage, getLevelKeyRewards } from "./itemDataService.ts";
 | 
				
			||||||
import type { IQuestKeyClient, IQuestKeyDatabase, IQuestStage } from "../types/inventoryTypes/inventoryTypes.ts";
 | 
					import type { IQuestKeyClient, IQuestKeyDatabase, IQuestStage } from "../types/inventoryTypes/inventoryTypes.ts";
 | 
				
			||||||
import { logger } from "../utils/logger.ts";
 | 
					import { logger } from "../utils/logger.ts";
 | 
				
			||||||
import type { Types } from "mongoose";
 | 
					 | 
				
			||||||
import { ExportKeys } from "warframe-public-export-plus";
 | 
					import { ExportKeys } from "warframe-public-export-plus";
 | 
				
			||||||
import { addFixedLevelRewards } from "./missionInventoryUpdateService.ts";
 | 
					import { addFixedLevelRewards } from "./missionInventoryUpdateService.ts";
 | 
				
			||||||
import type { IInventoryChanges } from "../types/purchaseTypes.ts";
 | 
					import type { IInventoryChanges } from "../types/purchaseTypes.ts";
 | 
				
			||||||
@ -44,7 +43,12 @@ export const updateQuestKey = async (
 | 
				
			|||||||
        inventory.QuestKeys[questKeyIndex].CompletionDate = new Date();
 | 
					        inventory.QuestKeys[questKeyIndex].CompletionDate = new Date();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const questKey = questKeyUpdate[0].ItemType;
 | 
					        const questKey = questKeyUpdate[0].ItemType;
 | 
				
			||||||
        await handleQuestCompletion(inventory, questKey, inventoryChanges);
 | 
					        await handleQuestCompletion(
 | 
				
			||||||
 | 
					            inventory,
 | 
				
			||||||
 | 
					            questKey,
 | 
				
			||||||
 | 
					            inventoryChanges,
 | 
				
			||||||
 | 
					            (questKeyUpdate[0].Progress?.[0]?.c ?? 0) > 0
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return inventoryChanges;
 | 
					    return inventoryChanges;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@ -52,7 +56,7 @@ export const updateQuestKey = async (
 | 
				
			|||||||
export const updateQuestStage = (
 | 
					export const updateQuestStage = (
 | 
				
			||||||
    inventory: TInventoryDatabaseDocument,
 | 
					    inventory: TInventoryDatabaseDocument,
 | 
				
			||||||
    { KeyChain, ChainStage }: IKeyChainRequest,
 | 
					    { KeyChain, ChainStage }: IKeyChainRequest,
 | 
				
			||||||
    questStageUpdate: IQuestStage
 | 
					    questStageUpdate: Partial<IQuestStage>
 | 
				
			||||||
): void => {
 | 
					): void => {
 | 
				
			||||||
    const quest = inventory.QuestKeys.find(quest => quest.ItemType === KeyChain);
 | 
					    const quest = inventory.QuestKeys.find(quest => quest.ItemType === KeyChain);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -68,14 +72,22 @@ export const updateQuestStage = (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
					    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
				
			||||||
    if (!questStage) {
 | 
					    if (!questStage) {
 | 
				
			||||||
        const questStageIndex = quest.Progress.push(questStageUpdate) - 1;
 | 
					        const questStageIndex =
 | 
				
			||||||
 | 
					            quest.Progress.push({
 | 
				
			||||||
 | 
					                c: questStageUpdate.c ?? 0,
 | 
				
			||||||
 | 
					                i: questStageUpdate.i ?? false,
 | 
				
			||||||
 | 
					                m: questStageUpdate.m ?? false,
 | 
				
			||||||
 | 
					                b: questStageUpdate.b ?? []
 | 
				
			||||||
 | 
					            }) - 1;
 | 
				
			||||||
        if (questStageIndex !== ChainStage) {
 | 
					        if (questStageIndex !== ChainStage) {
 | 
				
			||||||
            throw new Error(`Quest stage index mismatch: ${questStageIndex} !== ${ChainStage}`);
 | 
					            throw new Error(`Quest stage index mismatch: ${questStageIndex} !== ${ChainStage}`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Object.assign(questStage, questStageUpdate);
 | 
					    for (const [key, value] of Object.entries(questStageUpdate) as [keyof IQuestStage, number | boolean | any[]][]) {
 | 
				
			||||||
 | 
					        (questStage[key] as any) = value;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const addQuestKey = (
 | 
					export const addQuestKey = (
 | 
				
			||||||
@ -112,58 +124,53 @@ export const completeQuest = async (inventory: TInventoryDatabaseDocument, quest
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const chainStageTotal = chainStages.length;
 | 
					    const chainStageTotal = chainStages.length;
 | 
				
			||||||
 | 
					    let existingQuestKey = inventory.QuestKeys.find(qk => qk.ItemType === questKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const existingQuestKey = inventory.QuestKeys.find(qk => qk.ItemType === questKey);
 | 
					    if (!existingQuestKey) {
 | 
				
			||||||
 | 
					        const completedQuestKey: IQuestKeyDatabase = {
 | 
				
			||||||
    const startingStage = Math.max((existingQuestKey?.Progress?.length ?? 0) - 1, 0);
 | 
					            ItemType: questKey,
 | 
				
			||||||
 | 
					            Completed: false,
 | 
				
			||||||
    if (existingQuestKey?.Completed) {
 | 
					            unlock: true,
 | 
				
			||||||
 | 
					            Progress: Array.from({ length: chainStageTotal }, () => ({
 | 
				
			||||||
 | 
					                c: 0,
 | 
				
			||||||
 | 
					                i: false,
 | 
				
			||||||
 | 
					                m: false,
 | 
				
			||||||
 | 
					                b: []
 | 
				
			||||||
 | 
					            }))
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        addQuestKey(inventory, completedQuestKey);
 | 
				
			||||||
 | 
					        existingQuestKey = inventory.QuestKeys.find(qk => qk.ItemType === questKey)!;
 | 
				
			||||||
 | 
					    } else if (existingQuestKey.Completed) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (existingQuestKey) {
 | 
					
 | 
				
			||||||
    existingQuestKey.Progress = existingQuestKey.Progress ?? [];
 | 
					    existingQuestKey.Progress = existingQuestKey.Progress ?? [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const existingProgressLength = existingQuestKey.Progress.length;
 | 
					    const run = existingQuestKey.Progress[0]?.c ?? 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const existingProgressLength = existingQuestKey.Progress.length;
 | 
				
			||||||
    if (existingProgressLength < chainStageTotal) {
 | 
					    if (existingProgressLength < chainStageTotal) {
 | 
				
			||||||
        const missingProgress: IQuestStage[] = Array.from(
 | 
					        const missingProgress: IQuestStage[] = Array.from(
 | 
				
			||||||
            { length: chainStageTotal - existingProgressLength },
 | 
					            { length: chainStageTotal - existingProgressLength },
 | 
				
			||||||
                () =>
 | 
					            () => ({ c: run, i: false, m: false, b: [] }) as IQuestStage
 | 
				
			||||||
                    ({
 | 
					 | 
				
			||||||
                        c: 0,
 | 
					 | 
				
			||||||
                        i: false,
 | 
					 | 
				
			||||||
                        m: false,
 | 
					 | 
				
			||||||
                        b: []
 | 
					 | 
				
			||||||
                    }) as IQuestStage
 | 
					 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					 | 
				
			||||||
        existingQuestKey.Progress.push(...missingProgress);
 | 
					        existingQuestKey.Progress.push(...missingProgress);
 | 
				
			||||||
            existingQuestKey.CompletionDate = new Date();
 | 
					 | 
				
			||||||
            existingQuestKey.Completed = true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        const completedQuestKey: IQuestKeyDatabase = {
 | 
					 | 
				
			||||||
            ItemType: questKey,
 | 
					 | 
				
			||||||
            Completed: true,
 | 
					 | 
				
			||||||
            unlock: true,
 | 
					 | 
				
			||||||
            Progress: Array(chainStageTotal).fill({
 | 
					 | 
				
			||||||
                c: 0,
 | 
					 | 
				
			||||||
                i: false,
 | 
					 | 
				
			||||||
                m: false,
 | 
					 | 
				
			||||||
                b: []
 | 
					 | 
				
			||||||
            } satisfies IQuestStage),
 | 
					 | 
				
			||||||
            CompletionDate: new Date()
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        addQuestKey(inventory, completedQuestKey);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (let i = startingStage; i < chainStageTotal; i++) {
 | 
					    for (let i = 0; i < chainStageTotal; i++) {
 | 
				
			||||||
 | 
					        const stage = existingQuestKey.Progress[i];
 | 
				
			||||||
 | 
					        if (stage.c <= run) {
 | 
				
			||||||
 | 
					            stage.c = run;
 | 
				
			||||||
            await giveKeyChainStageTriggered(inventory, { KeyChain: questKey, ChainStage: i });
 | 
					            await giveKeyChainStageTriggered(inventory, { KeyChain: questKey, ChainStage: i });
 | 
				
			||||||
 | 
					 | 
				
			||||||
            await giveKeyChainMissionReward(inventory, { KeyChain: questKey, ChainStage: i });
 | 
					            await giveKeyChainMissionReward(inventory, { KeyChain: questKey, ChainStage: i });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await handleQuestCompletion(inventory, questKey);
 | 
					    if (existingQuestKey.Progress.every(p => p.c == run)) {
 | 
				
			||||||
 | 
					        existingQuestKey.Completed = true;
 | 
				
			||||||
 | 
					        existingQuestKey.CompletionDate = new Date();
 | 
				
			||||||
 | 
					        await handleQuestCompletion(inventory, questKey, undefined, run > 0);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getQuestCompletionItems = (questKey: string): ITypeCount[] | undefined => {
 | 
					const getQuestCompletionItems = (questKey: string): ITypeCount[] | undefined => {
 | 
				
			||||||
@ -214,28 +221,35 @@ const doesQuestCompletionFinishSet = (
 | 
				
			|||||||
const handleQuestCompletion = async (
 | 
					const handleQuestCompletion = async (
 | 
				
			||||||
    inventory: TInventoryDatabaseDocument,
 | 
					    inventory: TInventoryDatabaseDocument,
 | 
				
			||||||
    questKey: string,
 | 
					    questKey: string,
 | 
				
			||||||
    inventoryChanges: IInventoryChanges = {}
 | 
					    inventoryChanges: IInventoryChanges = {},
 | 
				
			||||||
 | 
					    isRerun: boolean = false
 | 
				
			||||||
): Promise<void> => {
 | 
					): Promise<void> => {
 | 
				
			||||||
    logger.debug(`completed quest ${questKey}`);
 | 
					    logger.debug(`completed quest ${questKey}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (inventory.ActiveQuest == questKey) inventory.ActiveQuest = "";
 | 
				
			||||||
    if (questKey == "/Lotus/Types/Keys/OrokinMoonQuest/OrokinMoonQuestKeyChain") {
 | 
					    if (questKey == "/Lotus/Types/Keys/OrokinMoonQuest/OrokinMoonQuestKeyChain") {
 | 
				
			||||||
 | 
					        const att = isRerun
 | 
				
			||||||
 | 
					            ? []
 | 
				
			||||||
 | 
					            : [
 | 
				
			||||||
 | 
					                  "/Lotus/Weapons/Tenno/Melee/Swords/StalkerTwo/StalkerTwoSmallSword",
 | 
				
			||||||
 | 
					                  "/Lotus/Upgrades/Skins/Sigils/ScarSigil"
 | 
				
			||||||
 | 
					              ];
 | 
				
			||||||
        await createMessage(inventory.accountOwnerId, [
 | 
					        await createMessage(inventory.accountOwnerId, [
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                sndr: "/Lotus/Language/Bosses/Ordis",
 | 
					                sndr: "/Lotus/Language/Bosses/Ordis",
 | 
				
			||||||
                msg: "/Lotus/Language/G1Quests/SecondDreamFinishInboxMessage",
 | 
					                msg: "/Lotus/Language/G1Quests/SecondDreamFinishInboxMessage",
 | 
				
			||||||
                att: [
 | 
					                att,
 | 
				
			||||||
                    "/Lotus/Weapons/Tenno/Melee/Swords/StalkerTwo/StalkerTwoSmallSword",
 | 
					 | 
				
			||||||
                    "/Lotus/Upgrades/Skins/Sigils/ScarSigil"
 | 
					 | 
				
			||||||
                ],
 | 
					 | 
				
			||||||
                sub: "/Lotus/Language/G1Quests/SecondDreamFinishInboxTitle",
 | 
					                sub: "/Lotus/Language/G1Quests/SecondDreamFinishInboxTitle",
 | 
				
			||||||
                icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
 | 
					                icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
 | 
				
			||||||
                highPriority: true
 | 
					                highPriority: true
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
    } else if (questKey == "/Lotus/Types/Keys/NewWarQuest/NewWarQuestKeyChain") {
 | 
					    } else if (questKey == "/Lotus/Types/Keys/NewWarQuest/NewWarQuestKeyChain" && !isRerun) {
 | 
				
			||||||
        setupKahlSyndicate(inventory);
 | 
					        setupKahlSyndicate(inventory);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (isRerun) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Whispers in the Walls is unlocked once The New War + Heart of Deimos are completed.
 | 
					    // Whispers in the Walls is unlocked once The New War + Heart of Deimos are completed.
 | 
				
			||||||
    if (
 | 
					    if (
 | 
				
			||||||
        doesQuestCompletionFinishSet(inventory, questKey, [
 | 
					        doesQuestCompletionFinishSet(inventory, questKey, [
 | 
				
			||||||
@ -279,21 +293,24 @@ const handleQuestCompletion = async (
 | 
				
			|||||||
    if (questCompletionItems) {
 | 
					    if (questCompletionItems) {
 | 
				
			||||||
        await addItems(inventory, questCompletionItems, inventoryChanges);
 | 
					        await addItems(inventory, questCompletionItems, inventoryChanges);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (inventory.ActiveQuest == questKey) inventory.ActiveQuest = "";
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const giveKeyChainItem = async (
 | 
					export const giveKeyChainItem = async (
 | 
				
			||||||
    inventory: TInventoryDatabaseDocument,
 | 
					    inventory: TInventoryDatabaseDocument,
 | 
				
			||||||
    keyChainInfo: IKeyChainRequest
 | 
					    keyChainInfo: IKeyChainRequest,
 | 
				
			||||||
 | 
					    questKey: IQuestKeyDatabase
 | 
				
			||||||
): Promise<IInventoryChanges> => {
 | 
					): Promise<IInventoryChanges> => {
 | 
				
			||||||
    const inventoryChanges = await addKeyChainItems(inventory, keyChainInfo);
 | 
					    let inventoryChanges: IInventoryChanges = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!questKey.Progress?.[keyChainInfo.ChainStage]?.i) {
 | 
				
			||||||
 | 
					        inventoryChanges = await addKeyChainItems(inventory, keyChainInfo);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (isEmptyObject(inventoryChanges)) {
 | 
					        if (isEmptyObject(inventoryChanges)) {
 | 
				
			||||||
            logger.warn("inventory changes was empty after getting keychain items: should not happen");
 | 
					            logger.warn("inventory changes was empty after getting keychain items: should not happen");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        // items were added: update quest stage's i (item was given)
 | 
					        // items were added: update quest stage's i (item was given)
 | 
				
			||||||
        updateQuestStage(inventory, keyChainInfo, { i: true });
 | 
					        updateQuestStage(inventory, keyChainInfo, { i: true });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return inventoryChanges;
 | 
					    return inventoryChanges;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -309,12 +326,17 @@ export const giveKeyChainItem = async (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export const giveKeyChainMessage = async (
 | 
					export const giveKeyChainMessage = async (
 | 
				
			||||||
    inventory: TInventoryDatabaseDocument,
 | 
					    inventory: TInventoryDatabaseDocument,
 | 
				
			||||||
    accountId: string | Types.ObjectId,
 | 
					    keyChainInfo: IKeyChainRequest,
 | 
				
			||||||
    keyChainInfo: IKeyChainRequest
 | 
					    questKey: IQuestKeyDatabase
 | 
				
			||||||
): Promise<void> => {
 | 
					): Promise<void> => {
 | 
				
			||||||
    const keyChainMessage = getKeyChainMessage(keyChainInfo);
 | 
					    const keyChainMessage = getKeyChainMessage(keyChainInfo);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await createMessage(accountId, [keyChainMessage]);
 | 
					    if (questKey.Progress![0].c > 0) {
 | 
				
			||||||
 | 
					        keyChainMessage.att = [];
 | 
				
			||||||
 | 
					        keyChainMessage.countedAtt = [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await createMessage(inventory.accountOwnerId, [keyChainMessage]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    updateQuestStage(inventory, keyChainInfo, { m: true });
 | 
					    updateQuestStage(inventory, keyChainInfo, { m: true });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@ -328,8 +350,10 @@ export const giveKeyChainMissionReward = async (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if (chainStages) {
 | 
					    if (chainStages) {
 | 
				
			||||||
        const missionName = chainStages[keyChainInfo.ChainStage].key;
 | 
					        const missionName = chainStages[keyChainInfo.ChainStage].key;
 | 
				
			||||||
        if (missionName) {
 | 
					        const questKey = inventory.QuestKeys.find(q => q.ItemType === keyChainInfo.KeyChain);
 | 
				
			||||||
 | 
					        if (missionName && questKey) {
 | 
				
			||||||
            const fixedLevelRewards = getLevelKeyRewards(missionName);
 | 
					            const fixedLevelRewards = getLevelKeyRewards(missionName);
 | 
				
			||||||
 | 
					            const run = questKey.Progress?.[0]?.c ?? 0;
 | 
				
			||||||
            if (fixedLevelRewards.levelKeyRewards) {
 | 
					            if (fixedLevelRewards.levelKeyRewards) {
 | 
				
			||||||
                const missionRewards: { StoreItem: string; ItemCount: number }[] = [];
 | 
					                const missionRewards: { StoreItem: string; ItemCount: number }[] = [];
 | 
				
			||||||
                inventory.RegularCredits += addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, missionRewards);
 | 
					                inventory.RegularCredits += addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, missionRewards);
 | 
				
			||||||
@ -338,7 +362,7 @@ export const giveKeyChainMissionReward = async (
 | 
				
			|||||||
                    await addItem(inventory, fromStoreItem(reward.StoreItem), reward.ItemCount);
 | 
					                    await addItem(inventory, fromStoreItem(reward.StoreItem), reward.ItemCount);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                updateQuestStage(inventory, keyChainInfo, { c: 0 });
 | 
					                updateQuestStage(inventory, keyChainInfo, { c: run });
 | 
				
			||||||
            } else if (fixedLevelRewards.levelKeyRewards2) {
 | 
					            } else if (fixedLevelRewards.levelKeyRewards2) {
 | 
				
			||||||
                for (const reward of fixedLevelRewards.levelKeyRewards2) {
 | 
					                for (const reward of fixedLevelRewards.levelKeyRewards2) {
 | 
				
			||||||
                    if (reward.rewardType == "RT_CREDITS") {
 | 
					                    if (reward.rewardType == "RT_CREDITS") {
 | 
				
			||||||
@ -352,7 +376,7 @@ export const giveKeyChainMissionReward = async (
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                updateQuestStage(inventory, keyChainInfo, { c: 0 });
 | 
					                updateQuestStage(inventory, keyChainInfo, { c: run });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -364,14 +388,15 @@ export const giveKeyChainStageTriggered = async (
 | 
				
			|||||||
): Promise<void> => {
 | 
					): Promise<void> => {
 | 
				
			||||||
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
					    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
				
			||||||
    const chainStages = ExportKeys[keyChainInfo.KeyChain]?.chainStages;
 | 
					    const chainStages = ExportKeys[keyChainInfo.KeyChain]?.chainStages;
 | 
				
			||||||
 | 
					    const questKey = inventory.QuestKeys.find(qk => qk.ItemType === keyChainInfo.KeyChain);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (chainStages) {
 | 
					    if (chainStages && questKey) {
 | 
				
			||||||
        if (chainStages[keyChainInfo.ChainStage].itemsToGiveWhenTriggered.length > 0) {
 | 
					        if (chainStages[keyChainInfo.ChainStage].itemsToGiveWhenTriggered.length > 0) {
 | 
				
			||||||
            await giveKeyChainItem(inventory, keyChainInfo);
 | 
					            await giveKeyChainItem(inventory, keyChainInfo, questKey);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (chainStages[keyChainInfo.ChainStage].messageToSendWhenTriggered) {
 | 
					        if (chainStages[keyChainInfo.ChainStage].messageToSendWhenTriggered) {
 | 
				
			||||||
            await giveKeyChainMessage(inventory, inventory.accountOwnerId, keyChainInfo);
 | 
					            await giveKeyChainMessage(inventory, keyChainInfo, questKey);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -14,7 +14,6 @@ import type {
 | 
				
			|||||||
import { logger } from "../utils/logger.ts";
 | 
					import { logger } from "../utils/logger.ts";
 | 
				
			||||||
import { Types } from "mongoose";
 | 
					import { Types } from "mongoose";
 | 
				
			||||||
import { addFusionTreasures, addShipDecorations, getInventory } from "./inventoryService.ts";
 | 
					import { addFusionTreasures, addShipDecorations, getInventory } from "./inventoryService.ts";
 | 
				
			||||||
import { config } from "./configService.ts";
 | 
					 | 
				
			||||||
import { Guild } from "../models/guildModel.ts";
 | 
					import { Guild } from "../models/guildModel.ts";
 | 
				
			||||||
import { hasGuildPermission } from "./guildService.ts";
 | 
					import { hasGuildPermission } from "./guildService.ts";
 | 
				
			||||||
import { GuildPermission } from "../types/guildTypes.ts";
 | 
					import { GuildPermission } from "../types/guildTypes.ts";
 | 
				
			||||||
@ -137,7 +136,6 @@ export const handleSetShipDecorations = async (
 | 
				
			|||||||
        roomToPlaceIn.MaxCapacity += meta.capacityCost;
 | 
					        roomToPlaceIn.MaxCapacity += meta.capacityCost;
 | 
				
			||||||
        await personalRooms.save();
 | 
					        await personalRooms.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!config.unlockAllShipDecorations) {
 | 
					 | 
				
			||||||
        const inventory = await getInventory(accountId);
 | 
					        const inventory = await getInventory(accountId);
 | 
				
			||||||
        if (deco.Sockets !== undefined) {
 | 
					        if (deco.Sockets !== undefined) {
 | 
				
			||||||
            addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: deco.Sockets, ItemCount: 1 }]);
 | 
					            addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: deco.Sockets, ItemCount: 1 }]);
 | 
				
			||||||
@ -145,7 +143,6 @@ export const handleSetShipDecorations = async (
 | 
				
			|||||||
            addShipDecorations(inventory, [{ ItemType: itemType, ItemCount: 1 }]);
 | 
					            addShipDecorations(inventory, [{ ItemType: itemType, ItemCount: 1 }]);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        await inventory.save();
 | 
					        await inventory.save();
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            DecoId: placedDecoration.RemoveId,
 | 
					            DecoId: placedDecoration.RemoveId,
 | 
				
			||||||
@ -155,7 +152,6 @@ export const handleSetShipDecorations = async (
 | 
				
			|||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!config.unlockAllShipDecorations) {
 | 
					 | 
				
			||||||
    const inventory = await getInventory(accountId);
 | 
					    const inventory = await getInventory(accountId);
 | 
				
			||||||
    if (placedDecoration.Sockets !== undefined) {
 | 
					    if (placedDecoration.Sockets !== undefined) {
 | 
				
			||||||
        addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: placedDecoration.Sockets, ItemCount: -1 }]);
 | 
					        addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: placedDecoration.Sockets, ItemCount: -1 }]);
 | 
				
			||||||
@ -163,7 +159,6 @@ export const handleSetShipDecorations = async (
 | 
				
			|||||||
        addShipDecorations(inventory, [{ ItemType: itemType, ItemCount: -1 }]);
 | 
					        addShipDecorations(inventory, [{ ItemType: itemType, ItemCount: -1 }]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    await inventory.save();
 | 
					    await inventory.save();
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //place decoration
 | 
					    //place decoration
 | 
				
			||||||
    const decoId = new Types.ObjectId();
 | 
					    const decoId = new Types.ObjectId();
 | 
				
			||||||
@ -221,13 +216,11 @@ export const handleResetShipDecorations = async (
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // refund item
 | 
					        // refund item
 | 
				
			||||||
        if (!config.unlockAllShipDecorations) {
 | 
					 | 
				
			||||||
        if (deco.Sockets !== undefined) {
 | 
					        if (deco.Sockets !== undefined) {
 | 
				
			||||||
            addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: deco.Sockets, ItemCount: 1 }]);
 | 
					            addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: deco.Sockets, ItemCount: 1 }]);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            addShipDecorations(inventory, [{ ItemType: itemType, ItemCount: 1 }]);
 | 
					            addShipDecorations(inventory, [{ ItemType: itemType, ItemCount: 1 }]);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // refund capacity
 | 
					        // refund capacity
 | 
				
			||||||
        room.MaxCapacity += meta.capacityCost;
 | 
					        room.MaxCapacity += meta.capacityCost;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import http from "http";
 | 
					import http from "http";
 | 
				
			||||||
import https from "https";
 | 
					import https from "https";
 | 
				
			||||||
import fs from "node:fs";
 | 
					import fs from "node:fs";
 | 
				
			||||||
import { config } from "./configService.ts";
 | 
					import { configGetWebBindings, type IBindings } from "./configService.ts";
 | 
				
			||||||
import { logger } from "../utils/logger.ts";
 | 
					import { logger } from "../utils/logger.ts";
 | 
				
			||||||
import { app } from "../app.ts";
 | 
					import { app } from "../app.ts";
 | 
				
			||||||
import type { AddressInfo } from "node:net";
 | 
					import type { AddressInfo } from "node:net";
 | 
				
			||||||
@ -17,33 +17,35 @@ const tlsOptions = {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const startWebServer = (): void => {
 | 
					export const startWebServer = (): void => {
 | 
				
			||||||
    const httpPort = config.httpPort || 80;
 | 
					    const bindings = configGetWebBindings();
 | 
				
			||||||
    const httpsPort = config.httpsPort || 443;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // eslint-disable-next-line @typescript-eslint/no-misused-promises
 | 
					    // eslint-disable-next-line @typescript-eslint/no-misused-promises
 | 
				
			||||||
    httpServer = http.createServer(app);
 | 
					    httpServer = http.createServer(app);
 | 
				
			||||||
    httpServer.listen(httpPort, () => {
 | 
					    httpServer.listen(bindings.httpPort, bindings.address, () => {
 | 
				
			||||||
        startWsServer(httpServer!);
 | 
					        startWsServer(httpServer!);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        logger.info("HTTP server started on port " + httpPort);
 | 
					        logger.info(`HTTP server started on ${bindings.address}:${bindings.httpPort}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // eslint-disable-next-line @typescript-eslint/no-misused-promises
 | 
					        // eslint-disable-next-line @typescript-eslint/no-misused-promises
 | 
				
			||||||
        httpsServer = https.createServer(tlsOptions, app);
 | 
					        httpsServer = https.createServer(tlsOptions, app);
 | 
				
			||||||
        httpsServer.listen(httpsPort, () => {
 | 
					        httpsServer.listen(bindings.httpsPort, bindings.address, () => {
 | 
				
			||||||
            startWssServer(httpsServer!);
 | 
					            startWssServer(httpsServer!);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            logger.info("HTTPS server started on port " + httpsPort);
 | 
					            logger.info(`HTTPS server started on ${bindings.address}:${bindings.httpsPort}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            logger.info(
 | 
					            logger.info(
 | 
				
			||||||
                "Access the WebUI in your browser at http://localhost" + (httpPort == 80 ? "" : ":" + httpPort)
 | 
					                "Access the WebUI in your browser at http://localhost" +
 | 
				
			||||||
 | 
					                    (bindings.httpPort == 80 ? "" : ":" + bindings.httpPort)
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            void runWsSelfTest("wss", httpsPort).then(ok => {
 | 
					            void runWsSelfTest("wss", bindings.httpsPort).then(ok => {
 | 
				
			||||||
                if (!ok) {
 | 
					                if (!ok) {
 | 
				
			||||||
                    logger.warn(`WSS self-test failed. The server may not actually be reachable at port ${httpsPort}.`);
 | 
					                    logger.warn(
 | 
				
			||||||
 | 
					                        `WSS self-test failed. The server may not be reachable locally on port ${bindings.httpsPort}.`
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
                    if (process.platform == "win32") {
 | 
					                    if (process.platform == "win32") {
 | 
				
			||||||
                        logger.warn(
 | 
					                        logger.warn(
 | 
				
			||||||
                            `You can check who actually has that port via powershell: Get-Process -Id (Get-NetTCPConnection -LocalPort ${httpsPort}).OwningProcess`
 | 
					                            `You can check who has that port via powershell: Get-Process -Id (Get-NetTCPConnection -LocalPort ${bindings.httpsPort}).OwningProcess`
 | 
				
			||||||
                        );
 | 
					                        );
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@ -80,10 +82,11 @@ const runWsSelfTest = (protocol: "ws" | "wss", port: number): Promise<boolean> =
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getWebPorts = (): Record<"http" | "https", number | undefined> => {
 | 
					export const getWebBindings = (): Partial<IBindings> => {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        http: (httpServer?.address() as AddressInfo | undefined)?.port,
 | 
					        address: (httpServer?.address() as AddressInfo | undefined)?.address,
 | 
				
			||||||
        https: (httpsServer?.address() as AddressInfo | undefined)?.port
 | 
					        httpPort: (httpServer?.address() as AddressInfo | undefined)?.port,
 | 
				
			||||||
 | 
					        httpsPort: (httpsServer?.address() as AddressInfo | undefined)?.port
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -13,8 +13,8 @@ import { buildConfig } from "./buildConfigService.ts";
 | 
				
			|||||||
import { unixTimesInMs } from "../constants/timeConstants.ts";
 | 
					import { unixTimesInMs } from "../constants/timeConstants.ts";
 | 
				
			||||||
import { config } from "./configService.ts";
 | 
					import { config } from "./configService.ts";
 | 
				
			||||||
import { getRandomElement, getRandomInt, sequentiallyUniqueRandomElement, SRng } from "./rngService.ts";
 | 
					import { getRandomElement, getRandomInt, sequentiallyUniqueRandomElement, SRng } from "./rngService.ts";
 | 
				
			||||||
import type { IMissionReward, IRegion } from "warframe-public-export-plus";
 | 
					import type { IMissionReward, IRegion, TFaction } from "warframe-public-export-plus";
 | 
				
			||||||
import { eMissionType, ExportRegions, ExportSyndicates } from "warframe-public-export-plus";
 | 
					import { ExportRegions, ExportSyndicates } from "warframe-public-export-plus";
 | 
				
			||||||
import type {
 | 
					import type {
 | 
				
			||||||
    ICalendarDay,
 | 
					    ICalendarDay,
 | 
				
			||||||
    ICalendarEvent,
 | 
					    ICalendarEvent,
 | 
				
			||||||
@ -87,11 +87,11 @@ const sortieFactionToSystemIndexes: Record<string, number[]> = {
 | 
				
			|||||||
    FC_OROKIN: [14]
 | 
					    FC_OROKIN: [14]
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const sortieFactionToFactionIndexes: Record<string, number[]> = {
 | 
					const sortieFactionToFactions: Record<string, TFaction[]> = {
 | 
				
			||||||
    FC_GRINEER: [0],
 | 
					    FC_GRINEER: ["FC_GRINEER"],
 | 
				
			||||||
    FC_CORPUS: [1],
 | 
					    FC_CORPUS: ["FC_CORPUS"],
 | 
				
			||||||
    FC_INFESTATION: [0, 1, 2],
 | 
					    FC_INFESTATION: ["FC_GRINEER", "FC_CORPUS", "FC_INFESTATION"],
 | 
				
			||||||
    FC_OROKIN: [3]
 | 
					    FC_OROKIN: ["FC_OROKIN"]
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const sortieBossNode: Record<Exclude<TSortieBoss, "SORTIE_BOSS_CORRUPTED_VOR">, string> = {
 | 
					const sortieBossNode: Record<Exclude<TSortieBoss, "SORTIE_BOSS_CORRUPTED_VOR">, string> = {
 | 
				
			||||||
@ -266,14 +266,15 @@ export const getSortie = (day: number): ISortie => {
 | 
				
			|||||||
    const rng = new SRng(seed);
 | 
					    const rng = new SRng(seed);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const boss = rng.randomElement(sortieBosses)!;
 | 
					    const boss = rng.randomElement(sortieBosses)!;
 | 
				
			||||||
 | 
					    const enemyFaction = sortieBossToFaction[boss];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const nodes: string[] = [];
 | 
					    const nodes: string[] = [];
 | 
				
			||||||
    for (const [key, value] of Object.entries(ExportRegions)) {
 | 
					    for (const [key, value] of Object.entries(ExportRegions)) {
 | 
				
			||||||
        if (
 | 
					        if (
 | 
				
			||||||
            sortieFactionToSystemIndexes[sortieBossToFaction[boss]].includes(value.systemIndex) &&
 | 
					            sortieFactionToSystemIndexes[enemyFaction].includes(value.systemIndex) &&
 | 
				
			||||||
            sortieFactionToFactionIndexes[sortieBossToFaction[boss]].includes(value.factionIndex!) &&
 | 
					            sortieFactionToFactions[enemyFaction].includes(value.faction!) &&
 | 
				
			||||||
            key in sortieTilesets &&
 | 
					            key in sortieTilesets &&
 | 
				
			||||||
            (key != "SolNode228" || sortieBossToFaction[boss] == "FC_GRINEER") // PoE does not work for non-infested enemies
 | 
					            (key != "SolNode228" || enemyFaction == "FC_GRINEER") // PoE only works for grineer enemies
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
            nodes.push(key);
 | 
					            nodes.push(key);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -339,13 +340,7 @@ export const getSortie = (day: number): ISortie => {
 | 
				
			|||||||
            modifiers.push("SORTIE_MODIFIER_HAZARD_RADIATION");
 | 
					            modifiers.push("SORTIE_MODIFIER_HAZARD_RADIATION");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (ExportRegions[node].factionIndex == 0) {
 | 
					        modifiers.push(enemyFaction == "FC_CORPUS" ? "SORTIE_MODIFIER_SHIELDS" : "SORTIE_MODIFIER_ARMOR");
 | 
				
			||||||
            // Grineer
 | 
					 | 
				
			||||||
            modifiers.push("SORTIE_MODIFIER_ARMOR");
 | 
					 | 
				
			||||||
        } else if (ExportRegions[node].factionIndex == 1) {
 | 
					 | 
				
			||||||
            // Corpus
 | 
					 | 
				
			||||||
            modifiers.push("SORTIE_MODIFIER_SHIELDS");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const modifierType = rng.randomElement(modifiers)!;
 | 
					        const modifierType = rng.randomElement(modifiers)!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1306,7 +1301,7 @@ const createInvasion = (day: number, idx: number): IInvasion => {
 | 
				
			|||||||
        ),
 | 
					        ),
 | 
				
			||||||
        Goal: 30000, // Value seems to range from 30000 to 98000 in intervals of 1000. Higher values are increasingly rare. I don't think this is relevant for the frontend besides dividing count by it.
 | 
					        Goal: 30000, // Value seems to range from 30000 to 98000 in intervals of 1000. Higher values are increasingly rare. I don't think this is relevant for the frontend besides dividing count by it.
 | 
				
			||||||
        LocTag: isInfestationOutbreak
 | 
					        LocTag: isInfestationOutbreak
 | 
				
			||||||
            ? ExportRegions[node].missionIndex == 0
 | 
					            ? ExportRegions[node].missionType == "MT_ASSASSINATION"
 | 
				
			||||||
                ? "/Lotus/Language/Menu/InfestedInvasionBoss"
 | 
					                ? "/Lotus/Language/Menu/InfestedInvasionBoss"
 | 
				
			||||||
                : "/Lotus/Language/Menu/InfestedInvasionGeneric"
 | 
					                : "/Lotus/Language/Menu/InfestedInvasionGeneric"
 | 
				
			||||||
            : attacker == "FC_CORPUS"
 | 
					            : attacker == "FC_CORPUS"
 | 
				
			||||||
@ -2803,7 +2798,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        const activeStartDay = day - ghoulsCycleDay + 17;
 | 
					        const activeStartDay = day - ghoulsCycleDay + 17;
 | 
				
			||||||
        const activeEndDay = activeStartDay + 5;
 | 
					        const activeEndDay = activeStartDay + 5;
 | 
				
			||||||
        const dayWithFraction = (timeMs - EPOCH) / 86400000;
 | 
					        const dayWithFraction = (timeMs - EPOCH) / unixTimesInMs.day;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const progress = (dayWithFraction - activeStartDay) / (activeEndDay - activeStartDay);
 | 
					        const progress = (dayWithFraction - activeStartDay) / (activeEndDay - activeStartDay);
 | 
				
			||||||
        const healthPct = 1 - Math.min(Math.max(progress, 0), 1);
 | 
					        const healthPct = 1 - Math.min(Math.max(progress, 0), 1);
 | 
				
			||||||
@ -2814,22 +2809,14 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
 | 
				
			|||||||
                $date: {
 | 
					                $date: {
 | 
				
			||||||
                    $numberLong: config.worldState?.ghoulEmergenceOverride
 | 
					                    $numberLong: config.worldState?.ghoulEmergenceOverride
 | 
				
			||||||
                        ? "1753204900185"
 | 
					                        ? "1753204900185"
 | 
				
			||||||
                        : Date.UTC(
 | 
					                        : (EPOCH + activeStartDay * unixTimesInMs.day).toString()
 | 
				
			||||||
                              date.getUTCFullYear(),
 | 
					 | 
				
			||||||
                              date.getUTCMonth(),
 | 
					 | 
				
			||||||
                              date.getUTCDate() + activeStartDay
 | 
					 | 
				
			||||||
                          ).toString()
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            Expiry: {
 | 
					            Expiry: {
 | 
				
			||||||
                $date: {
 | 
					                $date: {
 | 
				
			||||||
                    $numberLong: config.worldState?.ghoulEmergenceOverride
 | 
					                    $numberLong: config.worldState?.ghoulEmergenceOverride
 | 
				
			||||||
                        ? "2000000000000"
 | 
					                        ? "2000000000000"
 | 
				
			||||||
                        : Date.UTC(
 | 
					                        : (EPOCH + activeEndDay * unixTimesInMs.day).toString()
 | 
				
			||||||
                              date.getUTCFullYear(),
 | 
					 | 
				
			||||||
                              date.getUTCMonth(),
 | 
					 | 
				
			||||||
                              date.getUTCDate() + activeEndDay
 | 
					 | 
				
			||||||
                          ).toString()
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            HealthPct: config.worldState?.ghoulEmergenceOverride ? 1 : healthPct,
 | 
					            HealthPct: config.worldState?.ghoulEmergenceOverride ? 1 : healthPct,
 | 
				
			||||||
@ -3179,7 +3166,7 @@ export const populateFissures = async (worldState: IWorldState): Promise<void> =
 | 
				
			|||||||
                    Activation: { $date: { $numberLong: "1000000000000" } },
 | 
					                    Activation: { $date: { $numberLong: "1000000000000" } },
 | 
				
			||||||
                    Expiry: { $date: { $numberLong: "2000000000000" } },
 | 
					                    Expiry: { $date: { $numberLong: "2000000000000" } },
 | 
				
			||||||
                    Node: node,
 | 
					                    Node: node,
 | 
				
			||||||
                    MissionType: eMissionType[meta.missionIndex].tag,
 | 
					                    MissionType: meta.missionType,
 | 
				
			||||||
                    Modifier: tier,
 | 
					                    Modifier: tier,
 | 
				
			||||||
                    Hard: config.worldState.allTheFissures == "hard"
 | 
					                    Hard: config.worldState.allTheFissures == "hard"
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
@ -3199,7 +3186,7 @@ export const populateFissures = async (worldState: IWorldState): Promise<void> =
 | 
				
			|||||||
                        : toMongoDate(fissure.Activation),
 | 
					                        : toMongoDate(fissure.Activation),
 | 
				
			||||||
                Expiry: toMongoDate(fissure.Expiry),
 | 
					                Expiry: toMongoDate(fissure.Expiry),
 | 
				
			||||||
                Node: fissure.Node,
 | 
					                Node: fissure.Node,
 | 
				
			||||||
                MissionType: eMissionType[meta.missionIndex].tag,
 | 
					                MissionType: meta.missionType,
 | 
				
			||||||
                Modifier: fissure.Modifier,
 | 
					                Modifier: fissure.Modifier,
 | 
				
			||||||
                Hard: fissure.Hard
 | 
					                Hard: fissure.Hard
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
@ -3246,13 +3233,12 @@ export const getLiteSortie = (week: number): ILiteSortie => {
 | 
				
			|||||||
    for (const [key, value] of Object.entries(ExportRegions)) {
 | 
					    for (const [key, value] of Object.entries(ExportRegions)) {
 | 
				
			||||||
        if (
 | 
					        if (
 | 
				
			||||||
            value.systemIndex === systemIndex &&
 | 
					            value.systemIndex === systemIndex &&
 | 
				
			||||||
            value.factionIndex !== undefined &&
 | 
					            (value.faction == "FC_GRINEER" || value.faction == "FC_CORPUS") &&
 | 
				
			||||||
            value.factionIndex < 2 &&
 | 
					 | 
				
			||||||
            !isArchwingMission(value) &&
 | 
					            !isArchwingMission(value) &&
 | 
				
			||||||
            value.missionIndex != 0 && // Exclude MT_ASSASSINATION
 | 
					            value.missionType != "MT_ASSASSINATION" &&
 | 
				
			||||||
            value.missionIndex != 23 && // Exclude junctions
 | 
					            value.missionType != "MT_JUNCTION" &&
 | 
				
			||||||
            value.missionIndex != 28 && // Exclude open worlds
 | 
					            value.missionType != "MT_LANDSCAPE" &&
 | 
				
			||||||
            value.missionIndex != 32 // Exclude railjack
 | 
					            value.missionType != "MT_RAILJACK"
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
            nodes.push(key);
 | 
					            nodes.push(key);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -3309,7 +3295,7 @@ export const isArchwingMission = (node: IRegion): boolean => {
 | 
				
			|||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // SettlementNode10
 | 
					    // SettlementNode10
 | 
				
			||||||
    if (node.missionIndex == 25) {
 | 
					    if (node.missionType == "MT_RACE") {
 | 
				
			||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,12 +1,13 @@
 | 
				
			|||||||
import type http from "http";
 | 
					import type http from "http";
 | 
				
			||||||
import type https from "https";
 | 
					import type https from "https";
 | 
				
			||||||
import type { default as ws } from "ws";
 | 
					import type { WebSocket } from "ws";
 | 
				
			||||||
import { WebSocketServer } from "ws";
 | 
					import { WebSocketServer } from "ws";
 | 
				
			||||||
import { Account } from "../models/loginModel.ts";
 | 
					import { Account } from "../models/loginModel.ts";
 | 
				
			||||||
import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "./loginService.ts";
 | 
					import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "./loginService.ts";
 | 
				
			||||||
import type { IDatabaseAccountJson } from "../types/loginTypes.ts";
 | 
					import type { IDatabaseAccountJson } from "../types/loginTypes.ts";
 | 
				
			||||||
import type { HydratedDocument } from "mongoose";
 | 
					import type { HydratedDocument } from "mongoose";
 | 
				
			||||||
import { logError } from "../utils/logger.ts";
 | 
					import { logError, logger } from "../utils/logger.ts";
 | 
				
			||||||
 | 
					import type { Request } from "express";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let wsServer: WebSocketServer | undefined;
 | 
					let wsServer: WebSocketServer | undefined;
 | 
				
			||||||
let wssServer: WebSocketServer | undefined;
 | 
					let wssServer: WebSocketServer | undefined;
 | 
				
			||||||
@ -44,9 +45,10 @@ export const stopWsServers = (promises: Promise<void>[]): void => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
let lastWsid: number = 0;
 | 
					let lastWsid: number = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IWsCustomData extends ws {
 | 
					interface IWsCustomData extends WebSocket {
 | 
				
			||||||
    id: number;
 | 
					    id: number;
 | 
				
			||||||
    accountId?: string;
 | 
					    accountId?: string;
 | 
				
			||||||
 | 
					    isGame?: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IWsMsgFromClient {
 | 
					interface IWsMsgFromClient {
 | 
				
			||||||
@ -55,11 +57,19 @@ interface IWsMsgFromClient {
 | 
				
			|||||||
        password: string;
 | 
					        password: string;
 | 
				
			||||||
        isRegister: boolean;
 | 
					        isRegister: boolean;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					    auth_game?: {
 | 
				
			||||||
 | 
					        accountId: string;
 | 
				
			||||||
 | 
					        nonce: number;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
    logout?: boolean;
 | 
					    logout?: boolean;
 | 
				
			||||||
 | 
					    sync_inventory?: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IWsMsgToClient {
 | 
					interface IWsMsgToClient {
 | 
				
			||||||
    //wsid?: number;
 | 
					    // common
 | 
				
			||||||
 | 
					    wsid?: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // to webui
 | 
				
			||||||
    reload?: boolean;
 | 
					    reload?: boolean;
 | 
				
			||||||
    ports?: {
 | 
					    ports?: {
 | 
				
			||||||
        http: number | undefined;
 | 
					        http: number | undefined;
 | 
				
			||||||
@ -77,9 +87,13 @@ interface IWsMsgToClient {
 | 
				
			|||||||
    nonce_updated?: boolean;
 | 
					    nonce_updated?: boolean;
 | 
				
			||||||
    update_inventory?: boolean;
 | 
					    update_inventory?: boolean;
 | 
				
			||||||
    logged_out?: boolean;
 | 
					    logged_out?: boolean;
 | 
				
			||||||
 | 
					    have_game_ws?: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // to game
 | 
				
			||||||
 | 
					    sync_inventory?: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
 | 
					const wsOnConnect = (ws: WebSocket, req: http.IncomingMessage): void => {
 | 
				
			||||||
    if (req.url == "/custom/selftest") {
 | 
					    if (req.url == "/custom/selftest") {
 | 
				
			||||||
        ws.send("SpaceNinjaServer");
 | 
					        ws.send("SpaceNinjaServer");
 | 
				
			||||||
        ws.close();
 | 
					        ws.close();
 | 
				
			||||||
@ -87,11 +101,12 @@ const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    (ws as IWsCustomData).id = ++lastWsid;
 | 
					    (ws as IWsCustomData).id = ++lastWsid;
 | 
				
			||||||
    ws.send(JSON.stringify({ wsid: lastWsid }));
 | 
					    ws.send(JSON.stringify({ wsid: lastWsid } satisfies IWsMsgToClient));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // eslint-disable-next-line @typescript-eslint/no-misused-promises
 | 
					    // eslint-disable-next-line @typescript-eslint/no-misused-promises
 | 
				
			||||||
    ws.on("message", async msg => {
 | 
					    ws.on("message", async msg => {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
 | 
					            //console.log(String(msg));
 | 
				
			||||||
            const data = JSON.parse(String(msg)) as IWsMsgFromClient;
 | 
					            const data = JSON.parse(String(msg)) as IWsMsgFromClient;
 | 
				
			||||||
            if (data.auth) {
 | 
					            if (data.auth) {
 | 
				
			||||||
                let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email });
 | 
					                let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email });
 | 
				
			||||||
@ -124,7 +139,8 @@ const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
 | 
				
			|||||||
                                id: account.id,
 | 
					                                id: account.id,
 | 
				
			||||||
                                DisplayName: account.DisplayName,
 | 
					                                DisplayName: account.DisplayName,
 | 
				
			||||||
                                Nonce: account.Nonce
 | 
					                                Nonce: account.Nonce
 | 
				
			||||||
                            }
 | 
					                            },
 | 
				
			||||||
 | 
					                            have_game_ws: haveGameWs(account.id)
 | 
				
			||||||
                        } satisfies IWsMsgToClient)
 | 
					                        } satisfies IWsMsgToClient)
 | 
				
			||||||
                    );
 | 
					                    );
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
@ -137,8 +153,23 @@ const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
 | 
				
			|||||||
                    );
 | 
					                    );
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            if (data.auth_game) {
 | 
				
			||||||
 | 
					                (ws as IWsCustomData).isGame = true;
 | 
				
			||||||
 | 
					                if (data.auth_game.nonce) {
 | 
				
			||||||
 | 
					                    const account: IDatabaseAccountJson | null = await Account.findOne({
 | 
				
			||||||
 | 
					                        _id: data.auth_game.accountId,
 | 
				
			||||||
 | 
					                        Nonce: data.auth_game.nonce
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                    if (account) {
 | 
				
			||||||
 | 
					                        (ws as IWsCustomData).accountId = account.id;
 | 
				
			||||||
 | 
					                        logger.debug(`got bootstrapper connection for ${account.id}`);
 | 
				
			||||||
 | 
					                        sendWsBroadcastToWebui({ have_game_ws: true }, account.id);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            if (data.logout) {
 | 
					            if (data.logout) {
 | 
				
			||||||
                const accountId = (ws as IWsCustomData).accountId;
 | 
					                const accountId = (ws as IWsCustomData).accountId;
 | 
				
			||||||
 | 
					                if (accountId) {
 | 
				
			||||||
                    (ws as IWsCustomData).accountId = undefined;
 | 
					                    (ws as IWsCustomData).accountId = undefined;
 | 
				
			||||||
                    await Account.updateOne(
 | 
					                    await Account.updateOne(
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
@ -150,64 +181,124 @@ const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
 | 
				
			|||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    );
 | 
					                    );
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (data.sync_inventory) {
 | 
				
			||||||
 | 
					                const accountId = (ws as IWsCustomData).accountId;
 | 
				
			||||||
 | 
					                if (accountId) {
 | 
				
			||||||
 | 
					                    sendWsBroadcastToGame(accountId, { sync_inventory: true });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        } catch (e) {
 | 
					        } catch (e) {
 | 
				
			||||||
            logError(e as Error, `processing websocket message`);
 | 
					            logError(e as Error, `processing websocket message`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    // eslint-disable-next-line @typescript-eslint/no-misused-promises
 | 
				
			||||||
 | 
					    ws.on("close", async () => {
 | 
				
			||||||
 | 
					        if ((ws as IWsCustomData).isGame && (ws as IWsCustomData).accountId) {
 | 
				
			||||||
 | 
					            logger.debug(`lost bootstrapper connection for ${(ws as IWsCustomData).accountId}`);
 | 
				
			||||||
 | 
					            sendWsBroadcastToWebui({ have_game_ws: false }, (ws as IWsCustomData).accountId);
 | 
				
			||||||
 | 
					            await Account.updateOne(
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _id: (ws as IWsCustomData).accountId
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Dropped: true
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const forEachClient = (cb: (client: IWsCustomData) => void): void => {
 | 
				
			||||||
 | 
					    if (wsServer) {
 | 
				
			||||||
 | 
					        for (const client of wsServer.clients) {
 | 
				
			||||||
 | 
					            cb(client as IWsCustomData);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (wssServer) {
 | 
				
			||||||
 | 
					        for (const client of wssServer.clients) {
 | 
				
			||||||
 | 
					            cb(client as IWsCustomData);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const haveGameWs = (accountId: string): boolean => {
 | 
				
			||||||
 | 
					    let ret = false;
 | 
				
			||||||
 | 
					    forEachClient(client => {
 | 
				
			||||||
 | 
					        if (client.isGame && client.accountId == accountId) {
 | 
				
			||||||
 | 
					            ret = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return ret;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const sendWsBroadcast = (data: IWsMsgToClient): void => {
 | 
					export const sendWsBroadcast = (data: IWsMsgToClient): void => {
 | 
				
			||||||
    const msg = JSON.stringify(data);
 | 
					    const msg = JSON.stringify(data);
 | 
				
			||||||
    if (wsServer) {
 | 
					    forEachClient(client => {
 | 
				
			||||||
        for (const client of wsServer.clients) {
 | 
					 | 
				
			||||||
        client.send(msg);
 | 
					        client.send(msg);
 | 
				
			||||||
        }
 | 
					    });
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (wssServer) {
 | 
					 | 
				
			||||||
        for (const client of wssServer.clients) {
 | 
					 | 
				
			||||||
            client.send(msg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void => {
 | 
					export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void => {
 | 
				
			||||||
    const msg = JSON.stringify(data);
 | 
					    const msg = JSON.stringify(data);
 | 
				
			||||||
    if (wsServer) {
 | 
					    forEachClient(client => {
 | 
				
			||||||
        for (const client of wsServer.clients) {
 | 
					        if (client.accountId == accountId) {
 | 
				
			||||||
            if ((client as IWsCustomData).accountId == accountId) {
 | 
					 | 
				
			||||||
            client.send(msg);
 | 
					            client.send(msg);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        }
 | 
					    });
 | 
				
			||||||
    }
 | 
					};
 | 
				
			||||||
    if (wssServer) {
 | 
					
 | 
				
			||||||
        for (const client of wssServer.clients) {
 | 
					export const sendWsBroadcastToGame = (accountId: string, data: IWsMsgToClient): void => {
 | 
				
			||||||
            if ((client as IWsCustomData).accountId == accountId) {
 | 
					    const msg = JSON.stringify(data);
 | 
				
			||||||
 | 
					    forEachClient(client => {
 | 
				
			||||||
 | 
					        if (client.isGame && client.accountId == accountId) {
 | 
				
			||||||
            client.send(msg);
 | 
					            client.send(msg);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        }
 | 
					    });
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId?: string, excludeWsid?: number): void => {
 | 
					export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId?: string, excludeWsid?: number): void => {
 | 
				
			||||||
    const msg = JSON.stringify(data);
 | 
					    const msg = JSON.stringify(data);
 | 
				
			||||||
    if (wsServer) {
 | 
					    forEachClient(client => {
 | 
				
			||||||
        for (const client of wsServer.clients) {
 | 
					        if ((!accountId || client.accountId == accountId) && client.id != excludeWsid) {
 | 
				
			||||||
            if (
 | 
					 | 
				
			||||||
                (!accountId || (client as IWsCustomData).accountId == accountId) &&
 | 
					 | 
				
			||||||
                (client as IWsCustomData).id != excludeWsid
 | 
					 | 
				
			||||||
            ) {
 | 
					 | 
				
			||||||
            client.send(msg);
 | 
					            client.send(msg);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        }
 | 
					    });
 | 
				
			||||||
    }
 | 
					};
 | 
				
			||||||
    if (wssServer) {
 | 
					
 | 
				
			||||||
        for (const client of wssServer.clients) {
 | 
					export const sendWsBroadcastToWebui = (data: IWsMsgToClient, accountId?: string, excludeWsid?: number): void => {
 | 
				
			||||||
            if (
 | 
					    const msg = JSON.stringify(data);
 | 
				
			||||||
                (!accountId || (client as IWsCustomData).accountId == accountId) &&
 | 
					    forEachClient(client => {
 | 
				
			||||||
                (client as IWsCustomData).id != excludeWsid
 | 
					        if (!client.isGame && (!accountId || client.accountId == accountId) && client.id != excludeWsid) {
 | 
				
			||||||
            ) {
 | 
					 | 
				
			||||||
            client.send(msg);
 | 
					            client.send(msg);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        }
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const broadcastInventoryUpdate = (req: Request): void => {
 | 
				
			||||||
 | 
					    const accountId = req.query.accountId as string;
 | 
				
			||||||
 | 
					    if (req.query.wsid) {
 | 
				
			||||||
 | 
					        // for webui requests, let other tabs and the game know
 | 
				
			||||||
 | 
					        sendWsBroadcastEx(
 | 
				
			||||||
 | 
					            { sync_inventory: true, update_inventory: true },
 | 
				
			||||||
 | 
					            accountId,
 | 
				
			||||||
 | 
					            parseInt(String(req.query.wsid))
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        // for game requests, let all webui tabs know
 | 
				
			||||||
 | 
					        sendWsBroadcastToWebui({ update_inventory: true }, accountId, parseInt(String(req.query.wsid)));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const handleNonceInvalidation = (accountId: string): void => {
 | 
				
			||||||
 | 
					    forEachClient(client => {
 | 
				
			||||||
 | 
					        if (client.accountId == accountId) {
 | 
				
			||||||
 | 
					            if (client.isGame) {
 | 
				
			||||||
 | 
					                client.accountId = undefined; // prevent processing of the close event
 | 
				
			||||||
 | 
					                client.close();
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                client.send(JSON.stringify({ nonce_updated: true, have_game_ws: false } satisfies IWsMsgToClient));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -31,8 +31,17 @@ export interface IGuildClient {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    GoalProgress?: IGoalProgressClient[];
 | 
					    GoalProgress?: IGoalProgressClient[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					// Fields specific to SNS
 | 
				
			||||||
 | 
					export interface IGuildCheats {
 | 
				
			||||||
 | 
					    noDojoRoomBuildStage?: boolean;
 | 
				
			||||||
 | 
					    noDojoDecoBuildStage?: boolean;
 | 
				
			||||||
 | 
					    fastDojoRoomDestruction?: boolean;
 | 
				
			||||||
 | 
					    noDojoResearchCosts?: boolean;
 | 
				
			||||||
 | 
					    noDojoResearchTime?: boolean;
 | 
				
			||||||
 | 
					    fastClanAscension?: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IGuildDatabase {
 | 
					export interface IGuildDatabase extends IGuildCheats {
 | 
				
			||||||
    _id: Types.ObjectId;
 | 
					    _id: Types.ObjectId;
 | 
				
			||||||
    Name: string;
 | 
					    Name: string;
 | 
				
			||||||
    MOTD: string;
 | 
					    MOTD: string;
 | 
				
			||||||
 | 
				
			|||||||
@ -977,10 +977,10 @@ export interface IQuestKeyClient extends Omit<IQuestKeyDatabase, "CompletionDate
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IQuestStage {
 | 
					export interface IQuestStage {
 | 
				
			||||||
    c?: number;
 | 
					    c: number;
 | 
				
			||||||
    i?: boolean;
 | 
					    i: boolean;
 | 
				
			||||||
    m?: boolean;
 | 
					    m: boolean;
 | 
				
			||||||
    b?: any[];
 | 
					    b: any[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IRawUpgrade {
 | 
					export interface IRawUpgrade {
 | 
				
			||||||
 | 
				
			|||||||
@ -120,18 +120,6 @@ export type IBinChanges = {
 | 
				
			|||||||
    Extra?: number;
 | 
					    Extra?: number;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type SlotPurchaseName =
 | 
					 | 
				
			||||||
    | "SuitSlotItem"
 | 
					 | 
				
			||||||
    | "TwoSentinelSlotItem"
 | 
					 | 
				
			||||||
    | "TwoWeaponSlotItem"
 | 
					 | 
				
			||||||
    | "SpaceSuitSlotItem"
 | 
					 | 
				
			||||||
    | "TwoSpaceWeaponSlotItem"
 | 
					 | 
				
			||||||
    | "MechSlotItem"
 | 
					 | 
				
			||||||
    | "TwoOperatorWeaponSlotItem"
 | 
					 | 
				
			||||||
    | "RandomModSlotItem"
 | 
					 | 
				
			||||||
    | "TwoCrewShipSalvageSlotItem"
 | 
					 | 
				
			||||||
    | "CrewMemberSlotItem";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const slotNames = [
 | 
					export const slotNames = [
 | 
				
			||||||
    "SuitBin",
 | 
					    "SuitBin",
 | 
				
			||||||
    "WeaponBin",
 | 
					    "WeaponBin",
 | 
				
			||||||
@ -148,7 +136,3 @@ export const slotNames = [
 | 
				
			|||||||
] as const;
 | 
					] as const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type SlotNames = (typeof slotNames)[number];
 | 
					export type SlotNames = (typeof slotNames)[number];
 | 
				
			||||||
 | 
					 | 
				
			||||||
export type SlotPurchase = {
 | 
					 | 
				
			||||||
    [P in SlotPurchaseName]: { name: SlotNames; purchaseQuantity: number };
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -45,6 +45,15 @@ export type IMissionInventoryUpdateRequest = {
 | 
				
			|||||||
    EmailItems?: ITypeCount[];
 | 
					    EmailItems?: ITypeCount[];
 | 
				
			||||||
    ShipDecorations?: ITypeCount[];
 | 
					    ShipDecorations?: ITypeCount[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // flags for interstitial requests
 | 
				
			||||||
 | 
					    BMI?: boolean;
 | 
				
			||||||
 | 
					    TNT?: boolean; // Conservation; definitely need to include AffiliationMods in this case, so a normal 'inventory sync' would not work here.
 | 
				
			||||||
 | 
					    SSC?: boolean; // K-Drive race?
 | 
				
			||||||
 | 
					    RJ?: boolean; // Railjack. InventoryJson should only be returned when going back to dojo.
 | 
				
			||||||
 | 
					    SS?: boolean;
 | 
				
			||||||
 | 
					    CMI?: boolean;
 | 
				
			||||||
 | 
					    EJC?: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    SyndicateId?: string;
 | 
					    SyndicateId?: string;
 | 
				
			||||||
    SortieId?: string;
 | 
					    SortieId?: string;
 | 
				
			||||||
    CalendarProgress?: { challenge: string }[];
 | 
					    CalendarProgress?: { challenge: string }[];
 | 
				
			||||||
@ -149,7 +158,6 @@ export type IMissionInventoryUpdateRequest = {
 | 
				
			|||||||
        MultiProgress: unknown[];
 | 
					        MultiProgress: unknown[];
 | 
				
			||||||
    }[];
 | 
					    }[];
 | 
				
			||||||
    InvasionProgress?: IInvasionProgressClient[];
 | 
					    InvasionProgress?: IInvasionProgressClient[];
 | 
				
			||||||
    RJ?: boolean;
 | 
					 | 
				
			||||||
    ConquestMissionsCompleted?: number;
 | 
					    ConquestMissionsCompleted?: number;
 | 
				
			||||||
    duviriSuitSelection?: string;
 | 
					    duviriSuitSelection?: string;
 | 
				
			||||||
    duviriPistolSelection?: string;
 | 
					    duviriPistolSelection?: string;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,104 +0,0 @@
 | 
				
			|||||||
[
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/AmbulasEventBronzeTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/AmbulasEventGoldTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/AmbulasEventSilverTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/AmbulasEventTerracottaTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/AridFearBronzeTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/AridFearGoldTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/AridFearSilverTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/BreedingGroundsBronzeTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/BreedingGroundsGoldTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/BreedingGroundsSilverTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/CiceroCrisisBronzeTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/CiceroCrisisGoldTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/CiceroCrisisSilverTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DisruptionEventBronzeTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DisruptionEventGoldTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DisruptionEventSilverTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DisruptionEventTerracottaTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophyBronzeARecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophyGoldARecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophyPlatinumARecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophySilverARecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventBronzeTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventClayTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventGoldTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventSilverTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventBaseTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventBronzeTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventGoldTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventSilverTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventTerracottaTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EvacuationEventBronzeTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EvacuationEventGoldTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EvacuationEventSilverTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EvacuationEventTerracottaTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EyesOfBlightTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/FalseProfitBronzeTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/FalseProfitClayTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/FalseProfitGoldTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/FalseProfitSilverTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/FusionMoaTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/GradivusDilemmaCorpusBronzeTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/GradivusDilemmaCorpusGoldTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/GradivusDilemmaCorpusSilverTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/GradivusDilemmaGrineerBronzeTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/GradivusDilemmaGrineerGoldTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/GradivusDilemmaGrineerSilverTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/IcePlanetTrophyBronzeRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/IcePlanetTrophyGoldRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/IcePlanetTrophySilverRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/JadeShadowsEventBaseTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/JadeShadowsEventBronzeTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/JadeShadowsEventGoldTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/JadeShadowsEventPewterTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/JadeShadowsEventSilverTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/MechEventTrophyBronzeRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/MechEventTrophyGoldRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/MechEventTrophySilverRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/MechEventTrophyTerracottaRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/MutalistIncursionBronzeTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/MutalistIncursionGoldTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/MutalistIncursionSilverTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/OrokinMusicBoxRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/OrokinSabotageTrophyBronzeRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/OrokinSabotageTrophyGoldRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/OrokinSabotageTrophySilverRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ProjectSinisterBronzeTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ProjectSinisterClayTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ProjectSinisterGoldTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ProjectSinisterSilverTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/RailjackResearchTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/RathuumBronzeTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/RathuumClayTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/RathuumGoldTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/RathuumSilverTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ShipyardsEventBronzeTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ShipyardsEventGoldTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ShipyardsEventSilverTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/SlingStoneTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/SpyDroneTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/SurvivalEventBronzeTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/SurvivalEventGoldTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/SurvivalEventSilverTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/TennoConDojoGhostTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/TennoConDojoMoonTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/TennoConDojoMountainTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/TennoConDojoShadowTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/TennoConDojoStormTrophyRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophyBronzeRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophyCrystalRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophyGoldRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophySilverRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/CorpusPlaceables/GasTurbineConeRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NaturalPlaceables/CoralChunkARecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/TennoPlaceables/TnoBeaconEmitterRecipe",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/OstronFemaleSitting",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/OstronFemaleStanding",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/OstronMaleStanding",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/OstronMaleStandingTwo",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/SolarisForeman",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/SolarisHazard",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/SolarisStrikerOne",
 | 
					 | 
				
			||||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/SolarisStrikerThree"
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
@ -1,491 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/BirdOfPrey/CommonBirdOfPreyAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagCondrocCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/BirdOfPrey/CommonFemaleBirdOfPreyAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagCondrocCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/BirdOfPrey/CommonMaleBirdOfPreyAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagCondrocCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/BirdOfPrey/RareBirdOfPreyAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagCondrocRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/BirdOfPrey/RareFemaleBirdOfPreyAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagCondrocRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/BirdOfPrey/RareMaleBirdOfPreyAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagCondrocRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/BirdOfPrey/UncommonBirdOfPreyAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagCondrocUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/BirdOfPrey/UncommonFemaleBirdOfPreyAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagCondrocUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/BirdOfPrey/UncommonMaleBirdOfPreyAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagCondrocUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedCritter/BaseInfestedCritterAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Conservation/ConservationTagItem",
 | 
					 | 
				
			||||||
    "extraReward": "/Lotus/Types/Items/Conservation/WoundedAnimalRewardItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedCritter/CommonInfestedCritterAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedCritterCommon",
 | 
					 | 
				
			||||||
    "extraReward": "/Lotus/Types/Items/Deimos/WoundedInfestedCritterCommonRewardItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedCritter/RareInfestedCritterAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedCritterRare",
 | 
					 | 
				
			||||||
    "extraReward": "/Lotus/Types/Items/Deimos/WoundedInfestedCritterRareRewardItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedCritter/UncommonInfestedCritterAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedCritterUncommon",
 | 
					 | 
				
			||||||
    "extraReward": "/Lotus/Types/Items/Deimos/WoundedInfestedCritterUncommonRewardItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedKDrive/GrottoInfKDriveAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedKdriveUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedKDrive/HighlandInfKDriveAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedKdriveRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedKDrive/SwampInfKDriveAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedKdriveCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedMaggot/CommonInfestedMaggotAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMaggotCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedMaggot/RareInfestedMaggotAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMaggotRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedMaggot/UncommonInfestedMaggotAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMaggotUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedMergoo/CommonInfestedMergooAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMergooCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedMergoo/RareInfestedMergooAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMergooRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedMergoo/UncommonInfestedMergooAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMergooUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedNexifera/BaseInfestedNexiferaAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedNexiferaCommon",
 | 
					 | 
				
			||||||
    "extraReward": "/Lotus/Types/Items/Conservation/WoundedAnimalRewardItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedNexifera/CommonInfestedNexiferaAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedNexiferaCommon",
 | 
					 | 
				
			||||||
    "extraReward": "/Lotus/Types/Items/Conservation/WoundedAnimalRewardItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedNexifera/RareInfestedNexiferaAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedNexiferaRare",
 | 
					 | 
				
			||||||
    "extraReward": "/Lotus/Types/Items/Conservation/WoundedAnimalRewardItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedNexifera/UncommonInfestedNexiferaAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedNexiferaUncommon",
 | 
					 | 
				
			||||||
    "extraReward": "/Lotus/Types/Items/Conservation/WoundedAnimalRewardItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedPredator/BaseInfestedPredatorAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Conservation/ConservationTagItem",
 | 
					 | 
				
			||||||
    "extraReward": "/Lotus/Types/Items/Conservation/WoundedAnimalRewardItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedPredator/CommonInfestedPredatorAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedPredatorCommon",
 | 
					 | 
				
			||||||
    "extraReward": "/Lotus/Types/Items/Deimos/WoundedInfestedPredatorCommonRewardItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedPredator/RareInfestedPredatorAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedPredatorRare",
 | 
					 | 
				
			||||||
    "extraReward": "/Lotus/Types/Items/Deimos/WoundedInfestedPredatorRareRewardItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedPredator/UncommonInfestedPredatorAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedPredatorUncommon",
 | 
					 | 
				
			||||||
    "extraReward": "/Lotus/Types/Items/Deimos/WoundedInfestedPredatorUncommonRewardItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedUndazoa/BaseUndazoaAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedZongroCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedUndazoa/CommonUndazoaAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedZongroCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedUndazoa/RareUndazoaAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedZongroRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedUndazoa/UncommonUndazoaAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedZongroUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Duviri/Rabbit/BaseDuviriRabbitAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Duviri/Rabbit/TeshinRabbitAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Duviri/Rabbit/TeshinRabbitOnHandAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Duviri/Wolf/DuviriWolfAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/Duviri/Wolf/DuviriWolfConservationAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/ForestRodent/CommonFemaleForestRodentAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagKuakaCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/ForestRodent/CommonForestRodentAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagKuakaCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/ForestRodent/CommonMaleForestRodentAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagKuakaCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/ForestRodent/RareFemaleForestRodentAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagKuakaRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/ForestRodent/RareForestRodentAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagKuakaRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/ForestRodent/RareMaleForestRodentAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagKuakaRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/ForestRodent/TutorialForestRodentAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagKuakaCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/ForestRodent/UncommonFemaleForestRodentAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagKuakaUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/ForestRodent/UncommonForestRodentAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagKuakaUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/ForestRodent/UncommonMaleForestRodentAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagKuakaUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/BaseLegendaryKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/CommonFemaleLegendaryKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagKubrodonCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/CommonLegendaryKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagKubrodonCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/CommonMaleLegendaryKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagKubrodonCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/CommonPupLegendaryKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagKubrodonCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/RareFemaleLegendaryKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagKubrodonRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/RareLegendaryKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagKubrodonRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/RareMaleLegendaryKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagKubrodonRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/RarePupLegendaryKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagKubrodonRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/UncommonFemaleLegendaryKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagKubrodonUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/UncommonLegendaryKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagKubrodonUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/UncommonMaleLegendaryKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagKubrodonUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/UncommonPupLegendaryKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagKubrodonUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/BaseOrokinKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/CommonFemaleOrokinKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagStoverCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/CommonMaleOrokinKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagStoverCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/CommonOrokinKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagStoverCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/CommonPupOrokinKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagStoverCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/RareFemaleOrokinKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagStoverRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/RareMaleOrokinKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagStoverRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/RareOrokinKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagStoverRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/RarePupOrokinKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagStoverRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/UncommonFemaleOrokinKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagStoverUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/UncommonMaleOrokinKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagStoverUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/UncommonOrokinKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagStoverUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/UncommonPupOrokinKubrowAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagStoverUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/OstronSeaBird/CommonFemaleOstronSeaBirdAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagMergooCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/OstronSeaBird/CommonMaleOstronSeaBirdAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagMergooCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/OstronSeaBird/CommonOstronSeaBirdAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagMergooCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/OstronSeaBird/RareFemaleOstronSeaBirdAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagMergooRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/OstronSeaBird/RareMaleOstronSeaBirdAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagMergooRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/OstronSeaBird/RareOstronSeaBirdAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagMergooRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/OstronSeaBird/UncommonFemaleOstronSeaBirdAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagMergooUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/OstronSeaBird/UncommonMaleOstronSeaBirdAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagMergooUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/OstronSeaBird/UncommonOstronSeaBirdAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagMergooUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/BaseSnowArmadilloAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/CommonFemaleSnowArmadilloAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagBolarolaCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/CommonMaleSnowArmadilloAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagBolarolaCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/CommonPupSnowArmadilloAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagBolarolaCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/CommonSnowArmadilloAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagBolarolaCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/RareFemaleSnowArmadilloAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagBolarolaRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/RareMaleSnowArmadilloAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagBolarolaRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/RarePupSnowArmadilloAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagBolarolaRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/RareSnowArmadilloAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagBolarolaRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/UncommonFemaleSnowArmadilloAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagBolarolaUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/UncommonMaleSnowArmadilloAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagBolarolaUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/UncommonPupSnowArmadilloAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagBolarolaUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/UncommonSnowArmadilloAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagBolarolaUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowBird/BaseSnowBirdAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowBird/CommonFemaleSnowBirdAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagSawgawCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowBird/CommonMaleSnowBirdAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagSawgawCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowBird/CommonPupSnowBirdAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagSawgawCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowBird/CommonSnowBirdAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagSawgawCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowBird/RareFemaleSnowBirdAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagSawgawRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowBird/RareMaleSnowBirdAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagSawgawRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowBird/RarePupSnowBirdAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagSawgawRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowBird/RareSnowBirdAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagSawgawRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowBird/UncommonFemaleSnowBirdAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagSawgawUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowBird/UncommonMaleSnowBirdAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagSawgawUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowBird/UncommonPupSnowBirdAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagSawgawUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowBird/UncommonSnowBirdAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagSawgawUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/BaseSnowCritterAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/CommonFemaleSnowCritterAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagVirminkCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/CommonMaleSnowCritterAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagVirminkCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/CommonPupSnowCritterAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagVirminkCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/CommonSnowCritterAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagVirminkCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/RareFemaleSnowCritterAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagVirminkRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/RareMaleSnowCritterAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagVirminkRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/RarePupSnowCritterAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagVirminkRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/RareSnowCritterAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagVirminkRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/UncommonFemaleSnowCritterAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagVirminkUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/UncommonMaleSnowCritterAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagVirminkUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/UncommonPupSnowCritterAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagVirminkUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/UncommonSnowCritterAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagVirminkUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/BaseSnowPredatorAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/CommonFemaleSnowPredatorAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagHorrasqueCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/CommonMaleSnowPredatorAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagHorrasqueCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/CommonPupSnowPredatorAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagHorrasqueCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/CommonSnowPredatorAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagHorrasqueCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/RareFemaleSnowPredatorAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagHorrasqueRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/RareMaleSnowPredatorAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagHorrasqueRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/RarePupSnowPredatorAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagHorrasqueRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/RareSnowPredatorAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagHorrasqueRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/UncommonFemaleSnowPredatorAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagHorrasqueUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/UncommonMaleSnowPredatorAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagHorrasqueUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/UncommonPupSnowPredatorAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagHorrasqueUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/UncommonSnowPredatorAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagHorrasqueUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowRodent/BaseSnowRodentAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowRodent/CommonFemaleSnowRodentAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagPobbersCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowRodent/CommonMaleSnowRodentAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagPobbersCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowRodent/CommonSnowRodentAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagPobbersCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowRodent/RareFemaleSnowRodentAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagPobbersRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowRodent/RareMaleSnowRodentAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagPobbersRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowRodent/RareSnowRodentAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagPobbersRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowRodent/UncommonFemaleSnowRodentAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagPobbersUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowRodent/UncommonMaleSnowRodentAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagPobbersUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/SnowRodent/UncommonSnowRodentAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Solaris/AnimalTagPobbersUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/BaseVampireKavatAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/CommonVampireKavatAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagVampireKavatCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/CommonVampireKavatCubAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagVampireKavatCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/CommonVampireKavatFemaleAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagVampireKavatCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/CommonVampireKavatMaleAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagVampireKavatCommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/RareVampireKavatAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagVampireKavatRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/RareVampireKavatCubAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagVampireKavatRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/RareVampireKavatFemaleAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagVampireKavatRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/RareVampireKavatMaleAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagVampireKavatRare"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/UncommonVampireKavatAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagVampireKavatUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/UncommonVampireKavatCubAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagVampireKavatUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/UncommonVampireKavatFemaleAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagVampireKavatUncommon"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/UncommonVampireKavatMaleAvatar": {
 | 
					 | 
				
			||||||
    "tag": "/Lotus/Types/Items/Eidolon/AnimalTagVampireKavatUncommon"
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -6,7 +6,7 @@
 | 
				
			|||||||
      "Messages": [
 | 
					      "Messages": [
 | 
				
			||||||
        { "LanguageCode": "fr", "Message": "Rejoignez le Discord OpenWF!" },
 | 
					        { "LanguageCode": "fr", "Message": "Rejoignez le Discord OpenWF!" },
 | 
				
			||||||
        { "LanguageCode": "it", "Message": "Unisciti al Discord di OpenWF!" },
 | 
					        { "LanguageCode": "it", "Message": "Unisciti al Discord di OpenWF!" },
 | 
				
			||||||
        { "LanguageCode": "de", "Message": "Tritt dem OpenWF Discord bei!" },
 | 
					        { "LanguageCode": "de", "Message": "Trete dem OpenWF Discord bei!" },
 | 
				
			||||||
        { "LanguageCode": "es", "Message": "Únete al Discord de OpenWF!" },
 | 
					        { "LanguageCode": "es", "Message": "Únete al Discord de OpenWF!" },
 | 
				
			||||||
        { "LanguageCode": "pt", "Message": "Junte-se ao Discord do OpenWF!" },
 | 
					        { "LanguageCode": "pt", "Message": "Junte-se ao Discord do OpenWF!" },
 | 
				
			||||||
        { "LanguageCode": "ru", "Message": "Присоединяйтесь к OpenWF Discord!" },
 | 
					        { "LanguageCode": "ru", "Message": "Присоединяйтесь к OpenWF Discord!" },
 | 
				
			||||||
 | 
				
			|||||||
@ -71,6 +71,9 @@
 | 
				
			|||||||
                        <li class="nav-item">
 | 
					                        <li class="nav-item">
 | 
				
			||||||
                            <a class="nav-link" href="/webui/import" data-bs-dismiss="offcanvas" data-bs-target="#sidebar" data-loc="navbar_import"></a>
 | 
					                            <a class="nav-link" href="/webui/import" data-bs-dismiss="offcanvas" data-bs-target="#sidebar" data-loc="navbar_import"></a>
 | 
				
			||||||
                        </li>
 | 
					                        </li>
 | 
				
			||||||
 | 
					                        <li class="nav-item d-none" id="nav-guildView">
 | 
				
			||||||
 | 
					                            <a class="nav-link" href="/webui/guildView" data-bs-dismiss="offcanvas" data-bs-target="#sidebar" data-loc="navbar_guildView"></a>
 | 
				
			||||||
 | 
					                        </li>
 | 
				
			||||||
                    </ul>
 | 
					                    </ul>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
@ -90,7 +93,7 @@
 | 
				
			|||||||
                </form>
 | 
					                </form>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div data-route="/webui/inventory" data-title="Inventory | OpenWF WebUI">
 | 
					            <div data-route="/webui/inventory" data-title="Inventory | OpenWF WebUI">
 | 
				
			||||||
                <p class="mb-3" data-loc="general_inventoryUpdateNote"></p>
 | 
					                <p class="mb-3 inventory-update-note"></p>
 | 
				
			||||||
                <div class="card mb-3">
 | 
					                <div class="card mb-3">
 | 
				
			||||||
                    <div class="card-header">
 | 
					                    <div class="card-header">
 | 
				
			||||||
                        <ul class="nav nav-tabs card-header-tabs">
 | 
					                        <ul class="nav nav-tabs card-header-tabs">
 | 
				
			||||||
@ -105,9 +108,9 @@
 | 
				
			|||||||
                    <div class="card-body">
 | 
					                    <div class="card-body">
 | 
				
			||||||
                        <div class="tab-content">
 | 
					                        <div class="tab-content">
 | 
				
			||||||
                            <div class="tab-pane" id="miscItems-tab-content">
 | 
					                            <div class="tab-pane" id="miscItems-tab-content">
 | 
				
			||||||
                                <form class="card-body input-group" onsubmit="doAcquireMiscItems();return false;">
 | 
					                                <form class="card-body input-group" onsubmit="doAcquireCountItems('miscitems');return false;">
 | 
				
			||||||
                                    <input class="form-control" id="miscitem-count" type="number" value="1" />
 | 
					                                    <input class="form-control" id="miscitems-count" type="number" value="1" />
 | 
				
			||||||
                                    <input class="form-control w-50" id="miscitem-type" list="datalist-miscitems" />
 | 
					                                    <input class="form-control w-50" id="acquire-type-miscitems" list="datalist-miscitems" />
 | 
				
			||||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
					                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
@ -177,30 +180,33 @@
 | 
				
			|||||||
                    <div class="col-lg-6">
 | 
					                    <div class="col-lg-6">
 | 
				
			||||||
                        <div class="card" style="height: 400px;">
 | 
					                        <div class="card" style="height: 400px;">
 | 
				
			||||||
                            <h5 class="card-header" data-loc="inventory_suits"></h5>
 | 
					                            <h5 class="card-header" data-loc="inventory_suits"></h5>
 | 
				
			||||||
                            <div class="card-body overflow-auto">
 | 
					                            <div class="card-body d-flex flex-column">
 | 
				
			||||||
                                <form class="input-group mb-3" onsubmit="doAcquireEquipment('Suits');return false;">
 | 
					                                <form class="input-group mb-3" onsubmit="doAcquireEquipment('Suits');return false;">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-Suits" list="datalist-Suits" />
 | 
					                                    <input class="form-control" id="acquire-type-Suits" list="datalist-Suits" />
 | 
				
			||||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
					                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
 | 
					                                <div class="overflow-auto">
 | 
				
			||||||
                                    <table class="table table-hover w-100">
 | 
					                                    <table class="table table-hover w-100">
 | 
				
			||||||
                                        <tbody id="Suits-list"></tbody>
 | 
					                                        <tbody id="Suits-list"></tbody>
 | 
				
			||||||
                                    </table>
 | 
					                                    </table>
 | 
				
			||||||
                                </div>
 | 
					                                </div>
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
                    <div class="col-lg-6">
 | 
					                    <div class="col-lg-6">
 | 
				
			||||||
                        <div class="card" style="height: 400px;">
 | 
					                        <div class="card" style="height: 400px;">
 | 
				
			||||||
                            <h5 class="card-header" data-loc="inventory_longGuns"></h5>
 | 
					                            <h5 class="card-header" data-loc="inventory_longGuns"></h5>
 | 
				
			||||||
                            <div class="card-body overflow-auto">
 | 
					                            <div class="card-body d-flex flex-column">
 | 
				
			||||||
                                <form class="input-group mb-3" onsubmit="handleModularSelection('LongGuns');return false;">
 | 
					                                <form class="input-group mb-3" onsubmit="handleModularSelection('LongGuns');return false;">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-LongGuns" list="datalist-LongGuns" />
 | 
					                                    <input class="form-control" id="acquire-type-LongGuns" list="datalist-LongGuns" />
 | 
				
			||||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
					                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
                                <form class="input-group mb-3" id="modular-LongGuns" style="display: none;">
 | 
					                                <form class="input-group mb-3 d-none" id="modular-LongGuns">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-LongGuns-GUN_BARREL" list="datalist-ModularParts-GUN_BARREL" />
 | 
					                                    <input class="form-control" id="acquire-type-LongGuns-GUN_BARREL" list="datalist-ModularParts-GUN_BARREL" />
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-LongGuns-GUN_PRIMARY_HANDLE" list="datalist-ModularParts-GUN_PRIMARY_HANDLE" />
 | 
					                                    <input class="form-control" id="acquire-type-LongGuns-GUN_PRIMARY_HANDLE" list="datalist-ModularParts-GUN_PRIMARY_HANDLE" />
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-LongGuns-GUN_CLIP" list="datalist-ModularParts-GUN_CLIP" />
 | 
					                                    <input class="form-control" id="acquire-type-LongGuns-GUN_CLIP" list="datalist-ModularParts-GUN_CLIP" />
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
 | 
					                                <div class="overflow-auto">
 | 
				
			||||||
                                    <table class="table table-hover w-100">
 | 
					                                    <table class="table table-hover w-100">
 | 
				
			||||||
                                        <tbody id="LongGuns-list"></tbody>
 | 
					                                        <tbody id="LongGuns-list"></tbody>
 | 
				
			||||||
                                    </table>
 | 
					                                    </table>
 | 
				
			||||||
@ -208,39 +214,43 @@
 | 
				
			|||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
                <div class="row g-3 mb-3">
 | 
					                <div class="row g-3 mb-3">
 | 
				
			||||||
                    <div class="col-lg-6">
 | 
					                    <div class="col-lg-6">
 | 
				
			||||||
                        <div class="card" style="height: 400px;">
 | 
					                        <div class="card" style="height: 400px;">
 | 
				
			||||||
                            <h5 class="card-header" data-loc="inventory_pistols"></h5>
 | 
					                            <h5 class="card-header" data-loc="inventory_pistols"></h5>
 | 
				
			||||||
                            <div class="card-body overflow-auto">
 | 
					                            <div class="card-body d-flex flex-column">
 | 
				
			||||||
                                <form class="input-group mb-3" onsubmit="handleModularSelection('Pistols');return false;">
 | 
					                                <form class="input-group mb-3" onsubmit="handleModularSelection('Pistols');return false;">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-Pistols" list="datalist-Pistols" />
 | 
					                                    <input class="form-control" id="acquire-type-Pistols" list="datalist-Pistols" />
 | 
				
			||||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
					                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
                                <form class="input-group mb-3" id="modular-Pistols" style="display: none;">
 | 
					                                <form class="input-group mb-3 d-none" id="modular-Pistols">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-Pistols-GUN_BARREL" list="datalist-ModularParts-GUN_BARREL" />
 | 
					                                    <input class="form-control" id="acquire-type-Pistols-GUN_BARREL" list="datalist-ModularParts-GUN_BARREL" />
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-Pistols-GUN_SECONDARY_HANDLE" list="datalist-ModularParts-GUN_SECONDARY_HANDLE" />
 | 
					                                    <input class="form-control" id="acquire-type-Pistols-GUN_SECONDARY_HANDLE" list="datalist-ModularParts-GUN_SECONDARY_HANDLE" />
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-Pistols-GUN_CLIP" list="datalist-ModularParts-GUN_CLIP" />
 | 
					                                    <input class="form-control" id="acquire-type-Pistols-GUN_CLIP" list="datalist-ModularParts-GUN_CLIP" />
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
 | 
					                                <div class="overflow-auto">
 | 
				
			||||||
                                    <table class="table table-hover w-100">
 | 
					                                    <table class="table table-hover w-100">
 | 
				
			||||||
                                        <tbody id="Pistols-list"></tbody>
 | 
					                                        <tbody id="Pistols-list"></tbody>
 | 
				
			||||||
                                    </table>
 | 
					                                    </table>
 | 
				
			||||||
                                </div>
 | 
					                                </div>
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
                    <div class="col-lg-6">
 | 
					                    <div class="col-lg-6">
 | 
				
			||||||
                        <div class="card" style="height: 400px;">
 | 
					                        <div class="card" style="height: 400px;">
 | 
				
			||||||
                            <h5 class="card-header" data-loc="inventory_melee"></h5>
 | 
					                            <h5 class="card-header" data-loc="inventory_melee"></h5>
 | 
				
			||||||
                            <div class="card-body overflow-auto">
 | 
					                            <div class="card-body d-flex flex-column">
 | 
				
			||||||
                                <form class="input-group mb-3" onsubmit="handleModularSelection('Melee');return false;">
 | 
					                                <form class="input-group mb-3" onsubmit="handleModularSelection('Melee');return false;">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-Melee" list="datalist-Melee" />
 | 
					                                    <input class="form-control" id="acquire-type-Melee" list="datalist-Melee" />
 | 
				
			||||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
					                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
                                <form class="input-group mb-3" id="modular-Melee" style="display: none;">
 | 
					                                <form class="input-group mb-3 d-none" id="modular-Melee">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-Melee-BLADE" list="datalist-ModularParts-BLADE" />
 | 
					                                    <input class="form-control" id="acquire-type-Melee-BLADE" list="datalist-ModularParts-BLADE" />
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-Melee-HILT" list="datalist-ModularParts-HILT" />
 | 
					                                    <input class="form-control" id="acquire-type-Melee-HILT" list="datalist-ModularParts-HILT" />
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-Melee-HILT_WEIGHT" list="datalist-ModularParts-HILT_WEIGHT" />
 | 
					                                    <input class="form-control" id="acquire-type-Melee-HILT_WEIGHT" list="datalist-ModularParts-HILT_WEIGHT" />
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
 | 
					                                <div class="overflow-auto">
 | 
				
			||||||
                                    <table class="table table-hover w-100">
 | 
					                                    <table class="table table-hover w-100">
 | 
				
			||||||
                                        <tbody id="Melee-list"></tbody>
 | 
					                                        <tbody id="Melee-list"></tbody>
 | 
				
			||||||
                                    </table>
 | 
					                                    </table>
 | 
				
			||||||
@ -248,29 +258,33 @@
 | 
				
			|||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
                <div class="row g-3 mb-3">
 | 
					                <div class="row g-3 mb-3">
 | 
				
			||||||
                    <div class="col-lg-6">
 | 
					                    <div class="col-lg-6">
 | 
				
			||||||
                        <div class="card" style="height: 400px;">
 | 
					                        <div class="card" style="height: 400px;">
 | 
				
			||||||
                            <h5 class="card-header" data-loc="inventory_spaceSuits"></h5>
 | 
					                            <h5 class="card-header" data-loc="inventory_spaceSuits"></h5>
 | 
				
			||||||
                            <div class="card-body overflow-auto">
 | 
					                            <div class="card-body d-flex flex-column">
 | 
				
			||||||
                                <form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceSuits');return false;">
 | 
					                                <form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceSuits');return false;">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-SpaceSuits" list="datalist-SpaceSuits" />
 | 
					                                    <input class="form-control" id="acquire-type-SpaceSuits" list="datalist-SpaceSuits" />
 | 
				
			||||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
					                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
 | 
					                                <div class="overflow-auto">
 | 
				
			||||||
                                    <table class="table table-hover w-100">
 | 
					                                    <table class="table table-hover w-100">
 | 
				
			||||||
                                        <tbody id="SpaceSuits-list"></tbody>
 | 
					                                        <tbody id="SpaceSuits-list"></tbody>
 | 
				
			||||||
                                    </table>
 | 
					                                    </table>
 | 
				
			||||||
                                </div>
 | 
					                                </div>
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
                    <div class="col-lg-6">
 | 
					                    <div class="col-lg-6">
 | 
				
			||||||
                        <div class="card" style="height: 400px;">
 | 
					                        <div class="card" style="height: 400px;">
 | 
				
			||||||
                            <h5 class="card-header" data-loc="inventory_spaceGuns"></h5>
 | 
					                            <h5 class="card-header" data-loc="inventory_spaceGuns"></h5>
 | 
				
			||||||
                            <div class="card-body overflow-auto">
 | 
					                            <div class="card-body d-flex flex-column">
 | 
				
			||||||
                                <form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceGuns');return false;">
 | 
					                                <form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceGuns');return false;">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-SpaceGuns" list="datalist-SpaceGuns" />
 | 
					                                    <input class="form-control" id="acquire-type-SpaceGuns" list="datalist-SpaceGuns" />
 | 
				
			||||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
					                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
 | 
					                                <div class="overflow-auto">
 | 
				
			||||||
                                    <table class="table table-hover w-100">
 | 
					                                    <table class="table table-hover w-100">
 | 
				
			||||||
                                        <tbody id="SpaceGuns-list"></tbody>
 | 
					                                        <tbody id="SpaceGuns-list"></tbody>
 | 
				
			||||||
                                    </table>
 | 
					                                    </table>
 | 
				
			||||||
@ -278,29 +292,33 @@
 | 
				
			|||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
                <div class="row g-3 mb-3">
 | 
					                <div class="row g-3 mb-3">
 | 
				
			||||||
                    <div class="col-lg-6">
 | 
					                    <div class="col-lg-6">
 | 
				
			||||||
                        <div class="card" style="height: 400px;">
 | 
					                        <div class="card" style="height: 400px;">
 | 
				
			||||||
                            <h5 class="card-header" data-loc="inventory_spaceMelee"></h5>
 | 
					                            <h5 class="card-header" data-loc="inventory_spaceMelee"></h5>
 | 
				
			||||||
                            <div class="card-body overflow-auto">
 | 
					                            <div class="card-body d-flex flex-column">
 | 
				
			||||||
                                <form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceMelee');return false;">
 | 
					                                <form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceMelee');return false;">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-SpaceMelee" list="datalist-SpaceMelee" />
 | 
					                                    <input class="form-control" id="acquire-type-SpaceMelee" list="datalist-SpaceMelee" />
 | 
				
			||||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
					                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
 | 
					                                <div class="overflow-auto">
 | 
				
			||||||
                                    <table class="table table-hover w-100">
 | 
					                                    <table class="table table-hover w-100">
 | 
				
			||||||
                                        <tbody id="SpaceMelee-list"></tbody>
 | 
					                                        <tbody id="SpaceMelee-list"></tbody>
 | 
				
			||||||
                                    </table>
 | 
					                                    </table>
 | 
				
			||||||
                                </div>
 | 
					                                </div>
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
                    <div class="col-lg-6">
 | 
					                    <div class="col-lg-6">
 | 
				
			||||||
                        <div class="card" style="height: 400px;">
 | 
					                        <div class="card" style="height: 400px;">
 | 
				
			||||||
                            <h5 class="card-header" data-loc="inventory_mechSuits"></h5>
 | 
					                            <h5 class="card-header" data-loc="inventory_mechSuits"></h5>
 | 
				
			||||||
                            <div class="card-body overflow-auto">
 | 
					                            <div class="card-body d-flex flex-column">
 | 
				
			||||||
                                <form class="input-group mb-3" onsubmit="doAcquireEquipment('MechSuits');return false;">
 | 
					                                <form class="input-group mb-3" onsubmit="doAcquireEquipment('MechSuits');return false;">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-MechSuits" list="datalist-MechSuits" />
 | 
					                                    <input class="form-control" id="acquire-type-MechSuits" list="datalist-MechSuits" />
 | 
				
			||||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
					                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
 | 
					                                <div class="overflow-auto">
 | 
				
			||||||
                                    <table class="table table-hover w-100">
 | 
					                                    <table class="table table-hover w-100">
 | 
				
			||||||
                                        <tbody id="MechSuits-list"></tbody>
 | 
					                                        <tbody id="MechSuits-list"></tbody>
 | 
				
			||||||
                                    </table>
 | 
					                                    </table>
 | 
				
			||||||
@ -308,41 +326,45 @@
 | 
				
			|||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
                <div class="row g-3 mb-3">
 | 
					                <div class="row g-3 mb-3">
 | 
				
			||||||
                    <div class="col-lg-6">
 | 
					                    <div class="col-lg-6">
 | 
				
			||||||
                        <div class="card" style="height: 400px;">
 | 
					                        <div class="card" style="height: 400px;">
 | 
				
			||||||
                            <h5 class="card-header" data-loc="inventory_sentinels"></h5>
 | 
					                            <h5 class="card-header" data-loc="inventory_sentinels"></h5>
 | 
				
			||||||
                            <div class="card-body overflow-auto">
 | 
					                            <div class="card-body d-flex flex-column">
 | 
				
			||||||
                                <form class="input-group mb-3" onsubmit="doAcquireEquipment('Sentinels');return false;">
 | 
					                                <form class="input-group mb-3" onsubmit="doAcquireEquipment('Sentinels');return false;">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-Sentinels" list="datalist-Sentinels" />
 | 
					                                    <input class="form-control" id="acquire-type-Sentinels" list="datalist-Sentinels" />
 | 
				
			||||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
					                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
 | 
					                                <div class="overflow-auto">
 | 
				
			||||||
                                    <table class="table table-hover w-100">
 | 
					                                    <table class="table table-hover w-100">
 | 
				
			||||||
                                        <tbody id="Sentinels-list"></tbody>
 | 
					                                        <tbody id="Sentinels-list"></tbody>
 | 
				
			||||||
                                    </table>
 | 
					                                    </table>
 | 
				
			||||||
                                </div>
 | 
					                                </div>
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
                    <div class="col-lg-6">
 | 
					                    <div class="col-lg-6">
 | 
				
			||||||
                        <div class="card" style="height: 400px;">
 | 
					                        <div class="card" style="height: 400px;">
 | 
				
			||||||
                            <h5 class="card-header" data-loc="inventory_moaPets"></h5>
 | 
					                            <h5 class="card-header" data-loc="inventory_moaPets"></h5>
 | 
				
			||||||
                            <div class="card-body overflow-auto">
 | 
					                            <div class="card-body d-flex flex-column">
 | 
				
			||||||
                                <form class="input-group mb-3" onsubmit="handleModularSelection('MoaPets');return false;">
 | 
					                                <form class="input-group mb-3" onsubmit="handleModularSelection('MoaPets');return false;">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-MoaPets" list="datalist-MoaPets" />
 | 
					                                    <input class="form-control" id="acquire-type-MoaPets" list="datalist-MoaPets" />
 | 
				
			||||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
					                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
                                <form class="input-group mb-3" id="modular-MoaPets-Moa" style="display: none;">
 | 
					                                <form class="input-group mb-3 d-none" id="modular-MoaPets-Moa">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-MoaPets-MOA_ENGINE" list="datalist-ModularParts-MOA_ENGINE" />
 | 
					                                    <input class="form-control" id="acquire-type-MoaPets-MOA_ENGINE" list="datalist-ModularParts-MOA_ENGINE" />
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-MoaPets-MOA_PAYLOAD" list="datalist-ModularParts-MOA_PAYLOAD" />
 | 
					                                    <input class="form-control" id="acquire-type-MoaPets-MOA_PAYLOAD" list="datalist-ModularParts-MOA_PAYLOAD" />
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-MoaPets-MOA_HEAD" list="datalist-ModularParts-MOA_HEAD" />
 | 
					                                    <input class="form-control" id="acquire-type-MoaPets-MOA_HEAD" list="datalist-ModularParts-MOA_HEAD" />
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-MoaPets-MOA_LEG" list="datalist-ModularParts-MOA_LEG" />
 | 
					                                    <input class="form-control" id="acquire-type-MoaPets-MOA_LEG" list="datalist-ModularParts-MOA_LEG" />
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
                                <form class="input-group mb-3" id="modular-MoaPets-Zanuka" style="display: none;">
 | 
					                                <form class="input-group mb-3 d-none" id="modular-MoaPets-Zanuka">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-MoaPets-ZANUKA_HEAD" list="datalist-ModularParts-ZANUKA_HEAD" />
 | 
					                                    <input class="form-control" id="acquire-type-MoaPets-ZANUKA_HEAD" list="datalist-ModularParts-ZANUKA_HEAD" />
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-MoaPets-ZANUKA_BODY" list="datalist-ModularParts-ZANUKA_BODY" />
 | 
					                                    <input class="form-control" id="acquire-type-MoaPets-ZANUKA_BODY" list="datalist-ModularParts-ZANUKA_BODY" />
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-MoaPets-ZANUKA_LEG" list="datalist-ModularParts-ZANUKA_LEG" />
 | 
					                                    <input class="form-control" id="acquire-type-MoaPets-ZANUKA_LEG" list="datalist-ModularParts-ZANUKA_LEG" />
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-MoaPets-ZANUKA_TAIL" list="datalist-ModularParts-ZANUKA_TAIL" />
 | 
					                                    <input class="form-control" id="acquire-type-MoaPets-ZANUKA_TAIL" list="datalist-ModularParts-ZANUKA_TAIL" />
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
 | 
					                                <div class="overflow-auto">
 | 
				
			||||||
                                    <table class="table table-hover w-100">
 | 
					                                    <table class="table table-hover w-100">
 | 
				
			||||||
                                        <tbody id="MoaPets-list"></tbody>
 | 
					                                        <tbody id="MoaPets-list"></tbody>
 | 
				
			||||||
                                    </table>
 | 
					                                    </table>
 | 
				
			||||||
@ -350,37 +372,41 @@
 | 
				
			|||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
                <div class="row g-3 mb-3">
 | 
					                <div class="row g-3 mb-3">
 | 
				
			||||||
                    <div class="col-lg-6">
 | 
					                    <div class="col-lg-6">
 | 
				
			||||||
                        <div class="card" style="height: 400px;">
 | 
					                        <div class="card" style="height: 400px;">
 | 
				
			||||||
                            <h5 class="card-header" data-loc="inventory_kubrowPets"></h5>
 | 
					                            <h5 class="card-header" data-loc="inventory_kubrowPets"></h5>
 | 
				
			||||||
                            <div class="card-body overflow-auto">
 | 
					                            <div class="card-body d-flex flex-column">
 | 
				
			||||||
                                <form class="input-group mb-3" onsubmit="handleModularSelection('KubrowPets');return false;">
 | 
					                                <form class="input-group mb-3" onsubmit="handleModularSelection('KubrowPets');return false;">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-KubrowPets" list="datalist-KubrowPets" />
 | 
					                                    <input class="form-control" id="acquire-type-KubrowPets" list="datalist-KubrowPets" />
 | 
				
			||||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
					                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
                                <form class="input-group mb-3" id="modular-KubrowPets-Catbrow" style="display: none;">
 | 
					                                <form class="input-group mb-3 d-none" id="modular-KubrowPets-Catbrow">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-KubrowPets-CATBROW_ANTIGEN" list="datalist-ModularParts-CATBROW_ANTIGEN" />
 | 
					                                    <input class="form-control" id="acquire-type-KubrowPets-CATBROW_ANTIGEN" list="datalist-ModularParts-CATBROW_ANTIGEN" />
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-KubrowPets-CATBROW_MUTAGEN" list="datalist-ModularParts-CATBROW_MUTAGEN" />
 | 
					                                    <input class="form-control" id="acquire-type-KubrowPets-CATBROW_MUTAGEN" list="datalist-ModularParts-CATBROW_MUTAGEN" />
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
                                <form class="input-group mb-3" id="modular-KubrowPets-Kubrow" style="display: none;">
 | 
					                                <form class="input-group mb-3 d-none" id="modular-KubrowPets-Kubrow">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-KubrowPets-KUBROW_ANTIGEN" list="datalist-ModularParts-KUBROW_ANTIGEN" />
 | 
					                                    <input class="form-control" id="acquire-type-KubrowPets-KUBROW_ANTIGEN" list="datalist-ModularParts-KUBROW_ANTIGEN" />
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-KubrowPets-KUBROW_MUTAGEN" list="datalist-ModularParts-KUBROW_MUTAGEN" />
 | 
					                                    <input class="form-control" id="acquire-type-KubrowPets-KUBROW_MUTAGEN" list="datalist-ModularParts-KUBROW_MUTAGEN" />
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
 | 
					                                <div class="overflow-auto">
 | 
				
			||||||
                                    <table class="table table-hover w-100">
 | 
					                                    <table class="table table-hover w-100">
 | 
				
			||||||
                                        <tbody id="KubrowPets-list"></tbody>
 | 
					                                        <tbody id="KubrowPets-list"></tbody>
 | 
				
			||||||
                                    </table>
 | 
					                                    </table>
 | 
				
			||||||
                                </div>
 | 
					                                </div>
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
                    <div class="col-lg-6">
 | 
					                    <div class="col-lg-6">
 | 
				
			||||||
                        <div class="card" style="height: 400px;">
 | 
					                        <div class="card" style="height: 400px;">
 | 
				
			||||||
                            <h5 class="card-header" data-loc="inventory_sentinelWeapons"></h5>
 | 
					                            <h5 class="card-header" data-loc="inventory_sentinelWeapons"></h5>
 | 
				
			||||||
                            <div class="card-body overflow-auto">
 | 
					                            <div class="card-body d-flex flex-column">
 | 
				
			||||||
                                <form class="input-group mb-3" onsubmit="doAcquireEquipment('SentinelWeapons');return false;">
 | 
					                                <form class="input-group mb-3" onsubmit="doAcquireEquipment('SentinelWeapons');return false;">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-SentinelWeapons" list="datalist-SentinelWeapons" />
 | 
					                                    <input class="form-control" id="acquire-type-SentinelWeapons" list="datalist-SentinelWeapons" />
 | 
				
			||||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
					                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
 | 
					                                <div class="overflow-auto">
 | 
				
			||||||
                                    <table class="table table-hover w-100">
 | 
					                                    <table class="table table-hover w-100">
 | 
				
			||||||
                                        <tbody id="SentinelWeapons-list"></tbody>
 | 
					                                        <tbody id="SentinelWeapons-list"></tbody>
 | 
				
			||||||
                                    </table>
 | 
					                                    </table>
 | 
				
			||||||
@ -388,30 +414,33 @@
 | 
				
			|||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
                <div class="row g-3 mb-3">
 | 
					                <div class="row g-3 mb-3">
 | 
				
			||||||
                    <div class="col-lg-6">
 | 
					                    <div class="col-lg-6">
 | 
				
			||||||
                        <div class="card" style="height: 400px;">
 | 
					                        <div class="card" style="height: 400px;">
 | 
				
			||||||
                            <h5 class="card-header" data-loc="inventory_operatorAmps"></h5>
 | 
					                            <h5 class="card-header" data-loc="inventory_operatorAmps"></h5>
 | 
				
			||||||
                            <div class="card-body overflow-auto">
 | 
					                            <div class="card-body d-flex flex-column">
 | 
				
			||||||
                                <form class="input-group mb-3" onsubmit="handleModularSelection('OperatorAmps');return false;">
 | 
					                                <form class="input-group mb-3" onsubmit="handleModularSelection('OperatorAmps');return false;">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-OperatorAmps" list="datalist-OperatorAmps" />
 | 
					                                    <input class="form-control" id="acquire-type-OperatorAmps" list="datalist-OperatorAmps" />
 | 
				
			||||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
					                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
                                <form class="input-group mb-3" id="modular-OperatorAmps" style="display: none;">
 | 
					                                <form class="input-group mb-3 d-none" id="modular-OperatorAmps">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-OperatorAmps-AMP_OCULUS" list="datalist-ModularParts-AMP_OCULUS" />
 | 
					                                    <input class="form-control" id="acquire-type-OperatorAmps-AMP_OCULUS" list="datalist-ModularParts-AMP_OCULUS" />
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-OperatorAmps-AMP_CORE" list="datalist-ModularParts-AMP_CORE" />
 | 
					                                    <input class="form-control" id="acquire-type-OperatorAmps-AMP_CORE" list="datalist-ModularParts-AMP_CORE" />
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-OperatorAmps-AMP_BRACE" list="datalist-ModularParts-AMP_BRACE" />
 | 
					                                    <input class="form-control" id="acquire-type-OperatorAmps-AMP_BRACE" list="datalist-ModularParts-AMP_BRACE" />
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
 | 
					                                <div class="overflow-auto">
 | 
				
			||||||
                                    <table class="table table-hover w-100">
 | 
					                                    <table class="table table-hover w-100">
 | 
				
			||||||
                                        <tbody id="OperatorAmps-list"></tbody>
 | 
					                                        <tbody id="OperatorAmps-list"></tbody>
 | 
				
			||||||
                                    </table>
 | 
					                                    </table>
 | 
				
			||||||
                                </div>
 | 
					                                </div>
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
                    <div class="col-lg-6">
 | 
					                    <div class="col-lg-6">
 | 
				
			||||||
                        <div class="card" style="height: 400px;">
 | 
					                        <div class="card" style="height: 400px;">
 | 
				
			||||||
                            <h5 class="card-header" data-loc="inventory_hoverboards"></h5>
 | 
					                            <h5 class="card-header" data-loc="inventory_hoverboards"></h5>
 | 
				
			||||||
                            <div class="card-body overflow-auto">
 | 
					                            <div class="card-body d-flex flex-column">
 | 
				
			||||||
                                <form class="input-group mb-3" onsubmit="doAcquireModularEquipment('Hoverboards');return false;">
 | 
					                                <form class="input-group mb-3" onsubmit="doAcquireModularEquipment('Hoverboards');return false;">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-Hoverboards-HB_DECK" list="datalist-ModularParts-HB_DECK" />
 | 
					                                    <input class="form-control" id="acquire-type-Hoverboards-HB_DECK" list="datalist-ModularParts-HB_DECK" />
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-Hoverboards-HB_ENGINE" list="datalist-ModularParts-HB_ENGINE" />
 | 
					                                    <input class="form-control" id="acquire-type-Hoverboards-HB_ENGINE" list="datalist-ModularParts-HB_ENGINE" />
 | 
				
			||||||
@ -419,6 +448,7 @@
 | 
				
			|||||||
                                    <input class="form-control" id="acquire-type-Hoverboards-HB_JET" list="datalist-ModularParts-HB_JET" />
 | 
					                                    <input class="form-control" id="acquire-type-Hoverboards-HB_JET" list="datalist-ModularParts-HB_JET" />
 | 
				
			||||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
					                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
 | 
					                                <div class="overflow-auto">
 | 
				
			||||||
                                    <table class="table table-hover w-100">
 | 
					                                    <table class="table table-hover w-100">
 | 
				
			||||||
                                        <tbody id="Hoverboards-list"></tbody>
 | 
					                                        <tbody id="Hoverboards-list"></tbody>
 | 
				
			||||||
                                    </table>
 | 
					                                    </table>
 | 
				
			||||||
@ -426,29 +456,33 @@
 | 
				
			|||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
                <div class="row g-3 mb-3">
 | 
					                <div class="row g-3 mb-3">
 | 
				
			||||||
                    <div class="col-lg-6">
 | 
					                    <div class="col-lg-6">
 | 
				
			||||||
                        <div class="card" style="height: 400px;">
 | 
					                        <div class="card" style="height: 400px;">
 | 
				
			||||||
                            <h5 class="card-header" data-loc="inventory_evolutionProgress"></h5>
 | 
					                            <h5 class="card-header" data-loc="inventory_evolutionProgress"></h5>
 | 
				
			||||||
                            <div class="card-body overflow-auto">
 | 
					                            <div class="card-body d-flex flex-column">
 | 
				
			||||||
                                <form class="input-group mb-3" onsubmit="doAcquireEvolution();return false;">
 | 
					                                <form class="input-group mb-3" onsubmit="doAcquireEvolution();return false;">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-EvolutionProgress" list="datalist-EvolutionProgress" />
 | 
					                                    <input class="form-control" id="acquire-type-EvolutionProgress" list="datalist-EvolutionProgress" />
 | 
				
			||||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
					                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
 | 
					                                <div class="overflow-auto">
 | 
				
			||||||
                                    <table class="table table-hover w-100">
 | 
					                                    <table class="table table-hover w-100">
 | 
				
			||||||
                                        <tbody id="EvolutionProgress-list"></tbody>
 | 
					                                        <tbody id="EvolutionProgress-list"></tbody>
 | 
				
			||||||
                                    </table>
 | 
					                                    </table>
 | 
				
			||||||
                                </div>
 | 
					                                </div>
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
                    <div class="col-lg-6">
 | 
					                    <div class="col-lg-6">
 | 
				
			||||||
                        <div class="card" style="height: 400px;">
 | 
					                        <div class="card" style="height: 400px;">
 | 
				
			||||||
                            <h5 class="card-header" data-loc="inventory_Boosters"></h5>
 | 
					                            <h5 class="card-header" data-loc="inventory_boosters"></h5>
 | 
				
			||||||
                            <div class="card-body overflow-auto">
 | 
					                            <div class="card-body d-flex flex-column">
 | 
				
			||||||
                                <form class="input-group mb-3" onsubmit="doAcquireBoosters();return false;">
 | 
					                                <form class="input-group mb-3" onsubmit="doAcquireBooster();return false;">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-Boosters" list="datalist-Boosters" />
 | 
					                                    <input class="form-control" id="acquire-type-Boosters" list="datalist-Boosters" />
 | 
				
			||||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
					                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
 | 
					                                <div class="overflow-auto">
 | 
				
			||||||
                                    <table class="table table-hover w-100">
 | 
					                                    <table class="table table-hover w-100">
 | 
				
			||||||
                                        <tbody id="Boosters-list"></tbody>
 | 
					                                        <tbody id="Boosters-list"></tbody>
 | 
				
			||||||
                                    </table>
 | 
					                                    </table>
 | 
				
			||||||
@ -456,6 +490,42 @@
 | 
				
			|||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div class="row g-3 mb-3">
 | 
				
			||||||
 | 
					                    <div class="col-lg-6">
 | 
				
			||||||
 | 
					                        <div class="card" style="height: 400px;">
 | 
				
			||||||
 | 
					                            <h5 class="card-header" data-loc="inventory_flavourItems"></h5>
 | 
				
			||||||
 | 
					                            <div class="card-body d-flex flex-column">
 | 
				
			||||||
 | 
					                                <form class="input-group mb-3" onsubmit="doAcquireEquipment('FlavourItems');return false;">
 | 
				
			||||||
 | 
					                                    <input class="form-control" id="acquire-type-FlavourItems" list="datalist-FlavourItems" />
 | 
				
			||||||
 | 
					                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
				
			||||||
 | 
					                                </form>
 | 
				
			||||||
 | 
					                                <div class="overflow-auto">
 | 
				
			||||||
 | 
					                                    <table class="table table-hover w-100">
 | 
				
			||||||
 | 
					                                        <tbody id="FlavourItems-list"></tbody>
 | 
				
			||||||
 | 
					                                    </table>
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div class="col-lg-6">
 | 
				
			||||||
 | 
					                        <div class="card" style="height: 400px;">
 | 
				
			||||||
 | 
					                            <h5 class="card-header" data-loc="inventory_shipDecorations"></h5>
 | 
				
			||||||
 | 
					                            <div class="card-body d-flex flex-column">
 | 
				
			||||||
 | 
					                                <form class="input-group mb-3" onsubmit="doAcquireCountItems('ShipDecorations');return false;">
 | 
				
			||||||
 | 
					                                    <input class="form-control" id="ShipDecorations-count" type="number" value="1" />
 | 
				
			||||||
 | 
					                                    <input class="form-control w-50" id="acquire-type-ShipDecorations" list="datalist-ShipDecorations" />
 | 
				
			||||||
 | 
					                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
				
			||||||
 | 
					                                </form>
 | 
				
			||||||
 | 
					                                <div class="overflow-auto">
 | 
				
			||||||
 | 
					                                    <table class="table table-hover w-100">
 | 
				
			||||||
 | 
					                                        <tbody id="ShipDecorations-list"></tbody>
 | 
				
			||||||
 | 
					                                    </table>
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
                <div class="card">
 | 
					                <div class="card">
 | 
				
			||||||
                    <h5 class="card-header" data-loc="general_bulkActions"></h5>
 | 
					                    <h5 class="card-header" data-loc="general_bulkActions"></h5>
 | 
				
			||||||
                    <div class="card-body">
 | 
					                    <div class="card-body">
 | 
				
			||||||
@ -466,6 +536,8 @@
 | 
				
			|||||||
                            <button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['SpaceGuns', 'SpaceMelee']);" data-loc="inventory_bulkAddSpaceWeapons"></button>
 | 
					                            <button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['SpaceGuns', 'SpaceMelee']);" data-loc="inventory_bulkAddSpaceWeapons"></button>
 | 
				
			||||||
                            <button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['Sentinels']);" data-loc="inventory_bulkAddSentinels"></button>
 | 
					                            <button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['Sentinels']);" data-loc="inventory_bulkAddSentinels"></button>
 | 
				
			||||||
                            <button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['SentinelWeapons']);" data-loc="inventory_bulkAddSentinelWeapons"></button>
 | 
					                            <button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['SentinelWeapons']);" data-loc="inventory_bulkAddSentinelWeapons"></button>
 | 
				
			||||||
 | 
					                            <button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['FlavourItems']);" data-loc="inventory_bulkAddFlavourItems"></button>
 | 
				
			||||||
 | 
					                            <button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['ShipDecorations']);" data-loc="inventory_bulkAddShipDecorations"></button>
 | 
				
			||||||
                            <button class="btn btn-primary" onclick="debounce(addMissingEvolutionProgress);" data-loc="inventory_bulkAddEvolutionProgress"></button>
 | 
					                            <button class="btn btn-primary" onclick="debounce(addMissingEvolutionProgress);" data-loc="inventory_bulkAddEvolutionProgress"></button>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                        <div class="mb-2 d-flex flex-wrap gap-2">
 | 
					                        <div class="mb-2 d-flex flex-wrap gap-2">
 | 
				
			||||||
@ -480,6 +552,143 @@
 | 
				
			|||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div id="guild-route" data-route="/webui/guildView" data-title="Guild | OpenWF WebUI">
 | 
				
			||||||
 | 
					                <h3 id="guildView-loading" class="mb-0" data-loc="general_loading"></h3>
 | 
				
			||||||
 | 
					                <h3 id="guildView-title" class="mb-0"></h3>
 | 
				
			||||||
 | 
					                <p id="guildView-tier" class="text-body-secondary mb-0"></p>
 | 
				
			||||||
 | 
					                <p id="guildView-class" class="text-body-secondary mb-0"></p>
 | 
				
			||||||
 | 
					                <p id="guildView-alliance" class="text-body-secondary"></p>
 | 
				
			||||||
 | 
					                <div class="row g-3 mb-3">
 | 
				
			||||||
 | 
					                    <div class="col-md-6">
 | 
				
			||||||
 | 
					                        <div class="card">
 | 
				
			||||||
 | 
					                            <h5 class="card-header" data-loc="currency_RegularCredits"></h5>
 | 
				
			||||||
 | 
					                            <div class="card-body">
 | 
				
			||||||
 | 
					                                <p class="card-text" id="VaultRegularCredits-owned"></p>
 | 
				
			||||||
 | 
					                                <form id="vaultRegularCredits-form" class="input-group d-none" onsubmit="doAddCurrency('VaultRegularCredits');return false;">
 | 
				
			||||||
 | 
					                                    <input class="form-control" id="VaultRegularCredits-delta" type="number" value="1000000" />
 | 
				
			||||||
 | 
					                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
				
			||||||
 | 
					                                </form>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div class="col-md-6">
 | 
				
			||||||
 | 
					                        <div class="card">
 | 
				
			||||||
 | 
					                            <h5 class="card-header" data-loc="currency_PremiumCredits"></h5>
 | 
				
			||||||
 | 
					                            <div class="card-body">
 | 
				
			||||||
 | 
					                                <p class="card-text" id="VaultPremiumCredits-owned"></p>
 | 
				
			||||||
 | 
					                                <form id="vaultPremiumCredits-form" class="input-group d-none" onsubmit="doAddCurrency('VaultPremiumCredits');return false;">
 | 
				
			||||||
 | 
					                                    <input class="form-control" id="VaultPremiumCredits-delta" type="number" value="100" />
 | 
				
			||||||
 | 
					                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
				
			||||||
 | 
					                                </form>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div class="row g-3 mb-3">
 | 
				
			||||||
 | 
					                    <div class="col-lg-6">
 | 
				
			||||||
 | 
					                        <div class="card" style="height: 400px;">
 | 
				
			||||||
 | 
					                            <h5 class="card-header" data-loc="guildView_techProjects"></h5>
 | 
				
			||||||
 | 
					                            <div class="card-body d-flex flex-column">
 | 
				
			||||||
 | 
					                                <form id="techProjects-form" class="input-group mb-3 d-none" onsubmit="addGuildTechProject();return false;">
 | 
				
			||||||
 | 
					                                    <input class="form-control" id="acquire-type-TechProjects" list="datalist-TechProjects" />
 | 
				
			||||||
 | 
					                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
				
			||||||
 | 
					                                </form>
 | 
				
			||||||
 | 
					                                <div class="overflow-auto">
 | 
				
			||||||
 | 
					                                    <table class="table table-hover w-100">
 | 
				
			||||||
 | 
					                                        <tbody id="TechProjects-list"></tbody>
 | 
				
			||||||
 | 
					                                    </table>
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div class="col-lg-6">
 | 
				
			||||||
 | 
					                        <div class="card" style="height: 400px;">
 | 
				
			||||||
 | 
					                            <h5 class="card-header" data-loc="guildView_vaultDecoRecipes"></h5>
 | 
				
			||||||
 | 
					                            <div class="card-body d-flex flex-column">
 | 
				
			||||||
 | 
					                                <form id="vaultDecoRecipes-form" class="input-group mb-3 d-none" onsubmit="addVaultDecoRecipe();return false;">
 | 
				
			||||||
 | 
					                                    <input class="form-control" id="acquire-type-VaultDecoRecipes" list="datalist-VaultDecoRecipes" />
 | 
				
			||||||
 | 
					                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
				
			||||||
 | 
					                                </form>
 | 
				
			||||||
 | 
					                                <div class="overflow-auto">
 | 
				
			||||||
 | 
					                                    <table class="table table-hover w-100">
 | 
				
			||||||
 | 
					                                        <tbody id="VaultDecoRecipes-list"></tbody>
 | 
				
			||||||
 | 
					                                    </table>
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div class="row g-3 mb-3">
 | 
				
			||||||
 | 
					                    <div class="col-lg-6">
 | 
				
			||||||
 | 
					                        <div class="card" style="height: 400px;">
 | 
				
			||||||
 | 
					                            <h5 class="card-header" data-loc="guildView_members"></h5>
 | 
				
			||||||
 | 
					                            <div class="card-body overflow-auto">
 | 
				
			||||||
 | 
					                                <table class="table table-hover w-100">
 | 
				
			||||||
 | 
					                                    <tbody id="Members-list"></tbody>
 | 
				
			||||||
 | 
					                                </table>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div class="col-lg-6">
 | 
				
			||||||
 | 
					                        <div class="card" style="height: 400px;">
 | 
				
			||||||
 | 
					                            <h5 class="card-header" data-loc="guildView_alliance"></h5>
 | 
				
			||||||
 | 
					                            <div class="card-body overflow-auto">
 | 
				
			||||||
 | 
					                                <table class="table table-hover w-100">
 | 
				
			||||||
 | 
					                                    <tbody id="Alliance-list"></tbody>
 | 
				
			||||||
 | 
					                                </table>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div class="row g-3 mb-3">
 | 
				
			||||||
 | 
					                    <div class="col-lg-6">
 | 
				
			||||||
 | 
					                        <div class="card">
 | 
				
			||||||
 | 
					                            <h5 class="card-header" data-loc="general_bulkActions"></h5>
 | 
				
			||||||
 | 
					                            <div class="card-body" id="guild-actions">
 | 
				
			||||||
 | 
					                                <div class="mb-2 d-flex flex-wrap gap-2">
 | 
				
			||||||
 | 
					                                    <button class="btn btn-primary" onclick="debounce(addMissingTechProjects);" data-loc="guildView_bulkAddTechProjects"></button>
 | 
				
			||||||
 | 
					                                    <button class="btn btn-primary" onclick="debounce(addMissingVaultDecoRecipes);" data-loc="guildView_bulkAddVaultDecoRecipes"></button>
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                                <div class="mb-2 d-flex flex-wrap gap-2">
 | 
				
			||||||
 | 
					                                    <button class="btn btn-success" onclick="debounce(fundAllTechProjects);" data-loc="guildView_bulkFundTechProjects"></button>
 | 
				
			||||||
 | 
					                                    <button class="btn btn-success" onclick="debounce(completeAllTechProjects);" data-loc="guildView_bulkCompleteTechProjects"></button>
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div class="col-lg-6">
 | 
				
			||||||
 | 
					                        <div class="card mb-3">
 | 
				
			||||||
 | 
					                        <h5 class="card-header" data-loc="guildView_cheats"></h5>
 | 
				
			||||||
 | 
					                            <div class="card-body" id="guild-cheats">
 | 
				
			||||||
 | 
					                                <div class="form-check">
 | 
				
			||||||
 | 
					                                    <input class="form-check-input" type="checkbox" id="noDojoRoomBuildStage" />
 | 
				
			||||||
 | 
					                                    <label class="form-check-label" for="noDojoRoomBuildStage" data-loc="cheats_noDojoRoomBuildStage"></label>
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                                <div class="form-check">
 | 
				
			||||||
 | 
					                                    <input class="form-check-input" type="checkbox" id="noDojoDecoBuildStage" />
 | 
				
			||||||
 | 
					                                    <label class="form-check-label" for="noDojoDecoBuildStage" data-loc="cheats_noDojoDecoBuildStage"></label>
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                                <div class="form-check">
 | 
				
			||||||
 | 
					                                    <input class="form-check-input" type="checkbox" id="noDojoResearchCosts" />
 | 
				
			||||||
 | 
					                                    <label class="form-check-label" for="noDojoResearchCosts" data-loc="cheats_noDojoResearchCosts"></label>
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                                <div class="form-check">
 | 
				
			||||||
 | 
					                                    <input class="form-check-input" type="checkbox" id="noDojoResearchTime" />
 | 
				
			||||||
 | 
					                                    <label class="form-check-label" for="noDojoResearchTime" data-loc="cheats_noDojoResearchTime"></label>
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                                <div class="form-check">
 | 
				
			||||||
 | 
					                                    <input class="form-check-input" type="checkbox" id="fastDojoRoomDestruction" />
 | 
				
			||||||
 | 
					                                    <label class="form-check-label" for="fastDojoRoomDestruction" data-loc="cheats_fastDojoRoomDestruction"></label>
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                                <div class="form-check">
 | 
				
			||||||
 | 
					                                    <input class="form-check-input" type="checkbox" id="fastClanAscension" />
 | 
				
			||||||
 | 
					                                    <label class="form-check-label" for="fastClanAscension" data-loc="cheats_fastClanAscension"></label>
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
            <div id="detailedView-route" data-route="/webui/detailedView" data-title="Inventory | OpenWF WebUI">
 | 
					            <div id="detailedView-route" data-route="/webui/detailedView" data-title="Inventory | OpenWF WebUI">
 | 
				
			||||||
                <h3 id="detailedView-loading" class="mb-0" data-loc="general_loading"></h3>
 | 
					                <h3 id="detailedView-loading" class="mb-0" data-loc="general_loading"></h3>
 | 
				
			||||||
                <h3 id="detailedView-title" class="mb-0"></h3>
 | 
					                <h3 id="detailedView-title" class="mb-0"></h3>
 | 
				
			||||||
@ -579,7 +788,7 @@
 | 
				
			|||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div data-route="/webui/mods" data-title="Mods | OpenWF WebUI">
 | 
					            <div data-route="/webui/mods" data-title="Mods | OpenWF WebUI">
 | 
				
			||||||
                <p class="mb-3" data-loc="general_inventoryUpdateNote"></p>
 | 
					                <p class="mb-3 inventory-update-note"></p>
 | 
				
			||||||
                <div class="row g-3">
 | 
					                <div class="row g-3">
 | 
				
			||||||
                    <div class="col-xxl-6">
 | 
					                    <div class="col-xxl-6">
 | 
				
			||||||
                        <div class="card mb-3">
 | 
					                        <div class="card mb-3">
 | 
				
			||||||
@ -635,22 +844,24 @@
 | 
				
			|||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div data-route="/webui/quests" data-title="Quests | OpenWF WebUI">
 | 
					            <div data-route="/webui/quests" data-title="Quests | OpenWF WebUI">
 | 
				
			||||||
                <p class="mb-3" data-loc="general_inventoryUpdateNote"></p>
 | 
					                <p class="mb-3 inventory-update-note"></p>
 | 
				
			||||||
                <div class="row g-3">
 | 
					                <div class="row g-3">
 | 
				
			||||||
                    <div class="col-md-6">
 | 
					                    <div class="col-md-6">
 | 
				
			||||||
                        <div class="card">
 | 
					                        <div class="card" style="height: 800px;">
 | 
				
			||||||
                            <h5 class="card-header" data-loc="quests_list"></h5>
 | 
					                            <h5 class="card-header" data-loc="quests_list"></h5>
 | 
				
			||||||
                            <div class="card-body">
 | 
					                            <div class="card-body d-flex flex-column">
 | 
				
			||||||
                                <form class="input-group mb-3" onsubmit="doAcquireEquipment('QuestKeys');return false;">
 | 
					                                <form class="input-group mb-3" onsubmit="doAcquireEquipment('QuestKeys');return false;">
 | 
				
			||||||
                                    <input class="form-control" id="acquire-type-QuestKeys" list="datalist-QuestKeys" />
 | 
					                                    <input class="form-control" id="acquire-type-QuestKeys" list="datalist-QuestKeys" />
 | 
				
			||||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
					                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
				
			||||||
                                </form>
 | 
					                                </form>
 | 
				
			||||||
 | 
					                                <div class="overflow-auto">
 | 
				
			||||||
                                    <table class="table table-hover w-100">
 | 
					                                    <table class="table table-hover w-100">
 | 
				
			||||||
                                        <tbody id="QuestKeys-list"></tbody>
 | 
					                                        <tbody id="QuestKeys-list"></tbody>
 | 
				
			||||||
                                    </table>
 | 
					                                    </table>
 | 
				
			||||||
                                </div>
 | 
					                                </div>
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
                    <div class="col-md-6">
 | 
					                    <div class="col-md-6">
 | 
				
			||||||
                        <div class="card">
 | 
					                        <div class="card">
 | 
				
			||||||
                            <h5 class="card-header" data-loc="general_bulkActions"></h5>
 | 
					                            <h5 class="card-header" data-loc="general_bulkActions"></h5>
 | 
				
			||||||
@ -843,22 +1054,10 @@
 | 
				
			|||||||
                                        <input class="form-check-input" type="checkbox" id="skipTutorial" />
 | 
					                                        <input class="form-check-input" type="checkbox" id="skipTutorial" />
 | 
				
			||||||
                                        <label class="form-check-label" for="skipTutorial" data-loc="cheats_skipTutorial"></label>
 | 
					                                        <label class="form-check-label" for="skipTutorial" data-loc="cheats_skipTutorial"></label>
 | 
				
			||||||
                                    </div>
 | 
					                                    </div>
 | 
				
			||||||
                                    <div class="form-check">
 | 
					 | 
				
			||||||
                                        <input class="form-check-input" type="checkbox" id="unlockAllShipDecorations" />
 | 
					 | 
				
			||||||
                                        <label class="form-check-label" for="unlockAllShipDecorations" data-loc="cheats_unlockAllShipDecorations"></label>
 | 
					 | 
				
			||||||
                                    </div>
 | 
					 | 
				
			||||||
                                    <div class="form-check">
 | 
					 | 
				
			||||||
                                        <input class="form-check-input" type="checkbox" id="unlockAllFlavourItems" />
 | 
					 | 
				
			||||||
                                        <label class="form-check-label" for="unlockAllFlavourItems" data-loc="cheats_unlockAllFlavourItems"></label>
 | 
					 | 
				
			||||||
                                    </div>
 | 
					 | 
				
			||||||
                                    <div class="form-check">
 | 
					                                    <div class="form-check">
 | 
				
			||||||
                                        <input class="form-check-input" type="checkbox" id="unlockAllSkins" />
 | 
					                                        <input class="form-check-input" type="checkbox" id="unlockAllSkins" />
 | 
				
			||||||
                                        <label class="form-check-label" for="unlockAllSkins" data-loc="cheats_unlockAllSkins"></label>
 | 
					                                        <label class="form-check-label" for="unlockAllSkins" data-loc="cheats_unlockAllSkins"></label>
 | 
				
			||||||
                                    </div>
 | 
					                                    </div>
 | 
				
			||||||
                                    <div class="form-check">
 | 
					 | 
				
			||||||
                                        <input class="form-check-input" type="checkbox" id="unlockAllDecoRecipes" />
 | 
					 | 
				
			||||||
                                        <label class="form-check-label" for="unlockAllDecoRecipes" data-loc="cheats_unlockAllDecoRecipes"></label>
 | 
					 | 
				
			||||||
                                    </div>
 | 
					 | 
				
			||||||
                                    <div class="form-check">
 | 
					                                    <div class="form-check">
 | 
				
			||||||
                                        <input class="form-check-input" type="checkbox" id="fullyStockedVendors" />
 | 
					                                        <input class="form-check-input" type="checkbox" id="fullyStockedVendors" />
 | 
				
			||||||
                                        <label class="form-check-label" for="fullyStockedVendors" data-loc="cheats_fullyStockedVendors"></label>
 | 
					                                        <label class="form-check-label" for="fullyStockedVendors" data-loc="cheats_fullyStockedVendors"></label>
 | 
				
			||||||
@ -867,30 +1066,6 @@
 | 
				
			|||||||
                                        <input class="form-check-input" type="checkbox" id="skipClanKeyCrafting" />
 | 
					                                        <input class="form-check-input" type="checkbox" id="skipClanKeyCrafting" />
 | 
				
			||||||
                                        <label class="form-check-label" for="skipClanKeyCrafting" data-loc="cheats_skipClanKeyCrafting"></label>
 | 
					                                        <label class="form-check-label" for="skipClanKeyCrafting" data-loc="cheats_skipClanKeyCrafting"></label>
 | 
				
			||||||
                                    </div>
 | 
					                                    </div>
 | 
				
			||||||
                                    <div class="form-check">
 | 
					 | 
				
			||||||
                                        <input class="form-check-input" type="checkbox" id="noDojoRoomBuildStage" />
 | 
					 | 
				
			||||||
                                        <label class="form-check-label" for="noDojoRoomBuildStage" data-loc="cheats_noDojoRoomBuildStage"></label>
 | 
					 | 
				
			||||||
                                    </div>
 | 
					 | 
				
			||||||
                                    <div class="form-check">
 | 
					 | 
				
			||||||
                                        <input class="form-check-input" type="checkbox" id="noDojoDecoBuildStage" />
 | 
					 | 
				
			||||||
                                        <label class="form-check-label" for="noDojoDecoBuildStage" data-loc="cheats_noDojoDecoBuildStage"></label>
 | 
					 | 
				
			||||||
                                    </div>
 | 
					 | 
				
			||||||
                                    <div class="form-check">
 | 
					 | 
				
			||||||
                                        <input class="form-check-input" type="checkbox" id="fastDojoRoomDestruction" />
 | 
					 | 
				
			||||||
                                        <label class="form-check-label" for="fastDojoRoomDestruction" data-loc="cheats_fastDojoRoomDestruction"></label>
 | 
					 | 
				
			||||||
                                    </div>
 | 
					 | 
				
			||||||
                                    <div class="form-check">
 | 
					 | 
				
			||||||
                                        <input class="form-check-input" type="checkbox" id="noDojoResearchCosts" />
 | 
					 | 
				
			||||||
                                        <label class="form-check-label" for="noDojoResearchCosts" data-loc="cheats_noDojoResearchCosts"></label>
 | 
					 | 
				
			||||||
                                    </div>
 | 
					 | 
				
			||||||
                                    <div class="form-check">
 | 
					 | 
				
			||||||
                                        <input class="form-check-input" type="checkbox" id="noDojoResearchTime" />
 | 
					 | 
				
			||||||
                                        <label class="form-check-label" for="noDojoResearchTime" data-loc="cheats_noDojoResearchTime"></label>
 | 
					 | 
				
			||||||
                                    </div>
 | 
					 | 
				
			||||||
                                    <div class="form-check">
 | 
					 | 
				
			||||||
                                        <input class="form-check-input" type="checkbox" id="fastClanAscension" />
 | 
					 | 
				
			||||||
                                        <label class="form-check-label" for="fastClanAscension" data-loc="cheats_fastClanAscension"></label>
 | 
					 | 
				
			||||||
                                    </div>
 | 
					 | 
				
			||||||
                                    <form class="form-group mt-2" onsubmit="doSaveConfigInt('spoofMasteryRank'); return false;">
 | 
					                                    <form class="form-group mt-2" onsubmit="doSaveConfigInt('spoofMasteryRank'); return false;">
 | 
				
			||||||
                                        <label class="form-label" for="spoofMasteryRank" data-loc="cheats_spoofMasteryRank"></label>
 | 
					                                        <label class="form-label" for="spoofMasteryRank" data-loc="cheats_spoofMasteryRank"></label>
 | 
				
			||||||
                                        <div class="input-group">
 | 
					                                        <div class="input-group">
 | 
				
			||||||
@ -1277,6 +1452,10 @@
 | 
				
			|||||||
    <datalist id="datalist-ModularParts-KUBROW_MUTAGEN"></datalist>
 | 
					    <datalist id="datalist-ModularParts-KUBROW_MUTAGEN"></datalist>
 | 
				
			||||||
    <datalist id="datalist-Boosters"></datalist>
 | 
					    <datalist id="datalist-Boosters"></datalist>
 | 
				
			||||||
    <datalist id="datalist-Abilities"></datalist>
 | 
					    <datalist id="datalist-Abilities"></datalist>
 | 
				
			||||||
 | 
					    <datalist id="datalist-TechProjects"></datalist>
 | 
				
			||||||
 | 
					    <datalist id="datalist-VaultDecoRecipes"></datalist>
 | 
				
			||||||
 | 
					    <datalist id="datalist-FlavourItems"></datalist>
 | 
				
			||||||
 | 
					    <datalist id="datalist-ShipDecorations"></datalist>
 | 
				
			||||||
    <datalist id="datalist-circuitGameModes">
 | 
					    <datalist id="datalist-circuitGameModes">
 | 
				
			||||||
        <option>Survival</option>
 | 
					        <option>Survival</option>
 | 
				
			||||||
        <option>VoidFlood</option>
 | 
					        <option>VoidFlood</option>
 | 
				
			||||||
 | 
				
			|||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -1,6 +1,7 @@
 | 
				
			|||||||
// German translation by Animan8000
 | 
					// German translation by Animan8000
 | 
				
			||||||
dict = {
 | 
					dict = {
 | 
				
			||||||
    general_inventoryUpdateNote: `Hinweis: Um Änderungen im Spiel zu sehen, musst du dein Inventar neu synchronisieren, z. B. mit dem /sync Befehl des Bootstrappers, durch Besuch eines Dojo/Relais oder durch erneutes Einloggen.`,
 | 
					    general_inventoryUpdateNote: `Hinweis: Um Änderungen im Spiel zu sehen, musst du dein Inventar neu synchronisieren, z. B. mit dem /sync Befehl des Bootstrappers, durch Besuch eines Dojo/Relais oder durch erneutes Einloggen.`,
 | 
				
			||||||
 | 
					    general_inventoryUpdateNoteGameWs: `[UNTRANSLATED] Note: You may need to reopen any menu you are on for changes to be reflected.`,
 | 
				
			||||||
    general_addButton: `Hinzufügen`,
 | 
					    general_addButton: `Hinzufügen`,
 | 
				
			||||||
    general_setButton: `Festlegen`,
 | 
					    general_setButton: `Festlegen`,
 | 
				
			||||||
    general_none: `Keines`,
 | 
					    general_none: `Keines`,
 | 
				
			||||||
@ -31,6 +32,8 @@ dict = {
 | 
				
			|||||||
    code_renamePrompt: `Neuen benutzerdefinierten Namen eingeben:`,
 | 
					    code_renamePrompt: `Neuen benutzerdefinierten Namen eingeben:`,
 | 
				
			||||||
    code_remove: `Entfernen`,
 | 
					    code_remove: `Entfernen`,
 | 
				
			||||||
    code_addItemsConfirm: `Bist du sicher, dass du |COUNT| Gegenstände zu deinem Account hinzufügen möchtest?`,
 | 
					    code_addItemsConfirm: `Bist du sicher, dass du |COUNT| Gegenstände zu deinem Account hinzufügen möchtest?`,
 | 
				
			||||||
 | 
					    code_addTechProjectsConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| research to your clan?`,
 | 
				
			||||||
 | 
					    code_addDecoRecipesConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| deco recipes to your clan?`,
 | 
				
			||||||
    code_succRankUp: `Erfolgreich aufgestiegen.`,
 | 
					    code_succRankUp: `Erfolgreich aufgestiegen.`,
 | 
				
			||||||
    code_noEquipmentToRankUp: `Keine Ausstattung zum Rangaufstieg verfügbar.`,
 | 
					    code_noEquipmentToRankUp: `Keine Ausstattung zum Rangaufstieg verfügbar.`,
 | 
				
			||||||
    code_succAdded: `Erfolgreich hinzugefügt.`,
 | 
					    code_succAdded: `Erfolgreich hinzugefügt.`,
 | 
				
			||||||
@ -42,6 +45,7 @@ dict = {
 | 
				
			|||||||
    code_rank: `Rang`,
 | 
					    code_rank: `Rang`,
 | 
				
			||||||
    code_rankUp: `Rang erhöhen`,
 | 
					    code_rankUp: `Rang erhöhen`,
 | 
				
			||||||
    code_rankDown: `Rang verringern`,
 | 
					    code_rankDown: `Rang verringern`,
 | 
				
			||||||
 | 
					    code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`,
 | 
				
			||||||
    code_count: `Anzahl`,
 | 
					    code_count: `Anzahl`,
 | 
				
			||||||
    code_focusAllUnlocked: `Alle Fokus-Schulen sind bereits freigeschaltet.`,
 | 
					    code_focusAllUnlocked: `Alle Fokus-Schulen sind bereits freigeschaltet.`,
 | 
				
			||||||
    code_focusUnlocked: `|COUNT| neue Fokus-Schulen freigeschaltet! Ein Inventar-Update wird benötigt, damit die Änderungen im Spiel sichtbar werden. Die Sternenkarte zu besuchen, sollte der einfachste Weg sein, dies auszulösen.`,
 | 
					    code_focusUnlocked: `|COUNT| neue Fokus-Schulen freigeschaltet! Ein Inventar-Update wird benötigt, damit die Änderungen im Spiel sichtbar werden. Die Sternenkarte zu besuchen, sollte der einfachste Weg sein, dies auszulösen.`,
 | 
				
			||||||
@ -61,8 +65,16 @@ dict = {
 | 
				
			|||||||
    code_completed: `Abgeschlossen`,
 | 
					    code_completed: `Abgeschlossen`,
 | 
				
			||||||
    code_active: `Aktiv`,
 | 
					    code_active: `Aktiv`,
 | 
				
			||||||
    code_pigment: `Pigment`,
 | 
					    code_pigment: `Pigment`,
 | 
				
			||||||
 | 
					    code_controller: `[UNTRANSLATED] Controller cursor`,
 | 
				
			||||||
 | 
					    code_mouseLine: `[UNTRANSLATED] Line cursor`,
 | 
				
			||||||
 | 
					    code_mouse: `[UNTRANSLATED] Cursor`,
 | 
				
			||||||
 | 
					    code_itemColorPalette: `|ITEM| Farbpalette`,
 | 
				
			||||||
    code_mature: `Für den Kampf auswachsen lassen`,
 | 
					    code_mature: `Für den Kampf auswachsen lassen`,
 | 
				
			||||||
    code_unmature: `Genetisches Altern zurücksetzen`,
 | 
					    code_unmature: `Genetisches Altern zurücksetzen`,
 | 
				
			||||||
 | 
					    code_fund: `[UNTRANSLATED] Fund`,
 | 
				
			||||||
 | 
					    code_funded: `[UNTRANSLATED] Funded`,
 | 
				
			||||||
 | 
					    code_replays: `[UNTRANSLATED] Replays`,
 | 
				
			||||||
 | 
					    code_stalker: `Stalker`,
 | 
				
			||||||
    code_succChange: `Erfolgreich geändert.`,
 | 
					    code_succChange: `Erfolgreich geändert.`,
 | 
				
			||||||
    code_requiredInvigorationUpgrade: `Du musst sowohl ein offensives & defensives Upgrade auswählen.`,
 | 
					    code_requiredInvigorationUpgrade: `Du musst sowohl ein offensives & defensives Upgrade auswählen.`,
 | 
				
			||||||
    login_description: `Melde dich mit deinem OpenWF-Account an (denselben Angaben wie im Spiel, wenn du dich mit diesem Server verbindest).`,
 | 
					    login_description: `Melde dich mit deinem OpenWF-Account an (denselben Angaben wie im Spiel, wenn du dich mit diesem Server verbindest).`,
 | 
				
			||||||
@ -74,6 +86,7 @@ dict = {
 | 
				
			|||||||
    navbar_renameAccount: `Account umbenennen`,
 | 
					    navbar_renameAccount: `Account umbenennen`,
 | 
				
			||||||
    navbar_deleteAccount: `Account löschen`,
 | 
					    navbar_deleteAccount: `Account löschen`,
 | 
				
			||||||
    navbar_inventory: `Inventar`,
 | 
					    navbar_inventory: `Inventar`,
 | 
				
			||||||
 | 
					    navbar_guildView: `Clan`,
 | 
				
			||||||
    navbar_mods: `Mods`,
 | 
					    navbar_mods: `Mods`,
 | 
				
			||||||
    navbar_quests: `Quests`,
 | 
					    navbar_quests: `Quests`,
 | 
				
			||||||
    navbar_cheats: `Cheats`,
 | 
					    navbar_cheats: `Cheats`,
 | 
				
			||||||
@ -96,13 +109,17 @@ dict = {
 | 
				
			|||||||
    inventory_moaPets: `Moas`,
 | 
					    inventory_moaPets: `Moas`,
 | 
				
			||||||
    inventory_kubrowPets: `Bestien`,
 | 
					    inventory_kubrowPets: `Bestien`,
 | 
				
			||||||
    inventory_evolutionProgress: `Incarnon-Entwicklungsfortschritte`,
 | 
					    inventory_evolutionProgress: `Incarnon-Entwicklungsfortschritte`,
 | 
				
			||||||
    inventory_Boosters: `Booster`,
 | 
					    inventory_boosters: `Booster`,
 | 
				
			||||||
 | 
					    inventory_flavourItems: `<abbr title="Animationssets, Glyphen, Farbpaletten usw.">Sammlerstücke</abbr>`,
 | 
				
			||||||
 | 
					    inventory_shipDecorations: `Schiffsdekorationen`,
 | 
				
			||||||
    inventory_bulkAddSuits: `Fehlende Warframes hinzufügen`,
 | 
					    inventory_bulkAddSuits: `Fehlende Warframes hinzufügen`,
 | 
				
			||||||
    inventory_bulkAddWeapons: `Fehlende Waffen hinzufügen`,
 | 
					    inventory_bulkAddWeapons: `Fehlende Waffen hinzufügen`,
 | 
				
			||||||
    inventory_bulkAddSpaceSuits: `Fehlende Archwings hinzufügen`,
 | 
					    inventory_bulkAddSpaceSuits: `Fehlende Archwings hinzufügen`,
 | 
				
			||||||
    inventory_bulkAddSpaceWeapons: `Fehlende Archwing-Waffen hinzufügen`,
 | 
					    inventory_bulkAddSpaceWeapons: `Fehlende Archwing-Waffen hinzufügen`,
 | 
				
			||||||
    inventory_bulkAddSentinels: `Fehlende Wächter hinzufügen`,
 | 
					    inventory_bulkAddSentinels: `Fehlende Wächter hinzufügen`,
 | 
				
			||||||
    inventory_bulkAddSentinelWeapons: `Fehlende Wächter-Waffen hinzufügen`,
 | 
					    inventory_bulkAddSentinelWeapons: `Fehlende Wächter-Waffen hinzufügen`,
 | 
				
			||||||
 | 
					    inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`,
 | 
				
			||||||
 | 
					    inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`,
 | 
				
			||||||
    inventory_bulkAddEvolutionProgress: `Fehlende Incarnon-Entwicklungsfortschritte hinzufügen`,
 | 
					    inventory_bulkAddEvolutionProgress: `Fehlende Incarnon-Entwicklungsfortschritte hinzufügen`,
 | 
				
			||||||
    inventory_bulkRankUpSuits: `Alle Warframes auf Max. Rang`,
 | 
					    inventory_bulkRankUpSuits: `Alle Warframes auf Max. Rang`,
 | 
				
			||||||
    inventory_bulkRankUpWeapons: `Alle Waffen auf Max. Rang`,
 | 
					    inventory_bulkRankUpWeapons: `Alle Waffen auf Max. Rang`,
 | 
				
			||||||
@ -192,11 +209,8 @@ dict = {
 | 
				
			|||||||
    cheats_dontSubtractVoidTraces: `Void-Spuren nicht verbrauchen`,
 | 
					    cheats_dontSubtractVoidTraces: `Void-Spuren nicht verbrauchen`,
 | 
				
			||||||
    cheats_dontSubtractConsumables: `Verbrauchsgegenstände (Ausrüstung) nicht verbrauchen`,
 | 
					    cheats_dontSubtractConsumables: `Verbrauchsgegenstände (Ausrüstung) nicht verbrauchen`,
 | 
				
			||||||
    cheats_unlockAllShipFeatures: `Alle Schiffs-Funktionen freischalten`,
 | 
					    cheats_unlockAllShipFeatures: `Alle Schiffs-Funktionen freischalten`,
 | 
				
			||||||
    cheats_unlockAllShipDecorations: `Alle Schiffsdekorationen freischalten`,
 | 
					 | 
				
			||||||
    cheats_unlockAllFlavourItems: `Alle <abbr title="Animationssets, Glyphen, Farbpaletten usw.">Sammlerstücke</abbr> freischalten`,
 | 
					 | 
				
			||||||
    cheats_unlockAllSkins: `Alle Skins freischalten`,
 | 
					    cheats_unlockAllSkins: `Alle Skins freischalten`,
 | 
				
			||||||
    cheats_unlockAllCapturaScenes: `Alle Photora-Szenen freischalten`,
 | 
					    cheats_unlockAllCapturaScenes: `Alle Photora-Szenen freischalten`,
 | 
				
			||||||
    cheats_unlockAllDecoRecipes: `Alle Dojo-Deko-Baupläne freischalten`,
 | 
					 | 
				
			||||||
    cheats_universalPolarityEverywhere: `Universelle Polarität überall`,
 | 
					    cheats_universalPolarityEverywhere: `Universelle Polarität überall`,
 | 
				
			||||||
    cheats_unlockDoubleCapacityPotatoesEverywhere: `Orokin Reaktor & Beschleuniger überall`,
 | 
					    cheats_unlockDoubleCapacityPotatoesEverywhere: `Orokin Reaktor & Beschleuniger überall`,
 | 
				
			||||||
    cheats_unlockExilusEverywhere: `Exilus-Adapter überall`,
 | 
					    cheats_unlockExilusEverywhere: `Exilus-Adapter überall`,
 | 
				
			||||||
@ -387,5 +401,35 @@ dict = {
 | 
				
			|||||||
    theme_dark: `Dunkles Design`,
 | 
					    theme_dark: `Dunkles Design`,
 | 
				
			||||||
    theme_light: `Helles Design`,
 | 
					    theme_light: `Helles Design`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    guildView_cheats: `[UNTRANSLATED] Clan Cheats`,
 | 
				
			||||||
 | 
					    guildView_techProjects: `Forschung`,
 | 
				
			||||||
 | 
					    guildView_vaultDecoRecipes: `[UNTRANSLATED] Dojo Deco Recipes`,
 | 
				
			||||||
 | 
					    guildView_alliance: `Allianz`,
 | 
				
			||||||
 | 
					    guildView_members: `Mitglieder`,
 | 
				
			||||||
 | 
					    guildView_pending: `Ausstehend`,
 | 
				
			||||||
 | 
					    guildView_classDisplay: `Rang |CLASS|`,
 | 
				
			||||||
 | 
					    guildView_tierDisplay: `|TIER| Clan`,
 | 
				
			||||||
 | 
					    guildView_tier1: `Geist`,
 | 
				
			||||||
 | 
					    guildView_tier2: `Schatten`,
 | 
				
			||||||
 | 
					    guildView_tier3: `Sturm`,
 | 
				
			||||||
 | 
					    guildView_tier4: `Berg`,
 | 
				
			||||||
 | 
					    guildView_tier5: `Mond`,
 | 
				
			||||||
 | 
					    guildView_rank_creator: `Gründer Kriegsherr`,
 | 
				
			||||||
 | 
					    guildView_rank_general: `General`,
 | 
				
			||||||
 | 
					    guildView_rank_initiate: `Initiant`,
 | 
				
			||||||
 | 
					    guildView_rank_leader: `Anführer`,
 | 
				
			||||||
 | 
					    guildView_rank_officer: `Offizier`,
 | 
				
			||||||
 | 
					    guildView_rank_sage: `Weiser`,
 | 
				
			||||||
 | 
					    guildView_rank_soldier: `Soldat`,
 | 
				
			||||||
 | 
					    guildView_rank_utility: `Versorger`,
 | 
				
			||||||
 | 
					    guildView_rank_warlord: `Kriegsherr`,
 | 
				
			||||||
 | 
					    guildView_currency_owned: `[UNTRANSLATED] |COUNT| in Vault.`,
 | 
				
			||||||
 | 
					    guildView_bulkAddTechProjects: `[UNTRANSLATED] Add Missing Research`,
 | 
				
			||||||
 | 
					    guildView_bulkAddVaultDecoRecipes: `[UNTRANSLATED] Add Missing Dojo Deco Recipes`,
 | 
				
			||||||
 | 
					    guildView_bulkFundTechProjects: `[UNTRANSLATED] Fund All Research`,
 | 
				
			||||||
 | 
					    guildView_bulkCompleteTechProjects: `[UNTRANSLATED] Complete All Research`,
 | 
				
			||||||
 | 
					    guildView_promote: `Befördern`,
 | 
				
			||||||
 | 
					    guildView_demote: `Degradieren`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    prettier_sucks_ass: ``
 | 
					    prettier_sucks_ass: ``
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
dict = {
 | 
					dict = {
 | 
				
			||||||
    general_inventoryUpdateNote: `Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`,
 | 
					    general_inventoryUpdateNote: `Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`,
 | 
				
			||||||
 | 
					    general_inventoryUpdateNoteGameWs: `Note: You may need to reopen any menu you are on for changes to be reflected.`,
 | 
				
			||||||
    general_addButton: `Add`,
 | 
					    general_addButton: `Add`,
 | 
				
			||||||
    general_setButton: `Set`,
 | 
					    general_setButton: `Set`,
 | 
				
			||||||
    general_none: `None`,
 | 
					    general_none: `None`,
 | 
				
			||||||
@ -30,6 +31,8 @@ dict = {
 | 
				
			|||||||
    code_renamePrompt: `Enter new custom name:`,
 | 
					    code_renamePrompt: `Enter new custom name:`,
 | 
				
			||||||
    code_remove: `Remove`,
 | 
					    code_remove: `Remove`,
 | 
				
			||||||
    code_addItemsConfirm: `Are you sure you want to add |COUNT| items to your account?`,
 | 
					    code_addItemsConfirm: `Are you sure you want to add |COUNT| items to your account?`,
 | 
				
			||||||
 | 
					    code_addTechProjectsConfirm: `Are you sure you want to add |COUNT| research to your clan?`,
 | 
				
			||||||
 | 
					    code_addDecoRecipesConfirm: `Are you sure you want to add |COUNT| deco recipes to your clan?`,
 | 
				
			||||||
    code_succRankUp: `Successfully ranked up.`,
 | 
					    code_succRankUp: `Successfully ranked up.`,
 | 
				
			||||||
    code_noEquipmentToRankUp: `No equipment to rank up.`,
 | 
					    code_noEquipmentToRankUp: `No equipment to rank up.`,
 | 
				
			||||||
    code_succAdded: `Successfully added.`,
 | 
					    code_succAdded: `Successfully added.`,
 | 
				
			||||||
@ -41,6 +44,7 @@ dict = {
 | 
				
			|||||||
    code_rank: `Rank`,
 | 
					    code_rank: `Rank`,
 | 
				
			||||||
    code_rankUp: `Rank up`,
 | 
					    code_rankUp: `Rank up`,
 | 
				
			||||||
    code_rankDown: `Rank down`,
 | 
					    code_rankDown: `Rank down`,
 | 
				
			||||||
 | 
					    code_unlockLevelCap: `Unlock level cap`,
 | 
				
			||||||
    code_count: `Count`,
 | 
					    code_count: `Count`,
 | 
				
			||||||
    code_focusAllUnlocked: `All focus schools are already unlocked.`,
 | 
					    code_focusAllUnlocked: `All focus schools are already unlocked.`,
 | 
				
			||||||
    code_focusUnlocked: `Unlocked |COUNT| new focus schools! An inventory update will be needed for the changes to be reflected in-game. Visiting the navigation should be the easiest way to trigger that.`,
 | 
					    code_focusUnlocked: `Unlocked |COUNT| new focus schools! An inventory update will be needed for the changes to be reflected in-game. Visiting the navigation should be the easiest way to trigger that.`,
 | 
				
			||||||
@ -60,8 +64,16 @@ dict = {
 | 
				
			|||||||
    code_completed: `Completed`,
 | 
					    code_completed: `Completed`,
 | 
				
			||||||
    code_active: `Active`,
 | 
					    code_active: `Active`,
 | 
				
			||||||
    code_pigment: `Pigment`,
 | 
					    code_pigment: `Pigment`,
 | 
				
			||||||
 | 
					    code_controller: `Controller cursor`,
 | 
				
			||||||
 | 
					    code_mouseLine: `Line cursor`,
 | 
				
			||||||
 | 
					    code_mouse: `Cursor`,
 | 
				
			||||||
 | 
					    code_itemColorPalette: `|ITEM| Color Palette`,
 | 
				
			||||||
    code_mature: `Mature for combat`,
 | 
					    code_mature: `Mature for combat`,
 | 
				
			||||||
    code_unmature: `Regress genetic aging`,
 | 
					    code_unmature: `Regress genetic aging`,
 | 
				
			||||||
 | 
					    code_fund: `Fund`,
 | 
				
			||||||
 | 
					    code_funded: `Funded`,
 | 
				
			||||||
 | 
					    code_replays: `Replays`,
 | 
				
			||||||
 | 
					    code_stalker: `Stalker`,
 | 
				
			||||||
    code_succChange: `Successfully changed.`,
 | 
					    code_succChange: `Successfully changed.`,
 | 
				
			||||||
    code_requiredInvigorationUpgrade: `You must select both an offensive & defensive upgrade.`,
 | 
					    code_requiredInvigorationUpgrade: `You must select both an offensive & defensive upgrade.`,
 | 
				
			||||||
    login_description: `Login using your OpenWF account credentials (same as in-game when connecting to this server).`,
 | 
					    login_description: `Login using your OpenWF account credentials (same as in-game when connecting to this server).`,
 | 
				
			||||||
@ -73,6 +85,7 @@ dict = {
 | 
				
			|||||||
    navbar_renameAccount: `Rename Account`,
 | 
					    navbar_renameAccount: `Rename Account`,
 | 
				
			||||||
    navbar_deleteAccount: `Delete Account`,
 | 
					    navbar_deleteAccount: `Delete Account`,
 | 
				
			||||||
    navbar_inventory: `Inventory`,
 | 
					    navbar_inventory: `Inventory`,
 | 
				
			||||||
 | 
					    navbar_guildView: `Clan`,
 | 
				
			||||||
    navbar_mods: `Mods`,
 | 
					    navbar_mods: `Mods`,
 | 
				
			||||||
    navbar_quests: `Quests`,
 | 
					    navbar_quests: `Quests`,
 | 
				
			||||||
    navbar_cheats: `Cheats`,
 | 
					    navbar_cheats: `Cheats`,
 | 
				
			||||||
@ -95,13 +108,17 @@ dict = {
 | 
				
			|||||||
    inventory_moaPets: `Moas`,
 | 
					    inventory_moaPets: `Moas`,
 | 
				
			||||||
    inventory_kubrowPets: `Beasts`,
 | 
					    inventory_kubrowPets: `Beasts`,
 | 
				
			||||||
    inventory_evolutionProgress: `Incarnon Evolution Progress`,
 | 
					    inventory_evolutionProgress: `Incarnon Evolution Progress`,
 | 
				
			||||||
    inventory_Boosters: `Boosters`,
 | 
					    inventory_boosters: `Boosters`,
 | 
				
			||||||
 | 
					    inventory_flavourItems: `<abbr title="Animation Sets, Glyphs, Palettes, etc.">Flavour Items</abbr>`,
 | 
				
			||||||
 | 
					    inventory_shipDecorations: `Ship Decorations`,
 | 
				
			||||||
    inventory_bulkAddSuits: `Add Missing Warframes`,
 | 
					    inventory_bulkAddSuits: `Add Missing Warframes`,
 | 
				
			||||||
    inventory_bulkAddWeapons: `Add Missing Weapons`,
 | 
					    inventory_bulkAddWeapons: `Add Missing Weapons`,
 | 
				
			||||||
    inventory_bulkAddSpaceSuits: `Add Missing Archwings`,
 | 
					    inventory_bulkAddSpaceSuits: `Add Missing Archwings`,
 | 
				
			||||||
    inventory_bulkAddSpaceWeapons: `Add Missing Archwing Weapons`,
 | 
					    inventory_bulkAddSpaceWeapons: `Add Missing Archwing Weapons`,
 | 
				
			||||||
    inventory_bulkAddSentinels: `Add Missing Sentinels`,
 | 
					    inventory_bulkAddSentinels: `Add Missing Sentinels`,
 | 
				
			||||||
    inventory_bulkAddSentinelWeapons: `Add Missing Sentinel Weapons`,
 | 
					    inventory_bulkAddSentinelWeapons: `Add Missing Sentinel Weapons`,
 | 
				
			||||||
 | 
					    inventory_bulkAddFlavourItems: `Add Missing Flavour Items`,
 | 
				
			||||||
 | 
					    inventory_bulkAddShipDecorations: `Add Missing Ship Decorations`,
 | 
				
			||||||
    inventory_bulkAddEvolutionProgress: `Add Missing Incarnon Evolution Progress`,
 | 
					    inventory_bulkAddEvolutionProgress: `Add Missing Incarnon Evolution Progress`,
 | 
				
			||||||
    inventory_bulkRankUpSuits: `Max Rank All Warframes`,
 | 
					    inventory_bulkRankUpSuits: `Max Rank All Warframes`,
 | 
				
			||||||
    inventory_bulkRankUpWeapons: `Max Rank All Weapons`,
 | 
					    inventory_bulkRankUpWeapons: `Max Rank All Weapons`,
 | 
				
			||||||
@ -191,11 +208,8 @@ dict = {
 | 
				
			|||||||
    cheats_dontSubtractVoidTraces: `Don't Subtract Void Traces`,
 | 
					    cheats_dontSubtractVoidTraces: `Don't Subtract Void Traces`,
 | 
				
			||||||
    cheats_dontSubtractConsumables: `Don't Subtract Consumables`,
 | 
					    cheats_dontSubtractConsumables: `Don't Subtract Consumables`,
 | 
				
			||||||
    cheats_unlockAllShipFeatures: `Unlock All Ship Features`,
 | 
					    cheats_unlockAllShipFeatures: `Unlock All Ship Features`,
 | 
				
			||||||
    cheats_unlockAllShipDecorations: `Unlock All Ship Decorations`,
 | 
					 | 
				
			||||||
    cheats_unlockAllFlavourItems: `Unlock All <abbr title="Animation Sets, Glyphs, Palettes, etc.">Flavor Items</abbr>`,
 | 
					 | 
				
			||||||
    cheats_unlockAllSkins: `Unlock All Skins`,
 | 
					    cheats_unlockAllSkins: `Unlock All Skins`,
 | 
				
			||||||
    cheats_unlockAllCapturaScenes: `Unlock All Captura Scenes`,
 | 
					    cheats_unlockAllCapturaScenes: `Unlock All Captura Scenes`,
 | 
				
			||||||
    cheats_unlockAllDecoRecipes: `Unlock All Dojo Deco Recipes`,
 | 
					 | 
				
			||||||
    cheats_universalPolarityEverywhere: `Universal Polarity Everywhere`,
 | 
					    cheats_universalPolarityEverywhere: `Universal Polarity Everywhere`,
 | 
				
			||||||
    cheats_unlockDoubleCapacityPotatoesEverywhere: `Potatoes Everywhere`,
 | 
					    cheats_unlockDoubleCapacityPotatoesEverywhere: `Potatoes Everywhere`,
 | 
				
			||||||
    cheats_unlockExilusEverywhere: `Exilus Adapters Everywhere`,
 | 
					    cheats_unlockExilusEverywhere: `Exilus Adapters Everywhere`,
 | 
				
			||||||
@ -386,5 +400,35 @@ dict = {
 | 
				
			|||||||
    theme_dark: `Dark Theme`,
 | 
					    theme_dark: `Dark Theme`,
 | 
				
			||||||
    theme_light: `Light Theme`,
 | 
					    theme_light: `Light Theme`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    guildView_cheats: `Clan Cheats`,
 | 
				
			||||||
 | 
					    guildView_techProjects: `Research`,
 | 
				
			||||||
 | 
					    guildView_vaultDecoRecipes: `Dojo Deco Recipes`,
 | 
				
			||||||
 | 
					    guildView_alliance: `Alliance`,
 | 
				
			||||||
 | 
					    guildView_members: `Members`,
 | 
				
			||||||
 | 
					    guildView_pending: `Pending`,
 | 
				
			||||||
 | 
					    guildView_classDisplay: `Rank |CLASS|`,
 | 
				
			||||||
 | 
					    guildView_tierDisplay: `|TIER| Clan`,
 | 
				
			||||||
 | 
					    guildView_tier1: `Ghost`,
 | 
				
			||||||
 | 
					    guildView_tier2: `Shadow`,
 | 
				
			||||||
 | 
					    guildView_tier3: `Storm`,
 | 
				
			||||||
 | 
					    guildView_tier4: `Mountain`,
 | 
				
			||||||
 | 
					    guildView_tier5: `Moon`,
 | 
				
			||||||
 | 
					    guildView_rank_creator: `Founding Warlord`,
 | 
				
			||||||
 | 
					    guildView_rank_general: `General`,
 | 
				
			||||||
 | 
					    guildView_rank_initiate: `Initiate`,
 | 
				
			||||||
 | 
					    guildView_rank_leader: `Leader`,
 | 
				
			||||||
 | 
					    guildView_rank_officer: `Officer`,
 | 
				
			||||||
 | 
					    guildView_rank_sage: `Sage`,
 | 
				
			||||||
 | 
					    guildView_rank_soldier: `Soldier`,
 | 
				
			||||||
 | 
					    guildView_rank_utility: `Utility`,
 | 
				
			||||||
 | 
					    guildView_rank_warlord: `Warlord`,
 | 
				
			||||||
 | 
					    guildView_currency_owned: `|COUNT| in Vault.`,
 | 
				
			||||||
 | 
					    guildView_bulkAddTechProjects: `Add Missing Research`,
 | 
				
			||||||
 | 
					    guildView_bulkAddVaultDecoRecipes: `Add Missing Dojo Deco Recipes`,
 | 
				
			||||||
 | 
					    guildView_bulkFundTechProjects: `Fund All Research`,
 | 
				
			||||||
 | 
					    guildView_bulkCompleteTechProjects: `Complete All Research`,
 | 
				
			||||||
 | 
					    guildView_promote: `Promote`,
 | 
				
			||||||
 | 
					    guildView_demote: `Demote`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    prettier_sucks_ass: ``
 | 
					    prettier_sucks_ass: ``
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
// Spanish translation by hxedcl
 | 
					// Spanish translation by hxedcl
 | 
				
			||||||
dict = {
 | 
					dict = {
 | 
				
			||||||
    general_inventoryUpdateNote: `Para ver los cambios en el juego, necesitas volver a sincronizar tu inventario, por ejemplo, usando el comando /sync del bootstrapper, visitando un dojo o repetidor, o volviendo a iniciar sesión.`,
 | 
					    general_inventoryUpdateNote: `Para ver los cambios en el juego, necesitas volver a sincronizar tu inventario, por ejemplo, usando el comando /sync del bootstrapper, visitando un dojo o repetidor, o volviendo a iniciar sesión.`,
 | 
				
			||||||
 | 
					    general_inventoryUpdateNoteGameWs: `[UNTRANSLATED] Note: You may need to reopen any menu you are on for changes to be reflected.`,
 | 
				
			||||||
    general_addButton: `Agregar`,
 | 
					    general_addButton: `Agregar`,
 | 
				
			||||||
    general_setButton: `Establecer`,
 | 
					    general_setButton: `Establecer`,
 | 
				
			||||||
    general_none: `Ninguno`,
 | 
					    general_none: `Ninguno`,
 | 
				
			||||||
@ -31,6 +32,8 @@ dict = {
 | 
				
			|||||||
    code_renamePrompt: `Escribe tu nuevo nombre personalizado:`,
 | 
					    code_renamePrompt: `Escribe tu nuevo nombre personalizado:`,
 | 
				
			||||||
    code_remove: `Quitar`,
 | 
					    code_remove: `Quitar`,
 | 
				
			||||||
    code_addItemsConfirm: `¿Estás seguro de que deseas agregar |COUNT| objetos a tu cuenta?`,
 | 
					    code_addItemsConfirm: `¿Estás seguro de que deseas agregar |COUNT| objetos a tu cuenta?`,
 | 
				
			||||||
 | 
					    code_addTechProjectsConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| research to your clan?`,
 | 
				
			||||||
 | 
					    code_addDecoRecipesConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| deco recipes to your clan?`,
 | 
				
			||||||
    code_succRankUp: `Ascenso exitoso.`,
 | 
					    code_succRankUp: `Ascenso exitoso.`,
 | 
				
			||||||
    code_noEquipmentToRankUp: `No hay equipo para ascender.`,
 | 
					    code_noEquipmentToRankUp: `No hay equipo para ascender.`,
 | 
				
			||||||
    code_succAdded: `Agregado exitosamente.`,
 | 
					    code_succAdded: `Agregado exitosamente.`,
 | 
				
			||||||
@ -42,6 +45,7 @@ dict = {
 | 
				
			|||||||
    code_rank: `Rango`,
 | 
					    code_rank: `Rango`,
 | 
				
			||||||
    code_rankUp: `Subir de rango`,
 | 
					    code_rankUp: `Subir de rango`,
 | 
				
			||||||
    code_rankDown: `Bajar de rango`,
 | 
					    code_rankDown: `Bajar de rango`,
 | 
				
			||||||
 | 
					    code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`,
 | 
				
			||||||
    code_count: `Cantidad`,
 | 
					    code_count: `Cantidad`,
 | 
				
			||||||
    code_focusAllUnlocked: `Todas las escuelas de enfoque ya están desbloqueadas.`,
 | 
					    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_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.`,
 | 
				
			||||||
@ -61,8 +65,16 @@ dict = {
 | 
				
			|||||||
    code_completed: `Completada`,
 | 
					    code_completed: `Completada`,
 | 
				
			||||||
    code_active: `Activa`,
 | 
					    code_active: `Activa`,
 | 
				
			||||||
    code_pigment: `Pigmento`,
 | 
					    code_pigment: `Pigmento`,
 | 
				
			||||||
 | 
					    code_controller: `[UNTRANSLATED] Controller cursor`,
 | 
				
			||||||
 | 
					    code_mouseLine: `[UNTRANSLATED] Line cursor`,
 | 
				
			||||||
 | 
					    code_mouse: `[UNTRANSLATED] Cursor`,
 | 
				
			||||||
 | 
					    code_itemColorPalette: `Paleta de colores |ITEM|`,
 | 
				
			||||||
    code_mature: `Listo para el combate`,
 | 
					    code_mature: `Listo para el combate`,
 | 
				
			||||||
    code_unmature: `Regresar el envejecimiento genético`,
 | 
					    code_unmature: `Regresar el envejecimiento genético`,
 | 
				
			||||||
 | 
					    code_fund: `[UNTRANSLATED] Fund`,
 | 
				
			||||||
 | 
					    code_funded: `[UNTRANSLATED] Funded`,
 | 
				
			||||||
 | 
					    code_replays: `[UNTRANSLATED] Replays`,
 | 
				
			||||||
 | 
					    code_stalker: `Stalker`,
 | 
				
			||||||
    code_succChange: `Cambiado correctamente`,
 | 
					    code_succChange: `Cambiado correctamente`,
 | 
				
			||||||
    code_requiredInvigorationUpgrade: `Debes seleccionar una mejora ofensiva y una defensiva.`,
 | 
					    code_requiredInvigorationUpgrade: `Debes seleccionar una mejora ofensiva y una defensiva.`,
 | 
				
			||||||
    login_description: `Inicia sesión con las credenciales de tu cuenta OpenWF (las mismas que usas en el juego al conectarte a este servidor).`,
 | 
					    login_description: `Inicia sesión con las credenciales de tu cuenta OpenWF (las mismas que usas en el juego al conectarte a este servidor).`,
 | 
				
			||||||
@ -74,6 +86,7 @@ dict = {
 | 
				
			|||||||
    navbar_renameAccount: `Renombrar cuenta`,
 | 
					    navbar_renameAccount: `Renombrar cuenta`,
 | 
				
			||||||
    navbar_deleteAccount: `Eliminar cuenta`,
 | 
					    navbar_deleteAccount: `Eliminar cuenta`,
 | 
				
			||||||
    navbar_inventory: `Inventario`,
 | 
					    navbar_inventory: `Inventario`,
 | 
				
			||||||
 | 
					    navbar_guildView: `Clan`,
 | 
				
			||||||
    navbar_mods: `Mods`,
 | 
					    navbar_mods: `Mods`,
 | 
				
			||||||
    navbar_quests: `Misiones`,
 | 
					    navbar_quests: `Misiones`,
 | 
				
			||||||
    navbar_cheats: `Trucos`,
 | 
					    navbar_cheats: `Trucos`,
 | 
				
			||||||
@ -96,13 +109,17 @@ dict = {
 | 
				
			|||||||
    inventory_moaPets: `Moas`,
 | 
					    inventory_moaPets: `Moas`,
 | 
				
			||||||
    inventory_kubrowPets: `Bestias`,
 | 
					    inventory_kubrowPets: `Bestias`,
 | 
				
			||||||
    inventory_evolutionProgress: `Progreso de evolución Incarnon`,
 | 
					    inventory_evolutionProgress: `Progreso de evolución Incarnon`,
 | 
				
			||||||
    inventory_Boosters: `Potenciadores`,
 | 
					    inventory_boosters: `Potenciadores`,
 | 
				
			||||||
 | 
					    inventory_flavourItems: `<abbr title="Conjuntos de animaciones, glifos, paletas, etc.">Ítems estéticos</abbr>`,
 | 
				
			||||||
 | 
					    inventory_shipDecorations: `Decoraciones de nave`,
 | 
				
			||||||
    inventory_bulkAddSuits: `Agregar Warframes faltantes`,
 | 
					    inventory_bulkAddSuits: `Agregar Warframes faltantes`,
 | 
				
			||||||
    inventory_bulkAddWeapons: `Agregar armas faltantes`,
 | 
					    inventory_bulkAddWeapons: `Agregar armas faltantes`,
 | 
				
			||||||
    inventory_bulkAddSpaceSuits: `Agregar Archwings faltantes`,
 | 
					    inventory_bulkAddSpaceSuits: `Agregar Archwings faltantes`,
 | 
				
			||||||
    inventory_bulkAddSpaceWeapons: `Agregar armas Archwing faltantes`,
 | 
					    inventory_bulkAddSpaceWeapons: `Agregar armas Archwing faltantes`,
 | 
				
			||||||
    inventory_bulkAddSentinels: `Agregar centinelas faltantes`,
 | 
					    inventory_bulkAddSentinels: `Agregar centinelas faltantes`,
 | 
				
			||||||
    inventory_bulkAddSentinelWeapons: `Agregar armas de centinela faltantes`,
 | 
					    inventory_bulkAddSentinelWeapons: `Agregar armas de centinela faltantes`,
 | 
				
			||||||
 | 
					    inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`,
 | 
				
			||||||
 | 
					    inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`,
 | 
				
			||||||
    inventory_bulkAddEvolutionProgress: `Completar el progreso de evolución Incarnon faltante`,
 | 
					    inventory_bulkAddEvolutionProgress: `Completar el progreso de evolución Incarnon faltante`,
 | 
				
			||||||
    inventory_bulkRankUpSuits: `Maximizar rango de todos los Warframes`,
 | 
					    inventory_bulkRankUpSuits: `Maximizar rango de todos los Warframes`,
 | 
				
			||||||
    inventory_bulkRankUpWeapons: `Maximizar rango de todas las armas`,
 | 
					    inventory_bulkRankUpWeapons: `Maximizar rango de todas las armas`,
 | 
				
			||||||
@ -192,11 +209,8 @@ dict = {
 | 
				
			|||||||
    cheats_dontSubtractVoidTraces: `No descontar vestigios del Vacío`,
 | 
					    cheats_dontSubtractVoidTraces: `No descontar vestigios del Vacío`,
 | 
				
			||||||
    cheats_dontSubtractConsumables: `No restar consumibles`,
 | 
					    cheats_dontSubtractConsumables: `No restar consumibles`,
 | 
				
			||||||
    cheats_unlockAllShipFeatures: `Desbloquear todas las funciones de nave`,
 | 
					    cheats_unlockAllShipFeatures: `Desbloquear todas las funciones de nave`,
 | 
				
			||||||
    cheats_unlockAllShipDecorations: `Desbloquear todas las decoraciones de nave`,
 | 
					 | 
				
			||||||
    cheats_unlockAllFlavourItems: `Desbloquear todos los <abbr title="Conjuntos de animaciones, glifos, paletas, etc.">ítems estéticos</abbr>`,
 | 
					 | 
				
			||||||
    cheats_unlockAllSkins: `Desbloquear todas las skins`,
 | 
					    cheats_unlockAllSkins: `Desbloquear todas las skins`,
 | 
				
			||||||
    cheats_unlockAllCapturaScenes: `Desbloquear todas las escenas de Captura`,
 | 
					    cheats_unlockAllCapturaScenes: `Desbloquear todas las escenas de Captura`,
 | 
				
			||||||
    cheats_unlockAllDecoRecipes: `Desbloquear todas las recetas decorativas del dojo`,
 | 
					 | 
				
			||||||
    cheats_universalPolarityEverywhere: `Polaridad universal en todas partes`,
 | 
					    cheats_universalPolarityEverywhere: `Polaridad universal en todas partes`,
 | 
				
			||||||
    cheats_unlockDoubleCapacityPotatoesEverywhere: `Patatas en todas partes`,
 | 
					    cheats_unlockDoubleCapacityPotatoesEverywhere: `Patatas en todas partes`,
 | 
				
			||||||
    cheats_unlockExilusEverywhere: `Adaptadores Exilus en todas partes`,
 | 
					    cheats_unlockExilusEverywhere: `Adaptadores Exilus en todas partes`,
 | 
				
			||||||
@ -387,5 +401,35 @@ dict = {
 | 
				
			|||||||
    theme_dark: `Tema Oscuro`,
 | 
					    theme_dark: `Tema Oscuro`,
 | 
				
			||||||
    theme_light: `Tema Claro`,
 | 
					    theme_light: `Tema Claro`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    guildView_cheats: `[UNTRANSLATED] Clan Cheats`,
 | 
				
			||||||
 | 
					    guildView_techProjects: `Investigación`,
 | 
				
			||||||
 | 
					    guildView_vaultDecoRecipes: `[UNTRANSLATED] Dojo Deco Recipes`,
 | 
				
			||||||
 | 
					    guildView_alliance: `Alianza`,
 | 
				
			||||||
 | 
					    guildView_members: `Miembros`,
 | 
				
			||||||
 | 
					    guildView_pending: `Pendiente`,
 | 
				
			||||||
 | 
					    guildView_classDisplay: `Rango |CLASS|`,
 | 
				
			||||||
 | 
					    guildView_tierDisplay: `Clan |TIER|`,
 | 
				
			||||||
 | 
					    guildView_tier1: `Fantasma`,
 | 
				
			||||||
 | 
					    guildView_tier2: `Sombra`,
 | 
				
			||||||
 | 
					    guildView_tier3: `Tormenta`,
 | 
				
			||||||
 | 
					    guildView_tier4: `Montaña`,
 | 
				
			||||||
 | 
					    guildView_tier5: `Luna`,
 | 
				
			||||||
 | 
					    guildView_rank_creator: `Señor de la guerra fundador`,
 | 
				
			||||||
 | 
					    guildView_rank_general: `General`,
 | 
				
			||||||
 | 
					    guildView_rank_initiate: `Iniciado`,
 | 
				
			||||||
 | 
					    guildView_rank_leader: `Líder`,
 | 
				
			||||||
 | 
					    guildView_rank_officer: `Oficial`,
 | 
				
			||||||
 | 
					    guildView_rank_sage: `Sabio`,
 | 
				
			||||||
 | 
					    guildView_rank_soldier: `Soldado`,
 | 
				
			||||||
 | 
					    guildView_rank_utility: `Utilitario`,
 | 
				
			||||||
 | 
					    guildView_rank_warlord: `Señor de la guerra`,
 | 
				
			||||||
 | 
					    guildView_currency_owned: `[UNTRANSLATED] |COUNT| in Vault.`,
 | 
				
			||||||
 | 
					    guildView_bulkAddTechProjects: `[UNTRANSLATED] Add Missing Research`,
 | 
				
			||||||
 | 
					    guildView_bulkAddVaultDecoRecipes: `[UNTRANSLATED] Add Missing Dojo Deco Recipes`,
 | 
				
			||||||
 | 
					    guildView_bulkFundTechProjects: `[UNTRANSLATED] Fund All Research`,
 | 
				
			||||||
 | 
					    guildView_bulkCompleteTechProjects: `[UNTRANSLATED] Complete All Research`,
 | 
				
			||||||
 | 
					    guildView_promote: `Promover`,
 | 
				
			||||||
 | 
					    guildView_demote: `Degradar`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    prettier_sucks_ass: ``
 | 
					    prettier_sucks_ass: ``
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
// French translation by Vitruvio
 | 
					// French translation by Vitruvio
 | 
				
			||||||
dict = {
 | 
					dict = {
 | 
				
			||||||
    general_inventoryUpdateNote: `Note : Pour voir les changements en jeu, l'inventaire doit être actualisé. Cela se fait en tapant /sync dans le tchat, en visitant un dojo/relais ou en se reconnectant.`,
 | 
					    general_inventoryUpdateNote: `Note : Pour voir les changements en jeu, l'inventaire doit être actualisé. Cela se fait en tapant /sync dans le tchat, en visitant un dojo/relais ou en se reconnectant.`,
 | 
				
			||||||
 | 
					    general_inventoryUpdateNoteGameWs: `Note : Rouvrir un menu est nécessaire pour voir les changements.`,
 | 
				
			||||||
    general_addButton: `Ajouter`,
 | 
					    general_addButton: `Ajouter`,
 | 
				
			||||||
    general_setButton: `Définir`,
 | 
					    general_setButton: `Définir`,
 | 
				
			||||||
    general_none: `Aucun`,
 | 
					    general_none: `Aucun`,
 | 
				
			||||||
@ -10,7 +11,7 @@ dict = {
 | 
				
			|||||||
    code_loginFail: `Connexion échouée. Vérifiez le mot de passe.`,
 | 
					    code_loginFail: `Connexion échouée. Vérifiez le mot de passe.`,
 | 
				
			||||||
    code_regFail: `Enregistrement impossible. Compte existant?`,
 | 
					    code_regFail: `Enregistrement impossible. Compte existant?`,
 | 
				
			||||||
    code_changeNameConfirm: `Nouveau nom du compte :`,
 | 
					    code_changeNameConfirm: `Nouveau nom du compte :`,
 | 
				
			||||||
    code_changeNameRetry: `[UNTRANSLATED] |NAME| is already taken.`,
 | 
					    code_changeNameRetry: `|NAME| est déjà pris.`,
 | 
				
			||||||
    code_deleteAccountConfirm: `Supprimer |DISPLAYNAME| (|EMAIL|) ? Cette action est irreversible.`,
 | 
					    code_deleteAccountConfirm: `Supprimer |DISPLAYNAME| (|EMAIL|) ? Cette action est irreversible.`,
 | 
				
			||||||
    code_archgun: `Archgun`,
 | 
					    code_archgun: `Archgun`,
 | 
				
			||||||
    code_melee: `Melee`,
 | 
					    code_melee: `Melee`,
 | 
				
			||||||
@ -31,6 +32,8 @@ dict = {
 | 
				
			|||||||
    code_renamePrompt: `Nouveau nom :`,
 | 
					    code_renamePrompt: `Nouveau nom :`,
 | 
				
			||||||
    code_remove: `Retirer`,
 | 
					    code_remove: `Retirer`,
 | 
				
			||||||
    code_addItemsConfirm: `Ajouter |COUNT| items à l'inventaire ?`,
 | 
					    code_addItemsConfirm: `Ajouter |COUNT| items à l'inventaire ?`,
 | 
				
			||||||
 | 
					    code_addTechProjectsConfirm: `Ajouter |COUNT| recherches au clan ?`,
 | 
				
			||||||
 | 
					    code_addDecoRecipesConfirm: `Ajouter |COUNT| décorations au clan ?`,
 | 
				
			||||||
    code_succRankUp: `Montée de niveau effectuée.`,
 | 
					    code_succRankUp: `Montée de niveau effectuée.`,
 | 
				
			||||||
    code_noEquipmentToRankUp: `Aucun équipement à monter de niveau.`,
 | 
					    code_noEquipmentToRankUp: `Aucun équipement à monter de niveau.`,
 | 
				
			||||||
    code_succAdded: `Ajouté.`,
 | 
					    code_succAdded: `Ajouté.`,
 | 
				
			||||||
@ -42,6 +45,7 @@ dict = {
 | 
				
			|||||||
    code_rank: `Rang`,
 | 
					    code_rank: `Rang`,
 | 
				
			||||||
    code_rankUp: `Monter de rang`,
 | 
					    code_rankUp: `Monter de rang`,
 | 
				
			||||||
    code_rankDown: `Baisser de rang`,
 | 
					    code_rankDown: `Baisser de rang`,
 | 
				
			||||||
 | 
					    code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`,
 | 
				
			||||||
    code_count: `Quantité`,
 | 
					    code_count: `Quantité`,
 | 
				
			||||||
    code_focusAllUnlocked: `Les écoles de Focus sont déjà déverrouillées.`,
 | 
					    code_focusAllUnlocked: `Les écoles de Focus sont déjà déverrouillées.`,
 | 
				
			||||||
    code_focusUnlocked: `|COUNT| écoles de Focus déverrouillées ! Synchronisation de l'inventaire nécessaire.`,
 | 
					    code_focusUnlocked: `|COUNT| écoles de Focus déverrouillées ! Synchronisation de l'inventaire nécessaire.`,
 | 
				
			||||||
@ -61,8 +65,16 @@ dict = {
 | 
				
			|||||||
    code_completed: `Complétée`,
 | 
					    code_completed: `Complétée`,
 | 
				
			||||||
    code_active: `Active`,
 | 
					    code_active: `Active`,
 | 
				
			||||||
    code_pigment: `Pigment`,
 | 
					    code_pigment: `Pigment`,
 | 
				
			||||||
 | 
					    code_controller: `[UNTRANSLATED] Controller cursor`,
 | 
				
			||||||
 | 
					    code_mouseLine: `[UNTRANSLATED] Line cursor`,
 | 
				
			||||||
 | 
					    code_mouse: `[UNTRANSLATED] Cursor`,
 | 
				
			||||||
 | 
					    code_itemColorPalette: `Palette de couleurs |ITEM|`,
 | 
				
			||||||
    code_mature: `Maturer pour le combat`,
 | 
					    code_mature: `Maturer pour le combat`,
 | 
				
			||||||
    code_unmature: `Régrésser l'âge génétique`,
 | 
					    code_unmature: `Régrésser l'âge génétique`,
 | 
				
			||||||
 | 
					    code_fund: `Financer`,
 | 
				
			||||||
 | 
					    code_funded: `Complété`,
 | 
				
			||||||
 | 
					    code_replays: `[UNTRANSLATED] Replays`,
 | 
				
			||||||
 | 
					    code_stalker: `Stalker`,
 | 
				
			||||||
    code_succChange: `Changement effectué.`,
 | 
					    code_succChange: `Changement effectué.`,
 | 
				
			||||||
    code_requiredInvigorationUpgrade: `Augmentation offensive et défensive requises.`,
 | 
					    code_requiredInvigorationUpgrade: `Augmentation offensive et défensive requises.`,
 | 
				
			||||||
    login_description: `Connexion avec les informations de connexion OpenWF.`,
 | 
					    login_description: `Connexion avec les informations de connexion OpenWF.`,
 | 
				
			||||||
@ -74,13 +86,14 @@ dict = {
 | 
				
			|||||||
    navbar_renameAccount: `Renommer le compte`,
 | 
					    navbar_renameAccount: `Renommer le compte`,
 | 
				
			||||||
    navbar_deleteAccount: `Supprimer le compte`,
 | 
					    navbar_deleteAccount: `Supprimer le compte`,
 | 
				
			||||||
    navbar_inventory: `Inventaire`,
 | 
					    navbar_inventory: `Inventaire`,
 | 
				
			||||||
 | 
					    navbar_guildView: `Clan`,
 | 
				
			||||||
    navbar_mods: `Mods`,
 | 
					    navbar_mods: `Mods`,
 | 
				
			||||||
    navbar_quests: `Quêtes`,
 | 
					    navbar_quests: `Quêtes`,
 | 
				
			||||||
    navbar_cheats: `Cheats`,
 | 
					    navbar_cheats: `Cheats`,
 | 
				
			||||||
    navbar_import: `Importer`,
 | 
					    navbar_import: `Importer`,
 | 
				
			||||||
    inventory_addItems: `Ajouter des items`,
 | 
					    inventory_addItems: `Ajouter des items`,
 | 
				
			||||||
    inventory_addItemByItemType: `[UNTRANSLATED] Raw`,
 | 
					    inventory_addItemByItemType: `Brut`,
 | 
				
			||||||
    inventory_addItemByItemType_warning: `[UNTRANSLATED] Use this feature at your own risk. It may break your inventory, and you will need to remove items manually if something goes wrong.`,
 | 
					    inventory_addItemByItemType_warning: `Cette fonctionnalité comporte des risques. Il faudra rajouter les items manuellement si l'inventaire est compris.`,
 | 
				
			||||||
    inventory_suits: `Warframes`,
 | 
					    inventory_suits: `Warframes`,
 | 
				
			||||||
    inventory_longGuns: `Armes principales`,
 | 
					    inventory_longGuns: `Armes principales`,
 | 
				
			||||||
    inventory_pistols: `Armes secondaires`,
 | 
					    inventory_pistols: `Armes secondaires`,
 | 
				
			||||||
@ -96,13 +109,17 @@ dict = {
 | 
				
			|||||||
    inventory_moaPets: `Moas`,
 | 
					    inventory_moaPets: `Moas`,
 | 
				
			||||||
    inventory_kubrowPets: `Bêtes`,
 | 
					    inventory_kubrowPets: `Bêtes`,
 | 
				
			||||||
    inventory_evolutionProgress: `Progrès de l'évolution Incarnon`,
 | 
					    inventory_evolutionProgress: `Progrès de l'évolution Incarnon`,
 | 
				
			||||||
    inventory_Boosters: `Boosters`,
 | 
					    inventory_boosters: `Boosters`,
 | 
				
			||||||
 | 
					    inventory_flavourItems: `[UNTRANSLATED] <abbr title="Animation Sets, Glyphs, Palettes, etc.">Flavour Items</abbr>`,
 | 
				
			||||||
 | 
					    inventory_shipDecorations: `Décorations du vaisseau`,
 | 
				
			||||||
    inventory_bulkAddSuits: `Ajouter les Warframes manquantes`,
 | 
					    inventory_bulkAddSuits: `Ajouter les Warframes manquantes`,
 | 
				
			||||||
    inventory_bulkAddWeapons: `Ajouter les armes manquantes`,
 | 
					    inventory_bulkAddWeapons: `Ajouter les armes manquantes`,
 | 
				
			||||||
    inventory_bulkAddSpaceSuits: `Ajouter les Archwings manquants`,
 | 
					    inventory_bulkAddSpaceSuits: `Ajouter les Archwings manquants`,
 | 
				
			||||||
    inventory_bulkAddSpaceWeapons: `Ajouter les armes d'Archwing manquantes`,
 | 
					    inventory_bulkAddSpaceWeapons: `Ajouter les armes d'Archwing manquantes`,
 | 
				
			||||||
    inventory_bulkAddSentinels: `Ajouter les Sentinelles manquantes`,
 | 
					    inventory_bulkAddSentinels: `Ajouter les Sentinelles manquantes`,
 | 
				
			||||||
    inventory_bulkAddSentinelWeapons: `Ajouter les armes de Sentinelles manquantes`,
 | 
					    inventory_bulkAddSentinelWeapons: `Ajouter les armes de Sentinelles manquantes`,
 | 
				
			||||||
 | 
					    inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`,
 | 
				
			||||||
 | 
					    inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`,
 | 
				
			||||||
    inventory_bulkAddEvolutionProgress: `Ajouter les évolutions Incarnon manquantes`,
 | 
					    inventory_bulkAddEvolutionProgress: `Ajouter les évolutions Incarnon manquantes`,
 | 
				
			||||||
    inventory_bulkRankUpSuits: `Toutes les Warframes au rang max`,
 | 
					    inventory_bulkRankUpSuits: `Toutes les Warframes au rang max`,
 | 
				
			||||||
    inventory_bulkRankUpWeapons: `Toutes les armes au rang max`,
 | 
					    inventory_bulkRankUpWeapons: `Toutes les armes au rang max`,
 | 
				
			||||||
@ -176,7 +193,7 @@ dict = {
 | 
				
			|||||||
    cheats_skipTutorial: `Passer le tutoriel`,
 | 
					    cheats_skipTutorial: `Passer le tutoriel`,
 | 
				
			||||||
    cheats_skipAllDialogue: `Passer les dialogues`,
 | 
					    cheats_skipAllDialogue: `Passer les dialogues`,
 | 
				
			||||||
    cheats_unlockAllScans: `Débloquer tous les scans`,
 | 
					    cheats_unlockAllScans: `Débloquer tous les scans`,
 | 
				
			||||||
    cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please that you'll need to relog for the client to refresh this.`,
 | 
					    cheats_unlockSuccRelog: `Succès. Une reconnexion est requise pour appliquer les changements.`,
 | 
				
			||||||
    cheats_unlockAllMissions: `Débloquer toutes les missions`,
 | 
					    cheats_unlockAllMissions: `Débloquer toutes les missions`,
 | 
				
			||||||
    cheats_unlockAllMissions_ok: `Succès. Une actualisation de l'inventaire est nécessaire.`,
 | 
					    cheats_unlockAllMissions_ok: `Succès. Une actualisation de l'inventaire est nécessaire.`,
 | 
				
			||||||
    cheats_infiniteCredits: `Crédits infinis`,
 | 
					    cheats_infiniteCredits: `Crédits infinis`,
 | 
				
			||||||
@ -192,11 +209,8 @@ dict = {
 | 
				
			|||||||
    cheats_dontSubtractVoidTraces: `Ne pas consommer de Void Traces`,
 | 
					    cheats_dontSubtractVoidTraces: `Ne pas consommer de Void Traces`,
 | 
				
			||||||
    cheats_dontSubtractConsumables: `Ne pas retirer de consommables`,
 | 
					    cheats_dontSubtractConsumables: `Ne pas retirer de consommables`,
 | 
				
			||||||
    cheats_unlockAllShipFeatures: `Débloquer tous les segments du vaisseau`,
 | 
					    cheats_unlockAllShipFeatures: `Débloquer tous les segments du vaisseau`,
 | 
				
			||||||
    cheats_unlockAllShipDecorations: `Débloquer toutes les décorations du vaisseau`,
 | 
					 | 
				
			||||||
    cheats_unlockAllFlavourItems: `Débloquer tous les <abbr title="Animations, Glyphes, Palettes, etc.">Flavor Items</abbr>`,
 | 
					 | 
				
			||||||
    cheats_unlockAllSkins: `Débloquer tous les skins`,
 | 
					    cheats_unlockAllSkins: `Débloquer tous les skins`,
 | 
				
			||||||
    cheats_unlockAllCapturaScenes: `Débloquer toutes les scènes captura`,
 | 
					    cheats_unlockAllCapturaScenes: `Débloquer toutes les scènes captura`,
 | 
				
			||||||
    cheats_unlockAllDecoRecipes: `Débloquer toutes les recherches dojo`,
 | 
					 | 
				
			||||||
    cheats_universalPolarityEverywhere: `Polarités universelles partout`,
 | 
					    cheats_universalPolarityEverywhere: `Polarités universelles partout`,
 | 
				
			||||||
    cheats_unlockDoubleCapacityPotatoesEverywhere: `Réacteurs et Catalyseurs partout`,
 | 
					    cheats_unlockDoubleCapacityPotatoesEverywhere: `Réacteurs et Catalyseurs partout`,
 | 
				
			||||||
    cheats_unlockExilusEverywhere: `Adaptateurs Exilus partout`,
 | 
					    cheats_unlockExilusEverywhere: `Adaptateurs Exilus partout`,
 | 
				
			||||||
@ -213,7 +227,7 @@ dict = {
 | 
				
			|||||||
    cheats_baroFullyStocked: `Stock de Baro au max`,
 | 
					    cheats_baroFullyStocked: `Stock de Baro au max`,
 | 
				
			||||||
    cheats_syndicateMissionsRepeatable: `Mission syndicat répétables`,
 | 
					    cheats_syndicateMissionsRepeatable: `Mission syndicat répétables`,
 | 
				
			||||||
    cheats_unlockAllProfitTakerStages: `Débloquer toutes les étapes du Preneur de Profit`,
 | 
					    cheats_unlockAllProfitTakerStages: `Débloquer toutes les étapes du Preneur de Profit`,
 | 
				
			||||||
    cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging..`,
 | 
					    cheats_unlockSuccInventory: `Succès. Une resynchronisation est nécessaire en tapant "/sync" dans le tchat, en visitant un relais ou en se reconnectant.`,
 | 
				
			||||||
    cheats_instantFinishRivenChallenge: `Débloquer le challenge Riven instantanément`,
 | 
					    cheats_instantFinishRivenChallenge: `Débloquer le challenge Riven instantanément`,
 | 
				
			||||||
    cheats_instantResourceExtractorDrones: `Ressources de drones d'extraction instantannées`,
 | 
					    cheats_instantResourceExtractorDrones: `Ressources de drones d'extraction instantannées`,
 | 
				
			||||||
    cheats_noResourceExtractorDronesDamage: `Aucun dégâts aux drones d'extraction de resources`,
 | 
					    cheats_noResourceExtractorDronesDamage: `Aucun dégâts aux drones d'extraction de resources`,
 | 
				
			||||||
@ -242,7 +256,7 @@ dict = {
 | 
				
			|||||||
    cheats_changeSupportedSyndicate: `Allégeance`,
 | 
					    cheats_changeSupportedSyndicate: `Allégeance`,
 | 
				
			||||||
    cheats_changeButton: `Changer`,
 | 
					    cheats_changeButton: `Changer`,
 | 
				
			||||||
    cheats_markAllAsRead: `Marquer la boîte de réception comme lue`,
 | 
					    cheats_markAllAsRead: `Marquer la boîte de réception comme lue`,
 | 
				
			||||||
    cheats_finishInvasionsInOneMission: `[UNTRANSLATED] Finish Invasions in One Mission`,
 | 
					    cheats_finishInvasionsInOneMission: `Compléter les invasions en une mission.`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    worldState: `Carte Solaire`,
 | 
					    worldState: `Carte Solaire`,
 | 
				
			||||||
    worldState_creditBoost: `Booster de Crédit`,
 | 
					    worldState_creditBoost: `Booster de Crédit`,
 | 
				
			||||||
@ -252,30 +266,30 @@ dict = {
 | 
				
			|||||||
    worldState_baroTennoConRelay: `Relais Baro TennoCon`,
 | 
					    worldState_baroTennoConRelay: `Relais Baro TennoCon`,
 | 
				
			||||||
    worldState_starDays: `Jours Stellaires`,
 | 
					    worldState_starDays: `Jours Stellaires`,
 | 
				
			||||||
    worldState_galleonOfGhouls: `Galion des Goules`,
 | 
					    worldState_galleonOfGhouls: `Galion des Goules`,
 | 
				
			||||||
    worldState_anniversary: `[UNTRANSLATED] Warframe Anniversary`,
 | 
					    worldState_anniversary: `Anniversaire de Warframe`,
 | 
				
			||||||
    worldState_useAnniversaryTagForOldGoals: `[UNTRANSLATED] Use <code>Tag</code> from Warframe Anniversary for old Events`,
 | 
					    worldState_useAnniversaryTagForOldGoals: `Utiliser <code>Tag</code> de l'Anniversaire de Warframe pour les anciens événements`,
 | 
				
			||||||
    worldState_ghoulEmergence: `Purge des Goules`,
 | 
					    worldState_ghoulEmergence: `Purge des Goules`,
 | 
				
			||||||
    worldState_plagueStar: `Fléau Céleste`,
 | 
					    worldState_plagueStar: `Fléau Céleste`,
 | 
				
			||||||
    worldState_dogDays: `Bataille d'Eau`,
 | 
					    worldState_dogDays: `Bataille d'Eau`,
 | 
				
			||||||
    worldState_dogDaysRewards: `[UNTRANSLATED] Dog Days Rewards`,
 | 
					    worldState_dogDaysRewards: `Récompenses de la Bataille d'Eau`,
 | 
				
			||||||
    worldState_wolfHunt: `Chasse au Loup (2025)`,
 | 
					    worldState_wolfHunt: `Chasse au Loup (2025)`,
 | 
				
			||||||
    worldState_orphixVenom: `Venin Orphix`,
 | 
					    worldState_orphixVenom: `Venin Orphix`,
 | 
				
			||||||
    worldState_longShadow: `La Propagation des Ombres`,
 | 
					    worldState_longShadow: `La Propagation des Ombres`,
 | 
				
			||||||
    worldState_hallowedFlame: `Flamme Hantée`,
 | 
					    worldState_hallowedFlame: `Flamme Hantée`,
 | 
				
			||||||
    worldState_hallowedNightmares: `Cauchemars Hantés`,
 | 
					    worldState_hallowedNightmares: `Cauchemars Hantés`,
 | 
				
			||||||
    worldState_hallowedNightmaresRewards: `[UNTRANSLATED] Hallowed Nightmares Rewards`,
 | 
					    worldState_hallowedNightmaresRewards: `Récompenses Flamme Hantée Cauchemar`,
 | 
				
			||||||
    worldState_proxyRebellion: `Rébellion Proxy`,
 | 
					    worldState_proxyRebellion: `Rébellion Proxy`,
 | 
				
			||||||
    worldState_proxyRebellionRewards: `[UNTRANSLATED] Proxy Rebellion Rewards`,
 | 
					    worldState_proxyRebellionRewards: `Récompenses Rébellion Proxy`,
 | 
				
			||||||
    worldState_bellyOfTheBeast: `Ventre de la Bête`,
 | 
					    worldState_bellyOfTheBeast: `Ventre de la Bête`,
 | 
				
			||||||
    worldState_bellyOfTheBeastProgressOverride: `[UNTRANSLATED] Belly of the Beast Progress`,
 | 
					    worldState_bellyOfTheBeastProgressOverride: `Progrès du Ventre de la Bête`,
 | 
				
			||||||
    worldState_eightClaw: `Huitième Griffe`,
 | 
					    worldState_eightClaw: `Huitième Griffe`,
 | 
				
			||||||
    worldState_eightClawProgressOverride: `[UNTRANSLATED] Eight Claw Progress`,
 | 
					    worldState_eightClawProgressOverride: `Progrès de la Huitième Griffe`,
 | 
				
			||||||
    worldState_thermiaFractures: `Crevasses Thermia`,
 | 
					    worldState_thermiaFractures: `Crevasses Thermia`,
 | 
				
			||||||
    worldState_thermiaFracturesProgressOverride: `[UNTRANSLATED] Thermia Fractures Progress`,
 | 
					    worldState_thermiaFracturesProgressOverride: `Progrès des Fractures Thermia`,
 | 
				
			||||||
    worldState_from_year: `[UNTRANSLATED] from |VAL|`,
 | 
					    worldState_from_year: `de |VAL|`,
 | 
				
			||||||
    worldState_pre_year: `[UNTRANSLATED] pre |VAL|`,
 | 
					    worldState_pre_year: `pre-|VAL|`,
 | 
				
			||||||
    worldState_week: `[UNTRANSLATED] Week |VAL|`,
 | 
					    worldState_week: `Semaine |VAL|`,
 | 
				
			||||||
    worldState_incompatibleWith: `[UNTRANSLATED] Incompatible with:`,
 | 
					    worldState_incompatibleWith: `Incompatible avec :`,
 | 
				
			||||||
    enabled: `Activé`,
 | 
					    enabled: `Activé`,
 | 
				
			||||||
    disabled: `Désactivé`,
 | 
					    disabled: `Désactivé`,
 | 
				
			||||||
    worldState_we1: `Weekend 1`,
 | 
					    worldState_we1: `Weekend 1`,
 | 
				
			||||||
@ -319,8 +333,8 @@ dict = {
 | 
				
			|||||||
    worldState_varziaFullyStocked: `Stock de Varzia au max`,
 | 
					    worldState_varziaFullyStocked: `Stock de Varzia au max`,
 | 
				
			||||||
    worldState_varziaOverride: `Rotation de Varzia`,
 | 
					    worldState_varziaOverride: `Rotation de Varzia`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    import_importNote: `[UNTRANSLATED] You can provide a full or partial <code>inventory.php</code> or <code>getShip.php</code> response (client representation) here.`,
 | 
					    import_importNote: `Une réponse partielle ou complète de <code>inventory.php</code> ou <code>getShip.php</code> peut être incluse ici.`,
 | 
				
			||||||
    import_importNote2: `[UNTRANSLATED] All fields that are supported by the importer <b>will be overwritten</b> in your account.`,
 | 
					    import_importNote2: `Tous les champs sont supportés par l'outil d'import <b>serront écrasés</b> sur le compte.`,
 | 
				
			||||||
    import_submit: `Soumettre`,
 | 
					    import_submit: `Soumettre`,
 | 
				
			||||||
    import_samples: `Échantillons :`,
 | 
					    import_samples: `Échantillons :`,
 | 
				
			||||||
    import_samples_maxFocus: `Toutes les écoles de focus au rang max`,
 | 
					    import_samples_maxFocus: `Toutes les écoles de focus au rang max`,
 | 
				
			||||||
@ -387,5 +401,35 @@ dict = {
 | 
				
			|||||||
    theme_dark: `Thème sombre`,
 | 
					    theme_dark: `Thème sombre`,
 | 
				
			||||||
    theme_light: `Thème clair`,
 | 
					    theme_light: `Thème clair`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    guildView_cheats: `[UNTRANSLATED] Clan Cheats`,
 | 
				
			||||||
 | 
					    guildView_techProjects: `Recherche`,
 | 
				
			||||||
 | 
					    guildView_vaultDecoRecipes: `Schémas de décorations de dojo`,
 | 
				
			||||||
 | 
					    guildView_alliance: `Alliance`,
 | 
				
			||||||
 | 
					    guildView_members: `Members`,
 | 
				
			||||||
 | 
					    guildView_pending: `En Attente`,
 | 
				
			||||||
 | 
					    guildView_classDisplay: `Rang |CLASS|`,
 | 
				
			||||||
 | 
					    guildView_tierDisplay: `Clan |TIER|`,
 | 
				
			||||||
 | 
					    guildView_tier1: `Fantôme`,
 | 
				
			||||||
 | 
					    guildView_tier2: `Ombre`,
 | 
				
			||||||
 | 
					    guildView_tier3: `Tempête`,
 | 
				
			||||||
 | 
					    guildView_tier4: `Montagne`,
 | 
				
			||||||
 | 
					    guildView_tier5: `Lune`,
 | 
				
			||||||
 | 
					    guildView_rank_creator: `Seigneur de Guerre Fondateur`,
 | 
				
			||||||
 | 
					    guildView_rank_general: `Général`,
 | 
				
			||||||
 | 
					    guildView_rank_initiate: `Initié`,
 | 
				
			||||||
 | 
					    guildView_rank_leader: `Chef`,
 | 
				
			||||||
 | 
					    guildView_rank_officer: `Officier`,
 | 
				
			||||||
 | 
					    guildView_rank_sage: `Sage`,
 | 
				
			||||||
 | 
					    guildView_rank_soldier: `Soldat`,
 | 
				
			||||||
 | 
					    guildView_rank_utility: `Utilitaire`,
 | 
				
			||||||
 | 
					    guildView_rank_warlord: `Seigneur de guerre`,
 | 
				
			||||||
 | 
					    guildView_currency_owned: `|COUNT| dans le coffre.`,
 | 
				
			||||||
 | 
					    guildView_bulkAddTechProjects: `Ajouter les recherches manquantes`,
 | 
				
			||||||
 | 
					    guildView_bulkAddVaultDecoRecipes: `Ajouter les schémas de décorations de dojo manquantes`,
 | 
				
			||||||
 | 
					    guildView_bulkFundTechProjects: `Financer toutes les recherches`,
 | 
				
			||||||
 | 
					    guildView_bulkCompleteTechProjects: `Compléter toutes les recherches`,
 | 
				
			||||||
 | 
					    guildView_promote: `Promouvoir`,
 | 
				
			||||||
 | 
					    guildView_demote: `Rétrograder`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    prettier_sucks_ass: ``
 | 
					    prettier_sucks_ass: ``
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
// Russian translation by AMelonInsideLemon, LoseFace
 | 
					// Russian translation by AMelonInsideLemon, LoseFace
 | 
				
			||||||
dict = {
 | 
					dict = {
 | 
				
			||||||
    general_inventoryUpdateNote: `Примечание: Чтобы увидеть изменения в игре, вам нужно повторно синхронизировать свой инвентарь, например, используя команду /sync загрузчика, посетив Додзё/Реле или перезагрузив игру.`,
 | 
					    general_inventoryUpdateNote: `Примечание: Чтобы увидеть изменения в игре, вам нужно повторно синхронизировать свой инвентарь, например, используя команду /sync в программе bootstrapper, посетив Додзё/Реле или перезагрузив игру.`,
 | 
				
			||||||
 | 
					    general_inventoryUpdateNoteGameWs: `Примечание: для того, чтобы изменения вступили в силу, может потребоваться повторно открыть меню, в котором вы находитесь.`,
 | 
				
			||||||
    general_addButton: `Добавить`,
 | 
					    general_addButton: `Добавить`,
 | 
				
			||||||
    general_setButton: `Установить`,
 | 
					    general_setButton: `Установить`,
 | 
				
			||||||
    general_none: `Отсутствует`,
 | 
					    general_none: `Отсутствует`,
 | 
				
			||||||
@ -31,6 +32,8 @@ dict = {
 | 
				
			|||||||
    code_renamePrompt: `Введите новое имя:`,
 | 
					    code_renamePrompt: `Введите новое имя:`,
 | 
				
			||||||
    code_remove: `Удалить`,
 | 
					    code_remove: `Удалить`,
 | 
				
			||||||
    code_addItemsConfirm: `Вы уверены, что хотите добавить |COUNT| предметов на ваш аккаунт?`,
 | 
					    code_addItemsConfirm: `Вы уверены, что хотите добавить |COUNT| предметов на ваш аккаунт?`,
 | 
				
			||||||
 | 
					    code_addTechProjectsConfirm: `Вы уверены, что хотите добавить |COUNT| исследований в свой клан?`,
 | 
				
			||||||
 | 
					    code_addDecoRecipesConfirm: `Вы уверены, что хотите добавить |COUNT| рецептов декораций в свой клан?`,
 | 
				
			||||||
    code_succRankUp: `Ранг успешно повышен.`,
 | 
					    code_succRankUp: `Ранг успешно повышен.`,
 | 
				
			||||||
    code_noEquipmentToRankUp: `Нет снаряжения для повышения ранга.`,
 | 
					    code_noEquipmentToRankUp: `Нет снаряжения для повышения ранга.`,
 | 
				
			||||||
    code_succAdded: `Успешно добавлено.`,
 | 
					    code_succAdded: `Успешно добавлено.`,
 | 
				
			||||||
@ -42,6 +45,7 @@ dict = {
 | 
				
			|||||||
    code_rank: `Ранг`,
 | 
					    code_rank: `Ранг`,
 | 
				
			||||||
    code_rankUp: `Повысить ранг`,
 | 
					    code_rankUp: `Повысить ранг`,
 | 
				
			||||||
    code_rankDown: `Понизить ранг`,
 | 
					    code_rankDown: `Понизить ранг`,
 | 
				
			||||||
 | 
					    code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`,
 | 
				
			||||||
    code_count: `Количество`,
 | 
					    code_count: `Количество`,
 | 
				
			||||||
    code_focusAllUnlocked: `Все школы Фокуса уже разблокированы.`,
 | 
					    code_focusAllUnlocked: `Все школы Фокуса уже разблокированы.`,
 | 
				
			||||||
    code_focusUnlocked: `Разблокировано |COUNT| новых школ Фокуса! Для отображения изменений в игре потребуется обновление инвентаря. Посещение навигации — самый простой способ этого добиться.`,
 | 
					    code_focusUnlocked: `Разблокировано |COUNT| новых школ Фокуса! Для отображения изменений в игре потребуется обновление инвентаря. Посещение навигации — самый простой способ этого добиться.`,
 | 
				
			||||||
@ -61,8 +65,16 @@ dict = {
 | 
				
			|||||||
    code_completed: `Завершено`,
 | 
					    code_completed: `Завершено`,
 | 
				
			||||||
    code_active: `Активный`,
 | 
					    code_active: `Активный`,
 | 
				
			||||||
    code_pigment: `Пигмент`,
 | 
					    code_pigment: `Пигмент`,
 | 
				
			||||||
 | 
					    code_controller: `Курсор контроллера`,
 | 
				
			||||||
 | 
					    code_mouseLine: `Линейный курсор`,
 | 
				
			||||||
 | 
					    code_mouse: `Курсор`,
 | 
				
			||||||
 | 
					    code_itemColorPalette: `Цветовая палитра: |ITEM|`,
 | 
				
			||||||
    code_mature: `Подготовить к сражениям`,
 | 
					    code_mature: `Подготовить к сражениям`,
 | 
				
			||||||
    code_unmature: `Регрессия генетического старения`,
 | 
					    code_unmature: `Регрессия генетического старения`,
 | 
				
			||||||
 | 
					    code_fund: `Профинансировать`,
 | 
				
			||||||
 | 
					    code_funded: `Профинансировано`,
 | 
				
			||||||
 | 
					    code_replays: `Повторов`,
 | 
				
			||||||
 | 
					    code_stalker: `Сталкер`,
 | 
				
			||||||
    code_succChange: `Успешно изменено.`,
 | 
					    code_succChange: `Успешно изменено.`,
 | 
				
			||||||
    code_requiredInvigorationUpgrade: `Вы должны выбрать как атакующее, так и вспомогательное улучшение.`,
 | 
					    code_requiredInvigorationUpgrade: `Вы должны выбрать как атакующее, так и вспомогательное улучшение.`,
 | 
				
			||||||
    login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`,
 | 
					    login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`,
 | 
				
			||||||
@ -74,12 +86,13 @@ dict = {
 | 
				
			|||||||
    navbar_renameAccount: `Переименовать аккаунт`,
 | 
					    navbar_renameAccount: `Переименовать аккаунт`,
 | 
				
			||||||
    navbar_deleteAccount: `Удалить аккаунт`,
 | 
					    navbar_deleteAccount: `Удалить аккаунт`,
 | 
				
			||||||
    navbar_inventory: `Инвентарь`,
 | 
					    navbar_inventory: `Инвентарь`,
 | 
				
			||||||
 | 
					    navbar_guildView: `Клан`,
 | 
				
			||||||
    navbar_mods: `Моды`,
 | 
					    navbar_mods: `Моды`,
 | 
				
			||||||
    navbar_quests: `Квесты`,
 | 
					    navbar_quests: `Квесты`,
 | 
				
			||||||
    navbar_cheats: `Читы`,
 | 
					    navbar_cheats: `Читы`,
 | 
				
			||||||
    navbar_import: `Импорт`,
 | 
					    navbar_import: `Импорт`,
 | 
				
			||||||
    inventory_addItems: `Добавить предметы`,
 | 
					    inventory_addItems: `Добавить предметы`,
 | 
				
			||||||
    inventory_addItemByItemType: `[UNTRANSLATED] Raw`,
 | 
					    inventory_addItemByItemType: `Необработанные данные`,
 | 
				
			||||||
    inventory_addItemByItemType_warning: `Используйте эту функцию на свой страх и риск. Она может повредить ваш инвентарь, и в случае проблем вам придётся удалять предметы вручную.`,
 | 
					    inventory_addItemByItemType_warning: `Используйте эту функцию на свой страх и риск. Она может повредить ваш инвентарь, и в случае проблем вам придётся удалять предметы вручную.`,
 | 
				
			||||||
    inventory_suits: `Варфреймы`,
 | 
					    inventory_suits: `Варфреймы`,
 | 
				
			||||||
    inventory_longGuns: `Основное оружие`,
 | 
					    inventory_longGuns: `Основное оружие`,
 | 
				
			||||||
@ -96,13 +109,17 @@ dict = {
 | 
				
			|||||||
    inventory_moaPets: `МОА`,
 | 
					    inventory_moaPets: `МОА`,
 | 
				
			||||||
    inventory_kubrowPets: `Звери`,
 | 
					    inventory_kubrowPets: `Звери`,
 | 
				
			||||||
    inventory_evolutionProgress: `Прогресс эволюции Инкарнонов`,
 | 
					    inventory_evolutionProgress: `Прогресс эволюции Инкарнонов`,
 | 
				
			||||||
    inventory_Boosters: `Бустеры`,
 | 
					    inventory_boosters: `Бустеры`,
 | 
				
			||||||
 | 
					    inventory_flavourItems: `<abbr title="Наборы анимаций, глифы, палитры и т. д.">Уникальные предметы</abbr>`,
 | 
				
			||||||
 | 
					    inventory_shipDecorations: `Украшения корабля`,
 | 
				
			||||||
    inventory_bulkAddSuits: `Добавить отсутствующие Варфреймы`,
 | 
					    inventory_bulkAddSuits: `Добавить отсутствующие Варфреймы`,
 | 
				
			||||||
    inventory_bulkAddWeapons: `Добавить отсутствующее оружие`,
 | 
					    inventory_bulkAddWeapons: `Добавить отсутствующее оружие`,
 | 
				
			||||||
    inventory_bulkAddSpaceSuits: `Добавить отсутствующие Арчвинги`,
 | 
					    inventory_bulkAddSpaceSuits: `Добавить отсутствующие Арчвинги`,
 | 
				
			||||||
    inventory_bulkAddSpaceWeapons: `Добавить отсутствующее оружие Арчвингов`,
 | 
					    inventory_bulkAddSpaceWeapons: `Добавить отсутствующее оружие Арчвингов`,
 | 
				
			||||||
    inventory_bulkAddSentinels: `Добавить отсутствующих Стражей`,
 | 
					    inventory_bulkAddSentinels: `Добавить отсутствующих Стражей`,
 | 
				
			||||||
    inventory_bulkAddSentinelWeapons: `Добавить отсутствующее оружие Стражей`,
 | 
					    inventory_bulkAddSentinelWeapons: `Добавить отсутствующее оружие Стражей`,
 | 
				
			||||||
 | 
					    inventory_bulkAddFlavourItems: `Добавить отсутствующие уникальные предметы`,
 | 
				
			||||||
 | 
					    inventory_bulkAddShipDecorations: `Добавить отсутствующие украшения корабля`,
 | 
				
			||||||
    inventory_bulkAddEvolutionProgress: `Добавить отсутствующий прогресс эволюции Инкарнонов`,
 | 
					    inventory_bulkAddEvolutionProgress: `Добавить отсутствующий прогресс эволюции Инкарнонов`,
 | 
				
			||||||
    inventory_bulkRankUpSuits: `Макс. ранг всех Варфреймов`,
 | 
					    inventory_bulkRankUpSuits: `Макс. ранг всех Варфреймов`,
 | 
				
			||||||
    inventory_bulkRankUpWeapons: `Макс. ранг всего оружия`,
 | 
					    inventory_bulkRankUpWeapons: `Макс. ранг всего оружия`,
 | 
				
			||||||
@ -176,7 +193,7 @@ dict = {
 | 
				
			|||||||
    cheats_skipTutorial: `Пропустить обучение`,
 | 
					    cheats_skipTutorial: `Пропустить обучение`,
 | 
				
			||||||
    cheats_skipAllDialogue: `Пропустить все диалоги`,
 | 
					    cheats_skipAllDialogue: `Пропустить все диалоги`,
 | 
				
			||||||
    cheats_unlockAllScans: `Разблокировать все сканирования`,
 | 
					    cheats_unlockAllScans: `Разблокировать все сканирования`,
 | 
				
			||||||
    cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please that you'll need to relog for the client to refresh this.`,
 | 
					    cheats_unlockSuccRelog: `Успех. Вам необходимо повторно войти в игру, чтобы клиент обновил эту информацию.`,
 | 
				
			||||||
    cheats_unlockAllMissions: `Разблокировать все миссии`,
 | 
					    cheats_unlockAllMissions: `Разблокировать все миссии`,
 | 
				
			||||||
    cheats_unlockAllMissions_ok: `Успех. Пожалуйста, обратите внимание, что вам нужно будет войти в Додзё/Реле или перезайти, чтобы клиент обновил звездную карту.`,
 | 
					    cheats_unlockAllMissions_ok: `Успех. Пожалуйста, обратите внимание, что вам нужно будет войти в Додзё/Реле или перезайти, чтобы клиент обновил звездную карту.`,
 | 
				
			||||||
    cheats_infiniteCredits: `Бесконечные Кредиты`,
 | 
					    cheats_infiniteCredits: `Бесконечные Кредиты`,
 | 
				
			||||||
@ -192,11 +209,8 @@ dict = {
 | 
				
			|||||||
    cheats_dontSubtractVoidTraces: `Не вычитать количество Отголосков Бездны`,
 | 
					    cheats_dontSubtractVoidTraces: `Не вычитать количество Отголосков Бездны`,
 | 
				
			||||||
    cheats_dontSubtractConsumables: `Не вычитать количество расходников`,
 | 
					    cheats_dontSubtractConsumables: `Не вычитать количество расходников`,
 | 
				
			||||||
    cheats_unlockAllShipFeatures: `Разблокировать все функции корабля`,
 | 
					    cheats_unlockAllShipFeatures: `Разблокировать все функции корабля`,
 | 
				
			||||||
    cheats_unlockAllShipDecorations: `Разблокировать все украшения корабля`,
 | 
					 | 
				
			||||||
    cheats_unlockAllFlavourItems: `Разблокировать все <abbr title="Наборы анимаций, глифы, палитры и т. д.">уникальные предметы</abbr>`,
 | 
					 | 
				
			||||||
    cheats_unlockAllSkins: `Разблокировать все скины`,
 | 
					    cheats_unlockAllSkins: `Разблокировать все скины`,
 | 
				
			||||||
    cheats_unlockAllCapturaScenes: `Разблокировать все сцены Каптуры`,
 | 
					    cheats_unlockAllCapturaScenes: `Разблокировать все сцены Каптуры`,
 | 
				
			||||||
    cheats_unlockAllDecoRecipes: `Разблокировать все рецепты декораций Дoдзё`,
 | 
					 | 
				
			||||||
    cheats_universalPolarityEverywhere: `Универсальная полярность везде`,
 | 
					    cheats_universalPolarityEverywhere: `Универсальная полярность везде`,
 | 
				
			||||||
    cheats_unlockDoubleCapacityPotatoesEverywhere: `Реакторы/Катализаторы орокин везде`,
 | 
					    cheats_unlockDoubleCapacityPotatoesEverywhere: `Реакторы/Катализаторы орокин везде`,
 | 
				
			||||||
    cheats_unlockExilusEverywhere: `Адаптеры Эксилус везде`,
 | 
					    cheats_unlockExilusEverywhere: `Адаптеры Эксилус везде`,
 | 
				
			||||||
@ -213,7 +227,7 @@ dict = {
 | 
				
			|||||||
    cheats_baroFullyStocked: `Баро полностью укомплектован`,
 | 
					    cheats_baroFullyStocked: `Баро полностью укомплектован`,
 | 
				
			||||||
    cheats_syndicateMissionsRepeatable: `Повторять миссии синдиката`,
 | 
					    cheats_syndicateMissionsRepeatable: `Повторять миссии синдиката`,
 | 
				
			||||||
    cheats_unlockAllProfitTakerStages: `Разблокировать все этапы Сферы извлечения прибыли`,
 | 
					    cheats_unlockAllProfitTakerStages: `Разблокировать все этапы Сферы извлечения прибыли`,
 | 
				
			||||||
    cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging..`,
 | 
					    cheats_unlockSuccInventory: `Успех. Обратите внимание, что вам необходимо будет повторно синхронизировать свой инвентарь, например, с помощью команды /sync в программе bootstrapper, посетив Додзё/Реле или повторно войдя в игру.`,
 | 
				
			||||||
    cheats_instantFinishRivenChallenge: `Мгновенное завершение испытания мода Разлома`,
 | 
					    cheats_instantFinishRivenChallenge: `Мгновенное завершение испытания мода Разлома`,
 | 
				
			||||||
    cheats_instantResourceExtractorDrones: `Мгновенно добывающие Дроны-сборщики`,
 | 
					    cheats_instantResourceExtractorDrones: `Мгновенно добывающие Дроны-сборщики`,
 | 
				
			||||||
    cheats_noResourceExtractorDronesDamage: `Без урона по Дронам-сборщикам`,
 | 
					    cheats_noResourceExtractorDronesDamage: `Без урона по Дронам-сборщикам`,
 | 
				
			||||||
@ -387,5 +401,35 @@ dict = {
 | 
				
			|||||||
    theme_dark: `Темная тема`,
 | 
					    theme_dark: `Темная тема`,
 | 
				
			||||||
    theme_light: `Светлая тема`,
 | 
					    theme_light: `Светлая тема`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    guildView_cheats: `Читы Клана`,
 | 
				
			||||||
 | 
					    guildView_techProjects: `Исследовения`,
 | 
				
			||||||
 | 
					    guildView_vaultDecoRecipes: `Рецепты декораций Додзё`,
 | 
				
			||||||
 | 
					    guildView_alliance: `Альянс`,
 | 
				
			||||||
 | 
					    guildView_members: `Товарищи`,
 | 
				
			||||||
 | 
					    guildView_pending: `Ожидание`,
 | 
				
			||||||
 | 
					    guildView_classDisplay: `Ранг |CLASS|`,
 | 
				
			||||||
 | 
					    guildView_tierDisplay: `|TIER| Клан`,
 | 
				
			||||||
 | 
					    guildView_tier1: `Призрачный`,
 | 
				
			||||||
 | 
					    guildView_tier2: `Теневой`,
 | 
				
			||||||
 | 
					    guildView_tier3: `Штормовой`,
 | 
				
			||||||
 | 
					    guildView_tier4: `Горный`,
 | 
				
			||||||
 | 
					    guildView_tier5: `Лунный`,
 | 
				
			||||||
 | 
					    guildView_rank_creator: `Основатель`,
 | 
				
			||||||
 | 
					    guildView_rank_general: `Генерал`,
 | 
				
			||||||
 | 
					    guildView_rank_initiate: `Неофит`,
 | 
				
			||||||
 | 
					    guildView_rank_leader: `Лидер`,
 | 
				
			||||||
 | 
					    guildView_rank_officer: `Офицер`,
 | 
				
			||||||
 | 
					    guildView_rank_sage: `Мудрец`,
 | 
				
			||||||
 | 
					    guildView_rank_soldier: `Солдат`,
 | 
				
			||||||
 | 
					    guildView_rank_utility: `Инженер`,
 | 
				
			||||||
 | 
					    guildView_rank_warlord: `Военачальник`,
 | 
				
			||||||
 | 
					    guildView_currency_owned: `В хранилище |COUNT|.`,
 | 
				
			||||||
 | 
					    guildView_bulkAddTechProjects: `Добавить отсутствующие исследования`,
 | 
				
			||||||
 | 
					    guildView_bulkAddVaultDecoRecipes: `Добавить отсутствующие рецепты декораций Дoдзё`,
 | 
				
			||||||
 | 
					    guildView_bulkFundTechProjects: `Профинансировать все исследования`,
 | 
				
			||||||
 | 
					    guildView_bulkCompleteTechProjects: `Завершить все исследования`,
 | 
				
			||||||
 | 
					    guildView_promote: `Повысить`,
 | 
				
			||||||
 | 
					    guildView_demote: `Понизить`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    prettier_sucks_ass: ``
 | 
					    prettier_sucks_ass: ``
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
// Ukrainian translation by LoseFace
 | 
					// Ukrainian translation by LoseFace
 | 
				
			||||||
dict = {
 | 
					dict = {
 | 
				
			||||||
    general_inventoryUpdateNote: `Пам'ятка: Щоб побачити зміни в грі, вам потрібно повторно синхронізувати своє спорядження, наприклад, використовуючи команду /sync завантажувача, відвідавши Доджьо/Реле або перезавантаживши гру.`,
 | 
					    general_inventoryUpdateNote: `Пам'ятка: Щоб побачити зміни в грі, вам потрібно повторно синхронізувати своє спорядження, наприклад, використовуючи команду /sync в програмі bootstrapper, відвідавши Доджьо/Реле або перезавантаживши гру.`,
 | 
				
			||||||
 | 
					    general_inventoryUpdateNoteGameWs: `Примітка: для відображення змін може знадобитися повторно відкрити меню, в якому ви перебуваєте.`,
 | 
				
			||||||
    general_addButton: `Добавити`,
 | 
					    general_addButton: `Добавити`,
 | 
				
			||||||
    general_setButton: `Встановити`,
 | 
					    general_setButton: `Встановити`,
 | 
				
			||||||
    general_none: `Відсутній`,
 | 
					    general_none: `Відсутній`,
 | 
				
			||||||
@ -31,6 +32,8 @@ dict = {
 | 
				
			|||||||
    code_renamePrompt: `Введіть нове ім'я:`,
 | 
					    code_renamePrompt: `Введіть нове ім'я:`,
 | 
				
			||||||
    code_remove: `Видалити`,
 | 
					    code_remove: `Видалити`,
 | 
				
			||||||
    code_addItemsConfirm: `Ви впевнені, що хочете додати |COUNT| предметів на ваш обліковий запис?`,
 | 
					    code_addItemsConfirm: `Ви впевнені, що хочете додати |COUNT| предметів на ваш обліковий запис?`,
 | 
				
			||||||
 | 
					    code_addTechProjectsConfirm: `Ви впевнені, що хочете додати |COUNT| досліджень до свого клану?`,
 | 
				
			||||||
 | 
					    code_addDecoRecipesConfirm: `Ви впевнені, що хочете додати |COUNT| рецептів оздоблень до свого клану?`,
 | 
				
			||||||
    code_succRankUp: `Рівень успішно підвищено`,
 | 
					    code_succRankUp: `Рівень успішно підвищено`,
 | 
				
			||||||
    code_noEquipmentToRankUp: `Немає спорядження для підвищення рівня.`,
 | 
					    code_noEquipmentToRankUp: `Немає спорядження для підвищення рівня.`,
 | 
				
			||||||
    code_succAdded: `Успішно додано.`,
 | 
					    code_succAdded: `Успішно додано.`,
 | 
				
			||||||
@ -42,6 +45,7 @@ dict = {
 | 
				
			|||||||
    code_rank: `Рівень`,
 | 
					    code_rank: `Рівень`,
 | 
				
			||||||
    code_rankUp: `Підвищити рівень`,
 | 
					    code_rankUp: `Підвищити рівень`,
 | 
				
			||||||
    code_rankDown: `Понизити рівень`,
 | 
					    code_rankDown: `Понизити рівень`,
 | 
				
			||||||
 | 
					    code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`,
 | 
				
			||||||
    code_count: `Кількість`,
 | 
					    code_count: `Кількість`,
 | 
				
			||||||
    code_focusAllUnlocked: `Всі школи Фокусу вже розблоковані.`,
 | 
					    code_focusAllUnlocked: `Всі школи Фокусу вже розблоковані.`,
 | 
				
			||||||
    code_focusUnlocked: `Розблоковано |COUNT| нових шкіл Фокусу! Для відображення змін в грі знадобиться оновлення спорядження. Відвідування навігації — найпростіший спосіб цього досягти.`,
 | 
					    code_focusUnlocked: `Розблоковано |COUNT| нових шкіл Фокусу! Для відображення змін в грі знадобиться оновлення спорядження. Відвідування навігації — найпростіший спосіб цього досягти.`,
 | 
				
			||||||
@ -61,8 +65,16 @@ dict = {
 | 
				
			|||||||
    code_completed: `Завершено`,
 | 
					    code_completed: `Завершено`,
 | 
				
			||||||
    code_active: `Активний`,
 | 
					    code_active: `Активний`,
 | 
				
			||||||
    code_pigment: `Барвник`,
 | 
					    code_pigment: `Барвник`,
 | 
				
			||||||
 | 
					    code_controller: `Курсор контролера`,
 | 
				
			||||||
 | 
					    code_mouseLine: `Лінійний курсор`,
 | 
				
			||||||
 | 
					    code_mouse: `Курсор`,
 | 
				
			||||||
 | 
					    code_itemColorPalette: `Палітра кольорів «|ITEM|»`,
 | 
				
			||||||
    code_mature: `Виростити для бою`,
 | 
					    code_mature: `Виростити для бою`,
 | 
				
			||||||
    code_unmature: `Обернути старіння`,
 | 
					    code_unmature: `Обернути старіння`,
 | 
				
			||||||
 | 
					    code_fund: `Профінансувати`,
 | 
				
			||||||
 | 
					    code_funded: `Профінансовано`,
 | 
				
			||||||
 | 
					    code_replays: `Повтори`,
 | 
				
			||||||
 | 
					    code_stalker: `Сталкер`,
 | 
				
			||||||
    code_succChange: `Успішно змінено.`,
 | 
					    code_succChange: `Успішно змінено.`,
 | 
				
			||||||
    code_requiredInvigorationUpgrade: `Ви повинні вибрати як атакуюче, так і допоміжне вдосконалення.`,
 | 
					    code_requiredInvigorationUpgrade: `Ви повинні вибрати як атакуюче, так і допоміжне вдосконалення.`,
 | 
				
			||||||
    login_description: `Увійдіть, використовуючи облікові дані OpenWF (ті ж, що й у грі при підключенні до цього серверу).`,
 | 
					    login_description: `Увійдіть, використовуючи облікові дані OpenWF (ті ж, що й у грі при підключенні до цього серверу).`,
 | 
				
			||||||
@ -74,13 +86,14 @@ dict = {
 | 
				
			|||||||
    navbar_renameAccount: `Перейменувати обліковий запис`,
 | 
					    navbar_renameAccount: `Перейменувати обліковий запис`,
 | 
				
			||||||
    navbar_deleteAccount: `Видалити обліковий запис`,
 | 
					    navbar_deleteAccount: `Видалити обліковий запис`,
 | 
				
			||||||
    navbar_inventory: `Спорядження`,
 | 
					    navbar_inventory: `Спорядження`,
 | 
				
			||||||
 | 
					    navbar_guildView: `Клан`,
 | 
				
			||||||
    navbar_mods: `Модифікатори`,
 | 
					    navbar_mods: `Модифікатори`,
 | 
				
			||||||
    navbar_quests: `Пригоди`,
 | 
					    navbar_quests: `Пригоди`,
 | 
				
			||||||
    navbar_cheats: `Чити`,
 | 
					    navbar_cheats: `Чити`,
 | 
				
			||||||
    navbar_import: `Імпорт`,
 | 
					    navbar_import: `Імпорт`,
 | 
				
			||||||
    inventory_addItems: `Додати предмети`,
 | 
					    inventory_addItems: `Додати предмети`,
 | 
				
			||||||
    inventory_addItemByItemType: `[UNTRANSLATED] Raw`,
 | 
					    inventory_addItemByItemType: `Необроблені дані`,
 | 
				
			||||||
    inventory_addItemByItemType_warning: `[UNTRANSLATED] Use this feature at your own risk. It may break your inventory, and you will need to remove items manually if something goes wrong.`,
 | 
					    inventory_addItemByItemType_warning: `Використовуйте цю функцію на власний ризик. Вона може пошкодити ваше спорядження, і вам доведеться видаляти предмети вручну, якщо щось піде не так.`,
 | 
				
			||||||
    inventory_suits: `Ворфрейми`,
 | 
					    inventory_suits: `Ворфрейми`,
 | 
				
			||||||
    inventory_longGuns: `Основна зброя`,
 | 
					    inventory_longGuns: `Основна зброя`,
 | 
				
			||||||
    inventory_pistols: `Допоміжна зброя`,
 | 
					    inventory_pistols: `Допоміжна зброя`,
 | 
				
			||||||
@ -96,13 +109,17 @@ dict = {
 | 
				
			|||||||
    inventory_moaPets: `МОА`,
 | 
					    inventory_moaPets: `МОА`,
 | 
				
			||||||
    inventory_kubrowPets: `Тварини`,
 | 
					    inventory_kubrowPets: `Тварини`,
 | 
				
			||||||
    inventory_evolutionProgress: `Прогрес еволюції Інкарнонів`,
 | 
					    inventory_evolutionProgress: `Прогрес еволюції Інкарнонів`,
 | 
				
			||||||
    inventory_Boosters: `Посилення`,
 | 
					    inventory_boosters: `Посилення`,
 | 
				
			||||||
 | 
					    inventory_flavourItems: `<abbr title="Набори анімацій, гліфи, палітри і т. д.">Унікальні предмети</abbr>`,
 | 
				
			||||||
 | 
					    inventory_shipDecorations: `Прикраси судна`,
 | 
				
			||||||
    inventory_bulkAddSuits: `Додати відсутні Ворфрейми`,
 | 
					    inventory_bulkAddSuits: `Додати відсутні Ворфрейми`,
 | 
				
			||||||
    inventory_bulkAddWeapons: `Додати відсутню зброю`,
 | 
					    inventory_bulkAddWeapons: `Додати відсутню зброю`,
 | 
				
			||||||
    inventory_bulkAddSpaceSuits: `Додати відсутні Арквінґи`,
 | 
					    inventory_bulkAddSpaceSuits: `Додати відсутні Арквінґи`,
 | 
				
			||||||
    inventory_bulkAddSpaceWeapons: `Додати відсутню зброю Арквінґів`,
 | 
					    inventory_bulkAddSpaceWeapons: `Додати відсутню зброю Арквінґів`,
 | 
				
			||||||
    inventory_bulkAddSentinels: `Додати відсутніх Вартових`,
 | 
					    inventory_bulkAddSentinels: `Додати відсутніх Вартових`,
 | 
				
			||||||
    inventory_bulkAddSentinelWeapons: `Додати відсутню зброю Вартових`,
 | 
					    inventory_bulkAddSentinelWeapons: `Додати відсутню зброю Вартових`,
 | 
				
			||||||
 | 
					    inventory_bulkAddFlavourItems: `Додати відсутні унікальні предмети`,
 | 
				
			||||||
 | 
					    inventory_bulkAddShipDecorations: `Додати відсутні оздоби корабля`,
 | 
				
			||||||
    inventory_bulkAddEvolutionProgress: `Додати відсутній прогрес еволюції Інкарнонів`,
 | 
					    inventory_bulkAddEvolutionProgress: `Додати відсутній прогрес еволюції Інкарнонів`,
 | 
				
			||||||
    inventory_bulkRankUpSuits: `Макс. рівень всіх Ворфреймів`,
 | 
					    inventory_bulkRankUpSuits: `Макс. рівень всіх Ворфреймів`,
 | 
				
			||||||
    inventory_bulkRankUpWeapons: `Макс. рівень всієї зброї`,
 | 
					    inventory_bulkRankUpWeapons: `Макс. рівень всієї зброї`,
 | 
				
			||||||
@ -176,7 +193,7 @@ dict = {
 | 
				
			|||||||
    cheats_skipTutorial: `Пропустити навчання`,
 | 
					    cheats_skipTutorial: `Пропустити навчання`,
 | 
				
			||||||
    cheats_skipAllDialogue: `Пропустити всі діалоги`,
 | 
					    cheats_skipAllDialogue: `Пропустити всі діалоги`,
 | 
				
			||||||
    cheats_unlockAllScans: `Розблокувати всі сканування`,
 | 
					    cheats_unlockAllScans: `Розблокувати всі сканування`,
 | 
				
			||||||
    cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please that you'll need to relog for the client to refresh this.`,
 | 
					    cheats_unlockSuccRelog: `Успіх. Вам потрібно буде повторно увійти в гру, щоб клієнт оновив цю інформацію.`,
 | 
				
			||||||
    cheats_unlockAllMissions: `Розблокувати всі місії`,
 | 
					    cheats_unlockAllMissions: `Розблокувати всі місії`,
 | 
				
			||||||
    cheats_unlockAllMissions_ok: `Успіх. Будь ласка, зверніть увагу, що вам потрібно буде увійти в Доджьо/Реле або перезайти, щоб клієнт оновив Зоряну мапу.`,
 | 
					    cheats_unlockAllMissions_ok: `Успіх. Будь ласка, зверніть увагу, що вам потрібно буде увійти в Доджьо/Реле або перезайти, щоб клієнт оновив Зоряну мапу.`,
 | 
				
			||||||
    cheats_infiniteCredits: `Бескінечні Кредити`,
 | 
					    cheats_infiniteCredits: `Бескінечні Кредити`,
 | 
				
			||||||
@ -192,11 +209,8 @@ dict = {
 | 
				
			|||||||
    cheats_dontSubtractVoidTraces: `Не вираховувати кількість Відлуння`,
 | 
					    cheats_dontSubtractVoidTraces: `Не вираховувати кількість Відлуння`,
 | 
				
			||||||
    cheats_dontSubtractConsumables: `Не вираховувати кількість витратних матеріалів`,
 | 
					    cheats_dontSubtractConsumables: `Не вираховувати кількість витратних матеріалів`,
 | 
				
			||||||
    cheats_unlockAllShipFeatures: `Розблокувати всі функції судна`,
 | 
					    cheats_unlockAllShipFeatures: `Розблокувати всі функції судна`,
 | 
				
			||||||
    cheats_unlockAllShipDecorations: `Розблокувати всі прикраси судна`,
 | 
					 | 
				
			||||||
    cheats_unlockAllFlavourItems: `Розблокувати всі <abbr title="Набори анімацій, гліфи, палітри і т. д.">унікальні предмети</abbr>`,
 | 
					 | 
				
			||||||
    cheats_unlockAllSkins: `Розблокувати всі скіни`,
 | 
					    cheats_unlockAllSkins: `Розблокувати всі скіни`,
 | 
				
			||||||
    cheats_unlockAllCapturaScenes: `Розблокувати всі сцени Світлописця`,
 | 
					    cheats_unlockAllCapturaScenes: `Розблокувати всі сцени Світлописця`,
 | 
				
			||||||
    cheats_unlockAllDecoRecipes: `Розблокувати всі рецепти декорацій Доджьо`,
 | 
					 | 
				
			||||||
    cheats_universalPolarityEverywhere: `Будь-яка полярність скрізь`,
 | 
					    cheats_universalPolarityEverywhere: `Будь-яка полярність скрізь`,
 | 
				
			||||||
    cheats_unlockDoubleCapacityPotatoesEverywhere: `Орокінські Реактори/Каталізатори скрізь`,
 | 
					    cheats_unlockDoubleCapacityPotatoesEverywhere: `Орокінські Реактори/Каталізатори скрізь`,
 | 
				
			||||||
    cheats_unlockExilusEverywhere: `Ексилотримач скрізь`,
 | 
					    cheats_unlockExilusEverywhere: `Ексилотримач скрізь`,
 | 
				
			||||||
@ -213,7 +227,7 @@ dict = {
 | 
				
			|||||||
    cheats_baroFullyStocked: `Баро повністю укомплектований`,
 | 
					    cheats_baroFullyStocked: `Баро повністю укомплектований`,
 | 
				
			||||||
    cheats_syndicateMissionsRepeatable: `Повторювати місії синдиката`,
 | 
					    cheats_syndicateMissionsRepeatable: `Повторювати місії синдиката`,
 | 
				
			||||||
    cheats_unlockAllProfitTakerStages: `Розблокувати всі етапи Привласнювачки`,
 | 
					    cheats_unlockAllProfitTakerStages: `Розблокувати всі етапи Привласнювачки`,
 | 
				
			||||||
    cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging..`,
 | 
					    cheats_unlockSuccInventory: `Успішно. Зверніть увагу, що вам потрібно буде повторно синхронізувати своє спорядження, наприклад, за допомогою команди /sync в програмі bootstrapper, відвідавши Доджьо/Реле або повторно увійшовши в гру.`,
 | 
				
			||||||
    cheats_instantFinishRivenChallenge: `Миттєве завершення випробування модифікатора Розколу`,
 | 
					    cheats_instantFinishRivenChallenge: `Миттєве завершення випробування модифікатора Розколу`,
 | 
				
			||||||
    cheats_instantResourceExtractorDrones: `Миттєво добуваючі Дрони-видобувачі`,
 | 
					    cheats_instantResourceExtractorDrones: `Миттєво добуваючі Дрони-видобувачі`,
 | 
				
			||||||
    cheats_noResourceExtractorDronesDamage: `Без шкоди по Дронам-видобувачам`,
 | 
					    cheats_noResourceExtractorDronesDamage: `Без шкоди по Дронам-видобувачам`,
 | 
				
			||||||
@ -387,5 +401,35 @@ dict = {
 | 
				
			|||||||
    theme_dark: `Темна тема`,
 | 
					    theme_dark: `Темна тема`,
 | 
				
			||||||
    theme_light: `Світла тема`,
 | 
					    theme_light: `Світла тема`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    guildView_cheats: `Кланові чити`,
 | 
				
			||||||
 | 
					    guildView_techProjects: `Дослідження`,
 | 
				
			||||||
 | 
					    guildView_vaultDecoRecipes: `Рецепти оздоблень Доджьо`,
 | 
				
			||||||
 | 
					    guildView_alliance: `Альянс`,
 | 
				
			||||||
 | 
					    guildView_members: `Учасники`,
 | 
				
			||||||
 | 
					    guildView_pending: `Очікування`,
 | 
				
			||||||
 | 
					    guildView_classDisplay: `Ранг: |CLASS|`,
 | 
				
			||||||
 | 
					    guildView_tierDisplay: `|TIER| Клан`,
 | 
				
			||||||
 | 
					    guildView_tier1: `Примарний`,
 | 
				
			||||||
 | 
					    guildView_tier2: `Тіньовий`,
 | 
				
			||||||
 | 
					    guildView_tier3: `Грозовий`,
 | 
				
			||||||
 | 
					    guildView_tier4: `Гірський`,
 | 
				
			||||||
 | 
					    guildView_tier5: `Місячний`,
 | 
				
			||||||
 | 
					    guildView_rank_creator: `Воєвода-засновник`,
 | 
				
			||||||
 | 
					    guildView_rank_general: `Генерал`,
 | 
				
			||||||
 | 
					    guildView_rank_initiate: `Рекрут`,
 | 
				
			||||||
 | 
					    guildView_rank_leader: `Лідер`,
 | 
				
			||||||
 | 
					    guildView_rank_officer: `Офіцер`,
 | 
				
			||||||
 | 
					    guildView_rank_sage: `Ветеран`,
 | 
				
			||||||
 | 
					    guildView_rank_soldier: `Солдат`,
 | 
				
			||||||
 | 
					    guildView_rank_utility: `Наймит`,
 | 
				
			||||||
 | 
					    guildView_rank_warlord: `Воєвода`,
 | 
				
			||||||
 | 
					    guildView_currency_owned: `В сховищі |COUNT|.`,
 | 
				
			||||||
 | 
					    guildView_bulkAddTechProjects: `Додати відсутні дослідження`,
 | 
				
			||||||
 | 
					    guildView_bulkAddVaultDecoRecipes: `Додати відсутні рецепти оздоблень Доджьо`,
 | 
				
			||||||
 | 
					    guildView_bulkFundTechProjects: `Фінансувати всі дослідження`,
 | 
				
			||||||
 | 
					    guildView_bulkCompleteTechProjects: `Завершити всі дослідження`,
 | 
				
			||||||
 | 
					    guildView_promote: `Підвищити звання`,
 | 
				
			||||||
 | 
					    guildView_demote: `Понизити звання`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    prettier_sucks_ass: ``
 | 
					    prettier_sucks_ass: ``
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
// Chinese translation by meb154, bishan178, nyaoouo, qianlishun, CrazyZhang, Corvus, & qingchun
 | 
					// Chinese translation by meb154, bishan178, nyaoouo, qianlishun, CrazyZhang, Corvus, & qingchun
 | 
				
			||||||
dict = {
 | 
					dict = {
 | 
				
			||||||
    general_inventoryUpdateNote: `注意: 要在游戏中查看更改,您需要重新同步库存,例如使用客户端的 /sync 命令,访问道场/中继站或重新登录.`,
 | 
					    general_inventoryUpdateNote: `注意: 要在游戏中查看更改,您需要重新同步库存,例如使用客户端的 /sync 命令,访问道场/中继站或重新登录.`,
 | 
				
			||||||
 | 
					    general_inventoryUpdateNoteGameWs: `[UNTRANSLATED] Note: You may need to reopen any menu you are on for changes to be reflected.`,
 | 
				
			||||||
    general_addButton: `添加`,
 | 
					    general_addButton: `添加`,
 | 
				
			||||||
    general_setButton: `设置`,
 | 
					    general_setButton: `设置`,
 | 
				
			||||||
    general_none: `无`,
 | 
					    general_none: `无`,
 | 
				
			||||||
@ -31,6 +32,8 @@ dict = {
 | 
				
			|||||||
    code_renamePrompt: `输入新的自定义名称:`,
 | 
					    code_renamePrompt: `输入新的自定义名称:`,
 | 
				
			||||||
    code_remove: `移除`,
 | 
					    code_remove: `移除`,
 | 
				
			||||||
    code_addItemsConfirm: `确定要向您的账户添加 |COUNT| 件物品吗?`,
 | 
					    code_addItemsConfirm: `确定要向您的账户添加 |COUNT| 件物品吗?`,
 | 
				
			||||||
 | 
					    code_addTechProjectsConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| research to your clan?`,
 | 
				
			||||||
 | 
					    code_addDecoRecipesConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| deco recipes to your clan?`,
 | 
				
			||||||
    code_succRankUp: `等级已提升`,
 | 
					    code_succRankUp: `等级已提升`,
 | 
				
			||||||
    code_noEquipmentToRankUp: `没有可升级的装备`,
 | 
					    code_noEquipmentToRankUp: `没有可升级的装备`,
 | 
				
			||||||
    code_succAdded: `添加成功`,
 | 
					    code_succAdded: `添加成功`,
 | 
				
			||||||
@ -42,6 +45,7 @@ dict = {
 | 
				
			|||||||
    code_rank: `等级`,
 | 
					    code_rank: `等级`,
 | 
				
			||||||
    code_rankUp: `等级提升`,
 | 
					    code_rankUp: `等级提升`,
 | 
				
			||||||
    code_rankDown: `等级下降`,
 | 
					    code_rankDown: `等级下降`,
 | 
				
			||||||
 | 
					    code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`,
 | 
				
			||||||
    code_count: `数量`,
 | 
					    code_count: `数量`,
 | 
				
			||||||
    code_focusAllUnlocked: `所有专精学派均已解锁`,
 | 
					    code_focusAllUnlocked: `所有专精学派均已解锁`,
 | 
				
			||||||
    code_focusUnlocked: `已解锁 |COUNT| 个新专精学派!需要游戏内仓库更新才能生效,您可以通过访问星图来触发仓库更新.`,
 | 
					    code_focusUnlocked: `已解锁 |COUNT| 个新专精学派!需要游戏内仓库更新才能生效,您可以通过访问星图来触发仓库更新.`,
 | 
				
			||||||
@ -61,8 +65,16 @@ dict = {
 | 
				
			|||||||
    code_completed: `已完成`,
 | 
					    code_completed: `已完成`,
 | 
				
			||||||
    code_active: `正在执行`,
 | 
					    code_active: `正在执行`,
 | 
				
			||||||
    code_pigment: `颜料`,
 | 
					    code_pigment: `颜料`,
 | 
				
			||||||
 | 
					    code_controller: `[UNTRANSLATED] Controller cursor`,
 | 
				
			||||||
 | 
					    code_mouseLine: `[UNTRANSLATED] Line cursor`,
 | 
				
			||||||
 | 
					    code_mouse: `[UNTRANSLATED] Cursor`,
 | 
				
			||||||
 | 
					    code_itemColorPalette: `|ITEM| 调色盘`,
 | 
				
			||||||
    code_mature: `成长并战备`,
 | 
					    code_mature: `成长并战备`,
 | 
				
			||||||
    code_unmature: `逆转衰老基因`,
 | 
					    code_unmature: `逆转衰老基因`,
 | 
				
			||||||
 | 
					    code_fund: `[UNTRANSLATED] Fund`,
 | 
				
			||||||
 | 
					    code_funded: `[UNTRANSLATED] Funded`,
 | 
				
			||||||
 | 
					    code_replays: `[UNTRANSLATED] Replays`,
 | 
				
			||||||
 | 
					    code_stalker: `追猎者`,
 | 
				
			||||||
    code_succChange: `更改成功`,
 | 
					    code_succChange: `更改成功`,
 | 
				
			||||||
    code_requiredInvigorationUpgrade: `您必须同时选择一个进攻型和一个功能型活化属性.`,
 | 
					    code_requiredInvigorationUpgrade: `您必须同时选择一个进攻型和一个功能型活化属性.`,
 | 
				
			||||||
    login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)`,
 | 
					    login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)`,
 | 
				
			||||||
@ -74,6 +86,7 @@ dict = {
 | 
				
			|||||||
    navbar_renameAccount: `重命名账户`,
 | 
					    navbar_renameAccount: `重命名账户`,
 | 
				
			||||||
    navbar_deleteAccount: `删除账户`,
 | 
					    navbar_deleteAccount: `删除账户`,
 | 
				
			||||||
    navbar_inventory: `仓库`,
 | 
					    navbar_inventory: `仓库`,
 | 
				
			||||||
 | 
					    navbar_guildView: `氏族`,
 | 
				
			||||||
    navbar_mods: `Mods`,
 | 
					    navbar_mods: `Mods`,
 | 
				
			||||||
    navbar_quests: `系列任务`,
 | 
					    navbar_quests: `系列任务`,
 | 
				
			||||||
    navbar_cheats: `作弊选项`,
 | 
					    navbar_cheats: `作弊选项`,
 | 
				
			||||||
@ -96,13 +109,17 @@ dict = {
 | 
				
			|||||||
    inventory_moaPets: `恐鸟`,
 | 
					    inventory_moaPets: `恐鸟`,
 | 
				
			||||||
    inventory_kubrowPets: `动物同伴`,
 | 
					    inventory_kubrowPets: `动物同伴`,
 | 
				
			||||||
    inventory_evolutionProgress: `灵化之源进度`,
 | 
					    inventory_evolutionProgress: `灵化之源进度`,
 | 
				
			||||||
    inventory_Boosters: `加成器`,
 | 
					    inventory_boosters: `加成器`,
 | 
				
			||||||
 | 
					    inventory_flavourItems: `<abbr title="动作表情、浮印、调色板等">装饰物品</abbr>`,
 | 
				
			||||||
 | 
					    inventory_shipDecorations: `飞船装饰`,
 | 
				
			||||||
    inventory_bulkAddSuits: `添加缺失战甲`,
 | 
					    inventory_bulkAddSuits: `添加缺失战甲`,
 | 
				
			||||||
    inventory_bulkAddWeapons: `添加缺失武器`,
 | 
					    inventory_bulkAddWeapons: `添加缺失武器`,
 | 
				
			||||||
    inventory_bulkAddSpaceSuits: `添加缺失载具`,
 | 
					    inventory_bulkAddSpaceSuits: `添加缺失载具`,
 | 
				
			||||||
    inventory_bulkAddSpaceWeapons: `添加缺失载具武器`,
 | 
					    inventory_bulkAddSpaceWeapons: `添加缺失载具武器`,
 | 
				
			||||||
    inventory_bulkAddSentinels: `添加缺失守护`,
 | 
					    inventory_bulkAddSentinels: `添加缺失守护`,
 | 
				
			||||||
    inventory_bulkAddSentinelWeapons: `添加缺失守护武器`,
 | 
					    inventory_bulkAddSentinelWeapons: `添加缺失守护武器`,
 | 
				
			||||||
 | 
					    inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`,
 | 
				
			||||||
 | 
					    inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`,
 | 
				
			||||||
    inventory_bulkAddEvolutionProgress: `添加缺失的灵化之源进度`,
 | 
					    inventory_bulkAddEvolutionProgress: `添加缺失的灵化之源进度`,
 | 
				
			||||||
    inventory_bulkRankUpSuits: `所有战甲升满级`,
 | 
					    inventory_bulkRankUpSuits: `所有战甲升满级`,
 | 
				
			||||||
    inventory_bulkRankUpWeapons: `所有武器升满级`,
 | 
					    inventory_bulkRankUpWeapons: `所有武器升满级`,
 | 
				
			||||||
@ -192,11 +209,8 @@ dict = {
 | 
				
			|||||||
    cheats_dontSubtractVoidTraces: `虚空光体无消耗`,
 | 
					    cheats_dontSubtractVoidTraces: `虚空光体无消耗`,
 | 
				
			||||||
    cheats_dontSubtractConsumables: `消耗物品使用时无损耗`,
 | 
					    cheats_dontSubtractConsumables: `消耗物品使用时无损耗`,
 | 
				
			||||||
    cheats_unlockAllShipFeatures: `解锁所有飞船功能`,
 | 
					    cheats_unlockAllShipFeatures: `解锁所有飞船功能`,
 | 
				
			||||||
    cheats_unlockAllShipDecorations: `解锁所有飞船装饰`,
 | 
					 | 
				
			||||||
    cheats_unlockAllFlavourItems: `解锁所有<abbr title="动作表情、浮印、调色板等">装饰物品</abbr>`,
 | 
					 | 
				
			||||||
    cheats_unlockAllSkins: `解锁所有外观`,
 | 
					    cheats_unlockAllSkins: `解锁所有外观`,
 | 
				
			||||||
    cheats_unlockAllCapturaScenes: `解锁所有Captura场景`,
 | 
					    cheats_unlockAllCapturaScenes: `解锁所有Captura场景`,
 | 
				
			||||||
    cheats_unlockAllDecoRecipes: `解锁所有道场配方`,
 | 
					 | 
				
			||||||
    cheats_universalPolarityEverywhere: `全局万用极性`,
 | 
					    cheats_universalPolarityEverywhere: `全局万用极性`,
 | 
				
			||||||
    cheats_unlockDoubleCapacityPotatoesEverywhere: `全物品自带Orokin反应堆`,
 | 
					    cheats_unlockDoubleCapacityPotatoesEverywhere: `全物品自带Orokin反应堆`,
 | 
				
			||||||
    cheats_unlockExilusEverywhere: `全物品自带适配器`,
 | 
					    cheats_unlockExilusEverywhere: `全物品自带适配器`,
 | 
				
			||||||
@ -387,5 +401,35 @@ dict = {
 | 
				
			|||||||
    theme_dark: `暗色主题`,
 | 
					    theme_dark: `暗色主题`,
 | 
				
			||||||
    theme_light: `亮色主题`,
 | 
					    theme_light: `亮色主题`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    guildView_cheats: `[UNTRANSLATED] Clan Cheats`,
 | 
				
			||||||
 | 
					    guildView_techProjects: `研究`,
 | 
				
			||||||
 | 
					    guildView_vaultDecoRecipes: `[UNTRANSLATED] Dojo Deco Recipes`,
 | 
				
			||||||
 | 
					    guildView_alliance: `联盟`,
 | 
				
			||||||
 | 
					    guildView_members: `成员`,
 | 
				
			||||||
 | 
					    guildView_pending: `待处理`,
 | 
				
			||||||
 | 
					    guildView_classDisplay: `等級 |CLASS|`,
 | 
				
			||||||
 | 
					    guildView_tierDisplay: `|TIER| 氏族`,
 | 
				
			||||||
 | 
					    guildView_tier1: `幽灵`,
 | 
				
			||||||
 | 
					    guildView_tier2: `暗影`,
 | 
				
			||||||
 | 
					    guildView_tier3: `风暴`,
 | 
				
			||||||
 | 
					    guildView_tier4: `山脉`,
 | 
				
			||||||
 | 
					    guildView_tier5: `月亮`,
 | 
				
			||||||
 | 
					    guildView_rank_creator: `创始军阀`,
 | 
				
			||||||
 | 
					    guildView_rank_general: `将军`,
 | 
				
			||||||
 | 
					    guildView_rank_initiate: `新兵`,
 | 
				
			||||||
 | 
					    guildView_rank_leader: `首领`,
 | 
				
			||||||
 | 
					    guildView_rank_officer: `智者`,
 | 
				
			||||||
 | 
					    guildView_rank_sage: `贤者`,
 | 
				
			||||||
 | 
					    guildView_rank_soldier: `战士`,
 | 
				
			||||||
 | 
					    guildView_rank_utility: `实管`,
 | 
				
			||||||
 | 
					    guildView_rank_warlord: `军阀`,
 | 
				
			||||||
 | 
					    guildView_currency_owned: `[UNTRANSLATED] |COUNT| in Vault.`,
 | 
				
			||||||
 | 
					    guildView_bulkAddTechProjects: `[UNTRANSLATED] Add Missing Research`,
 | 
				
			||||||
 | 
					    guildView_bulkAddVaultDecoRecipes: `[UNTRANSLATED] Add Missing Dojo Deco Recipes`,
 | 
				
			||||||
 | 
					    guildView_bulkFundTechProjects: `[UNTRANSLATED] Fund All Research`,
 | 
				
			||||||
 | 
					    guildView_bulkCompleteTechProjects: `[UNTRANSLATED] Complete All Research`,
 | 
				
			||||||
 | 
					    guildView_promote: `升级`,
 | 
				
			||||||
 | 
					    guildView_demote: `降级`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    prettier_sucks_ass: ``
 | 
					    prettier_sucks_ass: ``
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user