diff --git a/package-lock.json b/package-lock.json index 45cca760..beab4607 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "morgan": "^1.10.0", "ncp": "^2.0.0", "typescript": "^5.5", - "warframe-public-export-plus": "^0.5.58", + "warframe-public-export-plus": "^0.5.59", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0" @@ -3789,9 +3789,9 @@ } }, "node_modules/warframe-public-export-plus": { - "version": "0.5.58", - "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.58.tgz", - "integrity": "sha512-2G3tKcoblUl7S3Rkk5k/qH+VGZBUmU2QjtIrEO/Bt6UlgO83s648elkNdDKOLBKXnxIsa194nVwz+ci1K86sXg==" + "version": "0.5.59", + "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.59.tgz", + "integrity": "sha512-/SUCVjngVDBz6gahz7CdVLywtHLODL6O5nmNtQcxFDUwrUGnF1lETcG8/UO+WLeGxBVAy4BDPbq+9ZWlYZM4uQ==" }, "node_modules/warframe-riven-info": { "version": "0.1.2", diff --git a/package.json b/package.json index 816118ff..e5deb9bc 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "morgan": "^1.10.0", "ncp": "^2.0.0", "typescript": "^5.5", - "warframe-public-export-plus": "^0.5.58", + "warframe-public-export-plus": "^0.5.59", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0" diff --git a/src/controllers/api/inboxController.ts b/src/controllers/api/inboxController.ts index 4c3b8830..7adc2d3d 100644 --- a/src/controllers/api/inboxController.ts +++ b/src/controllers/api/inboxController.ts @@ -13,6 +13,7 @@ import { addItems, combineInventoryChanges, getInventory } from "@/src/services/ import { logger } from "@/src/utils/logger"; import { ExportFlavour, ExportGear } from "warframe-public-export-plus"; import { handleStoreItemAcquisition } from "@/src/services/purchaseService"; +import { fromStoreItem, isStoreItem } from "@/src/services/itemDataService"; export const inboxController: RequestHandler = async (req, res) => { const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query; @@ -48,7 +49,7 @@ export const inboxController: RequestHandler = async (req, res) => { await addItems( inventory, attachmentItems.map(attItem => ({ - ItemType: attItem, + ItemType: isStoreItem(attItem) ? fromStoreItem(attItem) : attItem, ItemCount: attItem in ExportGear ? (ExportGear[attItem].purchaseQuantity ?? 1) : 1 })), inventoryChanges diff --git a/src/controllers/api/inventoryController.ts b/src/controllers/api/inventoryController.ts index ab739c4c..e9b15a63 100644 --- a/src/controllers/api/inventoryController.ts +++ b/src/controllers/api/inventoryController.ts @@ -18,10 +18,12 @@ import { addMiscItems, allDailyAffiliationKeys, cleanupInventory, - createLibraryDailyTask + createLibraryDailyTask, + generateRewardSeed } from "@/src/services/inventoryService"; import { logger } from "@/src/utils/logger"; import { catBreadHash } from "@/src/helpers/stringHelpers"; +import { Types } from "mongoose"; export const inventoryController: RequestHandler = async (request, response) => { const accountId = await getAccountIdForRequest(request); @@ -87,7 +89,7 @@ export const inventoryController: RequestHandler = async (request, response) => cleanupInventory(inventory); inventory.NextRefill = new Date((Math.trunc(Date.now() / 86400000) + 1) * 86400000); - await inventory.save(); + //await inventory.save(); } if ( @@ -96,9 +98,20 @@ export const inventoryController: RequestHandler = async (request, response) => new Date() >= inventory.InfestedFoundry.AbilityOverrideUnlockCooldown ) { handleSubsumeCompletion(inventory); - await inventory.save(); + //await inventory.save(); } + if (inventory.LastInventorySync) { + const lastSyncDuviriMood = Math.trunc(inventory.LastInventorySync.getTimestamp().getTime() / 7200000); + const currentDuviriMood = Math.trunc(Date.now() / 7200000); + if (lastSyncDuviriMood != currentDuviriMood) { + logger.debug(`refreshing duviri seed`); + inventory.DuviriInfo.Seed = generateRewardSeed(); + } + } + inventory.LastInventorySync = new Types.ObjectId(); + await inventory.save(); + response.json(await getInventoryResponse(inventory, "xpBasedLevelCapDisabled" in request.query)); }; @@ -274,7 +287,7 @@ export const getInventoryResponse = async ( } // Omitting this field so opening the navigation resyncs the inventory which is more desirable for typical usage. - //inventoryResponse.LastInventorySync = toOid(new Types.ObjectId()); + inventoryResponse.LastInventorySync = undefined; // Set 2FA enabled so trading post can be used inventoryResponse.HWIDProtectEnabled = true; diff --git a/src/controllers/api/nemesisController.ts b/src/controllers/api/nemesisController.ts index 252092f5..60b02a17 100644 --- a/src/controllers/api/nemesisController.ts +++ b/src/controllers/api/nemesisController.ts @@ -2,9 +2,12 @@ import { consumeModCharge, encodeNemesisGuess, getInfNodes, + getKnifeUpgrade, getNemesisPasscode, + getNemesisPasscodeModTypes, getWeaponsForManifest, - IKnifeResponse + IKnifeResponse, + showdownNodes } from "@/src/helpers/nemesisHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { Loadout } from "@/src/models/inventoryModels/loadoutModel"; @@ -15,6 +18,8 @@ import { IMongoDate, IOid } from "@/src/types/commonTypes"; import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IInnateDamageFingerprint, + IInventoryClient, + INemesisClient, InventorySlot, IUpgradeClient, IWeaponSkinClient, @@ -100,50 +105,45 @@ export const nemesisController: RequestHandler = async (req, res) => { encodeNemesisGuess(guess[0], result1, guess[1], result2, guess[2], result3) ); - // Increase antivirus - let antivirusGain = 5; - const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!; - const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid); - const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0; - const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!; + // Increase antivirus if correct antivirus mod is installed const response: IKnifeResponse = {}; - for (const upgrade of body.knife!.AttachedUpgrades) { - switch (upgrade.ItemType) { - case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod": - antivirusGain += 10; - consumeModCharge(response, inventory, upgrade, dataknifeUpgrades); - break; - case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndWeaponDamageOnUseMod": - antivirusGain += 10; - consumeModCharge(response, inventory, upgrade, dataknifeUpgrades); - break; - case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusLargeOnSingleUseMod": // Instant Secure - antivirusGain += 15; - consumeModCharge(response, inventory, upgrade, dataknifeUpgrades); - break; - case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusOnUseMod": // Immuno Shield - antivirusGain += 15; - consumeModCharge(response, inventory, upgrade, dataknifeUpgrades); - break; - case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod": - antivirusGain += 10; - consumeModCharge(response, inventory, upgrade, dataknifeUpgrades); - break; + if (result1 == 0 || result2 == 0 || result3 == 0) { + let antivirusGain = 5; + const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!; + const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid); + const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0; + const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!; + for (const upgrade of body.knife!.AttachedUpgrades) { + switch (upgrade.ItemType) { + case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod": + antivirusGain += 10; + consumeModCharge(response, inventory, upgrade, dataknifeUpgrades); + break; + case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndWeaponDamageOnUseMod": + antivirusGain += 10; + consumeModCharge(response, inventory, upgrade, dataknifeUpgrades); + break; + case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusLargeOnSingleUseMod": // Instant Secure + antivirusGain += 15; + consumeModCharge(response, inventory, upgrade, dataknifeUpgrades); + break; + case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusOnUseMod": // Immuno Shield + antivirusGain += 15; + consumeModCharge(response, inventory, upgrade, dataknifeUpgrades); + break; + case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod": + antivirusGain += 10; + consumeModCharge(response, inventory, upgrade, dataknifeUpgrades); + break; + } } + inventory.Nemesis!.HenchmenKilled += antivirusGain; } - inventory.Nemesis!.HenchmenKilled += antivirusGain; + if (inventory.Nemesis!.HenchmenKilled >= 100) { inventory.Nemesis!.HenchmenKilled = 100; - inventory.Nemesis!.InfNodes = [ - { - Node: "CrewBattleNode559", - Influence: 1 - } - ]; - inventory.Nemesis!.Weakened = true; - } else { - inventory.Nemesis!.InfNodes = getInfNodes("FC_INFESTATION", 0); } + inventory.Nemesis!.InfNodes = getInfNodes("FC_INFESTATION", 0); await inventory.save(); res.json(response); @@ -213,6 +213,38 @@ export const nemesisController: RequestHandler = async (req, res) => { res.json({ target: inventory.toJSON().Nemesis }); + } else if ((req.query.mode as string) == "w") { + const inventory = await getInventory( + accountId, + "Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades" + ); + //const body = getJSONfromString(String(req.body)); + + inventory.Nemesis!.InfNodes = [ + { + Node: showdownNodes[inventory.Nemesis!.Faction], + Influence: 1 + } + ]; + inventory.Nemesis!.Weakened = true; + + const response: IKnifeResponse & { target: INemesisClient } = { + target: inventory.toJSON().Nemesis! + }; + + // Consume charge of the correct requiem mod(s) + const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!; + const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid); + const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0; + const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!; + const modTypes = getNemesisPasscodeModTypes(inventory.Nemesis!); + for (const modType of modTypes) { + const upgrade = getKnifeUpgrade(inventory, dataknifeUpgrades, modType); + consumeModCharge(response, inventory, upgrade, dataknifeUpgrades); + } + + await inventory.save(); + res.json(response); } else { logger.debug(`data provided to ${req.path}: ${String(req.body)}`); throw new Error(`unknown nemesis mode: ${String(req.query.mode)}`); @@ -264,12 +296,19 @@ interface INemesisRequiemRequest { guess: number; // grn/crp: 4 bits | coda: 3x 4 bits position: number; // grn/crp: 0-2 | coda: 0 // knife field provided for coda only - knife?: { - Item: IEquipmentClient; - Skins: IWeaponSkinClient[]; - ModSlot: number; - CustSlot: number; - AttachedUpgrades: IUpgradeClient[]; - HiddenWhenHolstered: boolean; - }; + knife?: IKnife; +} + +// interface INemesisWeakenRequest { +// target: INemesisClient; +// knife: IKnife; +// } + +interface IKnife { + Item: IEquipmentClient; + Skins: IWeaponSkinClient[]; + ModSlot: number; + CustSlot: number; + AttachedUpgrades: IUpgradeClient[]; + HiddenWhenHolstered: boolean; } diff --git a/src/helpers/nemesisHelpers.ts b/src/helpers/nemesisHelpers.ts index 6543813f..3a146831 100644 --- a/src/helpers/nemesisHelpers.ts +++ b/src/helpers/nemesisHelpers.ts @@ -1,12 +1,14 @@ import { ExportRegions, ExportWarframes } from "warframe-public-export-plus"; -import { IInfNode } from "@/src/types/inventoryTypes/inventoryTypes"; -import { SRng } from "@/src/services/rngService"; +import { IInfNode, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes"; +import { getRewardAtPercentage, SRng } from "@/src/services/rngService"; import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel"; import { logger } from "../utils/logger"; import { IOid } from "../types/commonTypes"; import { Types } from "mongoose"; -import { addMods } from "../services/inventoryService"; +import { addMods, generateRewardSeed } from "../services/inventoryService"; import { isArchwingMission } from "../services/worldStateService"; +import { fromStoreItem, toStoreItem } from "../services/itemDataService"; +import { createMessage } from "../services/inboxService"; export const getInfNodes = (faction: string, rank: number): IInfNode[] => { const infNodes = []; @@ -38,17 +40,59 @@ const systemIndexes: Record = { FC_INFESTATION: [23] }; +export const showdownNodes: Record = { + FC_GRINEER: "CrewBattleNode557", + FC_CORPUS: "CrewBattleNode558", + FC_INFESTATION: "CrewBattleNode559" +}; + // Get a parazon 'passcode' based on the nemesis fingerprint so it's always the same for the same nemesis. export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: string }): number[] => { const rng = new SRng(nemesis.fp); - const passcode = [rng.randomInt(0, 7)]; + const choices = [0, 1, 2, 3, 5, 6, 7]; + let choiceIndex = rng.randomInt(0, choices.length - 1); + const passcode = [choices[choiceIndex]]; if (nemesis.Faction != "FC_INFESTATION") { - passcode.push(rng.randomInt(0, 7)); - passcode.push(rng.randomInt(0, 7)); + choices.splice(choiceIndex, 1); + choiceIndex = rng.randomInt(0, choices.length - 1); + passcode.push(choices[choiceIndex]); + + choices.splice(choiceIndex, 1); + choiceIndex = rng.randomInt(0, choices.length - 1); + passcode.push(choices[choiceIndex]); } return passcode; }; +const reqiuemMods: readonly string[] = [ + "/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod", + "/Lotus/Upgrades/Mods/Immortal/ImmortalTwoMod", + "/Lotus/Upgrades/Mods/Immortal/ImmortalThreeMod", + "/Lotus/Upgrades/Mods/Immortal/ImmortalFourMod", + "/Lotus/Upgrades/Mods/Immortal/ImmortalFiveMod", + "/Lotus/Upgrades/Mods/Immortal/ImmortalSixMod", + "/Lotus/Upgrades/Mods/Immortal/ImmortalSevenMod", + "/Lotus/Upgrades/Mods/Immortal/ImmortalEightMod" +]; + +const antivirusMods: readonly string[] = [ + "/Lotus/Upgrades/Mods/Immortal/AntivirusOneMod", + "/Lotus/Upgrades/Mods/Immortal/AntivirusTwoMod", + "/Lotus/Upgrades/Mods/Immortal/AntivirusThreeMod", + "/Lotus/Upgrades/Mods/Immortal/AntivirusFourMod", + "/Lotus/Upgrades/Mods/Immortal/AntivirusFiveMod", + "/Lotus/Upgrades/Mods/Immortal/AntivirusSixMod", + "/Lotus/Upgrades/Mods/Immortal/AntivirusSevenMod", + "/Lotus/Upgrades/Mods/Immortal/AntivirusEightMod" +]; + +export const getNemesisPasscodeModTypes = (nemesis: { fp: bigint; Faction: string }): string[] => { + const passcode = getNemesisPasscode(nemesis); + return nemesis.Faction == "FC_INFESTATION" + ? passcode.map(i => antivirusMods[i]) + : passcode.map(i => reqiuemMods[i]); +}; + export const encodeNemesisGuess = ( symbol1: number, result1: number, @@ -79,6 +123,31 @@ export interface IKnifeResponse { HasKnife?: boolean; } +export const getKnifeUpgrade = ( + inventory: TInventoryDatabaseDocument, + dataknifeUpgrades: string[], + type: string +): { ItemId: IOid; ItemType: string } => { + if (dataknifeUpgrades.indexOf(type) != -1) { + return { + ItemId: { $oid: "000000000000000000000000" }, + ItemType: type + }; + } + for (const upgradeId of dataknifeUpgrades) { + if (upgradeId.length == 24) { + const upgrade = inventory.Upgrades.id(upgradeId); + if (upgrade && upgrade.ItemType == type) { + return { + ItemId: { $oid: upgradeId }, + ItemType: type + }; + } + } + } + throw new Error(`${type} does not seem to be installed on parazon?!`); +}; + export const consumeModCharge = ( response: IKnifeResponse, inventory: TInventoryDatabaseDocument, @@ -177,7 +246,6 @@ export const getWeaponsForManifest = (manifest: string): readonly string[] => { throw new Error(`unknown nemesis manifest: ${manifest}`); }; -// TODO: This sucks. export const getInnateDamageTag = ( KillingSuit: string ): @@ -188,78 +256,7 @@ export const getInnateDamageTag = ( | "InnateMagDamage" | "InnateRadDamage" | "InnateToxinDamage" => { - const baseSuitType = ExportWarframes[KillingSuit].parentName; - switch (baseSuitType) { - case "/Lotus/Powersuits/Volt/VoltBaseSuit": - case "/Lotus/Powersuits/Excalibur/ExcaliburBaseSuit": - case "/Lotus/Powersuits/AntiMatter/NovaBaseSuit": - case "/Lotus/Powersuits/Banshee/BansheeBaseSuit": - case "/Lotus/Powersuits/Berserker/BerserkerBaseSuit": - case "/Lotus/Powersuits/Magician/MagicianBaseSuit": - case "/Lotus/Powersuits/Sentient/SentientBaseSuit": - case "/Lotus/Powersuits/Gyre/GyreBaseSuit": - return "InnateElectricityDamage"; - case "/Lotus/Powersuits/Ember/EmberBaseSuit": - case "/Lotus/Powersuits/Dragon/DragonBaseSuit": - case "/Lotus/Powersuits/Nezha/NezhaBaseSuit": - case "/Lotus/Powersuits/Sandman/SandmanBaseSuit": - case "/Lotus/Powersuits/Trapper/TrapperBaseSuit": - case "/Lotus/Powersuits/Wisp/WispBaseSuit": - case "/Lotus/Powersuits/Odalisk/OdaliskBaseSuit": - case "/Lotus/Powersuits/PaxDuviricus/PaxDuviricusBaseSuit": - case "/Lotus/Powersuits/Choir/ChoirBaseSuit": - case "/Lotus/Powersuits/Temple/TempleBaseSuit": - return "InnateHeatDamage"; - case "/Lotus/Powersuits/Frost/FrostBaseSuit": - case "/Lotus/Powersuits/Glass/GlassBaseSuit": - case "/Lotus/Powersuits/Fairy/FairyBaseSuit": - case "/Lotus/Powersuits/IronFrame/IronFrameBaseSuit": - case "/Lotus/Powersuits/Revenant/RevenantBaseSuit": - case "/Lotus/Powersuits/Trinity/TrinityBaseSuit": - case "/Lotus/Powersuits/Hoplite/HopliteBaseSuit": - case "/Lotus/Powersuits/Koumei/KoumeiBaseSuit": - return "InnateFreezeDamage"; - case "/Lotus/Powersuits/Saryn/SarynBaseSuit": - case "/Lotus/Powersuits/Paladin/PaladinBaseSuit": - case "/Lotus/Powersuits/Brawler/BrawlerBaseSuit": - case "/Lotus/Powersuits/Infestation/InfestationBaseSuit": - case "/Lotus/Powersuits/Necro/NecroBaseSuit": - case "/Lotus/Powersuits/Khora/KhoraBaseSuit": - case "/Lotus/Powersuits/Ranger/RangerBaseSuit": - case "/Lotus/Powersuits/Dagath/DagathBaseSuit": - return "InnateToxinDamage"; - case "/Lotus/Powersuits/Mag/MagBaseSuit": - case "/Lotus/Powersuits/Pirate/PirateBaseSuit": - case "/Lotus/Powersuits/Cowgirl/CowgirlBaseSuit": - case "/Lotus/Powersuits/Priest/PriestBaseSuit": - case "/Lotus/Powersuits/BrokenFrame/BrokenFrameBaseSuit": - case "/Lotus/Powersuits/Alchemist/AlchemistBaseSuit": - case "/Lotus/Powersuits/Yareli/YareliBaseSuit": - case "/Lotus/Powersuits/Geode/GeodeBaseSuit": - case "/Lotus/Powersuits/Frumentarius/FrumentariusBaseSuit": - return "InnateMagDamage"; - case "/Lotus/Powersuits/Loki/LokiBaseSuit": - case "/Lotus/Powersuits/Ninja/NinjaBaseSuit": - case "/Lotus/Powersuits/Jade/JadeBaseSuit": - case "/Lotus/Powersuits/Bard/BardBaseSuit": - case "/Lotus/Powersuits/Harlequin/HarlequinBaseSuit": - case "/Lotus/Powersuits/Garuda/GarudaBaseSuit": - case "/Lotus/Powersuits/YinYang/YinYangBaseSuit": - case "/Lotus/Powersuits/Werewolf/WerewolfBaseSuit": - case "/Lotus/Powersuits/ConcreteFrame/ConcreteFrameBaseSuit": - return "InnateRadDamage"; - case "/Lotus/Powersuits/Rhino/RhinoBaseSuit": - case "/Lotus/Powersuits/Tengu/TenguBaseSuit": - case "/Lotus/Powersuits/MonkeyKing/MonkeyKingBaseSuit": - case "/Lotus/Powersuits/Runner/RunnerBaseSuit": - case "/Lotus/Powersuits/Pacifist/PacifistBaseSuit": - case "/Lotus/Powersuits/Devourer/DevourerBaseSuit": - case "/Lotus/Powersuits/Wraith/WraithBaseSuit": - case "/Lotus/Powersuits/Pagemaster/PagemasterBaseSuit": - return "InnateImpactDamage"; - } - logger.warn(`unknown innate damage type for ${KillingSuit}, using heat as a fallback`); - return "InnateHeatDamage"; + return ExportWarframes[KillingSuit].nemesisUpgradeTag!; }; // TODO: For -1399275245665749231n, the value should be 75306944, but we're off by 59 with 75307003. @@ -273,3 +270,109 @@ export const getInnateDamageValue = (fp: bigint): number => { } return Math.trunc(value * 0x40000000); }; + +export const getKillTokenRewardCount = (fp: bigint): number => { + const rng = new SRng(fp); + return rng.randomInt(10, 15); +}; + +// /Lotus/Types/Enemies/InfestedLich/InfestedLichRewardManifest +const infestedLichRotA = [ + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyDJRomHuman", probability: 0.046 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyDJRomInfested", probability: 0.045 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyDrillbitHuman", probability: 0.046 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyDrillbitInfested", probability: 0.045 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyHarddriveHuman", probability: 0.046 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyHarddriveInfested", probability: 0.045 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyPacketHuman", probability: 0.046 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyPacketInfested", probability: 0.045 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyZekeHuman", probability: 0.046 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyZekeInfested", probability: 0.045 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandBillboardPosterA", probability: 0.045 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandBillboardPosterB", probability: 0.046 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandDespairPoster", probability: 0.045 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandGridPoster", probability: 0.046 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandHuddlePoster", probability: 0.045 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandJumpPoster", probability: 0.046 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandLimoPoster", probability: 0.045 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandLookingDownPosterDay", probability: 0.046 }, + { + type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandLookingDownPosterNight", + probability: 0.045 + }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandSillyPoster", probability: 0.046 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandWhiteBluePoster", probability: 0.045 }, + { type: "/Lotus/StoreItems/Types/Items/ShipDecos/BoybandPosters/BoybandWhitePinkPoster", probability: 0.045 } +]; +const infestedLichRotB = [ + { type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraA", probability: 0.072 }, + { type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraB", probability: 0.071 }, + { type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraC", probability: 0.072 }, + { type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraD", probability: 0.071 }, + { type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraE", probability: 0.072 }, + { type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraF", probability: 0.071 }, + { type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraG", probability: 0.071 }, + { type: "/Lotus/StoreItems/Upgrades/Skins/Effects/InfestedLichEphemeraH", probability: 0.072 }, + { type: "/Lotus/StoreItems/Types/Items/Emotes/DanceDJRomHype", probability: 0.071 }, + { type: "/Lotus/StoreItems/Types/Items/Emotes/DancePacketWindmillShuffle", probability: 0.072 }, + { type: "/Lotus/StoreItems/Types/Items/Emotes/DanceHarddrivePony", probability: 0.071 }, + { type: "/Lotus/StoreItems/Types/Items/Emotes/DanceDrillbitCrisscross", probability: 0.072 }, + { type: "/Lotus/StoreItems/Types/Items/Emotes/DanceZekeCanthavethis", probability: 0.071 }, + { type: "/Lotus/StoreItems/Types/Items/PhotoBooth/PhotoboothTileRJLasXStadiumBossArena", probability: 0.071 } +]; +export const getInfestedLichItemRewards = (fp: bigint): string[] => { + const rng = new SRng(fp); + const rotAReward = getRewardAtPercentage(infestedLichRotA, rng.randomFloat())!.type; + rng.randomFloat(); // unused afaict + const rotBReward = getRewardAtPercentage(infestedLichRotB, rng.randomFloat())!.type; + return [rotAReward, rotBReward]; +}; + +export const sendCodaFinishedMessage = async ( + inventory: TInventoryDatabaseDocument, + fp: bigint = generateRewardSeed(), + name: string = "ZEKE_BEATWOMAN_TM.1999", + killed: boolean = true +): Promise => { + const att: string[] = []; + + // First vanquish/convert gives a sigil + const sigil = killed + ? "/Lotus/Upgrades/Skins/Sigils/InfLichVanquishedSigil" + : "/Lotus/Upgrades/Skins/Sigils/InfLichConvertedSigil"; + if (!inventory.WeaponSkins.find(x => x.ItemType == sigil)) { + att.push(toStoreItem(sigil)); + } + + const [rotAReward, rotBReward] = getInfestedLichItemRewards(fp); + att.push(fromStoreItem(rotAReward)); + att.push(fromStoreItem(rotBReward)); + + let countedAtt: ITypeCount[] | undefined; + if (killed) { + countedAtt = [ + { + ItemType: "/Lotus/Types/Items/MiscItems/CodaWeaponBucks", + ItemCount: getKillTokenRewardCount(fp) + } + ]; + } + + await createMessage(inventory.accountOwnerId, [ + { + sndr: "/Lotus/Language/Bosses/Ordis", + msg: "/Lotus/Language/Inbox/VanquishBandMsgBody", + arg: [ + { + Key: "LICH_NAME", + Tag: name + } + ], + att: att, + countedAtt: countedAtt, + sub: "/Lotus/Language/Inbox/VanquishBandMsgTitle", + icon: "/Lotus/Interface/Icons/Npcs/Ordis.png", + highPriority: true + } + ]); +}; diff --git a/src/models/inventoryModels/inventoryModel.ts b/src/models/inventoryModels/inventoryModel.ts index 8aa2bc42..dcb5a536 100644 --- a/src/models/inventoryModels/inventoryModel.ts +++ b/src/models/inventoryModels/inventoryModel.ts @@ -1399,7 +1399,7 @@ const inventorySchema = new Schema( //How many Gift do you have left*(gift spends the trade) GiftsRemaining: { type: Number, default: 8 }, //Curent trade info Giving or Getting items - PendingTrades: [Schema.Types.Mixed], + //PendingTrades: [Schema.Types.Mixed], //Syndicate currently being pledged to. SupportedSyndicate: String, @@ -1449,7 +1449,7 @@ const inventorySchema = new Schema( KubrowPetEggs: [kubrowPetEggSchema], //Prints Cat(3 Prints)\Kubrow(2 Prints) Pets - KubrowPetPrints: [Schema.Types.Mixed], + //KubrowPetPrints: [Schema.Types.Mixed], //Item for EquippedGear example:Scaner,LoadoutTechSummon etc Consumables: [typeCountSchema], @@ -1495,7 +1495,7 @@ const inventorySchema = new Schema( //item like DojoKey or Boss missions key LevelKeys: [typeCountSchema], //Active quests - Quests: [Schema.Types.Mixed], + //Quests: [Schema.Types.Mixed], //Cosmetics like profile glyphs\Kavasa Prime Kubrow Collar\Game Theme etc FlavourItems: [FlavourItemSchema], @@ -1534,7 +1534,7 @@ const inventorySchema = new Schema( TauntHistory: { type: [tauntSchema], default: undefined }, //noShow2FA,VisitPrimeVault etc - WebFlags: Schema.Types.Mixed, + //WebFlags: Schema.Types.Mixed, //Id CompletedAlerts CompletedAlerts: [String], @@ -1554,7 +1554,7 @@ const inventorySchema = new Schema( //the color your clan requests like Items/Research/DojoColors/DojoColorPlainsB ActiveDojoColorResearch: String, - SentientSpawnChanceBoosters: Schema.Types.Mixed, + //SentientSpawnChanceBoosters: Schema.Types.Mixed, QualifyingInvasions: [invasionProgressSchema], FactionScores: [Number], @@ -1589,10 +1589,10 @@ const inventorySchema = new Schema( // open location store like EidolonPlainsDiscoverable or OrbVallisCaveDiscoverable DiscoveredMarkers: [discoveredMarkerSchema], //Open location mission like "JobId" + "StageCompletions" - CompletedJobs: [Schema.Types.Mixed], + //CompletedJobs: [Schema.Types.Mixed], //Game mission\ivent score example "Tag": "WaterFight", "Best": 170, "Count": 1258, - PersonalGoalProgress: [Schema.Types.Mixed], + //PersonalGoalProgress: [Schema.Types.Mixed], //Setting interface Style ThemeStyle: String, @@ -1622,13 +1622,13 @@ const inventorySchema = new Schema( LibraryActiveDailyTaskInfo: libraryDailyTaskInfoSchema, //https://warframe.fandom.com/wiki/Invasion - InvasionChainProgress: [Schema.Types.Mixed], + //InvasionChainProgress: [Schema.Types.Mixed], //CorpusLich or GrineerLich NemesisAbandonedRewards: { type: [String], default: [] }, Nemesis: nemesisSchema, NemesisHistory: { type: [nemesisSchema], default: undefined }, - LastNemesisAllySpawnTime: Schema.Types.Mixed, + //LastNemesisAllySpawnTime: Schema.Types.Mixed, //TradingRulesConfirmed,ShowFriendInvNotifications(Option->Social) Settings: settingsSchema, @@ -1642,7 +1642,7 @@ const inventorySchema = new Schema( PlayerSkills: { type: playerSkillsSchema, default: {} }, //TradeBannedUntil data - TradeBannedUntil: Schema.Types.Mixed, + //TradeBannedUntil: Schema.Types.Mixed, //https://warframe.fandom.com/wiki/Helminth InfestedFoundry: infestedFoundrySchema, @@ -1662,23 +1662,24 @@ const inventorySchema = new Schema( //Unknown and system DuviriInfo: DuviriInfoSchema, + LastInventorySync: Schema.Types.ObjectId, Mailbox: MailboxSchema, HandlerPoints: Number, ChallengesFixVersion: { type: Number, default: 6 }, PlayedParkourTutorial: Boolean, - ActiveLandscapeTraps: [Schema.Types.Mixed], - RepVotes: [Schema.Types.Mixed], - LeagueTickets: [Schema.Types.Mixed], + //ActiveLandscapeTraps: [Schema.Types.Mixed], + //RepVotes: [Schema.Types.Mixed], + //LeagueTickets: [Schema.Types.Mixed], HasContributedToDojo: Boolean, HWIDProtectEnabled: Boolean, LoadOutPresets: { type: Schema.Types.ObjectId, ref: "Loadout" }, CurrentLoadOutIds: [oidSchema], RandomUpgradesIdentified: Number, BountyScore: Number, - ChallengeInstanceStates: [Schema.Types.Mixed], + //ChallengeInstanceStates: [Schema.Types.Mixed], RecentVendorPurchases: { type: [recentVendorPurchaseSchema], default: undefined }, - Robotics: [Schema.Types.Mixed], - UsedDailyDeals: [Schema.Types.Mixed], + //Robotics: [Schema.Types.Mixed], + //UsedDailyDeals: [Schema.Types.Mixed], CollectibleSeries: { type: [collectibleEntrySchema], default: undefined }, HasResetAccount: { type: Boolean, default: false }, @@ -1759,6 +1760,9 @@ inventorySchema.set("toJSON", { sn: inventoryDatabase.LockedWeaponGroup.sn ? toOid(inventoryDatabase.LockedWeaponGroup.sn) : undefined }; } + if (inventoryDatabase.LastInventorySync) { + inventoryResponse.LastInventorySync = toOid(inventoryDatabase.LastInventorySync); + } } }); diff --git a/src/services/itemDataService.ts b/src/services/itemDataService.ts index 6e0edd23..3b3e997c 100644 --- a/src/services/itemDataService.ts +++ b/src/services/itemDataService.ts @@ -17,6 +17,7 @@ import { dict_uk, dict_zh, ExportArcanes, + ExportBoosters, ExportCustoms, ExportDrones, ExportGear, @@ -217,15 +218,30 @@ export const convertInboxMessage = (message: IInboxMessage): IMessage => { }; export const isStoreItem = (type: string): boolean => { - return type.startsWith("/Lotus/StoreItems/"); + return type.startsWith("/Lotus/StoreItems/") || type in ExportBoosters; }; export const toStoreItem = (type: string): string => { + if (type.startsWith("/Lotus/Types/StoreItems/Boosters/")) { + const boosterEntry = Object.entries(ExportBoosters).find(arr => arr[1].typeName == type); + if (boosterEntry) { + return boosterEntry[0]; + } + throw new Error(`could not convert ${type} to a store item`); + } return "/Lotus/StoreItems/" + type.substring("/Lotus/".length); }; export const fromStoreItem = (type: string): string => { - return "/Lotus/" + type.substring("/Lotus/StoreItems/".length); + if (type.startsWith("/Lotus/StoreItems/")) { + return "/Lotus/" + type.substring("/Lotus/StoreItems/".length); + } + + if (type in ExportBoosters) { + return ExportBoosters[type].typeName; + } + + throw new Error(`${type} is not a store item`); }; export const getDefaultUpgrades = (parts: string[]): IDefaultUpgrade[] | undefined => { diff --git a/src/services/loginRewardService.ts b/src/services/loginRewardService.ts index 2f42f04b..c897442e 100644 --- a/src/services/loginRewardService.ts +++ b/src/services/loginRewardService.ts @@ -77,7 +77,6 @@ const getRandomLoginReward = (rng: CRng, day: number, inventory: TInventoryDatab const reward = rng.randomReward(randomRewards)!; //const reward = randomRewards.find(x => x.RewardType == "RT_BOOSTER")!; if (reward.RewardType == "RT_RANDOM_RECIPE") { - // Not very faithful implementation but roughly the same idea const masteredItems = new Set(); for (const entry of inventory.XPInfo) { masteredItems.add(entry.ItemType); @@ -95,12 +94,12 @@ const getRandomLoginReward = (rng: CRng, day: number, inventory: TInventoryDatab } const eligibleRecipes: string[] = []; for (const [uniqueName, recipe] of Object.entries(ExportRecipes)) { - if (unmasteredItems.has(recipe.resultType)) { + if (!recipe.excludeFromMarket && unmasteredItems.has(recipe.resultType)) { eligibleRecipes.push(uniqueName); } } if (eligibleRecipes.length == 0) { - // This account has all warframes and weapons already mastered (filthy cheater), need a different reward. + // This account has all applicable warframes and weapons already mastered (filthy cheater), need a different reward. return getRandomLoginReward(rng, day, inventory); } reward.StoreItemType = toStoreItem(rng.randomElement(eligibleRecipes)); diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index 3ab2d680..89b55cea 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -55,7 +55,7 @@ import kuriaMessage50 from "@/static/fixed_responses/kuriaMessages/fiftyPercent. import kuriaMessage75 from "@/static/fixed_responses/kuriaMessages/seventyFivePercent.json"; import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPercent.json"; import conservationAnimals from "@/static/fixed_responses/conservationAnimals.json"; -import { getInfNodes, getWeaponsForManifest } from "@/src/helpers/nemesisHelpers"; +import { getInfNodes, getWeaponsForManifest, sendCodaFinishedMessage } from "@/src/helpers/nemesisHelpers"; import { Loadout } from "../models/inventoryModels/loadoutModel"; import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes"; import { getLiteSortie, getWorldState, idToWeek } from "./worldStateService"; @@ -639,7 +639,10 @@ export const addMissionInventoryUpdates = async ( }); if (value.killed) { - if (value.weaponLoc) { + if ( + value.weaponLoc && + inventory.Nemesis.Faction != "FC_INFESTATION" // weaponLoc is "/Lotus/Language/Weapons/DerelictCernosName" for these for some reason + ) { const weaponType = getWeaponsForManifest(inventory.Nemesis.manifest)[ inventory.Nemesis.WeaponIdx ]; @@ -657,6 +660,11 @@ export const addMissionInventoryUpdates = async ( } } + // TOVERIFY: Is the inbox message also sent when converting a lich? If not, how are the rewards given? + if (inventory.Nemesis.Faction == "FC_INFESTATION") { + await sendCodaFinishedMessage(inventory, inventory.Nemesis.fp, value.nemesisName, value.killed); + } + inventory.Nemesis = undefined; } break; diff --git a/src/services/rngService.ts b/src/services/rngService.ts index bb9028b2..84a4ed8d 100644 --- a/src/services/rngService.ts +++ b/src/services/rngService.ts @@ -18,7 +18,10 @@ export const getRandomInt = (min: number, max: number): number => { return Math.floor(Math.random() * (max - min + 1)) + min; }; -const getRewardAtPercentage = (pool: T[], percentage: number): T | undefined => { +export const getRewardAtPercentage = ( + pool: T[], + percentage: number +): T | undefined => { if (pool.length == 0) return; const totalChance = pool.reduce((accum, item) => accum + item.probability, 0); diff --git a/src/services/serversideVendorsService.ts b/src/services/serversideVendorsService.ts index 1ca1fa2a..d594e79f 100644 --- a/src/services/serversideVendorsService.ts +++ b/src/services/serversideVendorsService.ts @@ -1,4 +1,5 @@ import { unixTimesInMs } from "@/src/constants/timeConstants"; +import { catBreadHash } from "@/src/helpers/stringHelpers"; import { CRng, mixSeeds } from "@/src/services/rngService"; import { IMongoDate } from "@/src/types/commonTypes"; import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes"; @@ -6,7 +7,6 @@ import { ExportVendors, IRange } from "warframe-public-export-plus"; import ArchimedeanVendorManifest from "@/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json"; import DeimosEntratiFragmentVendorProductsManifest from "@/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json"; -import DeimosFishmongerVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosFishmongerVendorManifest.json"; import DeimosHivemindCommisionsManifestFishmonger from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestFishmonger.json"; import DeimosHivemindCommisionsManifestPetVendor from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestPetVendor.json"; import DeimosHivemindCommisionsManifestProspector from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestProspector.json"; @@ -22,12 +22,10 @@ import HubsIronwakeDondaVendorManifest from "@/static/fixed_responses/getVendorI import HubsRailjackCrewMemberVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsRailjackCrewMemberVendorManifest.json"; import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json"; import Nova1999ConquestShopManifest from "@/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json"; -import OstronFishmongerVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronFishmongerVendorManifest.json"; import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json"; import OstronProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronProspectorVendorManifest.json"; import RadioLegionIntermission12VendorManifest from "@/static/fixed_responses/getVendorInfo/RadioLegionIntermission12VendorManifest.json"; import SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json"; -import SolarisFishmongerVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisFishmongerVendorManifest.json"; import SolarisProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisProspectorVendorManifest.json"; import Temple1999VendorManifest from "@/static/fixed_responses/getVendorInfo/Temple1999VendorManifest.json"; import TeshinHardModeVendorManifest from "@/static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json"; @@ -36,7 +34,6 @@ import ZarimanCommisionsManifestArchimedean from "@/static/fixed_responses/getVe const rawVendorManifests: IVendorManifest[] = [ ArchimedeanVendorManifest, DeimosEntratiFragmentVendorProductsManifest, - DeimosFishmongerVendorManifest, DeimosHivemindCommisionsManifestFishmonger, DeimosHivemindCommisionsManifestPetVendor, DeimosHivemindCommisionsManifestProspector, @@ -52,12 +49,10 @@ const rawVendorManifests: IVendorManifest[] = [ HubsRailjackCrewMemberVendorManifest, MaskSalesmanManifest, Nova1999ConquestShopManifest, - OstronFishmongerVendorManifest, OstronPetVendorManifest, OstronProspectorVendorManifest, RadioLegionIntermission12VendorManifest, SolarisDebtTokenVendorRepossessionsManifest, - SolarisFishmongerVendorManifest, SolarisProspectorVendorManifest, Temple1999VendorManifest, TeshinHardModeVendorManifest, // uses preprocessing @@ -87,17 +82,11 @@ const generatableVendors: IGeneratableVendorInfo[] = [ cycleOffset: 1744934400_000, cycleDuration: 4 * unixTimesInMs.day }, - { - _id: { $oid: "5be4a159b144f3cdf1c22efa" }, - TypeName: "/Lotus/Types/Game/VendorManifests/Solaris/DebtTokenVendorManifest", - RandomSeedType: "VRST_FLAVOUR_TEXT", - cycleDuration: unixTimesInMs.hour - }, { _id: { $oid: "61ba123467e5d37975aeeb03" }, TypeName: "/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest", RandomSeedType: "VRST_FLAVOUR_TEXT", - cycleDuration: unixTimesInMs.week + cycleDuration: unixTimesInMs.week // TODO: Auto-detect this based on the items, so we don't need to specify it explicitly. } // { // _id: { $oid: "5dbb4c41e966f7886c3ce939" }, @@ -105,6 +94,10 @@ const generatableVendors: IGeneratableVendorInfo[] = [ // } ]; +const getVendorOid = (typeName: string): string => { + return "5be4a159b144f3cd" + catBreadHash(typeName).toString(16).padStart(8, "0"); +}; + export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | undefined => { for (const vendorManifest of rawVendorManifests) { if (vendorManifest.VendorInfo.TypeName == typeName) { @@ -116,6 +109,14 @@ export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | return generateVendorManifest(vendorInfo); } } + if (typeName in ExportVendors) { + return generateVendorManifest({ + _id: { $oid: getVendorOid(typeName) }, + TypeName: typeName, + RandomSeedType: ExportVendors[typeName].randomSeedType, + cycleDuration: unixTimesInMs.hour + }); + } return undefined; }; @@ -130,6 +131,17 @@ export const getVendorManifestByOid = (oid: string): IVendorManifest | undefined return generateVendorManifest(vendorInfo); } } + for (const [typeName, manifest] of Object.entries(ExportVendors)) { + const typeNameOid = getVendorOid(typeName); + if (typeNameOid == oid) { + return generateVendorManifest({ + _id: { $oid: typeNameOid }, + TypeName: typeName, + RandomSeedType: manifest.randomSeedType, + cycleDuration: unixTimesInMs.hour + }); + } + } return undefined; }; @@ -195,7 +207,7 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani const rng = new CRng(mixSeeds(vendorSeed, cycleIndex)); const manifest = ExportVendors[vendorInfo.TypeName]; const offersToAdd = []; - if (manifest.numItems && manifest.numItems.minValue != manifest.numItems.maxValue) { + if (manifest.numItems && !manifest.isOneBinPerCycle) { const numItemsTarget = rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue); while (processed.ItemManifest.length + offersToAdd.length < numItemsTarget) { // TODO: Consider per-bin item limits @@ -263,6 +275,13 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani ) * rawItem.credits.step; item.RegularPrice = [value, value]; } + if (rawItem.platinum) { + const value = + typeof rawItem.platinum == "number" + ? rawItem.platinum + : rng.randomInt(rawItem.platinum.minValue, rawItem.platinum.maxValue); + item.PremiumPrice = [value, value]; + } if (vendorInfo.RandomSeedType) { item.LocTagRandSeed = (rng.randomInt(0, 0xffff) << 16) | rng.randomInt(0, 0xffff); if (vendorInfo.RandomSeedType == "VRST_WEAPON") { diff --git a/src/types/inventoryTypes/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts index e294e1ce..4973ff87 100644 --- a/src/types/inventoryTypes/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -52,6 +52,7 @@ export interface IInventoryDatabase | "LastLiteSortieReward" | "CrewMembers" | "QualifyingInvasions" + | "LastInventorySync" | TEquipmentKey >, InventoryDatabaseEquipment { @@ -89,6 +90,7 @@ export interface IInventoryDatabase LastLiteSortieReward?: ILastSortieRewardDatabase[]; CrewMembers: ICrewMemberDatabase[]; QualifyingInvasions: IInvasionProgressDatabase[]; + LastInventorySync?: Types.ObjectId; } export interface IQuestKeyDatabase { @@ -258,7 +260,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu EquippedGear: string[]; DeathMarks: string[]; FusionTreasures: IFusionTreasure[]; - WebFlags: IWebFlags; + //WebFlags: IWebFlags; CompletedAlerts: string[]; Consumables: ITypeCount[]; LevelKeys: ITypeCount[]; @@ -268,10 +270,10 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu KubrowPetEggs?: IKubrowPetEggClient[]; LoreFragmentScans: ILoreFragmentScan[]; EquippedEmotes: string[]; - PendingTrades: IPendingTrade[]; + //PendingTrades: IPendingTrade[]; Boosters: IBooster[]; ActiveDojoColorResearch: string; - SentientSpawnChanceBoosters: ISentientSpawnChanceBoosters; + //SentientSpawnChanceBoosters: ISentientSpawnChanceBoosters; SupportedSyndicate?: string; Affiliations: IAffiliation[]; QualifyingInvasions: IInvasionProgressClient[]; @@ -293,19 +295,19 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu ActiveAvatarImageType: string; ShipDecorations: ITypeCount[]; DiscoveredMarkers: IDiscoveredMarker[]; - CompletedJobs: ICompletedJob[]; + //CompletedJobs: ICompletedJob[]; FocusAbility?: string; FocusUpgrades: IFocusUpgrade[]; HasContributedToDojo?: boolean; HWIDProtectEnabled?: boolean; - KubrowPetPrints: IKubrowPetPrint[]; + //KubrowPetPrints: IKubrowPetPrint[]; AlignmentReplay?: IAlignment; - PersonalGoalProgress: IPersonalGoalProgress[]; + //PersonalGoalProgress: IPersonalGoalProgress[]; ThemeStyle: string; ThemeBackground: string; ThemeSounds: string; BountyScore: number; - ChallengeInstanceStates: IChallengeInstanceState[]; + //ChallengeInstanceStates: IChallengeInstanceState[]; LoginMilestoneRewards: string[]; RecentVendorPurchases?: IRecentVendorPurchaseClient[]; NodeIntrosCompleted: string[]; @@ -313,17 +315,17 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu CompletedJobChains?: ICompletedJobChain[]; SeasonChallengeHistory: ISeasonChallenge[]; EquippedInstrument?: string; - InvasionChainProgress: IInvasionChainProgress[]; + //InvasionChainProgress: IInvasionChainProgress[]; Nemesis?: INemesisClient; NemesisHistory?: INemesisBaseClient[]; - LastNemesisAllySpawnTime?: IMongoDate; + //LastNemesisAllySpawnTime?: IMongoDate; Settings?: ISettings; PersonalTechProjects: IPersonalTechProjectClient[]; PlayerSkills: IPlayerSkills; CrewShipAmmo: ITypeCount[]; CrewShipWeaponSkins: IUpgradeClient[]; CrewShipSalvagedWeaponSkins: IUpgradeClient[]; - TradeBannedUntil?: IMongoDate; + //TradeBannedUntil?: IMongoDate; PlayedParkourTutorial: boolean; SubscribedToEmailsPersonalized: number; InfestedFoundry?: IInfestedFoundryClient; @@ -333,17 +335,17 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu LotusCustomization?: ILotusCustomization; UseAdultOperatorLoadout?: boolean; NemesisAbandonedRewards: string[]; - LastInventorySync: IOid; + LastInventorySync?: IOid; NextRefill?: IMongoDate; FoundToday?: IMiscItem[]; // for Argon Crystals CustomMarkers?: ICustomMarkers[]; - ActiveLandscapeTraps: any[]; + //ActiveLandscapeTraps: any[]; EvolutionProgress?: IEvolutionProgress[]; - RepVotes: any[]; - LeagueTickets: any[]; - Quests: any[]; - Robotics: any[]; - UsedDailyDeals: any[]; + //RepVotes: any[]; + //LeagueTickets: any[]; + //Quests: any[]; + //Robotics: any[]; + //UsedDailyDeals: any[]; LibraryPersonalTarget?: string; LibraryPersonalProgress: ILibraryPersonalProgress[]; CollectibleSeries?: ICollectibleEntry[]; diff --git a/static/fixed_responses/getVendorInfo/DeimosFishmongerVendorManifest.json b/static/fixed_responses/getVendorInfo/DeimosFishmongerVendorManifest.json deleted file mode 100644 index ac6c0951..00000000 --- a/static/fixed_responses/getVendorInfo/DeimosFishmongerVendorManifest.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "5f456e01c96976e97d6b8016" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Deimos/FishmongerVendorManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Deimos/FishParts/DeimosOrokinFishAPartItem", - "PremiumPrice": [9, 9], - "Bin": "BIN_1", - "QuantityMultiplier": 10, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e91b9" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishDPartItem", - "PremiumPrice": [17, 17], - "Bin": "BIN_0", - "QuantityMultiplier": 20, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e91ba" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishCPartItem", - "PremiumPrice": [10, 10], - "Bin": "BIN_1", - "QuantityMultiplier": 20, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e91bb" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishBPartItem", - "PremiumPrice": [6, 6], - "Bin": "BIN_0", - "QuantityMultiplier": 20, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e91bc" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishAPartItem", - "PremiumPrice": [5, 5], - "Bin": "BIN_0", - "QuantityMultiplier": 20, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e91bd" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Deimos/FishParts/DeimosGenericSharedFishPartItem", - "PremiumPrice": [7, 7], - "Bin": "BIN_0", - "QuantityMultiplier": 20, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e91be" - } - } - ], - "PropertyTextHash": "6DF13A7FB573C25B4B4F989CBEFFC615", - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - } - } -} diff --git a/static/fixed_responses/getVendorInfo/OstronFishmongerVendorManifest.json b/static/fixed_responses/getVendorInfo/OstronFishmongerVendorManifest.json deleted file mode 100644 index c6a3670b..00000000 --- a/static/fixed_responses/getVendorInfo/OstronFishmongerVendorManifest.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "59d6e27ebcc718474eb17115" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Ostron/FishmongerVendorManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Eidolon/FishParts/DayUncommonFishAPartItem", - "PremiumPrice": [14, 14], - "Bin": "BIN_1", - "QuantityMultiplier": 10, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9808" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Eidolon/FishParts/BothUncommonFishBPartItem", - "PremiumPrice": [12, 12], - "Bin": "BIN_1", - "QuantityMultiplier": 10, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9809" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Eidolon/FishParts/DayCommonFishCPartItem", - "PremiumPrice": [8, 8], - "Bin": "BIN_0", - "QuantityMultiplier": 20, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e980a" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Eidolon/FishParts/DayCommonFishBPartItem", - "PremiumPrice": [7, 7], - "Bin": "BIN_0", - "QuantityMultiplier": 20, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e980b" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Eidolon/FishParts/DayCommonFishAPartItem", - "PremiumPrice": [10, 10], - "Bin": "BIN_0", - "QuantityMultiplier": 20, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e980c" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Eidolon/FishParts/BothCommonFishBPartItem", - "PremiumPrice": [8, 8], - "Bin": "BIN_0", - "QuantityMultiplier": 20, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e980d" - } - } - ], - "PropertyTextHash": "CC3B9DAFB38F412998E90A41421A8986", - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - } - } -} diff --git a/static/fixed_responses/getVendorInfo/SolarisFishmongerVendorManifest.json b/static/fixed_responses/getVendorInfo/SolarisFishmongerVendorManifest.json deleted file mode 100644 index 4a4dcb64..00000000 --- a/static/fixed_responses/getVendorInfo/SolarisFishmongerVendorManifest.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "5b0de8556df82a56ea9bae82" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Solaris/FishmongerVendorManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Solaris/FishParts/CorpusFishThermalLaserItem", - "PremiumPrice": [15, 15], - "Bin": "BIN_1", - "QuantityMultiplier": 10, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9515" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Solaris/FishParts/CorpusFishVenedoCaseItem", - "PremiumPrice": [8, 8], - "Bin": "BIN_0", - "QuantityMultiplier": 20, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9516" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Solaris/FishParts/SolarisFishDissipatorCoilItem", - "PremiumPrice": [18, 18], - "Bin": "BIN_0", - "QuantityMultiplier": 20, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9517" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Solaris/FishParts/CorpusFishExaBrainItem", - "PremiumPrice": [5, 5], - "Bin": "BIN_0", - "QuantityMultiplier": 20, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9518" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Solaris/FishParts/CorpusFishAnoscopicSensorItem", - "PremiumPrice": [5, 5], - "Bin": "BIN_0", - "QuantityMultiplier": 20, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9519" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Fish/Solaris/FishParts/GenericFishScrapItem", - "PremiumPrice": [5, 5], - "Bin": "BIN_0", - "QuantityMultiplier": 20, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e951a" - } - } - ], - "PropertyTextHash": "946131D0CF5CDF7C2C03BB967DE0DF49", - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - } - } -} diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index f7deed8e..59c906c7 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -34,8 +34,8 @@ dict = { code_rerollsNumber: `Anzahl der Umrollversuche`, code_viewStats: `Statistiken anzeigen`, code_rank: `Rang`, - code_rankUp: `[UNTRANSLATED] Rank up`, - code_rankDown: `[UNTRANSLATED] Rank down`, + code_rankUp: `Rang erhöhen`, + code_rankDown: `Rang verringern`, code_count: `Anzahl`, code_focusAllUnlocked: `Alle Fokus-Schulen sind bereits freigeschaltet.`, code_focusUnlocked: `|COUNT| neue Fokus-Schulen freigeschaltet! Ein Inventar-Update wird benötigt, damit die Änderungen im Spiel sichtbar werden. Die Sternenkarte zu besuchen, sollte der einfachste Weg sein, dies auszulösen.`, @@ -86,21 +86,21 @@ dict = { inventory_hoverboards: `K-Drives`, inventory_moaPets: `Moas`, inventory_kubrowPets: `Bestien`, - inventory_evolutionProgress: `[UNTRANSLATED] Incarnon Evolution Progress`, + inventory_evolutionProgress: `Incarnon-Entwicklungsfortschritte`, inventory_bulkAddSuits: `Fehlende Warframes hinzufügen`, inventory_bulkAddWeapons: `Fehlende Waffen hinzufügen`, inventory_bulkAddSpaceSuits: `Fehlende Archwings hinzufügen`, inventory_bulkAddSpaceWeapons: `Fehlende Archwing-Waffen hinzufügen`, inventory_bulkAddSentinels: `Fehlende Wächter hinzufügen`, inventory_bulkAddSentinelWeapons: `Fehlende Wächter-Waffen hinzufügen`, - inventory_bulkAddEvolutionProgress: `[UNTRANSLATED] Add Missing Incarnon Evolution Progress`, + inventory_bulkAddEvolutionProgress: `Fehlende Incarnon-Entwicklungsfortschritte hinzufügen`, inventory_bulkRankUpSuits: `Alle Warframes auf Max. Rang`, inventory_bulkRankUpWeapons: `Alle Waffen auf Max. Rang`, inventory_bulkRankUpSpaceSuits: `Alle Archwings auf Max. Rang`, inventory_bulkRankUpSpaceWeapons: `Alle Archwing-Waffen auf Max. Rang`, inventory_bulkRankUpSentinels: `Alle Wächter auf Max. Rang`, inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`, - inventory_bulkRankUpEvolutionProgress: `[UNTRANSLATED] Max Rank All Incarnon Evolution Progress`, + inventory_bulkRankUpEvolutionProgress: `Alle Incarnon-Entwicklungsfortschritte auf Max. Rang`, quests_list: `Quests`, quests_completeAll: `Alle Quests abschließen`, @@ -120,9 +120,9 @@ dict = { mods_fingerprintHelp: `Benötigst du Hilfe mit dem Fingerabdruck?`, mods_rivens: `Rivens`, mods_mods: `Mods`, - mods_addMissingUnrankedMods: `[UNTRANSLATED] Add Missing Unranked Mods`, + mods_addMissingUnrankedMods: `Fehlende Mods ohne Rang hinzufügen`, mods_removeUnranked: `Mods ohne Rang entfernen`, - mods_addMissingMaxRankMods: `[UNTRANSLATED] Add Missing Max Rank Mods`, + mods_addMissingMaxRankMods: `Fehlende Mods mit Max. Rang hinzufügen`, cheats_administratorRequirement: `Du musst Administrator sein, um diese Funktion nutzen zu können. Um Administrator zu werden, füge |DISPLAYNAME| zu administratorNames in der config.json hinzu.`, cheats_server: `Server`, cheats_skipTutorial: `Tutorial überspringen`, @@ -134,7 +134,7 @@ dict = { cheats_infiniteEndo: `Unendlich Endo`, cheats_infiniteRegalAya: `Unendlich Reines Aya`, cheats_infiniteHelminthMaterials: `Unendlich Helminth-Materialien`, - cheats_dontSubtractConsumables: `[UNTRANSLATED] Don't Subtract Consumables`, + cheats_dontSubtractConsumables: `Verbrauchsgegenstände (Ausrüstung) nicht verbrauchen`, cheats_unlockAllShipFeatures: `Alle Schiffs-Funktionen freischalten`, cheats_unlockAllShipDecorations: `Alle Schiffsdekorationen freischalten`, cheats_unlockAllFlavourItems: `Alle Sammlerstücke freischalten`, @@ -154,7 +154,7 @@ dict = { cheats_noKimCooldowns: `Keine Wartezeit bei KIM`, cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`, cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`, - cheats_skipClanKeyCrafting: `[UNTRANSLATED] Skip Clan Key Crafting`, + cheats_skipClanKeyCrafting: `Clan-Schlüsselherstellung überspringen`, cheats_noDojoRoomBuildStage: `Kein Dojo-Raum-Bauvorgang`, cheats_noDojoDecoBuildStage: `Kein Dojo-Deko-Bauvorgang`, cheats_fastDojoRoomDestruction: `Schnelle Dojo-Raum-Zerstörung`,