feat: support websocket connections from game client #2735
@ -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 {
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
@ -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;
 | 
				
			||||||
 | 
				
			|||||||
@ -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 {
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -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 {
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@ 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 { getGuildForRequestEx, hasGuildPermission } from "../../services/guildService.ts";
 | 
				
			||||||
import { GuildPermission } from "../../types/guildTypes.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);
 | 
				
			||||||
@ -24,6 +25,7 @@ export const addCurrencyController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    if (!request.currency.startsWith("Vault")) {
 | 
					    if (!request.currency.startsWith("Vault")) {
 | 
				
			||||||
        await inventory.save();
 | 
					        await inventory.save();
 | 
				
			||||||
 | 
					        broadcastInventoryUpdate(req);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    res.end();
 | 
					    res.end();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
import { applyClientEquipmentUpdates, getInventory } from "../../services/inventoryService.ts";
 | 
					import { applyClientEquipmentUpdates, 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 { 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";
 | 
				
			||||||
@ -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);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
@ -157,4 +158,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;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -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 {
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@ 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 { ExportBoosters } from "warframe-public-export-plus";
 | 
				
			||||||
 | 
					import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const I32_MAX = 0x7fffffff;
 | 
					const I32_MAX = 0x7fffffff;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -42,4 +43,5 @@ export const setBoosterController: RequestHandler = async (req, res) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    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 = {
 | 
				
			||||||
 | 
				
			|||||||
@ -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 });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -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 {
 | 
				
			||||||
 | 
				
			|||||||
@ -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,77 +153,152 @@ 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;
 | 
				
			||||||
                (ws as IWsCustomData).accountId = undefined;
 | 
					                if (accountId) {
 | 
				
			||||||
                await Account.updateOne(
 | 
					                    (ws as IWsCustomData).accountId = undefined;
 | 
				
			||||||
                    {
 | 
					                    await Account.updateOne(
 | 
				
			||||||
                        _id: accountId,
 | 
					                        {
 | 
				
			||||||
                        ClientType: "webui"
 | 
					                            _id: accountId,
 | 
				
			||||||
                    },
 | 
					                            ClientType: "webui"
 | 
				
			||||||
                    {
 | 
					                        },
 | 
				
			||||||
                        Nonce: 0
 | 
					                        {
 | 
				
			||||||
                    }
 | 
					                            Nonce: 0
 | 
				
			||||||
                );
 | 
					                        }
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            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) {
 | 
					
 | 
				
			||||||
            if ((client as IWsCustomData).accountId == accountId) {
 | 
					export const sendWsBroadcastToGame = (accountId: string, data: IWsMsgToClient): void => {
 | 
				
			||||||
                client.send(msg);
 | 
					    const msg = JSON.stringify(data);
 | 
				
			||||||
            }
 | 
					    forEachClient(client => {
 | 
				
			||||||
 | 
					        if (client.isGame && client.accountId == accountId) {
 | 
				
			||||||
 | 
					            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 (
 | 
					            client.send(msg);
 | 
				
			||||||
                (!accountId || (client as IWsCustomData).accountId == accountId) &&
 | 
					 | 
				
			||||||
                (client as IWsCustomData).id != excludeWsid
 | 
					 | 
				
			||||||
            ) {
 | 
					 | 
				
			||||||
                client.send(msg);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    });
 | 
				
			||||||
    if (wssServer) {
 | 
					};
 | 
				
			||||||
        for (const client of wssServer.clients) {
 | 
					
 | 
				
			||||||
            if (
 | 
					export const sendWsBroadcastToWebui = (data: IWsMsgToClient, accountId?: string, excludeWsid?: number): void => {
 | 
				
			||||||
                (!accountId || (client as IWsCustomData).accountId == accountId) &&
 | 
					    const msg = JSON.stringify(data);
 | 
				
			||||||
                (client as IWsCustomData).id != excludeWsid
 | 
					    forEachClient(client => {
 | 
				
			||||||
            ) {
 | 
					        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));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -93,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">
 | 
				
			||||||
@ -715,7 +715,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">
 | 
				
			||||||
@ -771,7 +771,7 @@
 | 
				
			|||||||
                </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">
 | 
				
			||||||
 | 
				
			|||||||
@ -63,7 +63,7 @@ function openWebSocket() {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            $(".displayname").text(data.DisplayName);
 | 
					            $(".displayname").text(data.DisplayName);
 | 
				
			||||||
            window.accountId = data.id;
 | 
					            window.accountId = data.id;
 | 
				
			||||||
            window.authz = "accountId=" + data.id + "&nonce=" + data.Nonce;
 | 
					            window.authz = "accountId=" + data.id + "&nonce=" + data.Nonce + "&wsid=" + wsid;
 | 
				
			||||||
            if (window.dict) {
 | 
					            if (window.dict) {
 | 
				
			||||||
                updateLocElements();
 | 
					                updateLocElements();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -90,6 +90,14 @@ function openWebSocket() {
 | 
				
			|||||||
        if ("logged_out" in msg) {
 | 
					        if ("logged_out" in msg) {
 | 
				
			||||||
            logout();
 | 
					            logout();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        if ("have_game_ws" in msg) {
 | 
				
			||||||
 | 
					            window.have_game_ws = msg.have_game_ws;
 | 
				
			||||||
 | 
					            if (window.dict) {
 | 
				
			||||||
 | 
					                $(".inventory-update-note").text(
 | 
				
			||||||
 | 
					                    loc(msg.have_game_ws ? "general_inventoryUpdateNoteGameWs" : "general_inventoryUpdateNote")
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    window.ws.onclose = function () {
 | 
					    window.ws.onclose = function () {
 | 
				
			||||||
        ws_is_open = false;
 | 
					        ws_is_open = false;
 | 
				
			||||||
@ -223,6 +231,9 @@ function updateLocElements() {
 | 
				
			|||||||
    document.querySelectorAll("[data-loc-replace]").forEach(elm => {
 | 
					    document.querySelectorAll("[data-loc-replace]").forEach(elm => {
 | 
				
			||||||
        elm.innerHTML = elm.innerHTML.replace("|VAL|", elm.getAttribute("data-loc-replace"));
 | 
					        elm.innerHTML = elm.innerHTML.replace("|VAL|", elm.getAttribute("data-loc-replace"));
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    $(".inventory-update-note").text(
 | 
				
			||||||
 | 
					        loc(window.have_game_ws ? "general_inventoryUpdateNoteGameWs" : "general_inventoryUpdateNote")
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function setActiveLanguage(lang) {
 | 
					function setActiveLanguage(lang) {
 | 
				
			||||||
@ -2582,7 +2593,7 @@ function disposeOfGear(category, oid) {
 | 
				
			|||||||
        ];
 | 
					        ];
 | 
				
			||||||
        revalidateAuthz().then(() => {
 | 
					        revalidateAuthz().then(() => {
 | 
				
			||||||
            $.post({
 | 
					            $.post({
 | 
				
			||||||
                url: "/api/sell.php?" + window.authz + "&wsid=" + wsid,
 | 
					                url: "/api/sell.php?" + window.authz,
 | 
				
			||||||
                contentType: "text/plain",
 | 
					                contentType: "text/plain",
 | 
				
			||||||
                data: JSON.stringify(data)
 | 
					                data: JSON.stringify(data)
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
@ -2604,7 +2615,7 @@ function disposeOfItems(category, type, count) {
 | 
				
			|||||||
    ];
 | 
					    ];
 | 
				
			||||||
    revalidateAuthz().then(() => {
 | 
					    revalidateAuthz().then(() => {
 | 
				
			||||||
        $.post({
 | 
					        $.post({
 | 
				
			||||||
            url: "/api/sell.php?" + window.authz + "&wsid=" + wsid,
 | 
					            url: "/api/sell.php?" + window.authz,
 | 
				
			||||||
            contentType: "text/plain",
 | 
					            contentType: "text/plain",
 | 
				
			||||||
            data: JSON.stringify(data)
 | 
					            data: JSON.stringify(data)
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
@ -2858,7 +2869,7 @@ for (const id of uiConfigs) {
 | 
				
			|||||||
                value = parseInt(value);
 | 
					                value = parseInt(value);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            $.post({
 | 
					            $.post({
 | 
				
			||||||
                url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid,
 | 
					                url: "/custom/setConfig?" + window.authz,
 | 
				
			||||||
                contentType: "application/json",
 | 
					                contentType: "application/json",
 | 
				
			||||||
                data: JSON.stringify({ [id]: value })
 | 
					                data: JSON.stringify({ [id]: value })
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
@ -2866,13 +2877,9 @@ for (const id of uiConfigs) {
 | 
				
			|||||||
    } else if (elm.type == "checkbox") {
 | 
					    } else if (elm.type == "checkbox") {
 | 
				
			||||||
        elm.onchange = function () {
 | 
					        elm.onchange = function () {
 | 
				
			||||||
            $.post({
 | 
					            $.post({
 | 
				
			||||||
                url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid,
 | 
					                url: "/custom/setConfig?" + window.authz,
 | 
				
			||||||
                contentType: "application/json",
 | 
					                contentType: "application/json",
 | 
				
			||||||
                data: JSON.stringify({ [id]: this.checked })
 | 
					                data: JSON.stringify({ [id]: this.checked })
 | 
				
			||||||
            }).then(() => {
 | 
					 | 
				
			||||||
                if (["infiniteCredits", "infinitePlatinum", "infiniteEndo", "infiniteRegalAya"].indexOf(id) != -1) {
 | 
					 | 
				
			||||||
                    updateInventory();
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -2893,7 +2900,7 @@ document.querySelectorAll(".config-form .input-group").forEach(grp => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
function doSaveConfigInt(id) {
 | 
					function doSaveConfigInt(id) {
 | 
				
			||||||
    $.post({
 | 
					    $.post({
 | 
				
			||||||
        url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid,
 | 
					        url: "/custom/setConfig?" + window.authz,
 | 
				
			||||||
        contentType: "application/json",
 | 
					        contentType: "application/json",
 | 
				
			||||||
        data: JSON.stringify({
 | 
					        data: JSON.stringify({
 | 
				
			||||||
            [id]: parseInt(document.getElementById(id).value)
 | 
					            [id]: parseInt(document.getElementById(id).value)
 | 
				
			||||||
@ -2903,7 +2910,7 @@ function doSaveConfigInt(id) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
function doSaveConfigFloat(id) {
 | 
					function doSaveConfigFloat(id) {
 | 
				
			||||||
    $.post({
 | 
					    $.post({
 | 
				
			||||||
        url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid,
 | 
					        url: "/custom/setConfig?" + window.authz,
 | 
				
			||||||
        contentType: "application/json",
 | 
					        contentType: "application/json",
 | 
				
			||||||
        data: JSON.stringify({
 | 
					        data: JSON.stringify({
 | 
				
			||||||
            [id]: parseFloat(document.getElementById(id).value)
 | 
					            [id]: parseFloat(document.getElementById(id).value)
 | 
				
			||||||
@ -2913,7 +2920,7 @@ function doSaveConfigFloat(id) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
function doSaveConfigStringArray(id) {
 | 
					function doSaveConfigStringArray(id) {
 | 
				
			||||||
    $.post({
 | 
					    $.post({
 | 
				
			||||||
        url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid,
 | 
					        url: "/custom/setConfig?" + window.authz,
 | 
				
			||||||
        contentType: "application/json",
 | 
					        contentType: "application/json",
 | 
				
			||||||
        data: JSON.stringify({
 | 
					        data: JSON.stringify({
 | 
				
			||||||
            [id]: document
 | 
					            [id]: document
 | 
				
			||||||
@ -2997,6 +3004,9 @@ function doUnlockAllFocusSchools() {
 | 
				
			|||||||
                toast(loc("code_focusAllUnlocked"));
 | 
					                toast(loc("code_focusAllUnlocked"));
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                toast(loc("code_focusUnlocked").split("|COUNT|").join(Object.keys(missingFocusUpgrades).length));
 | 
					                toast(loc("code_focusUnlocked").split("|COUNT|").join(Object.keys(missingFocusUpgrades).length));
 | 
				
			||||||
 | 
					                if (ws_is_open) {
 | 
				
			||||||
 | 
					                    window.ws.send(JSON.stringify({ sync_inventory: true }));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@ -3040,7 +3050,7 @@ document.querySelectorAll("#account-cheats input[type=checkbox]").forEach(elm =>
 | 
				
			|||||||
    elm.onchange = function () {
 | 
					    elm.onchange = function () {
 | 
				
			||||||
        revalidateAuthz().then(() => {
 | 
					        revalidateAuthz().then(() => {
 | 
				
			||||||
            $.post({
 | 
					            $.post({
 | 
				
			||||||
                url: "/custom/setAccountCheat?" + window.authz /*+ "&wsid=" + wsid*/,
 | 
					                url: "/custom/setAccountCheat?" + window.authz,
 | 
				
			||||||
                contentType: "application/json",
 | 
					                contentType: "application/json",
 | 
				
			||||||
                data: JSON.stringify({
 | 
					                data: JSON.stringify({
 | 
				
			||||||
                    key: elm.id,
 | 
					                    key: elm.id,
 | 
				
			||||||
@ -3112,7 +3122,7 @@ function doRemoveUnrankedMods() {
 | 
				
			|||||||
        req.done(inventory => {
 | 
					        req.done(inventory => {
 | 
				
			||||||
            window.itemListPromise.then(itemMap => {
 | 
					            window.itemListPromise.then(itemMap => {
 | 
				
			||||||
                $.post({
 | 
					                $.post({
 | 
				
			||||||
                    url: "/api/sell.php?" + window.authz + "&wsid=" + wsid,
 | 
					                    url: "/api/sell.php?" + window.authz,
 | 
				
			||||||
                    contentType: "text/plain",
 | 
					                    contentType: "text/plain",
 | 
				
			||||||
                    data: JSON.stringify({
 | 
					                    data: JSON.stringify({
 | 
				
			||||||
                        SellCurrency: "SC_RegularCredits",
 | 
					                        SellCurrency: "SC_RegularCredits",
 | 
				
			||||||
@ -3469,13 +3479,13 @@ async function doUnlockAllScans() {
 | 
				
			|||||||
async function doUnlockAllShipFeatures() {
 | 
					async function doUnlockAllShipFeatures() {
 | 
				
			||||||
    await revalidateAuthz();
 | 
					    await revalidateAuthz();
 | 
				
			||||||
    await fetch("/custom/unlockAllShipFeatures?" + window.authz);
 | 
					    await fetch("/custom/unlockAllShipFeatures?" + window.authz);
 | 
				
			||||||
    toast(loc("cheats_unlockSuccInventory"));
 | 
					    toast(loc(window.have_game_ws ? "code_succAdded" : "cheats_unlockSuccInventory"));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function doUnlockAllCapturaScenes() {
 | 
					async function doUnlockAllCapturaScenes() {
 | 
				
			||||||
    await revalidateAuthz();
 | 
					    await revalidateAuthz();
 | 
				
			||||||
    await fetch("/custom/unlockAllCapturaScenes?" + window.authz);
 | 
					    await fetch("/custom/unlockAllCapturaScenes?" + window.authz);
 | 
				
			||||||
    toast(loc("cheats_unlockSuccInventory"));
 | 
					    toast(loc(window.have_game_ws ? "code_succAdded" : "cheats_unlockSuccInventory"));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function unlockAllMissions() {
 | 
					async function unlockAllMissions() {
 | 
				
			||||||
@ -3487,13 +3497,13 @@ async function unlockAllMissions() {
 | 
				
			|||||||
async function unlockAllProfitTakerStages() {
 | 
					async function unlockAllProfitTakerStages() {
 | 
				
			||||||
    await revalidateAuthz();
 | 
					    await revalidateAuthz();
 | 
				
			||||||
    await fetch("/custom/unlockAllProfitTakerStages?" + window.authz);
 | 
					    await fetch("/custom/unlockAllProfitTakerStages?" + window.authz);
 | 
				
			||||||
    toast(loc("cheats_unlockSuccInventory"));
 | 
					    toast(loc(window.have_game_ws ? "code_succAdded" : "cheats_unlockSuccInventory"));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function unlockAllSimarisResearchEntries() {
 | 
					async function unlockAllSimarisResearchEntries() {
 | 
				
			||||||
    await revalidateAuthz();
 | 
					    await revalidateAuthz();
 | 
				
			||||||
    await fetch("/custom/unlockAllSimarisResearchEntries?" + window.authz);
 | 
					    await fetch("/custom/unlockAllSimarisResearchEntries?" + window.authz);
 | 
				
			||||||
    toast(loc("cheats_unlockSuccInventory"));
 | 
					    toast(loc(window.have_game_ws ? "code_succAdded" : "cheats_unlockSuccInventory"));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const importSamples = {
 | 
					const importSamples = {
 | 
				
			||||||
 | 
				
			|||||||
@ -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`,
 | 
				
			||||||
 | 
				
			|||||||
@ -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`,
 | 
				
			||||||
 | 
				
			|||||||
@ -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`,
 | 
				
			||||||
 | 
				
			|||||||
@ -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: `[UNTRANSLATED] Note: You may need to reopen any menu you are on for changes to be reflected.`,
 | 
				
			||||||
    general_addButton: `Ajouter`,
 | 
					    general_addButton: `Ajouter`,
 | 
				
			||||||
    general_setButton: `Définir`,
 | 
					    general_setButton: `Définir`,
 | 
				
			||||||
    general_none: `Aucun`,
 | 
					    general_none: `Aucun`,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
// Russian translation by AMelonInsideLemon, LoseFace
 | 
					// Russian translation by AMelonInsideLemon, LoseFace
 | 
				
			||||||
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: `Отсутствует`,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
// Ukrainian translation by LoseFace
 | 
					// Ukrainian translation by LoseFace
 | 
				
			||||||
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: `Відсутній`,
 | 
				
			||||||
 | 
				
			|||||||
@ -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: `无`,
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user