forked from OpenWF/SpaceNinjaServer
		
	feat(webui): skins (#2816)
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Reviewed-on: OpenWF/SpaceNinjaServer#2816 Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com> Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									af4c3a93ce
								
							
						
					
					
						commit
						02f0935710
					
				
							
								
								
									
										8
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -18,7 +18,7 @@
 | 
			
		||||
        "morgan": "^1.10.0",
 | 
			
		||||
        "ncp": "^2.0.0",
 | 
			
		||||
        "undici": "^7.10.0",
 | 
			
		||||
        "warframe-public-export-plus": "^0.5.89",
 | 
			
		||||
        "warframe-public-export-plus": "^0.5.90",
 | 
			
		||||
        "warframe-riven-info": "^0.1.2",
 | 
			
		||||
        "winston": "^3.17.0",
 | 
			
		||||
        "winston-daily-rotate-file": "^5.0.0",
 | 
			
		||||
@ -5534,9 +5534,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/warframe-public-export-plus": {
 | 
			
		||||
      "version": "0.5.89",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.89.tgz",
 | 
			
		||||
      "integrity": "sha512-a6dM1MirzofSsuv3LlRQHFLSSIGKPVSN93dcXSDmA3njsWqOGjJJdWyXqcyxxYw8rEB8CNowSHst/MUmKvKlRg=="
 | 
			
		||||
      "version": "0.5.90",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.90.tgz",
 | 
			
		||||
      "integrity": "sha512-PzYFJ+qOZawPOVx9+hGlOosGdqSJMSRL6c1z3CCXMiY5SEHUs7p9JIMjkqnvg1g7vflVWnyG6E5/2gYkQs0d7w=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/warframe-riven-info": {
 | 
			
		||||
      "version": "0.1.2",
 | 
			
		||||
 | 
			
		||||
@ -36,7 +36,7 @@
 | 
			
		||||
    "morgan": "^1.10.0",
 | 
			
		||||
    "ncp": "^2.0.0",
 | 
			
		||||
    "undici": "^7.10.0",
 | 
			
		||||
    "warframe-public-export-plus": "^0.5.89",
 | 
			
		||||
    "warframe-public-export-plus": "^0.5.90",
 | 
			
		||||
    "warframe-riven-info": "^0.1.2",
 | 
			
		||||
    "winston": "^3.17.0",
 | 
			
		||||
    "winston-daily-rotate-file": "^5.0.0",
 | 
			
		||||
 | 
			
		||||
@ -177,7 +177,7 @@ export const inventoryController: RequestHandler = async (request, response) =>
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        cleanupInventory(inventory);
 | 
			
		||||
        await cleanupInventory(inventory);
 | 
			
		||||
 | 
			
		||||
        inventory.NextRefill = new Date((today + 1) * 86400000); // tomorrow at 0 UTC
 | 
			
		||||
        //await inventory.save();
 | 
			
		||||
 | 
			
		||||
@ -77,6 +77,9 @@ export const sellController: RequestHandler = async (req, res) => {
 | 
			
		||||
            requiredFields.add("CrewShipSalvagedWeaponSkins");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (payload.Items.WeaponSkins) {
 | 
			
		||||
        requiredFields.add("WeaponSkins");
 | 
			
		||||
    }
 | 
			
		||||
    const inventory = await getInventory(accountId, Array.from(requiredFields).join(" "));
 | 
			
		||||
 | 
			
		||||
    // Give currency
 | 
			
		||||
@ -302,6 +305,11 @@ export const sellController: RequestHandler = async (req, res) => {
 | 
			
		||||
            addFusionTreasures(inventory, [parseFusionTreasure(sellItem.String, sellItem.Count * -1)]);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    if (payload.Items.WeaponSkins) {
 | 
			
		||||
        payload.Items.WeaponSkins.forEach(sellItem => {
 | 
			
		||||
            inventory.WeaponSkins.pull({ _id: sellItem.String });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.json({
 | 
			
		||||
@ -335,6 +343,7 @@ interface ISellRequest {
 | 
			
		||||
        CrewShipWeapons?: ISellItem[];
 | 
			
		||||
        CrewShipWeaponSkins?: ISellItem[];
 | 
			
		||||
        FusionTreasures?: ISellItem[];
 | 
			
		||||
        WeaponSkins?: ISellItem[]; // SNS specific field
 | 
			
		||||
    };
 | 
			
		||||
    SellPrice: number;
 | 
			
		||||
    SellCurrency:
 | 
			
		||||
 | 
			
		||||
@ -66,6 +66,7 @@ interface ItemLists {
 | 
			
		||||
    VaultDecoRecipes: ListedItem[];
 | 
			
		||||
    FlavourItems: ListedItem[];
 | 
			
		||||
    ShipDecorations: ListedItem[];
 | 
			
		||||
    WeaponSkins: ListedItem[];
 | 
			
		||||
    //circuitGameModes: ListedItem[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -107,7 +108,8 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
			
		||||
        TechProjects: [],
 | 
			
		||||
        VaultDecoRecipes: [],
 | 
			
		||||
        FlavourItems: [],
 | 
			
		||||
        ShipDecorations: []
 | 
			
		||||
        ShipDecorations: [],
 | 
			
		||||
        WeaponSkins: []
 | 
			
		||||
        /*circuitGameModes: [
 | 
			
		||||
            {
 | 
			
		||||
                uniqueName: "Survival",
 | 
			
		||||
@ -298,10 +300,18 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    for (const [uniqueName, item] of Object.entries(ExportCustoms)) {
 | 
			
		||||
        res.miscitems.push({
 | 
			
		||||
            uniqueName: uniqueName,
 | 
			
		||||
            name: getString(item.name, lang)
 | 
			
		||||
        });
 | 
			
		||||
        if (
 | 
			
		||||
            item.productCategory == "WeaponSkins" &&
 | 
			
		||||
            !uniqueName.startsWith("/Lotus/Types/Game/Lotus") && // Base Items
 | 
			
		||||
            !uniqueName.endsWith("ProjectileSkin") && // UnrealTournament ProjectileSkins
 | 
			
		||||
            !uniqueName.endsWith("Coat") // Frost Prime stuff
 | 
			
		||||
        ) {
 | 
			
		||||
            res.WeaponSkins.push({
 | 
			
		||||
                uniqueName: uniqueName,
 | 
			
		||||
                name: getString(item.name, lang),
 | 
			
		||||
                alwaysAvailable: item.alwaysAvailable
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const [uniqueName, upgrade] of Object.entries(ExportUpgrades)) {
 | 
			
		||||
 | 
			
		||||
@ -20,13 +20,14 @@ import type {
 | 
			
		||||
} from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import { LoadoutIndex } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { catBreadHash, getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
			
		||||
import { ExportCustoms, ExportDojoRecipes } from "warframe-public-export-plus";
 | 
			
		||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
			
		||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
			
		||||
import type { IStatsClient } from "../../types/statTypes.ts";
 | 
			
		||||
import { toStoreItem } from "../../services/itemDataService.ts";
 | 
			
		||||
import type { FlattenMaps } from "mongoose";
 | 
			
		||||
import type { IEquipmentClient } from "../../types/equipmentTypes.ts";
 | 
			
		||||
import type { ILoadoutConfigClient } from "../../types/saveLoadoutTypes.ts";
 | 
			
		||||
import { skinLookupTable } from "../../helpers/skinLookupTable.ts";
 | 
			
		||||
 | 
			
		||||
const getProfileViewingDataByPlayerIdImpl = async (playerId: string): Promise<IProfileViewingData | undefined> => {
 | 
			
		||||
    const account = await Account.findById(playerId, "DisplayName");
 | 
			
		||||
@ -261,8 +262,6 @@ interface IXPComponentClient {
 | 
			
		||||
    locTags?: Record<string, string>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let skinLookupTable: Record<number, string> | undefined;
 | 
			
		||||
 | 
			
		||||
const resolveAndCollectSkins = (
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    skins: Set<string>,
 | 
			
		||||
@ -274,12 +273,6 @@ const resolveAndCollectSkins = (
 | 
			
		||||
                // Resolve oids to type names
 | 
			
		||||
                if (config.Skins[i].length == 24) {
 | 
			
		||||
                    if (config.Skins[i].substring(0, 16) == "ca70ca70ca70ca70") {
 | 
			
		||||
                        if (!skinLookupTable) {
 | 
			
		||||
                            skinLookupTable = {};
 | 
			
		||||
                            for (const key of Object.keys(ExportCustoms)) {
 | 
			
		||||
                                skinLookupTable[catBreadHash(key)] = key;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        config.Skins[i] = skinLookupTable[parseInt(config.Skins[i].substring(16), 16)];
 | 
			
		||||
                    } else {
 | 
			
		||||
                        const skinItem = inventory.WeaponSkins.id(config.Skins[i]);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								src/helpers/skinLookupTable.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/helpers/skinLookupTable.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
import { ExportCustoms } from "warframe-public-export-plus";
 | 
			
		||||
import { catBreadHash } from "./stringHelpers.ts";
 | 
			
		||||
 | 
			
		||||
export const skinLookupTable: Record<number, string> = {};
 | 
			
		||||
 | 
			
		||||
for (const key of Object.keys(ExportCustoms)) {
 | 
			
		||||
    skinLookupTable[catBreadHash(key)] = key;
 | 
			
		||||
}
 | 
			
		||||
@ -92,6 +92,7 @@ import type {
 | 
			
		||||
} from "../types/equipmentTypes.ts";
 | 
			
		||||
import { EquipmentFeatures, Status } from "../types/equipmentTypes.ts";
 | 
			
		||||
import type { ITypeCount } from "../types/commonTypes.ts";
 | 
			
		||||
import { skinLookupTable } from "../helpers/skinLookupTable.ts";
 | 
			
		||||
 | 
			
		||||
export const createInventory = async (
 | 
			
		||||
    accountOwnerId: Types.ObjectId,
 | 
			
		||||
@ -2260,7 +2261,7 @@ export const setupKahlSyndicate = (inventory: TInventoryDatabaseDocument): void
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const cleanupInventory = (inventory: TInventoryDatabaseDocument): void => {
 | 
			
		||||
export const cleanupInventory = async (inventory: TInventoryDatabaseDocument): Promise<void> => {
 | 
			
		||||
    inventory.CurrentLoadOutIds = inventory.CurrentLoadOutIds.map(fromDbOid);
 | 
			
		||||
 | 
			
		||||
    let index = inventory.MiscItems.findIndex(x => x.ItemType == "");
 | 
			
		||||
@ -2314,6 +2315,99 @@ export const cleanupInventory = (inventory: TInventoryDatabaseDocument): void =>
 | 
			
		||||
            logger.debug(`removed ModularParts from ${numFixed} non-modular items`);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        const weaponMap = new Map<string, string>();
 | 
			
		||||
        for (const skin of inventory.WeaponSkins) {
 | 
			
		||||
            weaponMap.set(skin.ItemType, skin._id.toString());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const itemsToAdd = new Set<string>();
 | 
			
		||||
 | 
			
		||||
        for (const key of equipmentKeys) {
 | 
			
		||||
            if (key in inventory) {
 | 
			
		||||
                for (const equipment of inventory[key]) {
 | 
			
		||||
                    for (const config of equipment.Configs) {
 | 
			
		||||
                        if (config.Skins) collectSkins(config.Skins, weaponMap, itemsToAdd);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (const key of ["AdultOperatorLoadOuts", "OperatorLoadOuts", "KahlLoadOuts"] as const) {
 | 
			
		||||
            if (key in inventory) {
 | 
			
		||||
                for (const loadOut of inventory[key]) {
 | 
			
		||||
                    if (loadOut.Skins) collectSkins(loadOut.Skins, weaponMap, itemsToAdd);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (inventory.LotusCustomization?.Skins)
 | 
			
		||||
            collectSkins(inventory.LotusCustomization.Skins, weaponMap, itemsToAdd);
 | 
			
		||||
 | 
			
		||||
        if (itemsToAdd.size > 0) {
 | 
			
		||||
            logger.debug(`Adding ${itemsToAdd.size} items due to migration from unlockAllSkins cheat`);
 | 
			
		||||
            const inventoryChanges = await addItems(inventory, Array.from(itemsToAdd));
 | 
			
		||||
 | 
			
		||||
            if (inventoryChanges.WeaponSkins) {
 | 
			
		||||
                for (const skin of inventoryChanges.WeaponSkins as IWeaponSkinClient[]) {
 | 
			
		||||
                    weaponMap.set(skin.ItemType, skin.ItemId.toString());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (const key of equipmentKeys) {
 | 
			
		||||
                if (key in inventory) {
 | 
			
		||||
                    for (const equipment of inventory[key]) {
 | 
			
		||||
                        for (const config of equipment.Configs) {
 | 
			
		||||
                            if (config.Skins) replaceSkinIds(config.Skins, weaponMap);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (const key of ["AdultOperatorLoadOuts", "OperatorLoadOuts", "KahlLoadOuts"] as const) {
 | 
			
		||||
                if (key in inventory) {
 | 
			
		||||
                    for (const loadOut of inventory[key]) {
 | 
			
		||||
                        if (loadOut.Skins) replaceSkinIds(loadOut.Skins, weaponMap);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (inventory.LotusCustomization?.Skins) replaceSkinIds(inventory.LotusCustomization.Skins, weaponMap);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const collectSkins = (skins: string[], weaponMap: Map<string, string>, itemsToAdd: Set<string>): void => {
 | 
			
		||||
    for (const skinId of skins) {
 | 
			
		||||
        if (skinId.startsWith("ca70ca70ca70ca70")) {
 | 
			
		||||
            const typeName = skinLookupTable[parseInt(skinId.slice(16), 16)];
 | 
			
		||||
            if (!weaponMap.has(typeName)) {
 | 
			
		||||
                const { requirement } = ExportCustoms[typeName];
 | 
			
		||||
                if (typeof requirement == "string") {
 | 
			
		||||
                    itemsToAdd.add(requirement);
 | 
			
		||||
                } else {
 | 
			
		||||
                    itemsToAdd.add(typeName);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const replaceSkinIds = (skins: string[], weaponMap: Map<string, string>): void => {
 | 
			
		||||
    for (let i = 0; i < skins.length; i++) {
 | 
			
		||||
        const skinId = skins[i];
 | 
			
		||||
        if (skinId.startsWith("ca70ca70ca70ca70")) {
 | 
			
		||||
            const typeName = skinLookupTable[parseInt(skinId.slice(16), 16)];
 | 
			
		||||
            const inventoryId = weaponMap.get(typeName);
 | 
			
		||||
            if (inventoryId) {
 | 
			
		||||
                skins[i] = inventoryId;
 | 
			
		||||
            } else if (typeName in ExportCustoms) {
 | 
			
		||||
                const { requirement } = ExportCustoms[typeName];
 | 
			
		||||
                skins[i] = typeof requirement == "string" ? typeName : "";
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getDialogue = (inventory: TInventoryDatabaseDocument, dialogueName: string): IDialogueDatabase => {
 | 
			
		||||
 | 
			
		||||
@ -526,6 +526,24 @@
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="row g-3 mb-3">
 | 
			
		||||
                    <div class="col-lg-6">
 | 
			
		||||
                        <div class="card" style="height: 400px;">
 | 
			
		||||
                            <h5 class="card-header" data-loc="inventory_weaponSkins"></h5>
 | 
			
		||||
                            <div class="card-body d-flex flex-column">
 | 
			
		||||
                                <form class="input-group mb-3" onsubmit="doAcquireEquipment('WeaponSkins');return false;">
 | 
			
		||||
                                    <input class="form-control" id="acquire-type-WeaponSkins" list="datalist-WeaponSkins" />
 | 
			
		||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
			
		||||
                                </form>
 | 
			
		||||
                                <div class="overflow-auto">
 | 
			
		||||
                                    <table class="table table-hover w-100">
 | 
			
		||||
                                        <tbody id="WeaponSkins-list"></tbody>
 | 
			
		||||
                                    </table>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card">
 | 
			
		||||
                    <h5 class="card-header" data-loc="general_bulkActions"></h5>
 | 
			
		||||
                    <div class="card-body">
 | 
			
		||||
@ -538,6 +556,7 @@
 | 
			
		||||
                            <button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['SentinelWeapons']);" data-loc="inventory_bulkAddSentinelWeapons"></button>
 | 
			
		||||
                            <button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['FlavourItems']);" data-loc="inventory_bulkAddFlavourItems"></button>
 | 
			
		||||
                            <button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['ShipDecorations']);" data-loc="inventory_bulkAddShipDecorations"></button>
 | 
			
		||||
                            <button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['WeaponSkins']);" data-loc="inventory_bulkAddWeaponSkins"></button>
 | 
			
		||||
                            <button class="btn btn-primary" onclick="debounce(addMissingEvolutionProgress);" data-loc="inventory_bulkAddEvolutionProgress"></button>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="mb-2 d-flex flex-wrap gap-2">
 | 
			
		||||
@ -1528,6 +1547,7 @@
 | 
			
		||||
    <datalist id="datalist-VaultDecoRecipes"></datalist>
 | 
			
		||||
    <datalist id="datalist-FlavourItems"></datalist>
 | 
			
		||||
    <datalist id="datalist-ShipDecorations"></datalist>
 | 
			
		||||
    <datalist id="datalist-WeaponSkins"></datalist>
 | 
			
		||||
    <datalist id="datalist-circuitGameModes">
 | 
			
		||||
        <option>Survival</option>
 | 
			
		||||
        <option>VoidFlood</option>
 | 
			
		||||
 | 
			
		||||
@ -599,6 +599,46 @@ function fetchItemList() {
 | 
			
		||||
                        }
 | 
			
		||||
                        itemMap[item.uniqueName] = { ...item, type };
 | 
			
		||||
                    });
 | 
			
		||||
                } else if (type == "WeaponSkins") {
 | 
			
		||||
                    let beardNumber = 1;
 | 
			
		||||
                    let cutNumber = 13;
 | 
			
		||||
                    let adultHeadNumber = 1;
 | 
			
		||||
                    let headNumber = 1;
 | 
			
		||||
                    items.forEach(item => {
 | 
			
		||||
                        if (item.name == "") {
 | 
			
		||||
                            if (item.uniqueName.includes("/Beards/")) {
 | 
			
		||||
                                item.name = loc("code_drifterBeardName")
 | 
			
		||||
                                    .split("|INDEX|")
 | 
			
		||||
                                    .join(beardNumber.toString().padStart(3, "0"));
 | 
			
		||||
                                beardNumber++;
 | 
			
		||||
                            } else if (item.uniqueName.includes("/Hair/")) {
 | 
			
		||||
                                item.name = loc("code_cutName")
 | 
			
		||||
                                    .split("|INDEX|")
 | 
			
		||||
                                    .join(cutNumber.toString().padStart(3, "0"));
 | 
			
		||||
                                cutNumber++;
 | 
			
		||||
                                if (cutNumber == 19) cutNumber = 21;
 | 
			
		||||
                            } else if (item.uniqueName.includes("/Heads/Adult")) {
 | 
			
		||||
                                item.name = loc("code_drifterFaceName")
 | 
			
		||||
                                    .split("|INDEX|")
 | 
			
		||||
                                    .join(adultHeadNumber.toString().padStart(3, "0"));
 | 
			
		||||
                                adultHeadNumber++;
 | 
			
		||||
                            } else if (item.uniqueName.includes("/Heads/")) {
 | 
			
		||||
                                item.name = loc("code_operatorFaceName")
 | 
			
		||||
                                    .split("|INDEX|")
 | 
			
		||||
                                    .join(headNumber.toString().padStart(3, "0"));
 | 
			
		||||
                                headNumber++;
 | 
			
		||||
                            } else {
 | 
			
		||||
                                item.name = item.uniqueName;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        if (!item.alwaysAvailable) {
 | 
			
		||||
                            const option = document.createElement("option");
 | 
			
		||||
                            option.setAttribute("data-key", item.uniqueName);
 | 
			
		||||
                            option.value = item.name;
 | 
			
		||||
                            document.getElementById("datalist-" + type).appendChild(option);
 | 
			
		||||
                        }
 | 
			
		||||
                        itemMap[item.uniqueName] = { ...item, type };
 | 
			
		||||
                    });
 | 
			
		||||
                } else {
 | 
			
		||||
                    const nameToItems = {};
 | 
			
		||||
                    items.forEach(item => {
 | 
			
		||||
@ -1103,6 +1143,44 @@ function updateInventory() {
 | 
			
		||||
                document.getElementById("FlavourItems-list").appendChild(tr);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            document.getElementById("WeaponSkins-list").innerHTML = "";
 | 
			
		||||
            data.WeaponSkins.forEach(item => {
 | 
			
		||||
                if (item.ItemId.$oid.startsWith("ca70ca70ca70ca70")) return;
 | 
			
		||||
                const datalist = document.getElementById("datalist-WeaponSkins");
 | 
			
		||||
                const optionToRemove = datalist.querySelector(`option[data-key="${item.ItemType}"]`);
 | 
			
		||||
                if (optionToRemove) {
 | 
			
		||||
                    datalist.removeChild(optionToRemove);
 | 
			
		||||
                }
 | 
			
		||||
                const tr = document.createElement("tr");
 | 
			
		||||
                {
 | 
			
		||||
                    const td = document.createElement("td");
 | 
			
		||||
                    const name = itemMap[item.ItemType]?.name?.trim();
 | 
			
		||||
                    td.textContent = name || item.ItemType;
 | 
			
		||||
                    tr.appendChild(td);
 | 
			
		||||
                }
 | 
			
		||||
                {
 | 
			
		||||
                    const td = document.createElement("td");
 | 
			
		||||
                    td.classList = "text-end text-nowrap";
 | 
			
		||||
                    {
 | 
			
		||||
                        const a = document.createElement("a");
 | 
			
		||||
                        a.href = "#";
 | 
			
		||||
                        a.onclick = function (event) {
 | 
			
		||||
                            event.preventDefault();
 | 
			
		||||
                            document.getElementById("WeaponSkins-list").removeChild(tr);
 | 
			
		||||
                            reAddToItemList(itemMap, "WeaponSkins", item.ItemType);
 | 
			
		||||
                            disposeOfGear("WeaponSkins", item.ItemId.$oid);
 | 
			
		||||
                        };
 | 
			
		||||
                        a.title = loc("code_remove");
 | 
			
		||||
                        a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/></svg>`;
 | 
			
		||||
                        td.appendChild(a);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    tr.appendChild(td);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                document.getElementById("WeaponSkins-list").appendChild(tr);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            const datalistEvolutionProgress = document.querySelectorAll("#datalist-EvolutionProgress option");
 | 
			
		||||
            const formEvolutionProgress = document.querySelector('form[onsubmit*="doAcquireEvolution()"]');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -75,6 +75,10 @@ dict = {
 | 
			
		||||
    code_funded: `[UNTRANSLATED] Funded`,
 | 
			
		||||
    code_replays: `[UNTRANSLATED] Replays`,
 | 
			
		||||
    code_stalker: `Stalker`,
 | 
			
		||||
    code_cutName: `Frisur: |INDEX|`,
 | 
			
		||||
    code_drifterBeardName: `Drifter-Bart: |INDEX|`,
 | 
			
		||||
    code_drifterFaceName: `Drifter-Gesicht: |INDEX|`,
 | 
			
		||||
    code_operatorFaceName: `Operator-Gesicht: |INDEX|`,
 | 
			
		||||
    code_succChange: `Erfolgreich geändert.`,
 | 
			
		||||
    code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`,
 | 
			
		||||
    login_description: `Melde dich mit deinem OpenWF-Account an (denselben Angaben wie im Spiel, wenn du dich mit diesem Server verbindest).`,
 | 
			
		||||
@ -112,6 +116,7 @@ dict = {
 | 
			
		||||
    inventory_boosters: `Booster`,
 | 
			
		||||
    inventory_flavourItems: `<abbr title="Animationssets, Glyphen, Farbpaletten usw.">Sammlerstücke</abbr>`,
 | 
			
		||||
    inventory_shipDecorations: `Schiffsdekorationen`,
 | 
			
		||||
    inventory_weaponSkins: `Skins`,
 | 
			
		||||
    inventory_bulkAddSuits: `Fehlende Warframes hinzufügen`,
 | 
			
		||||
    inventory_bulkAddWeapons: `Fehlende Waffen hinzufügen`,
 | 
			
		||||
    inventory_bulkAddSpaceSuits: `Fehlende Archwings hinzufügen`,
 | 
			
		||||
@ -121,6 +126,7 @@ dict = {
 | 
			
		||||
    inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`,
 | 
			
		||||
    inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`,
 | 
			
		||||
    inventory_bulkAddEvolutionProgress: `Fehlende Incarnon-Entwicklungsfortschritte hinzufügen`,
 | 
			
		||||
    inventory_bulkAddWeaponSkins: `[UNTRANSLATED] Add Missing Skins`,
 | 
			
		||||
    inventory_bulkRankUpSuits: `Alle Warframes auf Max. Rang`,
 | 
			
		||||
    inventory_bulkRankUpWeapons: `Alle Waffen auf Max. Rang`,
 | 
			
		||||
    inventory_bulkRankUpSpaceSuits: `Alle Archwings auf Max. Rang`,
 | 
			
		||||
 | 
			
		||||
@ -74,6 +74,10 @@ dict = {
 | 
			
		||||
    code_funded: `Funded`,
 | 
			
		||||
    code_replays: `Replays`,
 | 
			
		||||
    code_stalker: `Stalker`,
 | 
			
		||||
    code_cutName: `Cut |INDEX|`,
 | 
			
		||||
    code_drifterBeardName: `Drifter Beard |INDEX|`,
 | 
			
		||||
    code_drifterFaceName: `Drifter Visage |INDEX|`,
 | 
			
		||||
    code_operatorFaceName: `Operator Visage |INDEX|`,
 | 
			
		||||
    code_succChange: `Successfully changed.`,
 | 
			
		||||
    code_requiredInvigorationUpgrade: `You must select both an offensive & utility upgrade.`,
 | 
			
		||||
    login_description: `Login using your OpenWF account credentials (same as in-game when connecting to this server).`,
 | 
			
		||||
@ -111,6 +115,7 @@ dict = {
 | 
			
		||||
    inventory_boosters: `Boosters`,
 | 
			
		||||
    inventory_flavourItems: `<abbr title="Animation Sets, Glyphs, Palettes, etc.">Flavour Items</abbr>`,
 | 
			
		||||
    inventory_shipDecorations: `Ship Decorations`,
 | 
			
		||||
    inventory_weaponSkins: `Skins`,
 | 
			
		||||
    inventory_bulkAddSuits: `Add Missing Warframes`,
 | 
			
		||||
    inventory_bulkAddWeapons: `Add Missing Weapons`,
 | 
			
		||||
    inventory_bulkAddSpaceSuits: `Add Missing Archwings`,
 | 
			
		||||
@ -120,6 +125,7 @@ dict = {
 | 
			
		||||
    inventory_bulkAddFlavourItems: `Add Missing Flavour Items`,
 | 
			
		||||
    inventory_bulkAddShipDecorations: `Add Missing Ship Decorations`,
 | 
			
		||||
    inventory_bulkAddEvolutionProgress: `Add Missing Incarnon Evolution Progress`,
 | 
			
		||||
    inventory_bulkAddWeaponSkins: `Add Missing Skins`,
 | 
			
		||||
    inventory_bulkRankUpSuits: `Max Rank All Warframes`,
 | 
			
		||||
    inventory_bulkRankUpWeapons: `Max Rank All Weapons`,
 | 
			
		||||
    inventory_bulkRankUpSpaceSuits: `Max Rank All Archwings`,
 | 
			
		||||
 | 
			
		||||
@ -75,6 +75,10 @@ dict = {
 | 
			
		||||
    code_funded: `Financiado`,
 | 
			
		||||
    code_replays: `Repeticiones`,
 | 
			
		||||
    code_stalker: `Stalker`,
 | 
			
		||||
    code_cutName: `[UNTRANSLATED] Cut |INDEX|`,
 | 
			
		||||
    code_drifterBeardName: `Barba del Viajero: |INDEX|`,
 | 
			
		||||
    code_drifterFaceName: `Rostro del Viajero |INDEX|`,
 | 
			
		||||
    code_operatorFaceName: `Rostro del operador |INDEX|`,
 | 
			
		||||
    code_succChange: `Cambiado correctamente`,
 | 
			
		||||
    code_requiredInvigorationUpgrade: `Debes seleccionar una mejora ofensiva y una mejora de utilidad.`,
 | 
			
		||||
    login_description: `Inicia sesión con las credenciales de tu cuenta OpenWF (las mismas que usas en el juego al conectarte a este servidor).`,
 | 
			
		||||
@ -112,6 +116,7 @@ dict = {
 | 
			
		||||
    inventory_boosters: `Potenciadores`,
 | 
			
		||||
    inventory_flavourItems: `<abbr title="Conjuntos de animaciones, glifos, paletas, etc.">Ítems estéticos</abbr>`,
 | 
			
		||||
    inventory_shipDecorations: `Decoraciones de nave`,
 | 
			
		||||
    inventory_weaponSkins: `Diseños`,
 | 
			
		||||
    inventory_bulkAddSuits: `Agregar Warframes faltantes`,
 | 
			
		||||
    inventory_bulkAddWeapons: `Agregar armas faltantes`,
 | 
			
		||||
    inventory_bulkAddSpaceSuits: `Agregar Archwings faltantes`,
 | 
			
		||||
@ -121,6 +126,7 @@ dict = {
 | 
			
		||||
    inventory_bulkAddFlavourItems: `Añadir items estéticos faltantes`,
 | 
			
		||||
    inventory_bulkAddShipDecorations: `Añadir decoraciones de Nave Faltantes`,
 | 
			
		||||
    inventory_bulkAddEvolutionProgress: `Completar el progreso de evolución Incarnon faltante`,
 | 
			
		||||
    inventory_bulkAddWeaponSkins: `[UNTRANSLATED] Add Missing Skins`,
 | 
			
		||||
    inventory_bulkRankUpSuits: `Maximizar rango de todos los Warframes`,
 | 
			
		||||
    inventory_bulkRankUpWeapons: `Maximizar rango de todas las armas`,
 | 
			
		||||
    inventory_bulkRankUpSpaceSuits: `Maximizar rango de todos los Archwings`,
 | 
			
		||||
 | 
			
		||||
@ -75,6 +75,10 @@ dict = {
 | 
			
		||||
    code_funded: `Complété`,
 | 
			
		||||
    code_replays: `[UNTRANSLATED] Replays`,
 | 
			
		||||
    code_stalker: `Stalker`,
 | 
			
		||||
    code_cutName: `[UNTRANSLATED] Cut |INDEX|`,
 | 
			
		||||
    code_drifterBeardName: `Barbe du Voyageur |INDEX|`,
 | 
			
		||||
    code_drifterFaceName: `Visage du Voyageur |INDEX|`,
 | 
			
		||||
    code_operatorFaceName: `Visage de l'Opérateur |INDEX|`,
 | 
			
		||||
    code_succChange: `Changement effectué.`,
 | 
			
		||||
    code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`,
 | 
			
		||||
    login_description: `Connexion avec les informations de connexion OpenWF.`,
 | 
			
		||||
@ -112,6 +116,7 @@ dict = {
 | 
			
		||||
    inventory_boosters: `Boosters`,
 | 
			
		||||
    inventory_flavourItems: `[UNTRANSLATED] <abbr title="Animation Sets, Glyphs, Palettes, etc.">Flavour Items</abbr>`,
 | 
			
		||||
    inventory_shipDecorations: `Décorations du vaisseau`,
 | 
			
		||||
    inventory_weaponSkins: `Aspects`,
 | 
			
		||||
    inventory_bulkAddSuits: `Ajouter les Warframes manquantes`,
 | 
			
		||||
    inventory_bulkAddWeapons: `Ajouter les armes manquantes`,
 | 
			
		||||
    inventory_bulkAddSpaceSuits: `Ajouter les Archwings manquants`,
 | 
			
		||||
@ -121,6 +126,7 @@ dict = {
 | 
			
		||||
    inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`,
 | 
			
		||||
    inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`,
 | 
			
		||||
    inventory_bulkAddEvolutionProgress: `Ajouter les évolutions Incarnon manquantes`,
 | 
			
		||||
    inventory_bulkAddWeaponSkins: `[UNTRANSLATED] Add Missing Skins`,
 | 
			
		||||
    inventory_bulkRankUpSuits: `Toutes les Warframes au rang max`,
 | 
			
		||||
    inventory_bulkRankUpWeapons: `Toutes les armes au rang max`,
 | 
			
		||||
    inventory_bulkRankUpSpaceSuits: `Tous les Archwings au rang max`,
 | 
			
		||||
 | 
			
		||||
@ -75,6 +75,10 @@ dict = {
 | 
			
		||||
    code_funded: `Профинансировано`,
 | 
			
		||||
    code_replays: `Повторов`,
 | 
			
		||||
    code_stalker: `Сталкер`,
 | 
			
		||||
    code_cutName: `Причёска: |INDEX|`,
 | 
			
		||||
    code_drifterBeardName: `Борода скитальца: |INDEX|`,
 | 
			
		||||
    code_drifterFaceName: `Внешность скитальца: |INDEX|`,
 | 
			
		||||
    code_operatorFaceName: `Внешность оператора: |INDEX|`,
 | 
			
		||||
    code_succChange: `Успешно изменено.`,
 | 
			
		||||
    code_requiredInvigorationUpgrade: `Вы должны выбрать как атакующее, так и вспомогательное улучшение.`,
 | 
			
		||||
    login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`,
 | 
			
		||||
@ -112,6 +116,7 @@ dict = {
 | 
			
		||||
    inventory_boosters: `Бустеры`,
 | 
			
		||||
    inventory_flavourItems: `<abbr title="Наборы анимаций, глифы, палитры и т. д.">Уникальные предметы</abbr>`,
 | 
			
		||||
    inventory_shipDecorations: `Украшения корабля`,
 | 
			
		||||
    inventory_weaponSkins: `Скины`,
 | 
			
		||||
    inventory_bulkAddSuits: `Добавить отсутствующие Варфреймы`,
 | 
			
		||||
    inventory_bulkAddWeapons: `Добавить отсутствующее оружие`,
 | 
			
		||||
    inventory_bulkAddSpaceSuits: `Добавить отсутствующие Арчвинги`,
 | 
			
		||||
@ -121,6 +126,7 @@ dict = {
 | 
			
		||||
    inventory_bulkAddFlavourItems: `Добавить отсутствующие уникальные предметы`,
 | 
			
		||||
    inventory_bulkAddShipDecorations: `Добавить отсутствующие украшения корабля`,
 | 
			
		||||
    inventory_bulkAddEvolutionProgress: `Добавить отсутствующий прогресс эволюции Инкарнонов`,
 | 
			
		||||
    inventory_bulkAddWeaponSkins: `Добавить отсутствующие скины`,
 | 
			
		||||
    inventory_bulkRankUpSuits: `Макс. ранг всех Варфреймов`,
 | 
			
		||||
    inventory_bulkRankUpWeapons: `Макс. ранг всего оружия`,
 | 
			
		||||
    inventory_bulkRankUpSpaceSuits: `Макс. ранг всех Арчвингов`,
 | 
			
		||||
 | 
			
		||||
@ -75,6 +75,10 @@ dict = {
 | 
			
		||||
    code_funded: `Профінансовано`,
 | 
			
		||||
    code_replays: `Повтори`,
 | 
			
		||||
    code_stalker: `Сталкер`,
 | 
			
		||||
    code_cutName: `Зачіска: |INDEX|`,
 | 
			
		||||
    code_drifterBeardName: `Борода мандрівника: |INDEX|`,
 | 
			
		||||
    code_drifterFaceName: `Зовнішність мандрівника: |INDEX|`,
 | 
			
		||||
    code_operatorFaceName: `Зовнішність оператора: |INDEX|`,
 | 
			
		||||
    code_succChange: `Успішно змінено.`,
 | 
			
		||||
    code_requiredInvigorationUpgrade: `Ви повинні вибрати як атакуюче, так і допоміжне вдосконалення.`,
 | 
			
		||||
    login_description: `Увійдіть, використовуючи облікові дані OpenWF (ті ж, що й у грі при підключенні до цього серверу).`,
 | 
			
		||||
@ -112,6 +116,7 @@ dict = {
 | 
			
		||||
    inventory_boosters: `Посилення`,
 | 
			
		||||
    inventory_flavourItems: `<abbr title="Набори анімацій, гліфи, палітри і т. д.">Унікальні предмети</abbr>`,
 | 
			
		||||
    inventory_shipDecorations: `Прикраси судна`,
 | 
			
		||||
    inventory_weaponSkins: `Вигляди`,
 | 
			
		||||
    inventory_bulkAddSuits: `Додати відсутні Ворфрейми`,
 | 
			
		||||
    inventory_bulkAddWeapons: `Додати відсутню зброю`,
 | 
			
		||||
    inventory_bulkAddSpaceSuits: `Додати відсутні Арквінґи`,
 | 
			
		||||
@ -121,6 +126,7 @@ dict = {
 | 
			
		||||
    inventory_bulkAddFlavourItems: `Додати відсутні унікальні предмети`,
 | 
			
		||||
    inventory_bulkAddShipDecorations: `Додати відсутні оздоби корабля`,
 | 
			
		||||
    inventory_bulkAddEvolutionProgress: `Додати відсутній прогрес еволюції Інкарнонів`,
 | 
			
		||||
    inventory_bulkAddWeaponSkins: `[UNTRANSLATED] Add Missing Skins`,
 | 
			
		||||
    inventory_bulkRankUpSuits: `Макс. рівень всіх Ворфреймів`,
 | 
			
		||||
    inventory_bulkRankUpWeapons: `Макс. рівень всієї зброї`,
 | 
			
		||||
    inventory_bulkRankUpSpaceSuits: `Макс. рівень всіх Арквінґів`,
 | 
			
		||||
 | 
			
		||||
@ -75,6 +75,10 @@ dict = {
 | 
			
		||||
    code_funded: `[UNTRANSLATED] Funded`,
 | 
			
		||||
    code_replays: `[UNTRANSLATED] Replays`,
 | 
			
		||||
    code_stalker: `追猎者`,
 | 
			
		||||
    code_cutName: `[UNTRANSLATED] Cut |INDEX|`,
 | 
			
		||||
    code_drifterBeardName: `漂泊者胡须 |INDEX|`,
 | 
			
		||||
    code_drifterFaceName: `漂泊者面部 |INDEX|`,
 | 
			
		||||
    code_operatorFaceName: `指挥官面部 |INDEX|`,
 | 
			
		||||
    code_succChange: `更改成功`,
 | 
			
		||||
    code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`,
 | 
			
		||||
    login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)`,
 | 
			
		||||
@ -112,6 +116,7 @@ dict = {
 | 
			
		||||
    inventory_boosters: `加成器`,
 | 
			
		||||
    inventory_flavourItems: `<abbr title="动作表情、浮印、调色板等">装饰物品</abbr>`,
 | 
			
		||||
    inventory_shipDecorations: `飞船装饰`,
 | 
			
		||||
    inventory_weaponSkins: `外观`,
 | 
			
		||||
    inventory_bulkAddSuits: `添加缺失战甲`,
 | 
			
		||||
    inventory_bulkAddWeapons: `添加缺失武器`,
 | 
			
		||||
    inventory_bulkAddSpaceSuits: `添加缺失载具`,
 | 
			
		||||
@ -121,6 +126,7 @@ dict = {
 | 
			
		||||
    inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`,
 | 
			
		||||
    inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`,
 | 
			
		||||
    inventory_bulkAddEvolutionProgress: `添加缺失的灵化之源进度`,
 | 
			
		||||
    inventory_bulkAddWeaponSkins: `[UNTRANSLATED] Add Missing Skins`,
 | 
			
		||||
    inventory_bulkRankUpSuits: `所有战甲升满级`,
 | 
			
		||||
    inventory_bulkRankUpWeapons: `所有武器升满级`,
 | 
			
		||||
    inventory_bulkRankUpSpaceSuits: `所有载具升满级`,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user