forked from OpenWF/SpaceNinjaServer
		
	Compare commits
	
		
			1 Commits
		
	
	
		
			main
			...
			quests-not
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 5dc21f35b4 | 
@ -60,7 +60,6 @@
 | 
			
		||||
  "unlockAllSimarisResearchEntries": false,
 | 
			
		||||
  "disableDailyTribute": false,
 | 
			
		||||
  "spoofMasteryRank": -1,
 | 
			
		||||
  "relicRewardItemCountMultiplier": 1,
 | 
			
		||||
  "nightwaveStandingMultiplier": 1,
 | 
			
		||||
  "unfaithfulBugFixes": {
 | 
			
		||||
    "ignore1999LastRegionPlayed": false,
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -23,7 +23,7 @@
 | 
			
		||||
        "ncp": "^2.0.0",
 | 
			
		||||
        "typescript": "^5.5",
 | 
			
		||||
        "undici": "^7.10.0",
 | 
			
		||||
        "warframe-public-export-plus": "^0.5.76",
 | 
			
		||||
        "warframe-public-export-plus": "^0.5.74",
 | 
			
		||||
        "warframe-riven-info": "^0.1.2",
 | 
			
		||||
        "winston": "^3.17.0",
 | 
			
		||||
        "winston-daily-rotate-file": "^5.0.0",
 | 
			
		||||
@ -3386,9 +3386,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/warframe-public-export-plus": {
 | 
			
		||||
      "version": "0.5.76",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.76.tgz",
 | 
			
		||||
      "integrity": "sha512-0gX3NTWaxFyzUmqBSUHhPY8pMRX92iXQFqoBuMQlMG1+6uC6JMKtwP5t8cuXR3pvV2vkaCi/cDWjP1JUChkZ9g=="
 | 
			
		||||
      "version": "0.5.74",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.74.tgz",
 | 
			
		||||
      "integrity": "sha512-pA7dxA0lKn9w/2Sc97oxnn+CEzL1SrT9XriNLTDF4Xp+2SBEpGcfbqbdR9ljPQJopIbrc9Zy02R+uBQVomcwyA=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/warframe-riven-info": {
 | 
			
		||||
      "version": "0.1.2",
 | 
			
		||||
 | 
			
		||||
@ -37,7 +37,7 @@
 | 
			
		||||
    "ncp": "^2.0.0",
 | 
			
		||||
    "typescript": "^5.5",
 | 
			
		||||
    "undici": "^7.10.0",
 | 
			
		||||
    "warframe-public-export-plus": "^0.5.76",
 | 
			
		||||
    "warframe-public-export-plus": "^0.5.74",
 | 
			
		||||
    "warframe-riven-info": "^0.1.2",
 | 
			
		||||
    "winston": "^3.17.0",
 | 
			
		||||
    "winston-daily-rotate-file": "^5.0.0",
 | 
			
		||||
 | 
			
		||||
@ -31,7 +31,7 @@ fs.readdirSync("../static/webui/translations").forEach(file => {
 | 
			
		||||
            const strings = extractStrings(line);
 | 
			
		||||
            if (Object.keys(strings).length > 0) {
 | 
			
		||||
                Object.entries(strings).forEach(([key, value]) => {
 | 
			
		||||
                    if (targetStrings.hasOwnProperty(key) && !targetStrings[key].startsWith("[UNTRANSLATED] ")) {
 | 
			
		||||
                    if (targetStrings.hasOwnProperty(key)) {
 | 
			
		||||
                        fs.writeSync(fileHandle, `    ${key}: \`${targetStrings[key]}\`,\n`);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        fs.writeSync(fileHandle, `    ${key}: \`[UNTRANSLATED] ${value}\`,\n`);
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,6 @@ import {
 | 
			
		||||
    getGuildVault,
 | 
			
		||||
    hasAccessToDojo,
 | 
			
		||||
    hasGuildPermission,
 | 
			
		||||
    processCompletedGuildTechProject,
 | 
			
		||||
    processFundedGuildTechProject,
 | 
			
		||||
    processGuildTechProjectContributionsUpdate,
 | 
			
		||||
    removePigmentsFromGuildMembers,
 | 
			
		||||
@ -52,12 +51,8 @@ export const guildTechController: RequestHandler = async (req, res) => {
 | 
			
		||||
                };
 | 
			
		||||
                if (project.CompletionDate) {
 | 
			
		||||
                    techProject.CompletionDate = toMongoDate(project.CompletionDate);
 | 
			
		||||
                    if (
 | 
			
		||||
                        Date.now() >= project.CompletionDate.getTime() &&
 | 
			
		||||
                        setGuildTechLogState(guild, project.ItemType, 4, project.CompletionDate)
 | 
			
		||||
                    ) {
 | 
			
		||||
                        processCompletedGuildTechProject(guild, project.ItemType);
 | 
			
		||||
                        needSave = true;
 | 
			
		||||
                    if (Date.now() >= project.CompletionDate.getTime()) {
 | 
			
		||||
                        needSave ||= setGuildTechLogState(guild, project.ItemType, 4, project.CompletionDate);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                techProjects.push(techProject);
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,6 @@ import { IPolarity, ArtifactPolarity, EquipmentFeatures } from "@/src/types/inve
 | 
			
		||||
import { ExportCustoms, ExportFlavour, ExportResources, ExportVirtuals } from "warframe-public-export-plus";
 | 
			
		||||
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService";
 | 
			
		||||
import {
 | 
			
		||||
    addEmailItem,
 | 
			
		||||
    addMiscItems,
 | 
			
		||||
    allDailyAffiliationKeys,
 | 
			
		||||
    cleanupInventory,
 | 
			
		||||
@ -111,58 +110,7 @@ export const inventoryController: RequestHandler = async (request, response) =>
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (inventory.CalendarProgress) {
 | 
			
		||||
            const previousYearIteration = inventory.CalendarProgress.Iteration;
 | 
			
		||||
            getCalendarProgress(inventory); // handle year rollover; the client expects to receive an inventory with an up-to-date CalendarProgress
 | 
			
		||||
 | 
			
		||||
            // also handle sending of kiss cinematic at year rollover
 | 
			
		||||
            if (
 | 
			
		||||
                inventory.CalendarProgress.Iteration != previousYearIteration &&
 | 
			
		||||
                inventory.DialogueHistory &&
 | 
			
		||||
                inventory.DialogueHistory.Dialogues
 | 
			
		||||
            ) {
 | 
			
		||||
                let kalymos = false;
 | 
			
		||||
                for (const { dialogueName, kissEmail } of [
 | 
			
		||||
                    {
 | 
			
		||||
                        dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/ArthurDialogue_rom.dialogue",
 | 
			
		||||
                        kissEmail: "/Lotus/Types/Items/EmailItems/ArthurKissEmailItem"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/EleanorDialogue_rom.dialogue",
 | 
			
		||||
                        kissEmail: "/Lotus/Types/Items/EmailItems/EleanorKissEmailItem"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/LettieDialogue_rom.dialogue",
 | 
			
		||||
                        kissEmail: "/Lotus/Types/Items/EmailItems/LettieKissEmailItem"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/JabirDialogue_rom.dialogue",
 | 
			
		||||
                        kissEmail: "/Lotus/Types/Items/EmailItems/AmirKissEmailItem"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/AoiDialogue_rom.dialogue",
 | 
			
		||||
                        kissEmail: "/Lotus/Types/Items/EmailItems/AoiKissEmailItem"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        dialogueName: "/Lotus/Types/Gameplay/1999Wf/Dialogue/QuincyDialogue_rom.dialogue",
 | 
			
		||||
                        kissEmail: "/Lotus/Types/Items/EmailItems/QuincyKissEmailItem"
 | 
			
		||||
                    }
 | 
			
		||||
                ]) {
 | 
			
		||||
                    const dialogue = inventory.DialogueHistory.Dialogues.find(x => x.DialogueName == dialogueName);
 | 
			
		||||
                    if (dialogue) {
 | 
			
		||||
                        if (dialogue.Rank == 7) {
 | 
			
		||||
                            await addEmailItem(inventory, kissEmail);
 | 
			
		||||
                            kalymos = false;
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
                        if (dialogue.Rank == 6) {
 | 
			
		||||
                            kalymos = true;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (kalymos) {
 | 
			
		||||
                    await addEmailItem(inventory, "/Lotus/Types/Items/EmailItems/KalymosKissEmailItem");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        cleanupInventory(inventory);
 | 
			
		||||
 | 
			
		||||
@ -48,8 +48,10 @@ export const loginRewardsController: RequestHandler = async (req, res) => {
 | 
			
		||||
        response.DailyTributeInfo.HasChosenReward = true;
 | 
			
		||||
        response.DailyTributeInfo.ChosenReward = randomRewards[0];
 | 
			
		||||
        response.DailyTributeInfo.NewInventory = await claimLoginReward(inventory, randomRewards[0]);
 | 
			
		||||
        await inventory.save();
 | 
			
		||||
 | 
			
		||||
        setAccountGotLoginRewardToday(account);
 | 
			
		||||
        await Promise.all([inventory.save(), account.save()]);
 | 
			
		||||
        await account.save();
 | 
			
		||||
 | 
			
		||||
        sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -35,8 +35,10 @@ export const loginRewardsSelectionController: RequestHandler = async (req, res)
 | 
			
		||||
        chosenReward = randomRewards.find(x => x.StoreItemType == body.ChosenReward)!;
 | 
			
		||||
        inventoryChanges = await claimLoginReward(inventory, chosenReward);
 | 
			
		||||
    }
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
 | 
			
		||||
    setAccountGotLoginRewardToday(account);
 | 
			
		||||
    await Promise.all([inventory.save(), account.save()]);
 | 
			
		||||
    await account.save();
 | 
			
		||||
 | 
			
		||||
    sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
 | 
			
		||||
    res.json({
 | 
			
		||||
 | 
			
		||||
@ -57,12 +57,8 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
 | 
			
		||||
                component.DecoCapacity -= meta.capacityCost;
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            const entry = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type);
 | 
			
		||||
            if (!entry) {
 | 
			
		||||
                throw new Error(`unknown deco type: ${deco.Type}`);
 | 
			
		||||
            }
 | 
			
		||||
            const [itemType, meta] = entry;
 | 
			
		||||
            if (meta.dojoCapacityCost === undefined) {
 | 
			
		||||
            const [itemType, meta] = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)!;
 | 
			
		||||
            if (!itemType || meta.dojoCapacityCost === undefined) {
 | 
			
		||||
                throw new Error(`unknown deco type: ${deco.Type}`);
 | 
			
		||||
            }
 | 
			
		||||
            component.DecoCapacity -= meta.dojoCapacityCost;
 | 
			
		||||
@ -79,13 +75,7 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
 | 
			
		||||
                if (meta) {
 | 
			
		||||
                    processDojoBuildMaterialsGathered(guild, meta);
 | 
			
		||||
                }
 | 
			
		||||
            } else if (
 | 
			
		||||
                deco.Type.startsWith("/Lotus/Objects/Tenno/Dojo/NpcPlaceables/") ||
 | 
			
		||||
                (guild.AutoContributeFromVault && guild.VaultRegularCredits && guild.VaultMiscItems)
 | 
			
		||||
            ) {
 | 
			
		||||
                if (!guild.VaultRegularCredits || !guild.VaultMiscItems) {
 | 
			
		||||
                    throw new Error(`dojo visitor placed without anything in vault?!`);
 | 
			
		||||
                }
 | 
			
		||||
            } else if (guild.AutoContributeFromVault && guild.VaultRegularCredits && guild.VaultMiscItems) {
 | 
			
		||||
                if (guild.VaultRegularCredits >= scaleRequiredCount(guild.Tier, meta.price)) {
 | 
			
		||||
                    let enoughMiscItems = true;
 | 
			
		||||
                    for (const ingredient of meta.ingredients) {
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,6 @@ import { sendWsBroadcastTo } from "@/src/services/webService";
 | 
			
		||||
 | 
			
		||||
export const sellController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const payload = JSON.parse(String(req.body)) as ISellRequest;
 | 
			
		||||
    //console.log(JSON.stringify(payload, null, 2));
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const requiredFields = new Set<keyof TInventoryDatabaseDocument>();
 | 
			
		||||
    if (payload.SellCurrency == "SC_RegularCredits") {
 | 
			
		||||
@ -185,11 +184,6 @@ export const sellController: RequestHandler = async (req, res) => {
 | 
			
		||||
            inventory.Drones.pull({ _id: sellItem.String });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    if (payload.Items.KubrowPetPrints) {
 | 
			
		||||
        payload.Items.KubrowPetPrints.forEach(sellItem => {
 | 
			
		||||
            inventory.KubrowPetPrints.pull({ _id: sellItem.String });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    if (payload.Items.CrewMembers) {
 | 
			
		||||
        payload.Items.CrewMembers.forEach(sellItem => {
 | 
			
		||||
            inventory.CrewMembers.pull({ _id: sellItem.String });
 | 
			
		||||
@ -318,7 +312,6 @@ interface ISellRequest {
 | 
			
		||||
        OperatorAmps?: ISellItem[];
 | 
			
		||||
        Hoverboards?: ISellItem[];
 | 
			
		||||
        Drones?: ISellItem[];
 | 
			
		||||
        KubrowPetPrints?: ISellItem[];
 | 
			
		||||
        CrewMembers?: ISellItem[];
 | 
			
		||||
        CrewShipWeapons?: ISellItem[];
 | 
			
		||||
        CrewShipWeaponSkins?: ISellItem[];
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,6 @@ import { getRecipeByResult } from "@/src/services/itemDataService";
 | 
			
		||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
			
		||||
import { addInfestedFoundryXP, applyCheatsToInfestedFoundry } from "@/src/services/infestedFoundryService";
 | 
			
		||||
import { config } from "@/src/services/configService";
 | 
			
		||||
import { sendWsBroadcastTo } from "@/src/services/webService";
 | 
			
		||||
 | 
			
		||||
export const upgradesController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
@ -121,7 +120,6 @@ export const upgradesController: RequestHandler = async (req, res) => {
 | 
			
		||||
                    setSlotPolarity(item, operation.PolarizeSlot, operation.PolarizeValue);
 | 
			
		||||
                    item.Polarized ??= 0;
 | 
			
		||||
                    item.Polarized += 1;
 | 
			
		||||
                    sendWsBroadcastTo(accountId, { update_inventory: true }); // webui may need to to re-add "max rank" button
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                case "/Lotus/Types/Items/MiscItems/ModSlotUnlocker": {
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,7 @@ export const addItemsController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const requests = req.body as IAddItemRequest[];
 | 
			
		||||
    const inventory = await getInventory(accountId);
 | 
			
		||||
    for (const request of requests) {
 | 
			
		||||
        await addItem(inventory, request.ItemType, request.ItemCount, true, undefined, request.Fingerprint, true);
 | 
			
		||||
        await addItem(inventory, request.ItemType, request.ItemCount, true, undefined, undefined, true);
 | 
			
		||||
    }
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
@ -16,5 +16,4 @@ export const addItemsController: RequestHandler = async (req, res) => {
 | 
			
		||||
interface IAddItemRequest {
 | 
			
		||||
    ItemType: string;
 | 
			
		||||
    ItemCount: number;
 | 
			
		||||
    Fingerprint?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,6 @@ import { logger } from "@/src/utils/logger";
 | 
			
		||||
import { addMiscItems, combineInventoryChanges } from "@/src/services/inventoryService";
 | 
			
		||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
 | 
			
		||||
import { IInventoryChanges } from "../types/purchaseTypes";
 | 
			
		||||
import { config } from "../services/configService";
 | 
			
		||||
 | 
			
		||||
export const crackRelic = async (
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
@ -14,14 +13,7 @@ export const crackRelic = async (
 | 
			
		||||
    inventoryChanges: IInventoryChanges = {}
 | 
			
		||||
): Promise<IRngResult> => {
 | 
			
		||||
    const relic = ExportRelics[participant.VoidProjection];
 | 
			
		||||
    let weights = refinementToWeights[relic.quality];
 | 
			
		||||
    if (relic.quality == "VPQ_SILVER" && config.exceptionalRelicsAlwaysGiveBronzeReward) {
 | 
			
		||||
        weights = { COMMON: 1, UNCOMMON: 0, RARE: 0, LEGENDARY: 0 };
 | 
			
		||||
    } else if (relic.quality == "VPQ_GOLD" && config.flawlessRelicsAlwaysGiveSilverReward) {
 | 
			
		||||
        weights = { COMMON: 0, UNCOMMON: 1, RARE: 0, LEGENDARY: 0 };
 | 
			
		||||
    } else if (relic.quality == "VPQ_PLATINUM" && config.radiantRelicsAlwaysGiveGoldReward) {
 | 
			
		||||
        weights = { COMMON: 0, UNCOMMON: 0, RARE: 1, LEGENDARY: 0 };
 | 
			
		||||
    }
 | 
			
		||||
    const weights = refinementToWeights[relic.quality];
 | 
			
		||||
    logger.debug(`opening a relic of quality ${relic.quality}; rarity weights are`, weights);
 | 
			
		||||
    const reward = getRandomWeightedReward(
 | 
			
		||||
        ExportRewards[relic.rewardManifest][0] as { type: string; itemCount: number; rarity: TRarity }[], // rarity is nullable in PE+ typings, but always present for relics
 | 
			
		||||
@ -43,13 +35,7 @@ export const crackRelic = async (
 | 
			
		||||
    // Give reward
 | 
			
		||||
    combineInventoryChanges(
 | 
			
		||||
        inventoryChanges,
 | 
			
		||||
        (
 | 
			
		||||
            await handleStoreItemAcquisition(
 | 
			
		||||
                reward.type,
 | 
			
		||||
                inventory,
 | 
			
		||||
                reward.itemCount * (config.relicRewardItemCountMultiplier ?? 1)
 | 
			
		||||
            )
 | 
			
		||||
        ).InventoryChanges
 | 
			
		||||
        (await handleStoreItemAcquisition(reward.type, inventory, reward.itemCount)).InventoryChanges
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return reward;
 | 
			
		||||
 | 
			
		||||
@ -23,9 +23,7 @@ export interface IMessageDatabase extends IMessage {
 | 
			
		||||
export interface IMessage {
 | 
			
		||||
    sndr: string;
 | 
			
		||||
    msg: string;
 | 
			
		||||
    cinematic?: string;
 | 
			
		||||
    sub: string;
 | 
			
		||||
    customData?: string;
 | 
			
		||||
    icon?: string;
 | 
			
		||||
    highPriority?: boolean;
 | 
			
		||||
    lowPrioNewPlayers?: boolean;
 | 
			
		||||
@ -104,9 +102,7 @@ const messageSchema = new Schema<IMessageDatabase>(
 | 
			
		||||
        ownerId: Schema.Types.ObjectId,
 | 
			
		||||
        sndr: String,
 | 
			
		||||
        msg: String,
 | 
			
		||||
        cinematic: String,
 | 
			
		||||
        sub: String,
 | 
			
		||||
        customData: String,
 | 
			
		||||
        icon: String,
 | 
			
		||||
        highPriority: Boolean,
 | 
			
		||||
        lowPrioNewPlayers: Boolean,
 | 
			
		||||
 | 
			
		||||
@ -64,13 +64,9 @@ export interface IConfig {
 | 
			
		||||
    noDojoResearchTime?: boolean;
 | 
			
		||||
    fastClanAscension?: boolean;
 | 
			
		||||
    missionsCanGiveAllRelics?: boolean;
 | 
			
		||||
    exceptionalRelicsAlwaysGiveBronzeReward?: boolean;
 | 
			
		||||
    flawlessRelicsAlwaysGiveSilverReward?: boolean;
 | 
			
		||||
    radiantRelicsAlwaysGiveGoldReward?: boolean;
 | 
			
		||||
    unlockAllSimarisResearchEntries?: boolean;
 | 
			
		||||
    disableDailyTribute?: boolean;
 | 
			
		||||
    spoofMasteryRank?: number;
 | 
			
		||||
    relicRewardItemCountMultiplier?: number;
 | 
			
		||||
    nightwaveStandingMultiplier?: number;
 | 
			
		||||
    unfaithfulBugFixes?: {
 | 
			
		||||
        ignore1999LastRegionPlayed?: boolean;
 | 
			
		||||
 | 
			
		||||
@ -550,19 +550,6 @@ export const processFundedGuildTechProject = (
 | 
			
		||||
        guild.XP += recipe.guildXpValue;
 | 
			
		||||
    }
 | 
			
		||||
    setGuildTechLogState(guild, techProject.ItemType, config.noDojoResearchTime ? 4 : 3, techProject.CompletionDate);
 | 
			
		||||
    if (config.noDojoResearchTime) {
 | 
			
		||||
        processCompletedGuildTechProject(guild, techProject.ItemType);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const processCompletedGuildTechProject = (guild: TGuildDatabaseDocument, type: string): void => {
 | 
			
		||||
    if (type.startsWith("/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/")) {
 | 
			
		||||
        guild.VaultDecoRecipes ??= [];
 | 
			
		||||
        guild.VaultDecoRecipes.push({
 | 
			
		||||
            ItemType: type,
 | 
			
		||||
            ItemCount: 1
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const setGuildTechLogState = (
 | 
			
		||||
 | 
			
		||||
@ -83,7 +83,7 @@ import { addQuestKey, completeQuest } from "@/src/services/questService";
 | 
			
		||||
import { handleBundleAcqusition } from "./purchaseService";
 | 
			
		||||
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
 | 
			
		||||
import { getRandomElement, getRandomInt, getRandomWeightedReward, SRng } from "./rngService";
 | 
			
		||||
import { createMessage, IMessageCreationTemplate } from "./inboxService";
 | 
			
		||||
import { createMessage } from "./inboxService";
 | 
			
		||||
import { getMaxStanding, getMinStanding } from "@/src/helpers/syndicateStandingHelper";
 | 
			
		||||
import { getNightwaveSyndicateTag, getWorldState } from "./worldStateService";
 | 
			
		||||
import { ICalendarSeason } from "@/src/types/worldStateTypes";
 | 
			
		||||
@ -483,16 +483,6 @@ export const addItem = async (
 | 
			
		||||
        return addCustomization(inventory, typeName);
 | 
			
		||||
    }
 | 
			
		||||
    if (typeName in ExportUpgrades || typeName in ExportArcanes) {
 | 
			
		||||
        if (targetFingerprint) {
 | 
			
		||||
            if (quantity != 1) {
 | 
			
		||||
                logger.warn(`adding 1 of ${typeName} ${targetFingerprint} even tho quantity ${quantity} was requested`);
 | 
			
		||||
            }
 | 
			
		||||
            inventory.Upgrades.push({
 | 
			
		||||
                ItemType: typeName,
 | 
			
		||||
                UpgradeFingerprint: targetFingerprint
 | 
			
		||||
            });
 | 
			
		||||
            return {}; // there's not exactly a common "InventoryChanges" format for these
 | 
			
		||||
        }
 | 
			
		||||
        const changes = [
 | 
			
		||||
            {
 | 
			
		||||
                ItemType: typeName,
 | 
			
		||||
@ -1573,22 +1563,7 @@ export const addEmailItem = async (
 | 
			
		||||
    const meta = ExportEmailItems[typeName];
 | 
			
		||||
    const emailItem = inventory.EmailItems.find(x => x.ItemType == typeName);
 | 
			
		||||
    if (!emailItem || !meta.sendOnlyOnce) {
 | 
			
		||||
        const msg: IMessageCreationTemplate = convertInboxMessage(meta.message);
 | 
			
		||||
        if (msg.cinematic == "/Lotus/Levels/1999/PlayerHomeBalconyCinematics.level") {
 | 
			
		||||
            msg.customData = JSON.stringify({
 | 
			
		||||
                Tag: msg.customData + "KissCin",
 | 
			
		||||
                CinLoadout: {
 | 
			
		||||
                    Skins: inventory.AdultOperatorLoadOuts[0].Skins,
 | 
			
		||||
                    Upgrades: inventory.AdultOperatorLoadOuts[0].Upgrades,
 | 
			
		||||
                    attcol: inventory.AdultOperatorLoadOuts[0].attcol,
 | 
			
		||||
                    cloth: inventory.AdultOperatorLoadOuts[0].cloth,
 | 
			
		||||
                    eyecol: inventory.AdultOperatorLoadOuts[0].eyecol,
 | 
			
		||||
                    pricol: inventory.AdultOperatorLoadOuts[0].pricol,
 | 
			
		||||
                    syancol: inventory.AdultOperatorLoadOuts[0].syancol
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        await createMessage(inventory.accountOwnerId, [msg]);
 | 
			
		||||
        await createMessage(inventory.accountOwnerId, [convertInboxMessage(meta.message)]);
 | 
			
		||||
 | 
			
		||||
        if (emailItem) {
 | 
			
		||||
            emailItem.ItemCount += 1;
 | 
			
		||||
 | 
			
		||||
@ -251,9 +251,7 @@ export const convertInboxMessage = (message: IInboxMessage): IMessage => {
 | 
			
		||||
    return {
 | 
			
		||||
        sndr: message.sender,
 | 
			
		||||
        msg: message.body,
 | 
			
		||||
        cinematic: message.cinematic,
 | 
			
		||||
        sub: message.title,
 | 
			
		||||
        customData: message.customData,
 | 
			
		||||
        att: message.attachments.length > 0 ? message.attachments : undefined,
 | 
			
		||||
        countedAtt: message.countedAttachments.length > 0 ? message.countedAttachments : undefined,
 | 
			
		||||
        icon: message.icon ?? "",
 | 
			
		||||
 | 
			
		||||
@ -167,13 +167,8 @@ export const handleInventoryItemConfigChange = async (
 | 
			
		||||
                inventory.LotusCustomization = equipmentChanges.LotusCustomization;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            case "ValidNewLoadoutId": {
 | 
			
		||||
                logger.debug(`ignoring ValidNewLoadoutId (${equipmentChanges.ValidNewLoadoutId})`);
 | 
			
		||||
                // seems always equal to the id of loadout config NORMAL[0], likely has no purpose and we're free to ignore it
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            default: {
 | 
			
		||||
                if (equipmentKeys.includes(equipmentName as TEquipmentKey)) {
 | 
			
		||||
                if (equipmentKeys.includes(equipmentName as TEquipmentKey) && equipmentName != "ValidNewLoadoutId") {
 | 
			
		||||
                    logger.debug(`general Item config saved of type ${equipmentName}`, {
 | 
			
		||||
                        config: equipment
 | 
			
		||||
                    });
 | 
			
		||||
@ -221,7 +216,7 @@ export const handleInventoryItemConfigChange = async (
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
                } else {
 | 
			
		||||
                    logger.error(`loadout category not implemented, changes will be lost: ${equipmentName}`, {
 | 
			
		||||
                    logger.warn(`loadout category not implemented, changes may be lost: ${equipmentName}`, {
 | 
			
		||||
                        config: equipment
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -64,12 +64,8 @@ export const handleSetShipDecorations = async (
 | 
			
		||||
        throw new Error(`unknown room: ${placedDecoration.Room}`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const entry = Object.entries(ExportResources).find(arr => arr[1].deco == placedDecoration.Type);
 | 
			
		||||
    if (!entry) {
 | 
			
		||||
        throw new Error(`unknown deco type: ${placedDecoration.Type}`);
 | 
			
		||||
    }
 | 
			
		||||
    const [itemType, meta] = entry;
 | 
			
		||||
    if (meta.capacityCost === undefined) {
 | 
			
		||||
    const [itemType, meta] = Object.entries(ExportResources).find(arr => arr[1].deco == placedDecoration.Type)!;
 | 
			
		||||
    if (!itemType || meta.capacityCost === undefined) {
 | 
			
		||||
        throw new Error(`unknown deco type: ${placedDecoration.Type}`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -92,13 +92,5 @@
 | 
			
		||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophySilverRecipe",
 | 
			
		||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/CorpusPlaceables/GasTurbineConeRecipe",
 | 
			
		||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NaturalPlaceables/CoralChunkARecipe",
 | 
			
		||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/TennoPlaceables/TnoBeaconEmitterRecipe",
 | 
			
		||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/OstronFemaleSitting",
 | 
			
		||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/OstronFemaleStanding",
 | 
			
		||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/OstronMaleStanding",
 | 
			
		||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/OstronMaleStandingTwo",
 | 
			
		||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/SolarisForeman",
 | 
			
		||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/SolarisHazard",
 | 
			
		||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/SolarisStrikerOne",
 | 
			
		||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/SolarisStrikerThree"
 | 
			
		||||
  "/Lotus/Levels/ClanDojo/ComponentPropRecipes/TennoPlaceables/TnoBeaconEmitterRecipe"
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@ -527,7 +527,6 @@
 | 
			
		||||
                                <form class="input-group mb-3" onsubmit="doAcquireMod();return false;">
 | 
			
		||||
                                    <input class="form-control" id="mod-count" type="number" value="1"/>
 | 
			
		||||
                                    <input class="form-control w-50" id="mod-to-acquire" list="datalist-mods" />
 | 
			
		||||
                                    <button class="btn btn-success" onclick="window.maxed=true" type="submit" data-loc="mods_addMax"></button>
 | 
			
		||||
                                    <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
 | 
			
		||||
                                </form>
 | 
			
		||||
                                <table class="table table-hover w-100">
 | 
			
		||||
@ -779,18 +778,6 @@
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="missionsCanGiveAllRelics" />
 | 
			
		||||
                                        <label class="form-check-label" for="missionsCanGiveAllRelics" data-loc="cheats_missionsCanGiveAllRelics"></label>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div class="form-check">
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="exceptionalRelicsAlwaysGiveBronzeReward" />
 | 
			
		||||
                                        <label class="form-check-label" for="exceptionalRelicsAlwaysGiveBronzeReward" data-loc="cheats_exceptionalRelicsAlwaysGiveBronzeReward"></label>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div class="form-check">
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="flawlessRelicsAlwaysGiveSilverReward" />
 | 
			
		||||
                                        <label class="form-check-label" for="flawlessRelicsAlwaysGiveSilverReward" data-loc="cheats_flawlessRelicsAlwaysGiveSilverReward"></label>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div class="form-check">
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="radiantRelicsAlwaysGiveGoldReward" />
 | 
			
		||||
                                        <label class="form-check-label" for="radiantRelicsAlwaysGiveGoldReward" data-loc="cheats_radiantRelicsAlwaysGiveGoldReward"></label>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div class="form-check">
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="unlockAllSimarisResearchEntries" />
 | 
			
		||||
                                        <label class="form-check-label" for="unlockAllSimarisResearchEntries" data-loc="cheats_unlockAllSimarisResearchEntries"></label>
 | 
			
		||||
@ -802,21 +789,14 @@
 | 
			
		||||
                                    <form class="form-group mt-2" onsubmit="doSaveConfigInt('spoofMasteryRank'); return false;">
 | 
			
		||||
                                        <label class="form-label" for="spoofMasteryRank" data-loc="cheats_spoofMasteryRank"></label>
 | 
			
		||||
                                        <div class="input-group">
 | 
			
		||||
                                            <input class="form-control" id="spoofMasteryRank" type="number" min="-1" max="65535" data-default="-1" />
 | 
			
		||||
                                            <button class="btn btn-primary" type="submit" data-loc="cheats_save"></button>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </form>
 | 
			
		||||
                                    <form class="form-group mt-2" onsubmit="doSaveConfigInt('relicRewardItemCountMultiplier'); return false;">
 | 
			
		||||
                                        <label class="form-label" for="relicRewardItemCountMultiplier" data-loc="cheats_relicRewardItemCountMultiplier"></label>
 | 
			
		||||
                                        <div class="input-group">
 | 
			
		||||
                                            <input class="form-control" id="relicRewardItemCountMultiplier" type="number" min="1" max="1000000" data-default="1" />
 | 
			
		||||
                                            <input class="form-control" id="spoofMasteryRank" type="number" min="-1" max="65535" />
 | 
			
		||||
                                            <button class="btn btn-primary" type="submit" data-loc="cheats_save"></button>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </form>
 | 
			
		||||
                                    <form class="form-group mt-2" onsubmit="doSaveConfigInt('nightwaveStandingMultiplier'); return false;">
 | 
			
		||||
                                        <label class="form-label" for="nightwaveStandingMultiplier" data-loc="cheats_nightwaveStandingMultiplier"></label>
 | 
			
		||||
                                        <div class="input-group">
 | 
			
		||||
                                            <input class="form-control" id="nightwaveStandingMultiplier" type="number" min="1" max="1000000" data-default="1" />
 | 
			
		||||
                                            <input class="form-control" id="nightwaveStandingMultiplier" type="number" min="1" max="1000000" value="1" />
 | 
			
		||||
                                            <button class="btn btn-primary" type="submit" data-loc="cheats_save"></button>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </form>
 | 
			
		||||
@ -865,7 +845,7 @@
 | 
			
		||||
                                    <label class="form-check-label" for="worldState.starDays" data-loc="worldState_starDays"></label>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="form-group mt-2">
 | 
			
		||||
                                    <label class="form-label" for="worldState.galleonOfGhouls" data-loc="worldState_galleonOfGhouls"></label>
 | 
			
		||||
                                    <label class="form-label" for="changeSyndicate" data-loc="worldState_galleonOfGhouls"></label>
 | 
			
		||||
                                    <select class="form-control" id="worldState.galleonOfGhouls">
 | 
			
		||||
                                        <option value="0" data-loc="disabled"></option>
 | 
			
		||||
                                        <option value="1" data-loc="worldState_we1"></option>
 | 
			
		||||
@ -874,7 +854,7 @@
 | 
			
		||||
                                    </select>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="form-group mt-2">
 | 
			
		||||
                                    <label class="form-label" for="worldState.eidolonOverride" data-loc="worldState_eidolonOverride"></label>
 | 
			
		||||
                                    <label class="form-label" for="changeSyndicate" data-loc="worldState_eidolonOverride"></label>
 | 
			
		||||
                                    <select class="form-control" id="worldState.eidolonOverride">
 | 
			
		||||
                                        <option value="" data-loc="disabled"></option>
 | 
			
		||||
                                        <option value="day" data-loc="worldState_day"></option>
 | 
			
		||||
@ -882,7 +862,7 @@
 | 
			
		||||
                                    </select>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="form-group mt-2">
 | 
			
		||||
                                    <label class="form-label" for="worldState.vallisOverride" data-loc="worldState_vallisOverride"></label>
 | 
			
		||||
                                    <label class="form-label" for="changeSyndicate" data-loc="worldState_vallisOverride"></label>
 | 
			
		||||
                                    <select class="form-control" id="worldState.vallisOverride">
 | 
			
		||||
                                        <option value="" data-loc="disabled"></option>
 | 
			
		||||
                                        <option value="warm" data-loc="worldState_warm"></option>
 | 
			
		||||
@ -890,7 +870,7 @@
 | 
			
		||||
                                    </select>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="form-group mt-2">
 | 
			
		||||
                                    <label class="form-label" for="worldState.duviriOverride" data-loc="worldState_duviriOverride"></label>
 | 
			
		||||
                                    <label class="form-label" for="changeSyndicate" data-loc="worldState_duviriOverride"></label>
 | 
			
		||||
                                    <select class="form-control" id="worldState.duviriOverride">
 | 
			
		||||
                                        <option value="" data-loc="disabled"></option>
 | 
			
		||||
                                        <option value="joy" data-loc="worldState_joy"></option>
 | 
			
		||||
@ -901,7 +881,7 @@
 | 
			
		||||
                                    </select>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="form-group mt-2">
 | 
			
		||||
                                    <label class="form-label" for="worldState.nightwaveOverride" data-loc="worldState_nightwaveOverride"></label>
 | 
			
		||||
                                    <label class="form-label" for="changeSyndicate" data-loc="worldState_nightwaveOverride"></label>
 | 
			
		||||
                                    <select class="form-control" id="worldState.nightwaveOverride">
 | 
			
		||||
                                        <option value="" data-loc="disabled"></option>
 | 
			
		||||
                                        <option value="RadioLegionIntermission13Syndicate" data-loc="worldState_RadioLegionIntermission13Syndicate"></option>
 | 
			
		||||
@ -923,7 +903,7 @@
 | 
			
		||||
                                    </select>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="form-group mt-2">
 | 
			
		||||
                                    <label class="form-label" for="worldState.allTheFissures" data-loc="worldState_fissures"></label>
 | 
			
		||||
                                    <label class="form-label" for="changeSyndicate" data-loc="worldState_fissures"></label>
 | 
			
		||||
                                    <select class="form-control" id="worldState.allTheFissures">
 | 
			
		||||
                                        <option value="" data-loc="normal"></option>
 | 
			
		||||
                                        <option value="normal" data-loc="worldState_allAtOnceNormal"></option>
 | 
			
		||||
@ -938,9 +918,9 @@
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </form>
 | 
			
		||||
                                <form class="form-group mt-2" onsubmit="doSaveConfigFloat('worldState.darvoStockMultiplier'); return false;">
 | 
			
		||||
                                    <label class="form-label" for="worldState.darvoStockMultiplier" data-loc="worldState_darvoStockMultiplier"></label>
 | 
			
		||||
                                    <label class="form-label" for="worldState.circuitGameModes" data-loc="worldState_darvoStockMultiplier"></label>
 | 
			
		||||
                                    <div class="input-group">
 | 
			
		||||
                                        <input id="worldState.darvoStockMultiplier" class="form-control" type="number" step="0.01" data-default="1" />
 | 
			
		||||
                                        <input id="worldState.darvoStockMultiplier" class="form-control" type="number" step="0.01" placeholder="1" />
 | 
			
		||||
                                        <button class="btn btn-primary" type="submit" data-loc="cheats_save"></button>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </form>
 | 
			
		||||
 | 
			
		||||
@ -651,7 +651,6 @@ function updateInventory() {
 | 
			
		||||
                    {
 | 
			
		||||
                        const td = document.createElement("td");
 | 
			
		||||
                        td.classList = "text-end text-nowrap";
 | 
			
		||||
 | 
			
		||||
                        let maxXP = Math.pow(uniqueLevelCaps[item.ItemType] ?? 30, 2) * 1000;
 | 
			
		||||
                        if (
 | 
			
		||||
                            category != "Suits" &&
 | 
			
		||||
@ -664,6 +663,7 @@ function updateInventory() {
 | 
			
		||||
                        ) {
 | 
			
		||||
                            maxXP /= 2;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        let anyExaltedMissingXP = false;
 | 
			
		||||
                        if (item.XP >= maxXP && item.ItemType in itemMap && "exalted" in itemMap[item.ItemType]) {
 | 
			
		||||
                            for (const exaltedType of itemMap[item.ItemType].exalted) {
 | 
			
		||||
@ -677,6 +677,13 @@ function updateInventory() {
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (["Suits", "LongGuns", "Pistols", "Melee", "SpaceGuns", "SpaceMelee"].includes(category)) {
 | 
			
		||||
                            const a = document.createElement("a");
 | 
			
		||||
                            a.href = "/webui/detailedView?productCategory=" + category + "&itemId=" + item.ItemId.$oid;
 | 
			
		||||
                            a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M278.5 215.6L23 471c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l57-57h68c49.7 0 97.9-14.4 139-41c11.1-7.2 5.5-23-7.8-23c-5.1 0-9.2-4.1-9.2-9.2c0-4.1 2.7-7.6 6.5-8.8l81-24.3c2.5-.8 4.8-2.1 6.7-4l22.4-22.4c10.1-10.1 2.9-27.3-11.3-27.3l-32.2 0c-5.1 0-9.2-4.1-9.2-9.2c0-4.1 2.7-7.6 6.5-8.8l112-33.6c4-1.2 7.4-3.9 9.3-7.7C506.4 207.6 512 184.1 512 160c0-41-16.3-80.3-45.3-109.3l-5.5-5.5C432.3 16.3 393 0 352 0s-80.3 16.3-109.3 45.3L139 149C91 197 64 262.1 64 330v55.3L253.6 195.8c6.2-6.2 16.4-6.2 22.6 0c5.4 5.4 6.1 13.6 2.2 19.8z"/></svg>`;
 | 
			
		||||
                            td.appendChild(a);
 | 
			
		||||
                        }
 | 
			
		||||
                        if (item.XP < maxXP || anyExaltedMissingXP) {
 | 
			
		||||
                            const a = document.createElement("a");
 | 
			
		||||
                            a.href = "#";
 | 
			
		||||
@ -714,14 +721,6 @@ function updateInventory() {
 | 
			
		||||
                            a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.2L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"/></svg>`;
 | 
			
		||||
                            td.appendChild(a);
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (["Suits", "LongGuns", "Pistols", "Melee", "SpaceGuns", "SpaceMelee"].includes(category)) {
 | 
			
		||||
                            const a = document.createElement("a");
 | 
			
		||||
                            a.href = "/webui/detailedView?productCategory=" + category + "&itemId=" + item.ItemId.$oid;
 | 
			
		||||
                            a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M278.5 215.6L23 471c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l57-57h68c49.7 0 97.9-14.4 139-41c11.1-7.2 5.5-23-7.8-23c-5.1 0-9.2-4.1-9.2-9.2c0-4.1 2.7-7.6 6.5-8.8l81-24.3c2.5-.8 4.8-2.1 6.7-4l22.4-22.4c10.1-10.1 2.9-27.3-11.3-27.3l-32.2 0c-5.1 0-9.2-4.1-9.2-9.2c0-4.1 2.7-7.6 6.5-8.8l112-33.6c4-1.2 7.4-3.9 9.3-7.7C506.4 207.6 512 184.1 512 160c0-41-16.3-80.3-45.3-109.3l-5.5-5.5C432.3 16.3 393 0 352 0s-80.3 16.3-109.3 45.3L139 149C91 197 64 262.1 64 330v55.3L253.6 195.8c6.2-6.2 16.4-6.2 22.6 0c5.4 5.4 6.1 13.6 2.2 19.8z"/></svg>`;
 | 
			
		||||
                            td.appendChild(a);
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (!(item.Features & 8) && modularWeapons.includes(item.ItemType)) {
 | 
			
		||||
                            const a = document.createElement("a");
 | 
			
		||||
                            a.href = "#";
 | 
			
		||||
@ -1873,8 +1872,6 @@ function setFingerprint(ItemType, ItemId, fingerprint) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function doAcquireMod() {
 | 
			
		||||
    const maxed = !!window.maxed;
 | 
			
		||||
    window.maxed = false;
 | 
			
		||||
    const uniqueName = getKey(document.getElementById("mod-to-acquire"));
 | 
			
		||||
    if (!uniqueName) {
 | 
			
		||||
        $("#mod-to-acquire").addClass("is-invalid").focus();
 | 
			
		||||
@ -1882,15 +1879,14 @@ function doAcquireMod() {
 | 
			
		||||
    }
 | 
			
		||||
    const count = parseInt($("#mod-count").val());
 | 
			
		||||
    if (count != 0) {
 | 
			
		||||
        Promise.all([window.itemListPromise, revalidateAuthz()]).then(([itemList]) => {
 | 
			
		||||
        revalidateAuthz().then(() => {
 | 
			
		||||
            $.post({
 | 
			
		||||
                url: "/custom/addItems?" + window.authz,
 | 
			
		||||
                contentType: "application/json",
 | 
			
		||||
                data: JSON.stringify([
 | 
			
		||||
                    {
 | 
			
		||||
                        ItemType: uniqueName,
 | 
			
		||||
                        ItemCount: count,
 | 
			
		||||
                        Fingerprint: maxed ? JSON.stringify({ lvl: itemList[uniqueName].fusionLimit ?? 5 }) : undefined
 | 
			
		||||
                        ItemCount: count
 | 
			
		||||
                    }
 | 
			
		||||
                ])
 | 
			
		||||
            }).done(function () {
 | 
			
		||||
@ -1905,11 +1901,6 @@ function doAcquireMod() {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function doAcquireModMax() {
 | 
			
		||||
    const uniqueName = getKey(document.getElementById("mod-to-acquire"));
 | 
			
		||||
    alert("doAcquireModMax: " + uniqueName);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const uiConfigs = [...$(".config-form input[id], .config-form select[id]")].map(x => x.id);
 | 
			
		||||
 | 
			
		||||
for (const id of uiConfigs) {
 | 
			
		||||
@ -1993,14 +1984,16 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
 | 
			
		||||
                    $(".config-admin-show").removeClass("d-none");
 | 
			
		||||
                    Object.entries(json).forEach(entry => {
 | 
			
		||||
                        const [key, value] = entry;
 | 
			
		||||
                        const elm = document.getElementById(key);
 | 
			
		||||
                        if (elm.type == "checkbox") {
 | 
			
		||||
                            elm.checked = value;
 | 
			
		||||
                        } else if (elm.classList.contains("tags-input")) {
 | 
			
		||||
                            elm.value = value.join(", ");
 | 
			
		||||
                            elm.oninput();
 | 
			
		||||
                        } else {
 | 
			
		||||
                            elm.value = value ?? elm.getAttribute("data-default");
 | 
			
		||||
                        var x = document.getElementById(`${key}`);
 | 
			
		||||
                        if (x != null) {
 | 
			
		||||
                            if (x.type == "checkbox") {
 | 
			
		||||
                                x.checked = value;
 | 
			
		||||
                            } else if (x.classList.contains("tags-input")) {
 | 
			
		||||
                                x.value = value.join(", ");
 | 
			
		||||
                                x.oninput();
 | 
			
		||||
                            } else {
 | 
			
		||||
                                x.value = value;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                })
 | 
			
		||||
 | 
			
		||||
@ -127,7 +127,6 @@ dict = {
 | 
			
		||||
    mods_fingerprintHelp: `Benötigst du Hilfe mit dem Fingerabdruck?`,
 | 
			
		||||
    mods_rivens: `Rivens`,
 | 
			
		||||
    mods_mods: `Mods`,
 | 
			
		||||
    mods_addMax: `[UNTRANSLATED] Add Maxed`,
 | 
			
		||||
    mods_addMissingUnrankedMods: `Fehlende Mods ohne Rang hinzufügen`,
 | 
			
		||||
    mods_removeUnranked: `Mods ohne Rang entfernen`,
 | 
			
		||||
    mods_addMissingMaxRankMods: `Fehlende Mods mit Max. Rang hinzufügen`,
 | 
			
		||||
@ -183,13 +182,9 @@ dict = {
 | 
			
		||||
    cheats_noDojoResearchTime: `Keine Dojo-Forschungszeit`,
 | 
			
		||||
    cheats_fastClanAscension: `Schneller Clan-Aufstieg`,
 | 
			
		||||
    cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`,
 | 
			
		||||
    cheats_exceptionalRelicsAlwaysGiveBronzeReward: `[UNTRANSLATED] Exceptional Relics Always Give Bronze Reward`,
 | 
			
		||||
    cheats_flawlessRelicsAlwaysGiveSilverReward: `[UNTRANSLATED] Flawless Relics Always Give Silver Reward`,
 | 
			
		||||
    cheats_radiantRelicsAlwaysGiveGoldReward: `[UNTRANSLATED] Radiant Relics Always Give Gold Reward`,
 | 
			
		||||
    cheats_unlockAllSimarisResearchEntries: `[UNTRANSLATED] Unlock All Simaris Research Entries`,
 | 
			
		||||
    cheats_disableDailyTribute: `[UNTRANSLATED] Disable Daily Tribute`,
 | 
			
		||||
    cheats_spoofMasteryRank: `Gefälschter Meisterschaftsrang (-1 zum deaktivieren)`,
 | 
			
		||||
    cheats_relicRewardItemCountMultiplier: `[UNTRANSLATED] Relic Reward Item Count Multiplier`,
 | 
			
		||||
    cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`,
 | 
			
		||||
    cheats_save: `[UNTRANSLATED] Save`,
 | 
			
		||||
    cheats_account: `Account`,
 | 
			
		||||
@ -286,19 +281,19 @@ dict = {
 | 
			
		||||
    upgrade_AvatarLootRadar: `[UNTRANSLATED] +7m Loot Radar`,
 | 
			
		||||
    upgrade_WeaponAmmoMax: `[UNTRANSLATED] +15% Ammo Max`,
 | 
			
		||||
    upgrade_EnemyArmorReductionAura: `[UNTRANSLATED] -3% Enemy Armor`,
 | 
			
		||||
    upgrade_OnExecutionAmmo: `[UNTRANSLATED] +100% Primary and Secondary Magazine Refill on Mercy`,
 | 
			
		||||
    upgrade_OnExecutionHealthDrop: `[UNTRANSLATED] +100% Health Orb Chance on Mercy`,
 | 
			
		||||
    upgrade_OnExecutionEnergyDrop: `[UNTRANSLATED] +50% Energy Orb Chance on Mercy`,
 | 
			
		||||
    upgrade_OnFailHackReset: `[UNTRANSLATED] +50% Hacking Retry Chance`,
 | 
			
		||||
    upgrade_DamageReductionOnHack: `[UNTRANSLATED] +75% Damage Reduction while Hacking`,
 | 
			
		||||
    upgrade_OnExecutionAmmo: `[UNTRANSLATED] 100% Primary and Secondary Magazine Refill on Mercy`,
 | 
			
		||||
    upgrade_OnExecutionHealthDrop: `[UNTRANSLATED] 100% chance to drop a Health Orb on Mercy`,
 | 
			
		||||
    upgrade_OnExecutionEnergyDrop: `[UNTRANSLATED] 50% chance to drop an Energy Orb on Mercy`,
 | 
			
		||||
    upgrade_OnFailHackReset: `[UNTRANSLATED] +50% to retry on Hacking failure`,
 | 
			
		||||
    upgrade_DamageReductionOnHack: `[UNTRANSLATED] 75% Damage Reduction while Hacking`,
 | 
			
		||||
    upgrade_OnExecutionReviveCompanion: `[UNTRANSLATED] Mercy Kills reduce Companion Recovery by 15s`,
 | 
			
		||||
    upgrade_OnExecutionParkourSpeed: `[UNTRANSLATED] +60% Parkour Speed after a Mercy for 15s`,
 | 
			
		||||
    upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] +8s to Hacking`,
 | 
			
		||||
    upgrade_ElectrifyOnHack: `[UNTRANSLATED] Shock enemies within 20m while Hacking`,
 | 
			
		||||
    upgrade_OnExecutionTerrify: `[UNTRANSLATED] +50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`,
 | 
			
		||||
    upgrade_OnExecutionTerrify: `[UNTRANSLATED] 50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`,
 | 
			
		||||
    upgrade_OnHackLockers: `[UNTRANSLATED] Unlock 5 lockers within 20m after Hacking`,
 | 
			
		||||
    upgrade_OnExecutionBlind: `[UNTRANSLATED] Blind enemies within 18m on Mercy`,
 | 
			
		||||
    upgrade_OnExecutionDrainPower: `[UNTRANSLATED] +100% chance for next ability cast to gain +50% Ability Strength on Mercy`,
 | 
			
		||||
    upgrade_OnExecutionDrainPower: `[UNTRANSLATED] 100% chance for next ability cast to gain +50% Ability Strength on Mercy`,
 | 
			
		||||
    upgrade_OnHackSprintSpeed: `[UNTRANSLATED] +75% Sprint Speed for 15s after Hacking`,
 | 
			
		||||
    upgrade_SwiftExecute: `[UNTRANSLATED] Speed of Mercy Kills increased by 50%`,
 | 
			
		||||
    upgrade_OnHackInvis: `[UNTRANSLATED] Invisible for 15 seconds after hacking`,
 | 
			
		||||
 | 
			
		||||
@ -126,7 +126,6 @@ dict = {
 | 
			
		||||
    mods_fingerprintHelp: `Need help with the fingerprint?`,
 | 
			
		||||
    mods_rivens: `Rivens`,
 | 
			
		||||
    mods_mods: `Mods`,
 | 
			
		||||
    mods_addMax: `Add Maxed`,
 | 
			
		||||
    mods_addMissingUnrankedMods: `Add Missing Unranked Mods`,
 | 
			
		||||
    mods_removeUnranked: `Remove Unranked Mods`,
 | 
			
		||||
    mods_addMissingMaxRankMods: `Add Missing Max Rank Mods`,
 | 
			
		||||
@ -182,13 +181,9 @@ dict = {
 | 
			
		||||
    cheats_noDojoResearchTime: `No Dojo Research Time`,
 | 
			
		||||
    cheats_fastClanAscension: `Fast Clan Ascension`,
 | 
			
		||||
    cheats_missionsCanGiveAllRelics: `Missions Can Give All Relics`,
 | 
			
		||||
    cheats_exceptionalRelicsAlwaysGiveBronzeReward: `Exceptional Relics Always Give Bronze Reward`,
 | 
			
		||||
    cheats_flawlessRelicsAlwaysGiveSilverReward: `Flawless Relics Always Give Silver Reward`,
 | 
			
		||||
    cheats_radiantRelicsAlwaysGiveGoldReward: `Radiant Relics Always Give Gold Reward`,
 | 
			
		||||
    cheats_unlockAllSimarisResearchEntries: `Unlock All Simaris Research Entries`,
 | 
			
		||||
    cheats_disableDailyTribute: `Disable Daily Tribute`,
 | 
			
		||||
    cheats_spoofMasteryRank: `Spoofed Mastery Rank (-1 to disable)`,
 | 
			
		||||
    cheats_relicRewardItemCountMultiplier: `Relic Reward Item Count Multiplier`,
 | 
			
		||||
    cheats_nightwaveStandingMultiplier: `Nightwave Standing Multiplier`,
 | 
			
		||||
    cheats_save: `Save`,
 | 
			
		||||
    cheats_account: `Account`,
 | 
			
		||||
@ -285,19 +280,19 @@ dict = {
 | 
			
		||||
    upgrade_AvatarLootRadar: `+7m Loot Radar`,
 | 
			
		||||
    upgrade_WeaponAmmoMax: `+15% Ammo Max`,
 | 
			
		||||
    upgrade_EnemyArmorReductionAura: `-3% Enemy Armor`,
 | 
			
		||||
    upgrade_OnExecutionAmmo: `+100% Primary and Secondary Magazine Refill on Mercy`,
 | 
			
		||||
    upgrade_OnExecutionHealthDrop: `+100% Health Orb Chance on Mercy`,
 | 
			
		||||
    upgrade_OnExecutionEnergyDrop: `+50% Energy Orb Chance on Mercy`,
 | 
			
		||||
    upgrade_OnFailHackReset: `+50% Hacking Retry Chance`,
 | 
			
		||||
    upgrade_DamageReductionOnHack: `+75% Damage Reduction while Hacking`,
 | 
			
		||||
    upgrade_OnExecutionAmmo: `100% Primary and Secondary Magazine Refill on Mercy`,
 | 
			
		||||
    upgrade_OnExecutionHealthDrop: `100% chance to drop a Health Orb on Mercy`,
 | 
			
		||||
    upgrade_OnExecutionEnergyDrop: `50% chance to drop an Energy Orb on Mercy`,
 | 
			
		||||
    upgrade_OnFailHackReset: `+50% to retry on Hacking failure`,
 | 
			
		||||
    upgrade_DamageReductionOnHack: `75% Damage Reduction while Hacking`,
 | 
			
		||||
    upgrade_OnExecutionReviveCompanion: `Mercy Kills reduce Companion Recovery by 15s`,
 | 
			
		||||
    upgrade_OnExecutionParkourSpeed: `+60% Parkour Speed after a Mercy for 15s`,
 | 
			
		||||
    upgrade_AvatarTimeLimitIncrease: `+8s to Hacking`,
 | 
			
		||||
    upgrade_ElectrifyOnHack: `Shock enemies within 20m while Hacking`,
 | 
			
		||||
    upgrade_OnExecutionTerrify: `+50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`,
 | 
			
		||||
    upgrade_OnExecutionTerrify: `50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`,
 | 
			
		||||
    upgrade_OnHackLockers: `Unlock 5 lockers within 20m after Hacking`,
 | 
			
		||||
    upgrade_OnExecutionBlind: `Blind enemies within 18m on Mercy`,
 | 
			
		||||
    upgrade_OnExecutionDrainPower: `+100% chance for next ability cast to gain +50% Ability Strength on Mercy`,
 | 
			
		||||
    upgrade_OnExecutionDrainPower: `100% chance for next ability cast to gain +50% Ability Strength on Mercy`,
 | 
			
		||||
    upgrade_OnHackSprintSpeed: `+75% Sprint Speed for 15s after Hacking`,
 | 
			
		||||
    upgrade_SwiftExecute: `Speed of Mercy Kills increased by 50%`,
 | 
			
		||||
    upgrade_OnHackInvis: `Invisible for 15 seconds after hacking`,
 | 
			
		||||
 | 
			
		||||
@ -127,7 +127,6 @@ dict = {
 | 
			
		||||
    mods_fingerprintHelp: `¿Necesitas ayuda con la huella digital?`,
 | 
			
		||||
    mods_rivens: `Agrietados`,
 | 
			
		||||
    mods_mods: `Mods`,
 | 
			
		||||
    mods_addMax: `[UNTRANSLATED] Add Maxed`,
 | 
			
		||||
    mods_addMissingUnrankedMods: `Agregar mods sin rango faltantes`,
 | 
			
		||||
    mods_removeUnranked: `Quitar mods sin rango`,
 | 
			
		||||
    mods_addMissingMaxRankMods: `Agregar mods de rango máximo faltantes`,
 | 
			
		||||
@ -183,13 +182,9 @@ dict = {
 | 
			
		||||
    cheats_noDojoResearchTime: `Sin tiempo de investigación del dojo`,
 | 
			
		||||
    cheats_fastClanAscension: `Ascenso rápido del clan`,
 | 
			
		||||
    cheats_missionsCanGiveAllRelics: `Las misiones pueden otorgar todas las reliquias`,
 | 
			
		||||
    cheats_exceptionalRelicsAlwaysGiveBronzeReward: `[UNTRANSLATED] Exceptional Relics Always Give Bronze Reward`,
 | 
			
		||||
    cheats_flawlessRelicsAlwaysGiveSilverReward: `[UNTRANSLATED] Flawless Relics Always Give Silver Reward`,
 | 
			
		||||
    cheats_radiantRelicsAlwaysGiveGoldReward: `[UNTRANSLATED] Radiant Relics Always Give Gold Reward`,
 | 
			
		||||
    cheats_unlockAllSimarisResearchEntries: `Desbloquear todas las entradas de investigación de Simaris`,
 | 
			
		||||
    cheats_disableDailyTribute: `Desactivar tributo diario`,
 | 
			
		||||
    cheats_spoofMasteryRank: `Rango de maestría simulado (-1 para desactivar)`,
 | 
			
		||||
    cheats_relicRewardItemCountMultiplier: `[UNTRANSLATED] Relic Reward Item Count Multiplier`,
 | 
			
		||||
    cheats_nightwaveStandingMultiplier: `Multiplicador de Reputación de Onda Nocturna`,
 | 
			
		||||
    cheats_save: `Guardar`,
 | 
			
		||||
    cheats_account: `Cuenta`,
 | 
			
		||||
 | 
			
		||||
@ -127,7 +127,6 @@ dict = {
 | 
			
		||||
    mods_fingerprintHelp: `Besoin d'aide pour l'empreinte ?`,
 | 
			
		||||
    mods_rivens: `Rivens`,
 | 
			
		||||
    mods_mods: `Mods`,
 | 
			
		||||
    mods_addMax: `[UNTRANSLATED] Add Maxed`,
 | 
			
		||||
    mods_addMissingUnrankedMods: `Ajouter les mods sans rang manquants`,
 | 
			
		||||
    mods_removeUnranked: `Retirer les mods sans rang`,
 | 
			
		||||
    mods_addMissingMaxRankMods: `Ajouter les mods niveau max manquants`,
 | 
			
		||||
@ -183,13 +182,9 @@ dict = {
 | 
			
		||||
    cheats_noDojoResearchTime: `Aucun temps de recherche (Dojo)`,
 | 
			
		||||
    cheats_fastClanAscension: `Ascension de clan rapide`,
 | 
			
		||||
    cheats_missionsCanGiveAllRelics: `Les missions donnent toutes les reliques`,
 | 
			
		||||
    cheats_exceptionalRelicsAlwaysGiveBronzeReward: `[UNTRANSLATED] Exceptional Relics Always Give Bronze Reward`,
 | 
			
		||||
    cheats_flawlessRelicsAlwaysGiveSilverReward: `[UNTRANSLATED] Flawless Relics Always Give Silver Reward`,
 | 
			
		||||
    cheats_radiantRelicsAlwaysGiveGoldReward: `[UNTRANSLATED] Radiant Relics Always Give Gold Reward`,
 | 
			
		||||
    cheats_unlockAllSimarisResearchEntries: `Débloquer toute les recherches chez Simaris`,
 | 
			
		||||
    cheats_disableDailyTribute: `[UNTRANSLATED] Disable Daily Tribute`,
 | 
			
		||||
    cheats_spoofMasteryRank: `Rang de maîtrise personnalisé (-1 pour désactiver)`,
 | 
			
		||||
    cheats_relicRewardItemCountMultiplier: `[UNTRANSLATED] Relic Reward Item Count Multiplier`,
 | 
			
		||||
    cheats_nightwaveStandingMultiplier: `Multiplicateur de réputation d'Ondes Nocturnes`,
 | 
			
		||||
    cheats_save: `Sauvegarder`,
 | 
			
		||||
    cheats_account: `Compte`,
 | 
			
		||||
@ -289,7 +284,7 @@ dict = {
 | 
			
		||||
    upgrade_OnExecutionAmmo: `100% de rechargement des armes primaires et secondaires sur une une miséricorde`,
 | 
			
		||||
    upgrade_OnExecutionHealthDrop: `100% de chance de drop une orbe de santé sur une miséricorde`,
 | 
			
		||||
    upgrade_OnExecutionEnergyDrop: `50% de chance de drop une orbe d'énergie sur une miséricorde`,
 | 
			
		||||
    upgrade_OnFailHackReset: `[UNTRANSLATED] +50% Hacking Retry Chance`,
 | 
			
		||||
    upgrade_OnFailHackReset: `[UNTRANSLATED] +50% to retry on Hacking failure`,
 | 
			
		||||
    upgrade_DamageReductionOnHack: `75% de réduction de dégâts pendant un piratage`,
 | 
			
		||||
    upgrade_OnExecutionReviveCompanion: `Les miséricordes réduisent le temps de récupération du compagnon de 15s`,
 | 
			
		||||
    upgrade_OnExecutionParkourSpeed: `+60% de vitesse de parkour pendant 15s après une miséricorde`,
 | 
			
		||||
 | 
			
		||||
@ -127,7 +127,6 @@ dict = {
 | 
			
		||||
    mods_fingerprintHelp: `Нужна помощь с отпечатком?`,
 | 
			
		||||
    mods_rivens: `Моды Разлома`,
 | 
			
		||||
    mods_mods: `Моды`,
 | 
			
		||||
    mods_addMax: `[UNTRANSLATED] Add Maxed`,
 | 
			
		||||
    mods_addMissingUnrankedMods: `Добавить недостающие моды без ранга`,
 | 
			
		||||
    mods_removeUnranked: `Удалить моды без ранга`,
 | 
			
		||||
    mods_addMissingMaxRankMods: `Добавить недостающие моды максимального ранга`,
 | 
			
		||||
@ -183,13 +182,9 @@ dict = {
 | 
			
		||||
    cheats_noDojoResearchTime: `Мгновенные Исследование Додзё`,
 | 
			
		||||
    cheats_fastClanAscension: `Мгновенное Вознесение Клана`,
 | 
			
		||||
    cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`,
 | 
			
		||||
    cheats_exceptionalRelicsAlwaysGiveBronzeReward: `[UNTRANSLATED] Exceptional Relics Always Give Bronze Reward`,
 | 
			
		||||
    cheats_flawlessRelicsAlwaysGiveSilverReward: `[UNTRANSLATED] Flawless Relics Always Give Silver Reward`,
 | 
			
		||||
    cheats_radiantRelicsAlwaysGiveGoldReward: `[UNTRANSLATED] Radiant Relics Always Give Gold Reward`,
 | 
			
		||||
    cheats_unlockAllSimarisResearchEntries: `[UNTRANSLATED] Unlock All Simaris Research Entries`,
 | 
			
		||||
    cheats_disableDailyTribute: `[UNTRANSLATED] Disable Daily Tribute`,
 | 
			
		||||
    cheats_spoofMasteryRank: `Подделанный ранг мастерства (-1 для отключения)`,
 | 
			
		||||
    cheats_relicRewardItemCountMultiplier: `[UNTRANSLATED] Relic Reward Item Count Multiplier`,
 | 
			
		||||
    cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`,
 | 
			
		||||
    cheats_save: `[UNTRANSLATED] Save`,
 | 
			
		||||
    cheats_account: `Аккаунт`,
 | 
			
		||||
@ -286,19 +281,19 @@ dict = {
 | 
			
		||||
    upgrade_AvatarLootRadar: `[UNTRANSLATED] +7m Loot Radar`,
 | 
			
		||||
    upgrade_WeaponAmmoMax: `[UNTRANSLATED] +15% Ammo Max`,
 | 
			
		||||
    upgrade_EnemyArmorReductionAura: `[UNTRANSLATED] -3% Enemy Armor`,
 | 
			
		||||
    upgrade_OnExecutionAmmo: `[UNTRANSLATED] +100% Primary and Secondary Magazine Refill on Mercy`,
 | 
			
		||||
    upgrade_OnExecutionHealthDrop: `[UNTRANSLATED] +100% Health Orb Chance on Mercy`,
 | 
			
		||||
    upgrade_OnExecutionEnergyDrop: `[UNTRANSLATED] +50% Energy Orb Chance on Mercy`,
 | 
			
		||||
    upgrade_OnFailHackReset: `[UNTRANSLATED] +50% Hacking Retry Chance`,
 | 
			
		||||
    upgrade_DamageReductionOnHack: `[UNTRANSLATED] +75% Damage Reduction while Hacking`,
 | 
			
		||||
    upgrade_OnExecutionAmmo: `[UNTRANSLATED] 100% Primary and Secondary Magazine Refill on Mercy`,
 | 
			
		||||
    upgrade_OnExecutionHealthDrop: `[UNTRANSLATED] 100% chance to drop a Health Orb on Mercy`,
 | 
			
		||||
    upgrade_OnExecutionEnergyDrop: `[UNTRANSLATED] 50% chance to drop an Energy Orb on Mercy`,
 | 
			
		||||
    upgrade_OnFailHackReset: `[UNTRANSLATED] +50% to retry on Hacking failure`,
 | 
			
		||||
    upgrade_DamageReductionOnHack: `[UNTRANSLATED] 75% Damage Reduction while Hacking`,
 | 
			
		||||
    upgrade_OnExecutionReviveCompanion: `[UNTRANSLATED] Mercy Kills reduce Companion Recovery by 15s`,
 | 
			
		||||
    upgrade_OnExecutionParkourSpeed: `[UNTRANSLATED] +60% Parkour Speed after a Mercy for 15s`,
 | 
			
		||||
    upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] +8s to Hacking`,
 | 
			
		||||
    upgrade_ElectrifyOnHack: `[UNTRANSLATED] Shock enemies within 20m while Hacking`,
 | 
			
		||||
    upgrade_OnExecutionTerrify: `[UNTRANSLATED] +50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`,
 | 
			
		||||
    upgrade_OnExecutionTerrify: `[UNTRANSLATED] 50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`,
 | 
			
		||||
    upgrade_OnHackLockers: `[UNTRANSLATED] Unlock 5 lockers within 20m after Hacking`,
 | 
			
		||||
    upgrade_OnExecutionBlind: `[UNTRANSLATED] Blind enemies within 18m on Mercy`,
 | 
			
		||||
    upgrade_OnExecutionDrainPower: `[UNTRANSLATED] +100% chance for next ability cast to gain +50% Ability Strength on Mercy`,
 | 
			
		||||
    upgrade_OnExecutionDrainPower: `[UNTRANSLATED] 100% chance for next ability cast to gain +50% Ability Strength on Mercy`,
 | 
			
		||||
    upgrade_OnHackSprintSpeed: `[UNTRANSLATED] +75% Sprint Speed for 15s after Hacking`,
 | 
			
		||||
    upgrade_SwiftExecute: `[UNTRANSLATED] Speed of Mercy Kills increased by 50%`,
 | 
			
		||||
    upgrade_OnHackInvis: `[UNTRANSLATED] Invisible for 15 seconds after hacking`,
 | 
			
		||||
 | 
			
		||||
@ -2,8 +2,8 @@
 | 
			
		||||
dict = {
 | 
			
		||||
    general_inventoryUpdateNote: `注意:要在游戏中查看更改,您需要重新同步库存,例如使用引导程序的 /sync 命令、访问道场 / 中继站或重新登录`,
 | 
			
		||||
    general_addButton: `添加`,
 | 
			
		||||
    general_setButton: `设置`,
 | 
			
		||||
    general_removeButton: `移除`,
 | 
			
		||||
    general_setButton: `[UNTRANSLATED] Set`,
 | 
			
		||||
    general_removeButton: `[UNTRANSLATED] Remove`,
 | 
			
		||||
    general_bulkActions: `批量操作`,
 | 
			
		||||
 | 
			
		||||
    code_loginFail: `登录失败。请检查邮箱和密码。`,
 | 
			
		||||
@ -120,14 +120,13 @@ dict = {
 | 
			
		||||
    detailedView_archonShardsDescription: `您可以使用这些无限插槽应用各种强化效果`,
 | 
			
		||||
    detailedView_archonShardsDescription2: `请注意, 在加载时, 每个执政官源力石都需要一定的时间来生效。`,
 | 
			
		||||
    detailedView_valenceBonusLabel: `效价加成`,
 | 
			
		||||
    detailedView_valenceBonusDescription: `您可以设置或移除武器上的效价加成。`,
 | 
			
		||||
    detailedView_valenceBonusDescription: `[UNTRANSLATED] You can set or remove the Valence Bonus from your weapon.`,
 | 
			
		||||
 | 
			
		||||
    mods_addRiven: `添加裂罅MOD`,
 | 
			
		||||
    mods_fingerprint: `印记`,
 | 
			
		||||
    mods_fingerprintHelp: `需要印记相关的帮助?`,
 | 
			
		||||
    mods_rivens: `裂罅MOD`,
 | 
			
		||||
    mods_mods: `Mods`,
 | 
			
		||||
    mods_addMax: `设为满级`,
 | 
			
		||||
    mods_addMissingUnrankedMods: `添加所有缺失的Mods`,
 | 
			
		||||
    mods_removeUnranked: `删除所有未升级的Mods`,
 | 
			
		||||
    mods_addMissingMaxRankMods: `添加所有缺失的满级Mods`,
 | 
			
		||||
@ -137,17 +136,17 @@ dict = {
 | 
			
		||||
    cheats_skipAllDialogue: `跳过所有对话`,
 | 
			
		||||
    cheats_unlockAllScans: `解锁所有扫描`,
 | 
			
		||||
    cheats_unlockAllMissions: `解锁所有任务`,
 | 
			
		||||
    cheats_unlockAllMissions_ok: `操作成功。请注意,您需要进入道场 / 中继站或重新登录客户端以刷新星图数据。`,
 | 
			
		||||
    cheats_unlockAllMissions_ok: `[UNTRANSLATED] Success. Please note that you'll need to enter a dojo/relay or relog for the client to refresh the star chart.`,
 | 
			
		||||
    cheats_infiniteCredits: `无限现金`,
 | 
			
		||||
    cheats_infinitePlatinum: `无限白金`,
 | 
			
		||||
    cheats_infiniteEndo: `无限内融核心`,
 | 
			
		||||
    cheats_infiniteRegalAya: `无限御品阿耶`,
 | 
			
		||||
    cheats_infiniteHelminthMaterials: `无限Helminth材料`,
 | 
			
		||||
    cheats_claimingBlueprintRefundsIngredients: `取消蓝图制造时返还材料`,
 | 
			
		||||
    cheats_dontSubtractPurchaseCreditCost: `购物时不减少现金花费`,
 | 
			
		||||
    cheats_dontSubtractPurchasePlatinumCost: `购物时不减少白金花费`,
 | 
			
		||||
    cheats_dontSubtractPurchaseItemCost: `购物时不减少物品花费`,
 | 
			
		||||
    cheats_dontSubtractPurchaseStandingCost: `购物时不减少声望花费`,
 | 
			
		||||
    cheats_dontSubtractPurchaseCreditCost: `不减少现金花费`,
 | 
			
		||||
    cheats_dontSubtractPurchasePlatinumCost: `不减少白金花费`,
 | 
			
		||||
    cheats_dontSubtractPurchaseItemCost: `不减少物品花费`,
 | 
			
		||||
    cheats_dontSubtractPurchaseStandingCost: `不减少声望花费`,
 | 
			
		||||
    cheats_dontSubtractVoidTraces: `虚空光体无消耗`,
 | 
			
		||||
    cheats_dontSubtractConsumables: `消耗物品使用时无损耗`,
 | 
			
		||||
    cheats_unlockAllShipFeatures: `解锁所有飞船功能`,
 | 
			
		||||
@ -183,13 +182,9 @@ dict = {
 | 
			
		||||
    cheats_noDojoResearchTime: `无视道场研究时间`,
 | 
			
		||||
    cheats_fastClanAscension: `快速升级氏族`,
 | 
			
		||||
    cheats_missionsCanGiveAllRelics: `任务可获取所有遗物`,
 | 
			
		||||
    cheats_exceptionalRelicsAlwaysGiveBronzeReward: `卓越遗物必定掉落青铜奖励`,
 | 
			
		||||
    cheats_flawlessRelicsAlwaysGiveSilverReward: `无瑕遗物必定掉落白银奖励`,
 | 
			
		||||
    cheats_radiantRelicsAlwaysGiveGoldReward: `光辉遗物必定掉落黄金奖励`,
 | 
			
		||||
    cheats_unlockAllSimarisResearchEntries: `解锁所有Simaris研究条目`,
 | 
			
		||||
    cheats_disableDailyTribute: `禁用每日登录奖励`,
 | 
			
		||||
    cheats_spoofMasteryRank: `伪造精通段位(-1为禁用)`,
 | 
			
		||||
    cheats_relicRewardItemCountMultiplier: `虚空遗物奖励物品数量倍率`,
 | 
			
		||||
    cheats_nightwaveStandingMultiplier: `午夜电波声望倍率`,
 | 
			
		||||
    cheats_save: `保存`,
 | 
			
		||||
    cheats_account: `账户`,
 | 
			
		||||
@ -311,8 +306,8 @@ dict = {
 | 
			
		||||
    damageType_Poison: `毒素`,
 | 
			
		||||
    damageType_Radiation: `辐射`,
 | 
			
		||||
 | 
			
		||||
    theme_dark: `暗色主题`,
 | 
			
		||||
    theme_light: `亮色主题`,
 | 
			
		||||
    theme_dark: `[UNTRANSLATED] Dark Theme`,
 | 
			
		||||
    theme_light: `[UNTRANSLATED] Light Theme`,
 | 
			
		||||
 | 
			
		||||
    prettier_sucks_ass: ``
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user