merge upstream
This commit is contained in:
		
						commit
						48f671978d
					
				@ -1,4 +1,4 @@
 | 
			
		||||
import { checkCalendarChallengeCompletion, getCalendarProgress, getInventory } from "@/src/services/inventoryService";
 | 
			
		||||
import { checkCalendarAutoAdvance, getCalendarProgress, getInventory } from "@/src/services/inventoryService";
 | 
			
		||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
			
		||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
 | 
			
		||||
import { getWorldState } from "@/src/services/worldStateService";
 | 
			
		||||
@ -28,7 +28,7 @@ export const completeCalendarEventController: RequestHandler = async (req, res)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    calendarProgress.SeasonProgress.LastCompletedDayIdx = dayIndex;
 | 
			
		||||
    checkCalendarChallengeCompletion(calendarProgress, currentSeason);
 | 
			
		||||
    checkCalendarAutoAdvance(inventory, currentSeason);
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.json({
 | 
			
		||||
        InventoryChanges: inventoryChanges,
 | 
			
		||||
 | 
			
		||||
@ -12,6 +12,7 @@ import {
 | 
			
		||||
    addEmailItem,
 | 
			
		||||
    addMiscItems,
 | 
			
		||||
    allDailyAffiliationKeys,
 | 
			
		||||
    checkCalendarAutoAdvance,
 | 
			
		||||
    cleanupInventory,
 | 
			
		||||
    createLibraryDailyTask,
 | 
			
		||||
    getCalendarProgress
 | 
			
		||||
@ -29,6 +30,7 @@ import { unixTimesInMs } from "@/src/constants/timeConstants";
 | 
			
		||||
import { DailyDeal } from "@/src/models/worldStateModel";
 | 
			
		||||
import { EquipmentFeatures } from "@/src/types/equipmentTypes";
 | 
			
		||||
import { generateRewardSeed } from "@/src/services/rngService";
 | 
			
		||||
import { getWorldState } from "@/src/services/worldStateService";
 | 
			
		||||
 | 
			
		||||
export const inventoryController: RequestHandler = async (request, response) => {
 | 
			
		||||
    const account = await getAccountForRequest(request);
 | 
			
		||||
@ -111,58 +113,61 @@ 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
 | 
			
		||||
        // TODO: Setup CalendarProgress as part of 1999 mission completion?
 | 
			
		||||
 | 
			
		||||
            // 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 previousYearIteration = inventory.CalendarProgress?.Iteration;
 | 
			
		||||
 | 
			
		||||
        // We need to do the following to ensure the in-game calendar does not break:
 | 
			
		||||
        getCalendarProgress(inventory); // Keep the CalendarProgress up-to-date (at least for the current year iteration) (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2364)
 | 
			
		||||
        checkCalendarAutoAdvance(inventory, getWorldState().KnownCalendarSeasons[0]); // Skip birthday events for characters if we do not have them unlocked yet (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2424)
 | 
			
		||||
 | 
			
		||||
        // 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;
 | 
			
		||||
                    }
 | 
			
		||||
                ]) {
 | 
			
		||||
                    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 (dialogue.Rank == 6) {
 | 
			
		||||
                        kalymos = true;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (kalymos) {
 | 
			
		||||
                    await addEmailItem(inventory, "/Lotus/Types/Items/EmailItems/KalymosKissEmailItem");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (kalymos) {
 | 
			
		||||
                await addEmailItem(inventory, "/Lotus/Types/Items/EmailItems/KalymosKissEmailItem");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -37,6 +37,8 @@ const configIdToIndexable = (id: string): [Record<string, boolean | string | num
 | 
			
		||||
    let obj = config as unknown as Record<string, never>;
 | 
			
		||||
    const arr = id.split(".");
 | 
			
		||||
    while (arr.length > 1) {
 | 
			
		||||
        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
			
		||||
        obj[arr[0]] ??= {} as never;
 | 
			
		||||
        obj = obj[arr[0]];
 | 
			
		||||
        arr.splice(0, 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -51,6 +51,11 @@ export const fromMongoDate = (date: IMongoDate): Date => {
 | 
			
		||||
    return new Date(parseInt(date.$date.$numberLong));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type TTraitsPool = Record<
 | 
			
		||||
    "Colors" | "EyeColors" | "FurPatterns" | "BodyTypes" | "Heads" | "Tails",
 | 
			
		||||
    { type: string; rarity: TRarity }[]
 | 
			
		||||
>;
 | 
			
		||||
 | 
			
		||||
export const kubrowWeights: Record<TRarity, number> = {
 | 
			
		||||
    COMMON: 6,
 | 
			
		||||
    UNCOMMON: 4,
 | 
			
		||||
@ -65,126 +70,126 @@ export const kubrowFurPatternsWeights: Record<TRarity, number> = {
 | 
			
		||||
    LEGENDARY: 1
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const catbrowDetails = {
 | 
			
		||||
export const catbrowDetails: TTraitsPool = {
 | 
			
		||||
    Colors: [
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseA", rarity: "COMMON" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseB", rarity: "COMMON" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseC", rarity: "COMMON" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseD", rarity: "COMMON" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseA", rarity: "COMMON" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseB", rarity: "COMMON" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseC", rarity: "COMMON" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseD", rarity: "COMMON" },
 | 
			
		||||
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryA", rarity: "UNCOMMON" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryB", rarity: "UNCOMMON" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryC", rarity: "UNCOMMON" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryD", rarity: "UNCOMMON" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryA", rarity: "UNCOMMON" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryB", rarity: "UNCOMMON" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryC", rarity: "UNCOMMON" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryD", rarity: "UNCOMMON" },
 | 
			
		||||
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryA", rarity: "RARE" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryB", rarity: "RARE" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryC", rarity: "RARE" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryD", rarity: "RARE" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryA", rarity: "RARE" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryB", rarity: "RARE" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryC", rarity: "RARE" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryD", rarity: "RARE" },
 | 
			
		||||
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsA", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsB", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsC", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsD", rarity: "LEGENDARY" as TRarity }
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsA", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsB", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsC", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsD", rarity: "LEGENDARY" }
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    EyeColors: [
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesA", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesB", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesC", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesD", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesE", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesF", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesG", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesH", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesI", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesJ", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesK", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesL", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesM", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesN", rarity: "LEGENDARY" as TRarity }
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesA", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesB", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesC", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesD", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesE", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesF", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesG", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesH", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesI", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesJ", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesK", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesL", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesM", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesN", rarity: "LEGENDARY" }
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    FurPatterns: [{ type: "/Lotus/Types/Game/CatbrowPet/Patterns/CatbrowPetPatternA", rarity: "COMMON" as TRarity }],
 | 
			
		||||
    FurPatterns: [{ type: "/Lotus/Types/Game/CatbrowPet/Patterns/CatbrowPetPatternA", rarity: "COMMON" }],
 | 
			
		||||
 | 
			
		||||
    BodyTypes: [
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetRegularBodyType", rarity: "UNCOMMON" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetRegularBodyType", rarity: "LEGENDARY" as TRarity }
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetRegularBodyType", rarity: "UNCOMMON" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetRegularBodyType", rarity: "LEGENDARY" }
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    Heads: [
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadA", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadB", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadC", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadD", rarity: "LEGENDARY" as TRarity }
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadA", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadB", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadC", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadD", rarity: "LEGENDARY" }
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    Tails: [
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailA", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailB", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailC", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailD", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailE", rarity: "LEGENDARY" as TRarity }
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailA", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailB", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailC", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailD", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailE", rarity: "LEGENDARY" }
 | 
			
		||||
    ]
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const kubrowDetails = {
 | 
			
		||||
export const kubrowDetails: TTraitsPool = {
 | 
			
		||||
    Colors: [
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneA", rarity: "UNCOMMON" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneB", rarity: "UNCOMMON" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneC", rarity: "UNCOMMON" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneD", rarity: "UNCOMMON" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneE", rarity: "UNCOMMON" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneF", rarity: "UNCOMMON" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneG", rarity: "UNCOMMON" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneH", rarity: "UNCOMMON" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneA", rarity: "UNCOMMON" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneB", rarity: "UNCOMMON" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneC", rarity: "UNCOMMON" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneD", rarity: "UNCOMMON" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneE", rarity: "UNCOMMON" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneF", rarity: "UNCOMMON" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneG", rarity: "UNCOMMON" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneH", rarity: "UNCOMMON" },
 | 
			
		||||
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidA", rarity: "RARE" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidB", rarity: "RARE" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidC", rarity: "RARE" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidD", rarity: "RARE" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidE", rarity: "RARE" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidF", rarity: "RARE" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidG", rarity: "RARE" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidH", rarity: "RARE" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidA", rarity: "RARE" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidB", rarity: "RARE" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidC", rarity: "RARE" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidD", rarity: "RARE" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidE", rarity: "RARE" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidF", rarity: "RARE" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidG", rarity: "RARE" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidH", rarity: "RARE" },
 | 
			
		||||
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantA", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantB", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantC", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantD", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantE", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantF", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantG", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantH", rarity: "LEGENDARY" as TRarity }
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantA", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantB", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantC", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantD", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantE", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantF", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantG", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantH", rarity: "LEGENDARY" }
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    EyeColors: [
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesA", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesB", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesC", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesD", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesE", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesF", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesG", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesH", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesI", rarity: "LEGENDARY" as TRarity }
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesA", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesB", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesC", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesD", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesE", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesF", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesG", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesH", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesI", rarity: "LEGENDARY" }
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    FurPatterns: [
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternB", rarity: "UNCOMMON" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternA", rarity: "UNCOMMON" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternB", rarity: "UNCOMMON" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternA", rarity: "UNCOMMON" },
 | 
			
		||||
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternC", rarity: "RARE" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternD", rarity: "RARE" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternC", rarity: "RARE" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternD", rarity: "RARE" },
 | 
			
		||||
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternE", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternF", rarity: "LEGENDARY" as TRarity }
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternE", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternF", rarity: "LEGENDARY" }
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    BodyTypes: [
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetRegularBodyType", rarity: "UNCOMMON" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetHeavyBodyType", rarity: "LEGENDARY" as TRarity },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetThinBodyType", rarity: "LEGENDARY" as TRarity }
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetRegularBodyType", rarity: "UNCOMMON" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetHeavyBodyType", rarity: "LEGENDARY" },
 | 
			
		||||
        { type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetThinBodyType", rarity: "LEGENDARY" }
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    Heads: [],
 | 
			
		||||
 | 
			
		||||
@ -1216,8 +1216,8 @@ const calenderProgressSchema = new Schema<ICalendarProgress>(
 | 
			
		||||
        },
 | 
			
		||||
        SeasonProgress: {
 | 
			
		||||
            SeasonType: { type: String, required: true },
 | 
			
		||||
            LastCompletedDayIdx: { type: Number, default: 0 },
 | 
			
		||||
            LastCompletedChallengeDayIdx: { type: Number, default: 0 },
 | 
			
		||||
            LastCompletedDayIdx: { type: Number, default: -1 },
 | 
			
		||||
            LastCompletedChallengeDayIdx: { type: Number, default: -1 },
 | 
			
		||||
            ActivatedChallenges: { type: [String], default: [] }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
@ -67,7 +67,8 @@ import {
 | 
			
		||||
    kubrowDetails,
 | 
			
		||||
    kubrowFurPatternsWeights,
 | 
			
		||||
    kubrowWeights,
 | 
			
		||||
    toOid
 | 
			
		||||
    toOid,
 | 
			
		||||
    TTraitsPool
 | 
			
		||||
} from "@/src/helpers/inventoryHelpers";
 | 
			
		||||
import { addQuestKey, completeQuest } from "@/src/services/questService";
 | 
			
		||||
import { handleBundleAcqusition } from "@/src/services/purchaseService";
 | 
			
		||||
@ -1048,6 +1049,21 @@ export const addSpaceSuit = (
 | 
			
		||||
    return inventoryChanges;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const createRandomTraits = (kubrowPetName: string, traitsPool: TTraitsPool): ITraits => {
 | 
			
		||||
    return {
 | 
			
		||||
        BaseColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
 | 
			
		||||
        SecondaryColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
 | 
			
		||||
        TertiaryColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
 | 
			
		||||
        AccentColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
 | 
			
		||||
        EyeColor: getRandomWeightedReward(traitsPool.EyeColors, kubrowWeights)!.type,
 | 
			
		||||
        FurPattern: getRandomWeightedReward(traitsPool.FurPatterns, kubrowFurPatternsWeights)!.type,
 | 
			
		||||
        Personality: kubrowPetName,
 | 
			
		||||
        BodyType: getRandomWeightedReward(traitsPool.BodyTypes, kubrowWeights)!.type,
 | 
			
		||||
        Head: traitsPool.Heads.length ? getRandomWeightedReward(traitsPool.Heads, kubrowWeights)!.type : undefined,
 | 
			
		||||
        Tail: traitsPool.Tails.length ? getRandomWeightedReward(traitsPool.Tails, kubrowWeights)!.type : undefined
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const addKubrowPet = (
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    kubrowPetName: string,
 | 
			
		||||
@ -1064,7 +1080,6 @@ export const addKubrowPet = (
 | 
			
		||||
        addSpecialItem(inventory, specialItem, inventoryChanges);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
			
		||||
    const configs: IItemConfig[] = applyDefaultUpgrades(inventory, kubrowPet?.defaultUpgrades);
 | 
			
		||||
 | 
			
		||||
    if (!details) {
 | 
			
		||||
@ -1074,9 +1089,10 @@ export const addKubrowPet = (
 | 
			
		||||
            "/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit"
 | 
			
		||||
        ].includes(kubrowPetName);
 | 
			
		||||
 | 
			
		||||
        let traits: ITraits;
 | 
			
		||||
        const traitsPool = isCatbrow ? catbrowDetails : kubrowDetails;
 | 
			
		||||
        let dominantTraits: ITraits;
 | 
			
		||||
        if (kubrowPetName == "/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit") {
 | 
			
		||||
            traits = {
 | 
			
		||||
            dominantTraits = {
 | 
			
		||||
                BaseColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseVampire",
 | 
			
		||||
                SecondaryColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryVampire",
 | 
			
		||||
                TertiaryColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryVampire",
 | 
			
		||||
@ -1089,19 +1105,31 @@ export const addKubrowPet = (
 | 
			
		||||
                Tail: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailVampire"
 | 
			
		||||
            };
 | 
			
		||||
        } else {
 | 
			
		||||
            const traitsPool = isCatbrow ? catbrowDetails : kubrowDetails;
 | 
			
		||||
            traits = {
 | 
			
		||||
                BaseColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
 | 
			
		||||
                SecondaryColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
 | 
			
		||||
                TertiaryColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
 | 
			
		||||
                AccentColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
 | 
			
		||||
                EyeColor: getRandomWeightedReward(traitsPool.EyeColors, kubrowWeights)!.type,
 | 
			
		||||
                FurPattern: getRandomWeightedReward(traitsPool.FurPatterns, kubrowFurPatternsWeights)!.type,
 | 
			
		||||
                Personality: kubrowPetName,
 | 
			
		||||
                BodyType: getRandomWeightedReward(traitsPool.BodyTypes, kubrowWeights)!.type,
 | 
			
		||||
                Head: isCatbrow ? getRandomWeightedReward(traitsPool.Heads, kubrowWeights)!.type : undefined,
 | 
			
		||||
                Tail: isCatbrow ? getRandomWeightedReward(traitsPool.Tails, kubrowWeights)!.type : undefined
 | 
			
		||||
            };
 | 
			
		||||
            dominantTraits = createRandomTraits(kubrowPetName, traitsPool);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const recessiveTraits: ITraits = createRandomTraits(
 | 
			
		||||
            getRandomElement(
 | 
			
		||||
                isCatbrow
 | 
			
		||||
                    ? [
 | 
			
		||||
                          "/Lotus/Types/Game/CatbrowPet/MirrorCatbrowPetPowerSuit",
 | 
			
		||||
                          "/Lotus/Types/Game/CatbrowPet/CheshireCatbrowPetPowerSuit"
 | 
			
		||||
                      ]
 | 
			
		||||
                    : [
 | 
			
		||||
                          "/Lotus/Types/Game/KubrowPet/AdventurerKubrowPetPowerSuit",
 | 
			
		||||
                          "/Lotus/Types/Game/KubrowPet/FurtiveKubrowPetPowerSuit",
 | 
			
		||||
                          "/Lotus/Types/Game/KubrowPet/GuardKubrowPetPowerSuit",
 | 
			
		||||
                          "/Lotus/Types/Game/KubrowPet/HunterKubrowPetPowerSuit",
 | 
			
		||||
                          "/Lotus/Types/Game/KubrowPet/RetrieverKubrowPetPowerSuit"
 | 
			
		||||
                      ]
 | 
			
		||||
            )!,
 | 
			
		||||
            traitsPool
 | 
			
		||||
        );
 | 
			
		||||
        for (const key of Object.keys(recessiveTraits) as (keyof ITraits)[]) {
 | 
			
		||||
            // My heurstic approximation is a 20% chance for a dominant trait to be copied into the recessive traits. TODO: A more scientific statistical analysis maybe?
 | 
			
		||||
            if (Math.random() < 0.2) {
 | 
			
		||||
                recessiveTraits[key] = dominantTraits[key]!;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        details = {
 | 
			
		||||
@ -1113,8 +1141,8 @@ export const addKubrowPet = (
 | 
			
		||||
            HatchDate: premiumPurchase ? new Date() : new Date(Date.now() + 10 * unixTimesInMs.hour), // On live, this seems to be somewhat randomised so that the pet hatches 9~11 hours after start.
 | 
			
		||||
            IsMale: !!getRandomInt(0, 1),
 | 
			
		||||
            Size: getRandomInt(70, 100) / 100,
 | 
			
		||||
            DominantTraits: traits,
 | 
			
		||||
            RecessiveTraits: traits
 | 
			
		||||
            DominantTraits: dominantTraits,
 | 
			
		||||
            RecessiveTraits: recessiveTraits
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1897,7 +1925,7 @@ export const addCalendarProgress = (inventory: TInventoryDatabaseDocument, value
 | 
			
		||||
    calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx = currentSeason.Days.findIndex(
 | 
			
		||||
        day => day.events.length != 0 && day.events[0].challenge == value[value.length - 1].challenge
 | 
			
		||||
    );
 | 
			
		||||
    checkCalendarChallengeCompletion(calendarProgress, currentSeason);
 | 
			
		||||
    checkCalendarAutoAdvance(inventory, currentSeason);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag, Completes, Tier }: IMission): void => {
 | 
			
		||||
@ -2082,8 +2110,8 @@ export const getCalendarProgress = (inventory: TInventoryDatabaseDocument): ICal
 | 
			
		||||
            },
 | 
			
		||||
            SeasonProgress: {
 | 
			
		||||
                SeasonType: currentSeason.Season,
 | 
			
		||||
                LastCompletedDayIdx: 0,
 | 
			
		||||
                LastCompletedChallengeDayIdx: 0,
 | 
			
		||||
                LastCompletedDayIdx: -1,
 | 
			
		||||
                LastCompletedChallengeDayIdx: -1,
 | 
			
		||||
                ActivatedChallenges: []
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
@ -2104,16 +2132,44 @@ export const getCalendarProgress = (inventory: TInventoryDatabaseDocument): ICal
 | 
			
		||||
    return inventory.CalendarProgress;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const checkCalendarChallengeCompletion = (
 | 
			
		||||
    calendarProgress: ICalendarProgress,
 | 
			
		||||
export const checkCalendarAutoAdvance = (
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    currentSeason: ICalendarSeason
 | 
			
		||||
): void => {
 | 
			
		||||
    const dayIndex = calendarProgress.SeasonProgress.LastCompletedDayIdx + 1;
 | 
			
		||||
    if (calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx >= dayIndex) {
 | 
			
		||||
    const calendarProgress = inventory.CalendarProgress!;
 | 
			
		||||
    for (
 | 
			
		||||
        let dayIndex = calendarProgress.SeasonProgress.LastCompletedDayIdx + 1;
 | 
			
		||||
        dayIndex != currentSeason.Days.length;
 | 
			
		||||
        ++dayIndex
 | 
			
		||||
    ) {
 | 
			
		||||
        const day = currentSeason.Days[dayIndex];
 | 
			
		||||
        if (day.events.length != 0 && day.events[0].type == "CET_CHALLENGE") {
 | 
			
		||||
        if (day.events.length == 0) {
 | 
			
		||||
            // birthday
 | 
			
		||||
            if (day.day == 1) {
 | 
			
		||||
                // kaya
 | 
			
		||||
                if ((inventory.Affiliations.find(x => x.Tag == "HexSyndicate")?.Title || 0) >= 4) {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                logger.debug(`cannot talk to kaya, skipping birthday`);
 | 
			
		||||
                calendarProgress.SeasonProgress.LastCompletedDayIdx++;
 | 
			
		||||
            } else if (day.day == 74 || day.day == 355) {
 | 
			
		||||
                // minerva, velimir
 | 
			
		||||
                if ((inventory.Affiliations.find(x => x.Tag == "HexSyndicate")?.Title || 0) >= 5) {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                logger.debug(`cannot talk to minerva/velimir, skipping birthday`);
 | 
			
		||||
                calendarProgress.SeasonProgress.LastCompletedDayIdx++;
 | 
			
		||||
            } else {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        } else if (day.events[0].type == "CET_CHALLENGE") {
 | 
			
		||||
            if (calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx < dayIndex) {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            //logger.debug(`already completed the challenge, skipping ahead`);
 | 
			
		||||
            calendarProgress.SeasonProgress.LastCompletedDayIdx++;
 | 
			
		||||
        } else {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -151,4 +151,57 @@ export class SRng {
 | 
			
		||||
            arr[lastIdx] = tmp;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    shuffledArray<T>(inarr: readonly T[]): T[] {
 | 
			
		||||
        const arr = [...inarr];
 | 
			
		||||
        this.shuffleArray(arr);
 | 
			
		||||
        return arr;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const sequentiallyUniqueRandomElement = <T>(
 | 
			
		||||
    deck: readonly T[],
 | 
			
		||||
    idx: number,
 | 
			
		||||
    lookbehind: number,
 | 
			
		||||
    seed: number = 0
 | 
			
		||||
): T | undefined => {
 | 
			
		||||
    // This algorithm may modify a shuffle up to index `lookbehind + 1`. It assumes that the last `lookbehind` cards are not adjusted.
 | 
			
		||||
    if (lookbehind + 1 >= deck.length - lookbehind) {
 | 
			
		||||
        throw new Error(
 | 
			
		||||
            `this algorithm cannot guarantee ${lookbehind} unique cards in a row with a deck of size ${deck.length}`
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const iteration = Math.trunc(idx / deck.length);
 | 
			
		||||
    const card = idx % deck.length;
 | 
			
		||||
    const currentShuffle = new SRng(mixSeeds(new SRng(iteration).randomInt(0, 100_000), seed)).shuffledArray(deck);
 | 
			
		||||
    if (card < currentShuffle.length - lookbehind) {
 | 
			
		||||
        // We are indexing before the end of the deck, so adjustments may be needed to achieve uniqueness.
 | 
			
		||||
        const window: T[] = [];
 | 
			
		||||
        {
 | 
			
		||||
            const previousShuffle = new SRng(
 | 
			
		||||
                mixSeeds(new SRng(iteration - 1).randomInt(0, 100_000), seed)
 | 
			
		||||
            ).shuffledArray(deck);
 | 
			
		||||
            for (let i = previousShuffle.length - lookbehind; i != previousShuffle.length; ++i) {
 | 
			
		||||
                window.push(previousShuffle[i]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // From this point on, `window.length == lookbehind` should hold.
 | 
			
		||||
        for (let i = 0; i != lookbehind; ++i) {
 | 
			
		||||
            if (window.indexOf(currentShuffle[i]) != -1) {
 | 
			
		||||
                for (let j = i; ; ++j) {
 | 
			
		||||
                    // `j < currentShuffle.length - lookbehind` should hold.
 | 
			
		||||
                    if (window.indexOf(currentShuffle[j]) == -1) {
 | 
			
		||||
                        const tmp = currentShuffle[j];
 | 
			
		||||
                        currentShuffle[j] = currentShuffle[i];
 | 
			
		||||
                        currentShuffle[i] = tmp;
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            window.splice(0, 1);
 | 
			
		||||
            window.push(currentShuffle[i]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return currentShuffle[card];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ import darvoDeals from "@/static/fixed_responses/worldState/darvoDeals.json";
 | 
			
		||||
import { buildConfig } from "@/src/services/buildConfigService";
 | 
			
		||||
import { unixTimesInMs } from "@/src/constants/timeConstants";
 | 
			
		||||
import { config } from "@/src/services/configService";
 | 
			
		||||
import { getRandomElement, getRandomInt, SRng } from "@/src/services/rngService";
 | 
			
		||||
import { getRandomElement, getRandomInt, sequentiallyUniqueRandomElement, SRng } from "@/src/services/rngService";
 | 
			
		||||
import { eMissionType, ExportRegions, ExportSyndicates, IRegion } from "warframe-public-export-plus";
 | 
			
		||||
import {
 | 
			
		||||
    ICalendarDay,
 | 
			
		||||
@ -385,44 +385,35 @@ const getSeasonChallengePools = (syndicateTag: string): IRotatingSeasonChallenge
 | 
			
		||||
const getSeasonDailyChallenge = (pools: IRotatingSeasonChallengePools, day: number): ISeasonChallenge => {
 | 
			
		||||
    const dayStart = EPOCH + day * 86400000;
 | 
			
		||||
    const dayEnd = EPOCH + (day + 3) * 86400000;
 | 
			
		||||
    const rng = new SRng(new SRng(day).randomInt(0, 100_000));
 | 
			
		||||
    return {
 | 
			
		||||
        _id: { $oid: "67e1b5ca9d00cb47" + day.toString().padStart(8, "0") },
 | 
			
		||||
        Daily: true,
 | 
			
		||||
        Activation: { $date: { $numberLong: dayStart.toString() } },
 | 
			
		||||
        Expiry: { $date: { $numberLong: dayEnd.toString() } },
 | 
			
		||||
        Challenge: rng.randomElement(pools.daily)!
 | 
			
		||||
        Challenge: sequentiallyUniqueRandomElement(pools.daily, day, 2, 605732938)!
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getSeasonWeeklyChallenge = (pools: IRotatingSeasonChallengePools, week: number, id: number): ISeasonChallenge => {
 | 
			
		||||
    const weekStart = EPOCH + week * 604800000;
 | 
			
		||||
    const weekEnd = weekStart + 604800000;
 | 
			
		||||
    const challengeId = week * 7 + id;
 | 
			
		||||
    const rng = new SRng(new SRng(challengeId).randomInt(0, 100_000));
 | 
			
		||||
    return {
 | 
			
		||||
        _id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
 | 
			
		||||
        Activation: { $date: { $numberLong: weekStart.toString() } },
 | 
			
		||||
        Expiry: { $date: { $numberLong: weekEnd.toString() } },
 | 
			
		||||
        Challenge: rng.randomElement(pools.weekly)!
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getSeasonWeeklyHardChallenge = (
 | 
			
		||||
    pools: IRotatingSeasonChallengePools,
 | 
			
		||||
const pushSeasonWeeklyChallenge = (
 | 
			
		||||
    activeChallenges: ISeasonChallenge[],
 | 
			
		||||
    pool: string[],
 | 
			
		||||
    week: number,
 | 
			
		||||
    id: number
 | 
			
		||||
): ISeasonChallenge => {
 | 
			
		||||
): void => {
 | 
			
		||||
    const weekStart = EPOCH + week * 604800000;
 | 
			
		||||
    const weekEnd = weekStart + 604800000;
 | 
			
		||||
    const challengeId = week * 7 + id;
 | 
			
		||||
    const rng = new SRng(new SRng(challengeId).randomInt(0, 100_000));
 | 
			
		||||
    return {
 | 
			
		||||
    let challenge: string;
 | 
			
		||||
    do {
 | 
			
		||||
        challenge = rng.randomElement(pool)!;
 | 
			
		||||
    } while (activeChallenges.some(x => x.Challenge == challenge));
 | 
			
		||||
    activeChallenges.push({
 | 
			
		||||
        _id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
 | 
			
		||||
        Activation: { $date: { $numberLong: weekStart.toString() } },
 | 
			
		||||
        Expiry: { $date: { $numberLong: weekEnd.toString() } },
 | 
			
		||||
        Challenge: rng.randomElement(pools.hardWeekly)!
 | 
			
		||||
    };
 | 
			
		||||
        Challenge: challenge
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const pushWeeklyActs = (
 | 
			
		||||
@ -433,8 +424,8 @@ const pushWeeklyActs = (
 | 
			
		||||
    const weekStart = EPOCH + week * 604800000;
 | 
			
		||||
    const weekEnd = weekStart + 604800000;
 | 
			
		||||
 | 
			
		||||
    activeChallenges.push(getSeasonWeeklyChallenge(pools, week, 0));
 | 
			
		||||
    activeChallenges.push(getSeasonWeeklyChallenge(pools, week, 1));
 | 
			
		||||
    pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 0);
 | 
			
		||||
    pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 1);
 | 
			
		||||
    if (pools.hasWeeklyPermanent) {
 | 
			
		||||
        activeChallenges.push({
 | 
			
		||||
            _id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 0).toString().padStart(8, "0") },
 | 
			
		||||
@ -454,14 +445,14 @@ const pushWeeklyActs = (
 | 
			
		||||
            Expiry: { $date: { $numberLong: weekEnd.toString() } },
 | 
			
		||||
            Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEnemies"
 | 
			
		||||
        });
 | 
			
		||||
        activeChallenges.push(getSeasonWeeklyHardChallenge(pools, week, 2));
 | 
			
		||||
        activeChallenges.push(getSeasonWeeklyHardChallenge(pools, week, 3));
 | 
			
		||||
        pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, week, 2);
 | 
			
		||||
        pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, week, 3);
 | 
			
		||||
    } else {
 | 
			
		||||
        activeChallenges.push(getSeasonWeeklyChallenge(pools, week, 2));
 | 
			
		||||
        activeChallenges.push(getSeasonWeeklyChallenge(pools, week, 3));
 | 
			
		||||
        activeChallenges.push(getSeasonWeeklyChallenge(pools, week, 4));
 | 
			
		||||
        activeChallenges.push(getSeasonWeeklyHardChallenge(pools, week, 5));
 | 
			
		||||
        activeChallenges.push(getSeasonWeeklyHardChallenge(pools, week, 6));
 | 
			
		||||
        pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 2);
 | 
			
		||||
        pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 3);
 | 
			
		||||
        pushSeasonWeeklyChallenge(activeChallenges, pools.weekly, week, 4);
 | 
			
		||||
        pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, week, 5);
 | 
			
		||||
        pushSeasonWeeklyChallenge(activeChallenges, pools.hardWeekly, week, 6);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1476,9 +1467,10 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
 | 
			
		||||
        const pt: IPrimeVaultTrader = {
 | 
			
		||||
            _id: { $oid: ((weekStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "c36af423770eaa97" },
 | 
			
		||||
            Activation: { $date: { $numberLong: weekStart.toString() } },
 | 
			
		||||
            Expiry: { $date: { $numberLong: weekEnd.toString() } },
 | 
			
		||||
            InitialStartDate: { $date: { $numberLong: "1662738144266" } },
 | 
			
		||||
            Node: "TradeHUB1",
 | 
			
		||||
            Manifest: [],
 | 
			
		||||
            Expiry: { $date: { $numberLong: weekEnd.toString() } },
 | 
			
		||||
            EvergreenManifest: varzia.evergreen,
 | 
			
		||||
            ScheduleInfo: []
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
@ -349,7 +349,7 @@
 | 
			
		||||
  ],
 | 
			
		||||
  "PrimeAccessAvailability": { "State": "PRIME1" },
 | 
			
		||||
  "PrimeVaultAvailabilities": [false, false, false, false, false],
 | 
			
		||||
  "PrimeTokenAvailability": false,
 | 
			
		||||
  "PrimeTokenAvailability": true,
 | 
			
		||||
  "LibraryInfo": { "LastCompletedTargetType": "/Lotus/Types/Game/Library/Targets/Research7Target" },
 | 
			
		||||
  "PVPChallengeInstances": [
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
@ -485,7 +485,7 @@
 | 
			
		||||
                            <select class="form-control" id="valenceBonus-innateDamage"></select>
 | 
			
		||||
                            <input type="number" id="valenceBonus-procent" min="25" max="60" step="0.1" class="form-control" style="max-width:100px" />
 | 
			
		||||
                            <button class="btn btn-primary" type="submit" value="set" data-loc="general_setButton"></button>
 | 
			
		||||
                            <button class="btn btn-danger" type="submit" value="remove" data-loc="general_removeButton"></button>
 | 
			
		||||
                            <button class="btn btn-danger" type="submit" value="remove" data-loc="code_remove"></button>
 | 
			
		||||
                        </form>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
@ -870,7 +870,7 @@
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="form-group mt-2">
 | 
			
		||||
                                    <label class="form-label" for="worldState.galleonOfGhouls" data-loc="worldState_galleonOfGhouls"></label>
 | 
			
		||||
                                    <select class="form-control" id="worldState.galleonOfGhouls" data-default="">
 | 
			
		||||
                                    <select class="form-control" id="worldState.galleonOfGhouls" data-default="0">
 | 
			
		||||
                                        <option value="0" data-loc="disabled"></option>
 | 
			
		||||
                                        <option value="1" data-loc="worldState_we1"></option>
 | 
			
		||||
                                        <option value="2" data-loc="worldState_we2"></option>
 | 
			
		||||
 | 
			
		||||
@ -2010,7 +2010,7 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
 | 
			
		||||
                        if (elm.type == "checkbox") {
 | 
			
		||||
                            elm.checked = value;
 | 
			
		||||
                        } else if (elm.classList.contains("tags-input")) {
 | 
			
		||||
                            elm.value = value.join(", ");
 | 
			
		||||
                            elm.value = (value ?? []).join(", ");
 | 
			
		||||
                            elm.oninput();
 | 
			
		||||
                        } else {
 | 
			
		||||
                            elm.value = value ?? elm.getAttribute("data-default");
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,6 @@ dict = {
 | 
			
		||||
    general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`,
 | 
			
		||||
    general_addButton: `Hinzufügen`,
 | 
			
		||||
    general_setButton: `[UNTRANSLATED] Set`,
 | 
			
		||||
    general_removeButton: `[UNTRANSLATED] Remove`,
 | 
			
		||||
    general_bulkActions: `Massenaktionen`,
 | 
			
		||||
 | 
			
		||||
    code_loginFail: `[UNTRANSLATED] Login failed. Double-check the email and password.`,
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,6 @@ dict = {
 | 
			
		||||
    general_inventoryUpdateNote: `Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`,
 | 
			
		||||
    general_addButton: `Add`,
 | 
			
		||||
    general_setButton: `Set`,
 | 
			
		||||
    general_removeButton: `Remove`,
 | 
			
		||||
    general_bulkActions: `Bulk Actions`,
 | 
			
		||||
 | 
			
		||||
    code_loginFail: `Login failed. Double-check the email and password.`,
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,6 @@ dict = {
 | 
			
		||||
    general_inventoryUpdateNote: `Para ver los cambios en el juego, necesitas volver a sincronizar tu inventario, por ejemplo, usando el comando /sync del bootstrapper, visitando un dojo o repetidor, o volviendo a iniciar sesión.`,
 | 
			
		||||
    general_addButton: `Agregar`,
 | 
			
		||||
    general_setButton: `Establecer`,
 | 
			
		||||
    general_removeButton: `Quitar`,
 | 
			
		||||
    general_bulkActions: `Acciones masivas`,
 | 
			
		||||
 | 
			
		||||
    code_loginFail: `Error al iniciar sesión. Verifica el correo electrónico y la contraseña.`,
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,6 @@ dict = {
 | 
			
		||||
    general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`,
 | 
			
		||||
    general_addButton: `Ajouter`,
 | 
			
		||||
    general_setButton: `[UNTRANSLATED] Set`,
 | 
			
		||||
    general_removeButton: `[UNTRANSLATED] Remove`,
 | 
			
		||||
    general_bulkActions: `Action groupée`,
 | 
			
		||||
 | 
			
		||||
    code_loginFail: `Connexion échouée. Vérifiez le mot de passe.`,
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,6 @@ dict = {
 | 
			
		||||
    general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`,
 | 
			
		||||
    general_addButton: `Добавить`,
 | 
			
		||||
    general_setButton: `Установить`,
 | 
			
		||||
    general_removeButton: `Удалить`,
 | 
			
		||||
    general_bulkActions: `Массовые действия`,
 | 
			
		||||
 | 
			
		||||
    code_loginFail: `[UNTRANSLATED] Login failed. Double-check the email and password.`,
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,6 @@ dict = {
 | 
			
		||||
    general_inventoryUpdateNote: `注意:要在游戏中查看更改,您需要重新同步库存,例如使用引导程序的 /sync 命令、访问道场/中继站或重新登录客户端.`,
 | 
			
		||||
    general_addButton: `添加`,
 | 
			
		||||
    general_setButton: `设置`,
 | 
			
		||||
    general_removeButton: `移除`,
 | 
			
		||||
    general_bulkActions: `批量操作`,
 | 
			
		||||
 | 
			
		||||
    code_loginFail: `登录失败.请检查邮箱和密码.`,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user