From fcc11206ccd9d76c87d2d45e66b0360f4471b05f Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 13 Jun 2025 04:46:04 -0700 Subject: [PATCH 001/153] fix: multiple syndicate level ups (#2152) Regression from 54a73ad5d7eab867a1701ccf66d56446db96c226 because I forgot that levelIncrease could now be >1 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2152 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/syndicateSacrificeController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/api/syndicateSacrificeController.ts b/src/controllers/api/syndicateSacrificeController.ts index 64b46bbd..36b6e262 100644 --- a/src/controllers/api/syndicateSacrificeController.ts +++ b/src/controllers/api/syndicateSacrificeController.ts @@ -37,7 +37,7 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp // Process sacrifices and rewards for every level we're reaching const manifest = ExportSyndicates[data.AffiliationTag]; - for (let level = oldLevel + levelIncrease; level <= data.SacrificeLevel; ++level) { + for (let level = oldLevel + Math.min(levelIncrease, 1); level <= data.SacrificeLevel; ++level) { let sacrifice: ISyndicateSacrifice | undefined; if (level == 0) { sacrifice = manifest.initiationSacrifice; From 71d1b6094c60be7b9f20bd64338ca44cc072b6c4 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 13 Jun 2025 04:46:18 -0700 Subject: [PATCH 002/153] feat: randomise classic bounty xpAmounts (#2150) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2150 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/worldStateService.ts | 60 +++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/src/services/worldStateService.ts b/src/services/worldStateService.ts index e2041907..c5e30868 100644 --- a/src/services/worldStateService.ts +++ b/src/services/worldStateService.ts @@ -453,13 +453,37 @@ const pushWeeklyActs = ( } }; +const generateXpAmounts = (rng: SRng, stageCount: number, minXp: number, maxXp: number): number[] => { + const step = minXp < 1000 ? 1 : 10; + const totalDeciXp = rng.randomInt(minXp / step, maxXp / step); + const xpAmounts: number[] = []; + if (stageCount < 4) { + const perStage = Math.ceil(totalDeciXp / stageCount) * step; + for (let i = 0; i != stageCount; ++i) { + xpAmounts.push(perStage); + } + } else { + const perStage = Math.ceil(Math.round(totalDeciXp * 0.667) / (stageCount - 1)) * step; + for (let i = 0; i != stageCount - 1; ++i) { + xpAmounts.push(perStage); + } + xpAmounts.push(Math.ceil(totalDeciXp * 0.332) * step); + } + return xpAmounts; +}; +// Test vectors: +//console.log(generateXpAmounts(new SRng(1337n), 5, 5000, 5000)); // [840, 840, 840, 840, 1660] +//console.log(generateXpAmounts(new SRng(1337n), 3, 40, 40)); // [14, 14, 14] +//console.log(generateXpAmounts(new SRng(1337n), 5, 150, 150)); // [25, 25, 25, 25, 50] +//console.log(generateXpAmounts(new SRng(1337n), 4, 10, 10)); // [2, 2, 2, 4] +//console.log(generateXpAmounts(new SRng(1337n), 4, 15, 15)); // [4, 4, 4, 5] +//console.log(generateXpAmounts(new SRng(1337n), 4, 20, 20)); // [5, 5, 5, 7] + export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], bountyCycle: number): void => { const table = String.fromCharCode(65 + (bountyCycle % 3)); const vaultTable = String.fromCharCode(65 + ((bountyCycle + 1) % 3)); const deimosDTable = String.fromCharCode(65 + (bountyCycle % 2)); - // TODO: xpAmounts need to be calculated based on the jobType somehow? - const seed = new SRng(bountyCycle).randomInt(0, 100_000); const bountyCycleStart = bountyCycle * 9000000; const bountyCycleEnd = bountyCycleStart + 9000000; @@ -482,7 +506,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], masteryReq: 0, minEnemyLevel: 5, maxEnemyLevel: 15, - xpAmounts: [430, 430, 430] + xpAmounts: generateXpAmounts(rng, 3, 1000, 1500) }, { jobType: rng.randomElement(eidolonJobs), @@ -490,7 +514,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], masteryReq: 1, minEnemyLevel: 10, maxEnemyLevel: 30, - xpAmounts: [620, 620, 620] + xpAmounts: generateXpAmounts(rng, 3, 1750, 2250) }, { jobType: rng.randomElement(eidolonJobs), @@ -498,7 +522,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], masteryReq: 2, minEnemyLevel: 20, maxEnemyLevel: 40, - xpAmounts: [670, 670, 670, 990] + xpAmounts: generateXpAmounts(rng, 4, 2500, 3000) }, { jobType: rng.randomElement(eidolonJobs), @@ -506,7 +530,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], masteryReq: 3, minEnemyLevel: 30, maxEnemyLevel: 50, - xpAmounts: [570, 570, 570, 570, 1110] + xpAmounts: generateXpAmounts(rng, 5, 3250, 3750) }, { jobType: rng.randomElement(eidolonJobs), @@ -514,7 +538,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], masteryReq: 5, minEnemyLevel: 40, maxEnemyLevel: 60, - xpAmounts: [740, 740, 740, 740, 1450] + xpAmounts: generateXpAmounts(rng, 5, 4000, 4500) }, { jobType: rng.randomElement(eidolonJobs), @@ -530,7 +554,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], masteryReq: 0, minEnemyLevel: 50, maxEnemyLevel: 70, - xpAmounts: [840, 840, 840, 840, 1650] + xpAmounts: generateXpAmounts(rng, 5, 4500, 5000) } ] }); @@ -554,7 +578,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], masteryReq: 0, minEnemyLevel: 5, maxEnemyLevel: 15, - xpAmounts: [340, 340, 340] + xpAmounts: generateXpAmounts(rng, 3, 1000, 1500) }, { jobType: rng.randomElement(venusJobs), @@ -562,7 +586,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], masteryReq: 1, minEnemyLevel: 10, maxEnemyLevel: 30, - xpAmounts: [660, 660, 660] + xpAmounts: generateXpAmounts(rng, 3, 1750, 2250) }, { jobType: rng.randomElement(venusJobs), @@ -570,7 +594,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], masteryReq: 2, minEnemyLevel: 20, maxEnemyLevel: 40, - xpAmounts: [610, 610, 610, 900] + xpAmounts: generateXpAmounts(rng, 4, 2500, 3000) }, { jobType: rng.randomElement(venusJobs), @@ -578,7 +602,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], masteryReq: 3, minEnemyLevel: 30, maxEnemyLevel: 50, - xpAmounts: [600, 600, 600, 600, 1170] + xpAmounts: generateXpAmounts(rng, 5, 3250, 3750) }, { jobType: rng.randomElement(venusJobs), @@ -586,7 +610,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], masteryReq: 5, minEnemyLevel: 40, maxEnemyLevel: 60, - xpAmounts: [690, 690, 690, 690, 1350] + xpAmounts: generateXpAmounts(rng, 5, 4000, 4500) }, { jobType: rng.randomElement(venusJobs), @@ -602,7 +626,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], masteryReq: 0, minEnemyLevel: 50, maxEnemyLevel: 70, - xpAmounts: [780, 780, 780, 780, 1540] + xpAmounts: generateXpAmounts(rng, 5, 4500, 5000) } ] }); @@ -626,7 +650,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], masteryReq: 0, minEnemyLevel: 5, maxEnemyLevel: 15, - xpAmounts: [5, 5, 5] + xpAmounts: generateXpAmounts(rng, 3, 12, 18) }, { jobType: rng.randomElement(microplanetJobs), @@ -634,7 +658,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], masteryReq: 1, minEnemyLevel: 15, maxEnemyLevel: 25, - xpAmounts: [12, 12, 12] + xpAmounts: generateXpAmounts(rng, 3, 24, 36) }, { jobType: rng.randomElement(microplanetEndlessJobs), @@ -651,7 +675,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], masteryReq: 2, minEnemyLevel: 30, maxEnemyLevel: 40, - xpAmounts: [17, 17, 17, 25] + xpAmounts: generateXpAmounts(rng, 4, 72, 88) }, { jobType: rng.randomElement(microplanetJobs), @@ -659,7 +683,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[], masteryReq: 3, minEnemyLevel: 40, maxEnemyLevel: 60, - xpAmounts: [22, 22, 22, 22, 43] + xpAmounts: generateXpAmounts(rng, 5, 115, 135) }, { jobType: rng.randomElement(microplanetJobs), From 071ef528ea75adcab760869caea4f274630ba991 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 13 Jun 2025 10:58:41 -0700 Subject: [PATCH 003/153] fix: syndicate rank up from negative levels (#2156) For level <= 0, SacrificeLevel is the current level. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2156 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/syndicateSacrificeController.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/api/syndicateSacrificeController.ts b/src/controllers/api/syndicateSacrificeController.ts index 36b6e262..cdd640d0 100644 --- a/src/controllers/api/syndicateSacrificeController.ts +++ b/src/controllers/api/syndicateSacrificeController.ts @@ -31,7 +31,7 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp AffiliationTag: data.AffiliationTag, InventoryChanges: {}, Level: data.SacrificeLevel, - LevelIncrease: levelIncrease, + LevelIncrease: data.SacrificeLevel < 0 ? 1 : levelIncrease, NewEpisodeReward: false }; @@ -94,7 +94,7 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp } // Commit - syndicate.Title = data.SacrificeLevel; + syndicate.Title = data.SacrificeLevel < 0 ? data.SacrificeLevel + 1 : data.SacrificeLevel; await inventory.save(); response.json(res); From 8b27fcf459fc549d22b18a3e07d185d1ad4cff06 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 13 Jun 2025 19:57:19 -0700 Subject: [PATCH 004/153] chore(webui): add archon crystal upgrades to translation system (#2154) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2154 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- .../custom/getItemListsController.ts | 3 - .../webuiArchonCrystalUpgrades.json | 83 ------------------- static/webui/script.js | 80 ++++++++++++++++++ static/webui/translations/de.js | 52 ++++++++++++ static/webui/translations/en.js | 52 ++++++++++++ static/webui/translations/es.js | 52 ++++++++++++ static/webui/translations/fr.js | 52 ++++++++++++ static/webui/translations/ru.js | 52 ++++++++++++ static/webui/translations/zh.js | 52 ++++++++++++ 9 files changed, 392 insertions(+), 86 deletions(-) delete mode 100644 static/fixed_responses/webuiArchonCrystalUpgrades.json diff --git a/src/controllers/custom/getItemListsController.ts b/src/controllers/custom/getItemListsController.ts index a9f8095f..f804f81c 100644 --- a/src/controllers/custom/getItemListsController.ts +++ b/src/controllers/custom/getItemListsController.ts @@ -20,7 +20,6 @@ import { ExportWeapons, TRelicQuality } from "warframe-public-export-plus"; -import archonCrystalUpgrades from "@/static/fixed_responses/webuiArchonCrystalUpgrades.json"; import allIncarnons from "@/static/fixed_responses/allIncarnonList.json"; interface ListedItem { @@ -36,7 +35,6 @@ interface ListedItem { } interface ItemLists { - archonCrystalUpgrades: Record; uniqueLevelCaps: Record; Suits: ListedItem[]; LongGuns: ListedItem[]; @@ -69,7 +67,6 @@ const relicQualitySuffixes: Record = { const getItemListsController: RequestHandler = (req, response) => { const lang = getDict(typeof req.query.lang == "string" ? req.query.lang : "en"); const res: ItemLists = { - archonCrystalUpgrades, uniqueLevelCaps: ExportMisc.uniqueLevelCaps, Suits: [], LongGuns: [], diff --git a/static/fixed_responses/webuiArchonCrystalUpgrades.json b/static/fixed_responses/webuiArchonCrystalUpgrades.json deleted file mode 100644 index c23ba978..00000000 --- a/static/fixed_responses/webuiArchonCrystalUpgrades.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeEquilibrium": "+20% Energy from Health pickups, +20% Health from Energy pickups", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeEquilibriumMythic": "+30% Energy from Health pickups, +30% Health from Energy pickups", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeMeleeCritDamage": "+25% Melee Critical Damage", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeMeleeCritDamageMythic": "+37.5% Melee Critical Damage", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradePrimaryStatusChance": "+25% Primary Status Chance", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradePrimaryStatusChanceMythic": "+37.5% Primary Status Chance", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeSecondaryCritChance": "+25% Secondary Critical Chance", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeSecondaryCritChanceMythic": "+37.5% Secondary Critical Chance", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityDuration": "+10% Ability Duration", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityDurationMythic": "+15% Ability Duration", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityStrength": "+10% Ability Strength", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityStrengthMythic": "+15% Ability Strength", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeArmourMax": "+150 Armor", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeArmourMaxMythic": "+225 Armor", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeBlastProc": "+5 Shields on inflicting Blast Status", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeBlastProcMythic": "+7.5 Shields on inflicting Blast Status", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCastingSpeed": "+25% Casting Speed", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCastingSpeedMythic": "+37.5% Casting Speed", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveDamageBoost": "+10% Ability Damage on enemies affected by Corrosion Status", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveDamageBoostMythic": "+15% Ability Damage on enemies affected by Corrosion Status", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveStack": "Increase max stacks of Corrosion Status by +2", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveStackMythic": "Increase max stacks of Corrosion Status by +3", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCritDamageBoost": "+25% Melee Critical Damage (Doubles over 500 Energy)", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCritDamageBoostMythic": "+37% Melee Critical Damage (Doubles over 500 Energy)", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamage": "+30% Primary Electricity Damage (+10% per additional Shard)", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamageMythic": "+45% Primary Electricity Damage (+15% per additional Shard)", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamageBoost": "+10% Ability Damage on enemies affected by Electricity Status", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamageBoostMythic": "+15% Ability Damage on enemies affected by Electricity Status", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeEnergyMax": "+50 Energy Max", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeEnergyMaxMythic": "+75 Energy Max", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectEnergy": "+50% Energy Orb Effectiveness", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectEnergyMythic": "+75% Energy Orb Effectiveness", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectHealth": "+100% Health Orb Effectiveness", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectHealthMythic": "+150% Health Orb Effectiveness", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHealthMax": "+150 Health", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHealthMaxMythic": "+225 Health", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHPBoostFromImpact": "+1 Health per enemy killed with Blast Damage (Max 300 Health)", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHPBoostFromImpactMythic": "+2 Health per enemy killed with Blast Damage (Max 450 Health)", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeParkourVelocity": "+15% Parkour Velocity", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeParkourVelocityMythic": "+22.5% Parkour Velocity", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRadiationDamageBoost": "+10% Ability Damage on enemies affected by Radiation Status", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRadiationDamageBoostMythic": "+15% Ability Damage on enemies affected by Radiation Status", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRegen": "+5 Health Regen/s", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRegenMythic": "+7.5 Health Regen/s", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeShieldMax": "+150 Shield", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeShieldMaxMythic": "+225 Shield", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeStartingEnergy": "+30% Energy on Spawn", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeStartingEnergyMythic": "+45% Energy on Spawn", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinDamage": "+30% Toxin Status Effect Damage", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinDamageMythic": "+45% Toxin Status Effect Damage", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinHeal": "+2 Health on damaging enemies with Toxin Status", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinHealMythic": "+3 Health on damaging enemies with Toxin Status", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWeaponCritBoostFromHeat": "+1% Secondary Critical Chance per Heat-affected enemy killed (Max 50%)", - "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWeaponCritBoostFromHeatMythic": "+1.5% Secondary Critical Chance per Heat-affected enemy killed (Max 75%)", - - "/Lotus/Upgrades/Mods/Warframe/AvatarAbilityRangeMod": "+7.5% Ability Range", - "/Lotus/Upgrades/Mods/Warframe/AvatarAbilityEfficiencyMod": "+5% Ability Efficiency", - "/Lotus/Upgrades/Mods/Warframe/AvatarEnergyRegenMod": "+0.5 Energy Regen/s", - "/Lotus/Upgrades/Mods/Warframe/AvatarEnemyRadarMod": "+5m Enemy Radar", - "/Lotus/Upgrades/Mods/Warframe/AvatarLootRadarMod": "+7m Loot Radar", - - "/Lotus/Upgrades/Mods/Rifle/WeaponAmmoMaxMod": "+15% Ammo Max", - - "/Lotus/Upgrades/Mods/Aura/EnemyArmorReductionAuraMod": "-3% Enemy Armor", - - "/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionAmmoMod": "100% Primary and Secondary Magazine Refill on Mercy", - "/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionHealthDropMod": "100% chance to drop a Health Orb on Mercy", - "/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionEnergyDropMod": "50% chance to drop an Energy Orb on Mercy", - "/Lotus/Upgrades/Mods/DataSpike/Cipher/OnFailHackResetMod": "+50% to retry on Hacking failure", - "/Lotus/Upgrades/Mods/DataSpike/Cipher/DamageReductionOnHackMod": "75% Damage Reduction while Hacking", - "/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionReviveCompanionMod": "Mercy Kills reduce Companion Recovery by 15s", - "/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionParkourSpeedMod": "+60% Parkour Speed after a Mercy for 15s", - "/Lotus/Upgrades/Mods/Warframe/AvatarTimeLimitIncreaseMod": "+8s to Hacking", - "/Lotus/Upgrades/Mods/DataSpike/Cipher/ElectrifyOnHackMod": "Shock enemies within 20m while Hacking", - "/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionTerrifyMod": "50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy", - "/Lotus/Upgrades/Mods/DataSpike/Cipher/OnHackLockersMod": "Unlock 5 lockers within 20m after Hacking", - "/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionBlindMod": "Blind enemies within 18m on Mercy", - "/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionDrainPowerMod": "100% chance for next ability cast to gain +50% Ability Strength on Mercy", - "/Lotus/Upgrades/Mods/DataSpike/Cipher/OnHackSprintSpeedMod": "+75% Sprint Speed for 15s after Hacking", - "/Lotus/Upgrades/Mods/DataSpike/Assassin/SwiftExecuteMod": "Speed of Mercy Kills increased by 50%", - "/Lotus/Upgrades/Mods/DataSpike/Cipher/OnHackInvisMod": "Invisible for 15 seconds after hacking" -} diff --git a/static/webui/script.js b/static/webui/script.js index ee16ffdf..5cc9feaf 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -213,6 +213,86 @@ function fetchItemList() { document.getElementById("changeSyndicate").innerHTML = ""; document.getElementById("changeSyndicate").appendChild(syndicateNone); + // prettier-ignore + data.archonCrystalUpgrades = { + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeEquilibrium": loc("upgrade_Equilibrium").split("|VAL|").join("20"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeEquilibriumMythic": loc("upgrade_Equilibrium").split("|VAL|").join("30"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeMeleeCritDamage": loc("upgrade_MeleeCritDamage").split("|VAL|").join("25"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeMeleeCritDamageMythic": loc("upgrade_MeleeCritDamage").split("|VAL|").join("37.5"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradePrimaryStatusChance": loc("upgrade_PrimaryStatusChance").split("|VAL|").join("25"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradePrimaryStatusChanceMythic": loc("upgrade_PrimaryStatusChance").split("|VAL|").join("37.5"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeSecondaryCritChance": loc("upgrade_SecondaryCritChance").split("|VAL|").join("25"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeSecondaryCritChanceMythic": loc("upgrade_SecondaryCritChance").split("|VAL|").join("37.5"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityDuration": loc("upgrade_WarframeAbilityDuration").split("|VAL|").join("10"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityDurationMythic": loc("upgrade_WarframeAbilityDuration").split("|VAL|").join("15"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityStrength": loc("upgrade_WarframeAbilityStrength").split("|VAL|").join("10"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityStrengthMythic": loc("upgrade_WarframeAbilityStrength").split("|VAL|").join("15"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeArmourMax": loc("upgrade_WarframeArmourMax").split("|VAL|").join("150"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeArmourMaxMythic": loc("upgrade_WarframeArmourMax").split("|VAL|").join("225"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeBlastProc": loc("upgrade_WarframeBlastProc").split("|VAL|").join("5"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeBlastProcMythic": loc("upgrade_WarframeBlastProc").split("|VAL|").join("7.5"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCastingSpeed": loc("upgrade_WarframeCastingSpeed").split("|VAL|").join("25"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCastingSpeedMythic": loc("upgrade_WarframeCastingSpeed").split("|VAL|").join("37.5"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveDamageBoost": loc("upgrade_WarframeCorrosiveDamageBoost").split("|VAL|").join("10"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveDamageBoostMythic": loc("upgrade_WarframeCorrosiveDamageBoost").split("|VAL|").join("15"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveStack": loc("upgrade_WarframeCorrosiveStack").split("|VAL|").join("2"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveStackMythic": loc("upgrade_WarframeCorrosiveStack").split("|VAL|").join("3"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCritDamageBoost": loc("upgrade_WarframeCritDamageBoost").split("|VAL|").join("25"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCritDamageBoostMythic": loc("upgrade_WarframeCritDamageBoost").split("|VAL|").join("37"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamage": loc("upgrade_WarframeElectricDamage").split("|VAL1|").join("30").split("|VAL2|").join("10"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamageMythic": loc("upgrade_WarframeElectricDamage").split("|VAL1|").join("45").split("|VAL2|").join("15"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamageBoost": loc("upgrade_WarframeElectricDamageBoost").split("|VAL|").join("10"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamageBoostMythic": loc("upgrade_WarframeElectricDamageBoost").split("|VAL|").join("15"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeEnergyMax": loc("upgrade_WarframeEnergyMax").split("|VAL|").join("50"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeEnergyMaxMythic": loc("upgrade_WarframeEnergyMax").split("|VAL|").join("75"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectEnergy": loc("upgrade_WarframeGlobeEffectEnergy").split("|VAL|").join("50"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectEnergyMythic": loc("upgrade_WarframeGlobeEffectEnergy").split("|VAL|").join("75"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectHealth": loc("upgrade_WarframeGlobeEffectHealth").split("|VAL|").join("100"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectHealthMythic": loc("upgrade_WarframeGlobeEffectHealth").split("|VAL|").join("150"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHealthMax": loc("upgrade_WarframeHealthMax").split("|VAL|").join("150"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHealthMaxMythic": loc("upgrade_WarframeHealthMax").split("|VAL|").join("225"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHPBoostFromImpact": loc("upgrade_WarframeHPBoostFromImpact").split("|VAL1|").join("1").split("|VAL2|").join("300"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHPBoostFromImpactMythic": loc("upgrade_WarframeHPBoostFromImpact").split("|VAL1|").join("2").split("|VAL2|").join("450"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeParkourVelocity": loc("upgrade_WarframeParkourVelocity").split("|VAL|").join("15"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeParkourVelocityMythic": loc("upgrade_WarframeParkourVelocity").split("|VAL|").join("22.5"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRadiationDamageBoost": loc("upgrade_WarframeRadiationDamageBoost").split("|VAL|").join("10"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRadiationDamageBoostMythic": loc("upgrade_WarframeRadiationDamageBoost").split("|VAL|").join("15"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRegen": loc("upgrade_WarframeRegen").split("|VAL|").join("5"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRegenMythic": loc("upgrade_WarframeRegen").split("|VAL|").join("7.5"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeShieldMax": loc("upgrade_WarframeShieldMax").split("|VAL|").join("150"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeShieldMaxMythic": loc("upgrade_WarframeShieldMax").split("|VAL|").join("225"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeStartingEnergy": loc("upgrade_WarframeStartingEnergy").split("|VAL|").join("30"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeStartingEnergyMythic": loc("upgrade_WarframeStartingEnergy").split("|VAL|").join("45"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinDamage": loc("upgrade_WarframeToxinDamage").split("|VAL|").join("30"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinDamageMythic": loc("upgrade_WarframeToxinDamage").split("|VAL|").join("45"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinHeal": loc("upgrade_WarframeToxinHeal").split("|VAL|").join("2"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinHealMythic": loc("upgrade_WarframeToxinHeal").split("|VAL|").join("3"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWeaponCritBoostFromHeat": loc("upgrade_WeaponCritBoostFromHeat").split("|VAL1|").join("1").split("|VAL2|").join("50"), + "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWeaponCritBoostFromHeatMythic": loc("upgrade_WeaponCritBoostFromHeat").split("|VAL1|").join("1.5").split("|VAL2|").join("75"), + "/Lotus/Upgrades/Mods/Warframe/AvatarAbilityRangeMod": loc("upgrade_AvatarAbilityRange"), + "/Lotus/Upgrades/Mods/Warframe/AvatarAbilityEfficiencyMod": loc("upgrade_AvatarAbilityEfficiency"), + "/Lotus/Upgrades/Mods/Warframe/AvatarEnergyRegenMod": loc("upgrade_AvatarEnergyRegen"), + "/Lotus/Upgrades/Mods/Warframe/AvatarEnemyRadarMod": loc("upgrade_AvatarEnemyRadar"), + "/Lotus/Upgrades/Mods/Warframe/AvatarLootRadarMod": loc("upgrade_AvatarLootRadar"), + "/Lotus/Upgrades/Mods/Rifle/WeaponAmmoMaxMod": loc("upgrade_WeaponAmmoMax"), + "/Lotus/Upgrades/Mods/Aura/EnemyArmorReductionAuraMod": loc("upgrade_EnemyArmorReductionAura"), + "/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionAmmoMod": loc("upgrade_OnExecutionAmmo"), + "/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionHealthDropMod": loc("upgrade_OnExecutionHealthDrop"), + "/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionEnergyDropMod": loc("upgrade_OnExecutionEnergyDrop"), + "/Lotus/Upgrades/Mods/DataSpike/Cipher/OnFailHackResetMod": loc("upgrade_OnFailHackReset"), + "/Lotus/Upgrades/Mods/DataSpike/Cipher/DamageReductionOnHackMod": loc("upgrade_DamageReductionOnHack"), + "/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionReviveCompanionMod": loc("upgrade_OnExecutionReviveCompanion"), + "/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionParkourSpeedMod": loc("upgrade_OnExecutionParkourSpeed"), + "/Lotus/Upgrades/Mods/Warframe/AvatarTimeLimitIncreaseMod": loc("upgrade_AvatarTimeLimitIncrease"), + "/Lotus/Upgrades/Mods/DataSpike/Cipher/ElectrifyOnHackMod": loc("upgrade_ElectrifyOnHack"), + "/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionTerrifyMod": loc("upgrade_OnExecutionTerrify"), + "/Lotus/Upgrades/Mods/DataSpike/Cipher/OnHackLockersMod": loc("upgrade_OnHackLockers"), + "/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionBlindMod": loc("upgrade_OnExecutionBlind"), + "/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionDrainPowerMod": loc("upgrade_OnExecutionDrainPower"), + "/Lotus/Upgrades/Mods/DataSpike/Cipher/OnHackSprintSpeedMod": loc("upgrade_OnHackSprintSpeed"), + "/Lotus/Upgrades/Mods/DataSpike/Assassin/SwiftExecuteMod": loc("upgrade_SwiftExecute"), + "/Lotus/Upgrades/Mods/DataSpike/Cipher/OnHackInvisMod": loc("upgrade_OnHackInvis"), + }; window.archonCrystalUpgrades = data.archonCrystalUpgrades; // Add mods mising in data sources diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index db9492b3..4a3cd271 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -175,5 +175,57 @@ dict = { cheats_none: `Keines`, import_importNote: `Du kannst hier eine vollständige oder teilweise Inventarantwort (Client-Darstellung) einfügen. Alle Felder, die vom Importer unterstützt werden, werden in deinem Account überschrieben.`, import_submit: `Absenden`, + + upgrade_Equilibrium: `[UNTRANSLATED] +|VAL|% Energy from Health pickups, +|VAL|% Health from Energy pickups`, + upgrade_MeleeCritDamage: `[UNTRANSLATED] +|VAL|% Melee Critical Damage`, + upgrade_PrimaryStatusChance: `[UNTRANSLATED] +|VAL|% Primary Status Chance`, + upgrade_SecondaryCritChance: `[UNTRANSLATED] +|VAL|% Secondary Critical Chance`, + upgrade_WarframeAbilityDuration: `[UNTRANSLATED] +|VAL|% Ability Duration`, + upgrade_WarframeAbilityStrength: `[UNTRANSLATED] +|VAL|% Ability Strength`, + upgrade_WarframeArmourMax: `[UNTRANSLATED] +|VAL| Armor`, + upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on inflicting Blast Status`, + upgrade_WarframeCastingSpeed: `[UNTRANSLATED] +|VAL|% Casting Speed`, + upgrade_WarframeCorrosiveDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Corrosion Status`, + upgrade_WarframeCorrosiveStack: `[UNTRANSLATED] Increase max stacks of Corrosion Status by +|VAL|`, + upgrade_WarframeCritDamageBoost: `[UNTRANSLATED] +|VAL|% Melee Critical Damage (Doubles over 500 Energy)`, + upgrade_WarframeElectricDamage: `[UNTRANSLATED] +|VAL1|% Primary Electricity Damage (+|VAL2|% per additional Shard)`, + upgrade_WarframeElectricDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Electricity Status`, + upgrade_WarframeEnergyMax: `[UNTRANSLATED] +|VAL| Energy Max`, + upgrade_WarframeGlobeEffectEnergy: `[UNTRANSLATED] +|VAL|% Energy Orb Effectiveness`, + upgrade_WarframeGlobeEffectHealth: `[UNTRANSLATED] +|VAL|% Health Orb Effectiveness`, + upgrade_WarframeHealthMax: `[UNTRANSLATED] +|VAL| Health`, + upgrade_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health per enemy killed with Blast Damage (Max |VAL2| Health)`, + upgrade_WarframeParkourVelocity: `[UNTRANSLATED] +|VAL|% Parkour Velocity`, + upgrade_WarframeRadiationDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Radiation Status`, + upgrade_WarframeRegen: `[UNTRANSLATED] +|VAL| Health Regen/s`, + upgrade_WarframeShieldMax: `[UNTRANSLATED] +|VAL| Shield`, + upgrade_WarframeStartingEnergy: `[UNTRANSLATED] +|VAL|% Energy on Spawn`, + upgrade_WarframeToxinDamage: `[UNTRANSLATED] +|VAL|% Toxin Status Effect Damage`, + upgrade_WarframeToxinHeal: `[UNTRANSLATED] +|VAL| Health on damaging enemies with Toxin Status`, + upgrade_WeaponCritBoostFromHeat: `[UNTRANSLATED] +|VAL1|% Secondary Critical Chance per Heat-affected enemy killed (Max |VAL2|%)`, + upgrade_AvatarAbilityRange: `[UNTRANSLATED] +7.5% Ability Range`, + upgrade_AvatarAbilityEfficiency: `[UNTRANSLATED] +5% Ability Efficiency`, + upgrade_AvatarEnergyRegen: `[UNTRANSLATED] +0.5 Energy Regen/s`, + upgrade_AvatarEnemyRadar: `[UNTRANSLATED] +5m Enemy Radar`, + upgrade_AvatarLootRadar: `[UNTRANSLATED] +7m Loot Radar`, + upgrade_WeaponAmmoMax: `[UNTRANSLATED] +15% Ammo Max`, + upgrade_EnemyArmorReductionAura: `[UNTRANSLATED] -3% Enemy Armor`, + upgrade_OnExecutionAmmo: `[UNTRANSLATED] 100% Primary and Secondary Magazine Refill on Mercy`, + upgrade_OnExecutionHealthDrop: `[UNTRANSLATED] 100% chance to drop a Health Orb on Mercy`, + upgrade_OnExecutionEnergyDrop: `[UNTRANSLATED] 50% chance to drop an Energy Orb on Mercy`, + upgrade_OnFailHackReset: `[UNTRANSLATED] +50% to retry on Hacking failure`, + upgrade_DamageReductionOnHack: `[UNTRANSLATED] 75% Damage Reduction while Hacking`, + upgrade_OnExecutionReviveCompanion: `[UNTRANSLATED] Mercy Kills reduce Companion Recovery by 15s`, + upgrade_OnExecutionParkourSpeed: `[UNTRANSLATED] +60% Parkour Speed after a Mercy for 15s`, + upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] s to Hacking`, + upgrade_ElectrifyOnHack: `[UNTRANSLATED] Shock enemies within 20m while Hacking`, + upgrade_OnExecutionTerrify: `[UNTRANSLATED] 50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`, + upgrade_OnHackLockers: `[UNTRANSLATED] Unlock 5 lockers within 20m after Hacking`, + upgrade_OnExecutionBlind: `[UNTRANSLATED] Blind enemies within 18m on Mercy`, + upgrade_OnExecutionDrainPower: `[UNTRANSLATED] 100% chance for next ability cast to gain +50% Ability Strength on Mercy`, + upgrade_OnHackSprintSpeed: `[UNTRANSLATED] +75% Sprint Speed for 15s after Hacking`, + upgrade_SwiftExecute: `[UNTRANSLATED] Speed of Mercy Kills increased by 50%`, + upgrade_OnHackInvis: `[UNTRANSLATED] Invisible for 15 seconds after hacking`, + prettier_sucks_ass: `` }; diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index 1cb2b365..070dfa4c 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -174,5 +174,57 @@ dict = { cheats_none: `None`, import_importNote: `You can provide a full or partial inventory response (client respresentation) here. All fields that are supported by the importer will be overwritten in your account.`, import_submit: `Submit`, + + upgrade_Equilibrium: `+|VAL|% Energy from Health pickups, +|VAL|% Health from Energy pickups`, + upgrade_MeleeCritDamage: `+|VAL|% Melee Critical Damage`, + upgrade_PrimaryStatusChance: `+|VAL|% Primary Status Chance`, + upgrade_SecondaryCritChance: `+|VAL|% Secondary Critical Chance`, + upgrade_WarframeAbilityDuration: `+|VAL|% Ability Duration`, + upgrade_WarframeAbilityStrength: `+|VAL|% Ability Strength`, + upgrade_WarframeArmourMax: `+|VAL| Armor`, + upgrade_WarframeBlastProc: `+|VAL| Shields on inflicting Blast Status`, + upgrade_WarframeCastingSpeed: `+|VAL|% Casting Speed`, + upgrade_WarframeCorrosiveDamageBoost: `+|VAL|% Ability Damage on enemies affected by Corrosion Status`, + upgrade_WarframeCorrosiveStack: `Increase max stacks of Corrosion Status by +|VAL|`, + upgrade_WarframeCritDamageBoost: `+|VAL|% Melee Critical Damage (Doubles over 500 Energy)`, + upgrade_WarframeElectricDamage: `+|VAL1|% Primary Electricity Damage (+|VAL2|% per additional Shard)`, + upgrade_WarframeElectricDamageBoost: `+|VAL|% Ability Damage on enemies affected by Electricity Status`, + upgrade_WarframeEnergyMax: `+|VAL| Energy Max`, + upgrade_WarframeGlobeEffectEnergy: `+|VAL|% Energy Orb Effectiveness`, + upgrade_WarframeGlobeEffectHealth: `+|VAL|% Health Orb Effectiveness`, + upgrade_WarframeHealthMax: `+|VAL| Health`, + upgrade_WarframeHPBoostFromImpact: `+|VAL1| Health per enemy killed with Blast Damage (Max |VAL2| Health)`, + upgrade_WarframeParkourVelocity: `+|VAL|% Parkour Velocity`, + upgrade_WarframeRadiationDamageBoost: `+|VAL|% Ability Damage on enemies affected by Radiation Status`, + upgrade_WarframeRegen: `+|VAL| Health Regen/s`, + upgrade_WarframeShieldMax: `+|VAL| Shield`, + upgrade_WarframeStartingEnergy: `+|VAL|% Energy on Spawn`, + upgrade_WarframeToxinDamage: `+|VAL|% Toxin Status Effect Damage`, + upgrade_WarframeToxinHeal: `+|VAL| Health on damaging enemies with Toxin Status`, + upgrade_WeaponCritBoostFromHeat: `+|VAL1|% Secondary Critical Chance per Heat-affected enemy killed (Max |VAL2|%)`, + upgrade_AvatarAbilityRange: `+7.5% Ability Range`, + upgrade_AvatarAbilityEfficiency: `+5% Ability Efficiency`, + upgrade_AvatarEnergyRegen: `+0.5 Energy Regen/s`, + upgrade_AvatarEnemyRadar: `+5m Enemy Radar`, + upgrade_AvatarLootRadar: `+7m Loot Radar`, + upgrade_WeaponAmmoMax: `+15% Ammo Max`, + upgrade_EnemyArmorReductionAura: `-3% Enemy Armor`, + upgrade_OnExecutionAmmo: `100% Primary and Secondary Magazine Refill on Mercy`, + upgrade_OnExecutionHealthDrop: `100% chance to drop a Health Orb on Mercy`, + upgrade_OnExecutionEnergyDrop: `50% chance to drop an Energy Orb on Mercy`, + upgrade_OnFailHackReset: `+50% to retry on Hacking failure`, + upgrade_DamageReductionOnHack: `75% Damage Reduction while Hacking`, + upgrade_OnExecutionReviveCompanion: `Mercy Kills reduce Companion Recovery by 15s`, + upgrade_OnExecutionParkourSpeed: `+60% Parkour Speed after a Mercy for 15s`, + upgrade_AvatarTimeLimitIncrease: `s to Hacking`, + upgrade_ElectrifyOnHack: `Shock enemies within 20m while Hacking`, + upgrade_OnExecutionTerrify: `50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`, + upgrade_OnHackLockers: `Unlock 5 lockers within 20m after Hacking`, + upgrade_OnExecutionBlind: `Blind enemies within 18m on Mercy`, + upgrade_OnExecutionDrainPower: `100% chance for next ability cast to gain +50% Ability Strength on Mercy`, + upgrade_OnHackSprintSpeed: `+75% Sprint Speed for 15s after Hacking`, + upgrade_SwiftExecute: `Speed of Mercy Kills increased by 50%`, + upgrade_OnHackInvis: `Invisible for 15 seconds after hacking`, + prettier_sucks_ass: `` }; diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index 7d57a797..bfe10139 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -175,5 +175,57 @@ dict = { cheats_none: `Ninguno`, import_importNote: `Puedes proporcionar una respuesta de inventario completa o parcial (representación del cliente) aquí. Todos los campos compatibles con el importador serán sobrescritos en tu cuenta.`, import_submit: `Enviar`, + + upgrade_Equilibrium: `[UNTRANSLATED] +|VAL|% Energy from Health pickups, +|VAL|% Health from Energy pickups`, + upgrade_MeleeCritDamage: `[UNTRANSLATED] +|VAL|% Melee Critical Damage`, + upgrade_PrimaryStatusChance: `[UNTRANSLATED] +|VAL|% Primary Status Chance`, + upgrade_SecondaryCritChance: `[UNTRANSLATED] +|VAL|% Secondary Critical Chance`, + upgrade_WarframeAbilityDuration: `[UNTRANSLATED] +|VAL|% Ability Duration`, + upgrade_WarframeAbilityStrength: `[UNTRANSLATED] +|VAL|% Ability Strength`, + upgrade_WarframeArmourMax: `[UNTRANSLATED] +|VAL| Armor`, + upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on inflicting Blast Status`, + upgrade_WarframeCastingSpeed: `[UNTRANSLATED] +|VAL|% Casting Speed`, + upgrade_WarframeCorrosiveDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Corrosion Status`, + upgrade_WarframeCorrosiveStack: `[UNTRANSLATED] Increase max stacks of Corrosion Status by +|VAL|`, + upgrade_WarframeCritDamageBoost: `[UNTRANSLATED] +|VAL|% Melee Critical Damage (Doubles over 500 Energy)`, + upgrade_WarframeElectricDamage: `[UNTRANSLATED] +|VAL1|% Primary Electricity Damage (+|VAL2|% per additional Shard)`, + upgrade_WarframeElectricDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Electricity Status`, + upgrade_WarframeEnergyMax: `[UNTRANSLATED] +|VAL| Energy Max`, + upgrade_WarframeGlobeEffectEnergy: `[UNTRANSLATED] +|VAL|% Energy Orb Effectiveness`, + upgrade_WarframeGlobeEffectHealth: `[UNTRANSLATED] +|VAL|% Health Orb Effectiveness`, + upgrade_WarframeHealthMax: `[UNTRANSLATED] +|VAL| Health`, + upgrade_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health per enemy killed with Blast Damage (Max |VAL2| Health)`, + upgrade_WarframeParkourVelocity: `[UNTRANSLATED] +|VAL|% Parkour Velocity`, + upgrade_WarframeRadiationDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Radiation Status`, + upgrade_WarframeRegen: `[UNTRANSLATED] +|VAL| Health Regen/s`, + upgrade_WarframeShieldMax: `[UNTRANSLATED] +|VAL| Shield`, + upgrade_WarframeStartingEnergy: `[UNTRANSLATED] +|VAL|% Energy on Spawn`, + upgrade_WarframeToxinDamage: `[UNTRANSLATED] +|VAL|% Toxin Status Effect Damage`, + upgrade_WarframeToxinHeal: `[UNTRANSLATED] +|VAL| Health on damaging enemies with Toxin Status`, + upgrade_WeaponCritBoostFromHeat: `[UNTRANSLATED] +|VAL1|% Secondary Critical Chance per Heat-affected enemy killed (Max |VAL2|%)`, + upgrade_AvatarAbilityRange: `[UNTRANSLATED] +7.5% Ability Range`, + upgrade_AvatarAbilityEfficiency: `[UNTRANSLATED] +5% Ability Efficiency`, + upgrade_AvatarEnergyRegen: `[UNTRANSLATED] +0.5 Energy Regen/s`, + upgrade_AvatarEnemyRadar: `[UNTRANSLATED] +5m Enemy Radar`, + upgrade_AvatarLootRadar: `[UNTRANSLATED] +7m Loot Radar`, + upgrade_WeaponAmmoMax: `[UNTRANSLATED] +15% Ammo Max`, + upgrade_EnemyArmorReductionAura: `[UNTRANSLATED] -3% Enemy Armor`, + upgrade_OnExecutionAmmo: `[UNTRANSLATED] 100% Primary and Secondary Magazine Refill on Mercy`, + upgrade_OnExecutionHealthDrop: `[UNTRANSLATED] 100% chance to drop a Health Orb on Mercy`, + upgrade_OnExecutionEnergyDrop: `[UNTRANSLATED] 50% chance to drop an Energy Orb on Mercy`, + upgrade_OnFailHackReset: `[UNTRANSLATED] +50% to retry on Hacking failure`, + upgrade_DamageReductionOnHack: `[UNTRANSLATED] 75% Damage Reduction while Hacking`, + upgrade_OnExecutionReviveCompanion: `[UNTRANSLATED] Mercy Kills reduce Companion Recovery by 15s`, + upgrade_OnExecutionParkourSpeed: `[UNTRANSLATED] +60% Parkour Speed after a Mercy for 15s`, + upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] s to Hacking`, + upgrade_ElectrifyOnHack: `[UNTRANSLATED] Shock enemies within 20m while Hacking`, + upgrade_OnExecutionTerrify: `[UNTRANSLATED] 50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`, + upgrade_OnHackLockers: `[UNTRANSLATED] Unlock 5 lockers within 20m after Hacking`, + upgrade_OnExecutionBlind: `[UNTRANSLATED] Blind enemies within 18m on Mercy`, + upgrade_OnExecutionDrainPower: `[UNTRANSLATED] 100% chance for next ability cast to gain +50% Ability Strength on Mercy`, + upgrade_OnHackSprintSpeed: `[UNTRANSLATED] +75% Sprint Speed for 15s after Hacking`, + upgrade_SwiftExecute: `[UNTRANSLATED] Speed of Mercy Kills increased by 50%`, + upgrade_OnHackInvis: `[UNTRANSLATED] Invisible for 15 seconds after hacking`, + prettier_sucks_ass: `` }; diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index 4f039319..22f2dfc8 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -175,5 +175,57 @@ dict = { cheats_none: `Aucun`, import_importNote: `Import manuel. Toutes les modifcations supportées par l'inventaire écraseront celles présentes dans la base de données.`, import_submit: `Soumettre`, + + upgrade_Equilibrium: `[UNTRANSLATED] +|VAL|% Energy from Health pickups, +|VAL|% Health from Energy pickups`, + upgrade_MeleeCritDamage: `[UNTRANSLATED] +|VAL|% Melee Critical Damage`, + upgrade_PrimaryStatusChance: `[UNTRANSLATED] +|VAL|% Primary Status Chance`, + upgrade_SecondaryCritChance: `[UNTRANSLATED] +|VAL|% Secondary Critical Chance`, + upgrade_WarframeAbilityDuration: `[UNTRANSLATED] +|VAL|% Ability Duration`, + upgrade_WarframeAbilityStrength: `[UNTRANSLATED] +|VAL|% Ability Strength`, + upgrade_WarframeArmourMax: `[UNTRANSLATED] +|VAL| Armor`, + upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on inflicting Blast Status`, + upgrade_WarframeCastingSpeed: `[UNTRANSLATED] +|VAL|% Casting Speed`, + upgrade_WarframeCorrosiveDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Corrosion Status`, + upgrade_WarframeCorrosiveStack: `[UNTRANSLATED] Increase max stacks of Corrosion Status by +|VAL|`, + upgrade_WarframeCritDamageBoost: `[UNTRANSLATED] +|VAL|% Melee Critical Damage (Doubles over 500 Energy)`, + upgrade_WarframeElectricDamage: `[UNTRANSLATED] +|VAL1|% Primary Electricity Damage (+|VAL2|% per additional Shard)`, + upgrade_WarframeElectricDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Electricity Status`, + upgrade_WarframeEnergyMax: `[UNTRANSLATED] +|VAL| Energy Max`, + upgrade_WarframeGlobeEffectEnergy: `[UNTRANSLATED] +|VAL|% Energy Orb Effectiveness`, + upgrade_WarframeGlobeEffectHealth: `[UNTRANSLATED] +|VAL|% Health Orb Effectiveness`, + upgrade_WarframeHealthMax: `[UNTRANSLATED] +|VAL| Health`, + upgrade_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health per enemy killed with Blast Damage (Max |VAL2| Health)`, + upgrade_WarframeParkourVelocity: `[UNTRANSLATED] +|VAL|% Parkour Velocity`, + upgrade_WarframeRadiationDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Radiation Status`, + upgrade_WarframeRegen: `[UNTRANSLATED] +|VAL| Health Regen/s`, + upgrade_WarframeShieldMax: `[UNTRANSLATED] +|VAL| Shield`, + upgrade_WarframeStartingEnergy: `[UNTRANSLATED] +|VAL|% Energy on Spawn`, + upgrade_WarframeToxinDamage: `[UNTRANSLATED] +|VAL|% Toxin Status Effect Damage`, + upgrade_WarframeToxinHeal: `[UNTRANSLATED] +|VAL| Health on damaging enemies with Toxin Status`, + upgrade_WeaponCritBoostFromHeat: `[UNTRANSLATED] +|VAL1|% Secondary Critical Chance per Heat-affected enemy killed (Max |VAL2|%)`, + upgrade_AvatarAbilityRange: `[UNTRANSLATED] +7.5% Ability Range`, + upgrade_AvatarAbilityEfficiency: `[UNTRANSLATED] +5% Ability Efficiency`, + upgrade_AvatarEnergyRegen: `[UNTRANSLATED] +0.5 Energy Regen/s`, + upgrade_AvatarEnemyRadar: `[UNTRANSLATED] +5m Enemy Radar`, + upgrade_AvatarLootRadar: `[UNTRANSLATED] +7m Loot Radar`, + upgrade_WeaponAmmoMax: `[UNTRANSLATED] +15% Ammo Max`, + upgrade_EnemyArmorReductionAura: `[UNTRANSLATED] -3% Enemy Armor`, + upgrade_OnExecutionAmmo: `[UNTRANSLATED] 100% Primary and Secondary Magazine Refill on Mercy`, + upgrade_OnExecutionHealthDrop: `[UNTRANSLATED] 100% chance to drop a Health Orb on Mercy`, + upgrade_OnExecutionEnergyDrop: `[UNTRANSLATED] 50% chance to drop an Energy Orb on Mercy`, + upgrade_OnFailHackReset: `[UNTRANSLATED] +50% to retry on Hacking failure`, + upgrade_DamageReductionOnHack: `[UNTRANSLATED] 75% Damage Reduction while Hacking`, + upgrade_OnExecutionReviveCompanion: `[UNTRANSLATED] Mercy Kills reduce Companion Recovery by 15s`, + upgrade_OnExecutionParkourSpeed: `[UNTRANSLATED] +60% Parkour Speed after a Mercy for 15s`, + upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] s to Hacking`, + upgrade_ElectrifyOnHack: `[UNTRANSLATED] Shock enemies within 20m while Hacking`, + upgrade_OnExecutionTerrify: `[UNTRANSLATED] 50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`, + upgrade_OnHackLockers: `[UNTRANSLATED] Unlock 5 lockers within 20m after Hacking`, + upgrade_OnExecutionBlind: `[UNTRANSLATED] Blind enemies within 18m on Mercy`, + upgrade_OnExecutionDrainPower: `[UNTRANSLATED] 100% chance for next ability cast to gain +50% Ability Strength on Mercy`, + upgrade_OnHackSprintSpeed: `[UNTRANSLATED] +75% Sprint Speed for 15s after Hacking`, + upgrade_SwiftExecute: `[UNTRANSLATED] Speed of Mercy Kills increased by 50%`, + upgrade_OnHackInvis: `[UNTRANSLATED] Invisible for 15 seconds after hacking`, + prettier_sucks_ass: `` }; diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index e159c189..41dc15c7 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -175,5 +175,57 @@ dict = { cheats_none: `Отсутствует`, import_importNote: `Вы можете загрузить полный или частичный ответ инвентаря (клиентское представление) здесь. Все поддерживаемые поля будут перезаписаны в вашем аккаунте.`, import_submit: `Отправить`, + + upgrade_Equilibrium: `[UNTRANSLATED] +|VAL|% Energy from Health pickups, +|VAL|% Health from Energy pickups`, + upgrade_MeleeCritDamage: `[UNTRANSLATED] +|VAL|% Melee Critical Damage`, + upgrade_PrimaryStatusChance: `[UNTRANSLATED] +|VAL|% Primary Status Chance`, + upgrade_SecondaryCritChance: `[UNTRANSLATED] +|VAL|% Secondary Critical Chance`, + upgrade_WarframeAbilityDuration: `[UNTRANSLATED] +|VAL|% Ability Duration`, + upgrade_WarframeAbilityStrength: `[UNTRANSLATED] +|VAL|% Ability Strength`, + upgrade_WarframeArmourMax: `[UNTRANSLATED] +|VAL| Armor`, + upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on inflicting Blast Status`, + upgrade_WarframeCastingSpeed: `[UNTRANSLATED] +|VAL|% Casting Speed`, + upgrade_WarframeCorrosiveDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Corrosion Status`, + upgrade_WarframeCorrosiveStack: `[UNTRANSLATED] Increase max stacks of Corrosion Status by +|VAL|`, + upgrade_WarframeCritDamageBoost: `[UNTRANSLATED] +|VAL|% Melee Critical Damage (Doubles over 500 Energy)`, + upgrade_WarframeElectricDamage: `[UNTRANSLATED] +|VAL1|% Primary Electricity Damage (+|VAL2|% per additional Shard)`, + upgrade_WarframeElectricDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Electricity Status`, + upgrade_WarframeEnergyMax: `[UNTRANSLATED] +|VAL| Energy Max`, + upgrade_WarframeGlobeEffectEnergy: `[UNTRANSLATED] +|VAL|% Energy Orb Effectiveness`, + upgrade_WarframeGlobeEffectHealth: `[UNTRANSLATED] +|VAL|% Health Orb Effectiveness`, + upgrade_WarframeHealthMax: `[UNTRANSLATED] +|VAL| Health`, + upgrade_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health per enemy killed with Blast Damage (Max |VAL2| Health)`, + upgrade_WarframeParkourVelocity: `[UNTRANSLATED] +|VAL|% Parkour Velocity`, + upgrade_WarframeRadiationDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Radiation Status`, + upgrade_WarframeRegen: `[UNTRANSLATED] +|VAL| Health Regen/s`, + upgrade_WarframeShieldMax: `[UNTRANSLATED] +|VAL| Shield`, + upgrade_WarframeStartingEnergy: `[UNTRANSLATED] +|VAL|% Energy on Spawn`, + upgrade_WarframeToxinDamage: `[UNTRANSLATED] +|VAL|% Toxin Status Effect Damage`, + upgrade_WarframeToxinHeal: `[UNTRANSLATED] +|VAL| Health on damaging enemies with Toxin Status`, + upgrade_WeaponCritBoostFromHeat: `[UNTRANSLATED] +|VAL1|% Secondary Critical Chance per Heat-affected enemy killed (Max |VAL2|%)`, + upgrade_AvatarAbilityRange: `[UNTRANSLATED] +7.5% Ability Range`, + upgrade_AvatarAbilityEfficiency: `[UNTRANSLATED] +5% Ability Efficiency`, + upgrade_AvatarEnergyRegen: `[UNTRANSLATED] +0.5 Energy Regen/s`, + upgrade_AvatarEnemyRadar: `[UNTRANSLATED] +5m Enemy Radar`, + upgrade_AvatarLootRadar: `[UNTRANSLATED] +7m Loot Radar`, + upgrade_WeaponAmmoMax: `[UNTRANSLATED] +15% Ammo Max`, + upgrade_EnemyArmorReductionAura: `[UNTRANSLATED] -3% Enemy Armor`, + upgrade_OnExecutionAmmo: `[UNTRANSLATED] 100% Primary and Secondary Magazine Refill on Mercy`, + upgrade_OnExecutionHealthDrop: `[UNTRANSLATED] 100% chance to drop a Health Orb on Mercy`, + upgrade_OnExecutionEnergyDrop: `[UNTRANSLATED] 50% chance to drop an Energy Orb on Mercy`, + upgrade_OnFailHackReset: `[UNTRANSLATED] +50% to retry on Hacking failure`, + upgrade_DamageReductionOnHack: `[UNTRANSLATED] 75% Damage Reduction while Hacking`, + upgrade_OnExecutionReviveCompanion: `[UNTRANSLATED] Mercy Kills reduce Companion Recovery by 15s`, + upgrade_OnExecutionParkourSpeed: `[UNTRANSLATED] +60% Parkour Speed after a Mercy for 15s`, + upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] s to Hacking`, + upgrade_ElectrifyOnHack: `[UNTRANSLATED] Shock enemies within 20m while Hacking`, + upgrade_OnExecutionTerrify: `[UNTRANSLATED] 50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`, + upgrade_OnHackLockers: `[UNTRANSLATED] Unlock 5 lockers within 20m after Hacking`, + upgrade_OnExecutionBlind: `[UNTRANSLATED] Blind enemies within 18m on Mercy`, + upgrade_OnExecutionDrainPower: `[UNTRANSLATED] 100% chance for next ability cast to gain +50% Ability Strength on Mercy`, + upgrade_OnHackSprintSpeed: `[UNTRANSLATED] +75% Sprint Speed for 15s after Hacking`, + upgrade_SwiftExecute: `[UNTRANSLATED] Speed of Mercy Kills increased by 50%`, + upgrade_OnHackInvis: `[UNTRANSLATED] Invisible for 15 seconds after hacking`, + prettier_sucks_ass: `` }; diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 813c3d38..9bd48150 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -175,5 +175,57 @@ dict = { cheats_none: `无`, import_importNote: `您可以在此处提供完整或部分库存响应(客户端表示)。支持的所有字段将被覆盖到您的账户中。`, import_submit: `提交`, + + upgrade_Equilibrium: `[UNTRANSLATED] +|VAL|% Energy from Health pickups, +|VAL|% Health from Energy pickups`, + upgrade_MeleeCritDamage: `[UNTRANSLATED] +|VAL|% Melee Critical Damage`, + upgrade_PrimaryStatusChance: `[UNTRANSLATED] +|VAL|% Primary Status Chance`, + upgrade_SecondaryCritChance: `[UNTRANSLATED] +|VAL|% Secondary Critical Chance`, + upgrade_WarframeAbilityDuration: `[UNTRANSLATED] +|VAL|% Ability Duration`, + upgrade_WarframeAbilityStrength: `[UNTRANSLATED] +|VAL|% Ability Strength`, + upgrade_WarframeArmourMax: `[UNTRANSLATED] +|VAL| Armor`, + upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on inflicting Blast Status`, + upgrade_WarframeCastingSpeed: `[UNTRANSLATED] +|VAL|% Casting Speed`, + upgrade_WarframeCorrosiveDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Corrosion Status`, + upgrade_WarframeCorrosiveStack: `[UNTRANSLATED] Increase max stacks of Corrosion Status by +|VAL|`, + upgrade_WarframeCritDamageBoost: `[UNTRANSLATED] +|VAL|% Melee Critical Damage (Doubles over 500 Energy)`, + upgrade_WarframeElectricDamage: `[UNTRANSLATED] +|VAL1|% Primary Electricity Damage (+|VAL2|% per additional Shard)`, + upgrade_WarframeElectricDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Electricity Status`, + upgrade_WarframeEnergyMax: `[UNTRANSLATED] +|VAL| Energy Max`, + upgrade_WarframeGlobeEffectEnergy: `[UNTRANSLATED] +|VAL|% Energy Orb Effectiveness`, + upgrade_WarframeGlobeEffectHealth: `[UNTRANSLATED] +|VAL|% Health Orb Effectiveness`, + upgrade_WarframeHealthMax: `[UNTRANSLATED] +|VAL| Health`, + upgrade_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health per enemy killed with Blast Damage (Max |VAL2| Health)`, + upgrade_WarframeParkourVelocity: `[UNTRANSLATED] +|VAL|% Parkour Velocity`, + upgrade_WarframeRadiationDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Radiation Status`, + upgrade_WarframeRegen: `[UNTRANSLATED] +|VAL| Health Regen/s`, + upgrade_WarframeShieldMax: `[UNTRANSLATED] +|VAL| Shield`, + upgrade_WarframeStartingEnergy: `[UNTRANSLATED] +|VAL|% Energy on Spawn`, + upgrade_WarframeToxinDamage: `[UNTRANSLATED] +|VAL|% Toxin Status Effect Damage`, + upgrade_WarframeToxinHeal: `[UNTRANSLATED] +|VAL| Health on damaging enemies with Toxin Status`, + upgrade_WeaponCritBoostFromHeat: `[UNTRANSLATED] +|VAL1|% Secondary Critical Chance per Heat-affected enemy killed (Max |VAL2|%)`, + upgrade_AvatarAbilityRange: `[UNTRANSLATED] +7.5% Ability Range`, + upgrade_AvatarAbilityEfficiency: `[UNTRANSLATED] +5% Ability Efficiency`, + upgrade_AvatarEnergyRegen: `[UNTRANSLATED] +0.5 Energy Regen/s`, + upgrade_AvatarEnemyRadar: `[UNTRANSLATED] +5m Enemy Radar`, + upgrade_AvatarLootRadar: `[UNTRANSLATED] +7m Loot Radar`, + upgrade_WeaponAmmoMax: `[UNTRANSLATED] +15% Ammo Max`, + upgrade_EnemyArmorReductionAura: `[UNTRANSLATED] -3% Enemy Armor`, + upgrade_OnExecutionAmmo: `[UNTRANSLATED] 100% Primary and Secondary Magazine Refill on Mercy`, + upgrade_OnExecutionHealthDrop: `[UNTRANSLATED] 100% chance to drop a Health Orb on Mercy`, + upgrade_OnExecutionEnergyDrop: `[UNTRANSLATED] 50% chance to drop an Energy Orb on Mercy`, + upgrade_OnFailHackReset: `[UNTRANSLATED] +50% to retry on Hacking failure`, + upgrade_DamageReductionOnHack: `[UNTRANSLATED] 75% Damage Reduction while Hacking`, + upgrade_OnExecutionReviveCompanion: `[UNTRANSLATED] Mercy Kills reduce Companion Recovery by 15s`, + upgrade_OnExecutionParkourSpeed: `[UNTRANSLATED] +60% Parkour Speed after a Mercy for 15s`, + upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] s to Hacking`, + upgrade_ElectrifyOnHack: `[UNTRANSLATED] Shock enemies within 20m while Hacking`, + upgrade_OnExecutionTerrify: `[UNTRANSLATED] 50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`, + upgrade_OnHackLockers: `[UNTRANSLATED] Unlock 5 lockers within 20m after Hacking`, + upgrade_OnExecutionBlind: `[UNTRANSLATED] Blind enemies within 18m on Mercy`, + upgrade_OnExecutionDrainPower: `[UNTRANSLATED] 100% chance for next ability cast to gain +50% Ability Strength on Mercy`, + upgrade_OnHackSprintSpeed: `[UNTRANSLATED] +75% Sprint Speed for 15s after Hacking`, + upgrade_SwiftExecute: `[UNTRANSLATED] Speed of Mercy Kills increased by 50%`, + upgrade_OnHackInvis: `[UNTRANSLATED] Invisible for 15 seconds after hacking`, + prettier_sucks_ass: `` }; From b67ddf6df2c12c055c6d28a5ea9ff1bf4945c7d9 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sat, 14 Jun 2025 12:57:40 -0700 Subject: [PATCH 005/153] feat: handle classic syndicate alignments when trading in medals (#2157) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2157 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/fishmongerController.ts | 5 ++- .../api/syndicateStandingBonusController.ts | 7 +++-- src/helpers/syndicateStandingHelper.ts | 11 +++++++ src/services/inventoryService.ts | 31 ++++++++++++++++--- src/services/missionInventoryUpdateService.ts | 19 ++++++------ 5 files changed, 53 insertions(+), 20 deletions(-) diff --git a/src/controllers/api/fishmongerController.ts b/src/controllers/api/fishmongerController.ts index d85f7a4c..16fcc69b 100644 --- a/src/controllers/api/fishmongerController.ts +++ b/src/controllers/api/fishmongerController.ts @@ -30,15 +30,14 @@ export const fishmongerController: RequestHandler = async (req, res) => { miscItemChanges.push({ ItemType: fish.ItemType, ItemCount: fish.ItemCount * -1 }); } addMiscItems(inventory, miscItemChanges); - let affiliationMod; - if (gainedStanding && syndicateTag) affiliationMod = addStanding(inventory, syndicateTag, gainedStanding); + if (gainedStanding && syndicateTag) addStanding(inventory, syndicateTag, gainedStanding); await inventory.save(); res.json({ InventoryChanges: { MiscItems: miscItemChanges }, SyndicateTag: syndicateTag, - StandingChange: affiliationMod?.Standing || 0 + StandingChange: gainedStanding }); }; diff --git a/src/controllers/api/syndicateStandingBonusController.ts b/src/controllers/api/syndicateStandingBonusController.ts index 3170bc93..f5f0a5d2 100644 --- a/src/controllers/api/syndicateStandingBonusController.ts +++ b/src/controllers/api/syndicateStandingBonusController.ts @@ -5,7 +5,7 @@ import { IMiscItem, InventorySlot } from "@/src/types/inventoryTypes/inventoryTy import { IOid } from "@/src/types/commonTypes"; import { ExportSyndicates, ExportWeapons } from "warframe-public-export-plus"; import { logger } from "@/src/utils/logger"; -import { IInventoryChanges } from "@/src/types/purchaseTypes"; +import { IAffiliationMods, IInventoryChanges } from "@/src/types/purchaseTypes"; import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes"; export const syndicateStandingBonusController: RequestHandler = async (req, res) => { @@ -54,13 +54,14 @@ export const syndicateStandingBonusController: RequestHandler = async (req, res) inventoryChanges[slotBin] = { count: -1, platinum: 0, Slots: 1 }; } - const affiliationMod = addStanding(inventory, request.Operation.AffiliationTag, gainedStanding, true); + const affiliationMods: IAffiliationMods[] = []; + addStanding(inventory, request.Operation.AffiliationTag, gainedStanding, affiliationMods, true); await inventory.save(); res.json({ InventoryChanges: inventoryChanges, - AffiliationMods: [affiliationMod] + AffiliationMods: affiliationMods }); }; diff --git a/src/helpers/syndicateStandingHelper.ts b/src/helpers/syndicateStandingHelper.ts index a412bdbf..ac478dda 100644 --- a/src/helpers/syndicateStandingHelper.ts +++ b/src/helpers/syndicateStandingHelper.ts @@ -10,3 +10,14 @@ export const getMaxStanding = (syndicate: ISyndicate, title: number): number => } return syndicate.titles.find(x => x.level == title)!.maxStanding; }; + +export const getMinStanding = (syndicate: ISyndicate, title: number): number => { + if (!syndicate.titles) { + // LibrarySyndicate + return 0; + } + if (title == 0) { + return syndicate.titles.find(x => x.level == -1)!.maxStanding; + } + return syndicate.titles.find(x => x.level == title)!.minStanding; +}; diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index ae9158c5..ad53cad5 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -82,7 +82,7 @@ import { handleBundleAcqusition } from "./purchaseService"; import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json"; import { getRandomElement, getRandomInt, getRandomWeightedReward, SRng } from "./rngService"; import { createMessage } from "./inboxService"; -import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper"; +import { getMaxStanding, getMinStanding } from "@/src/helpers/syndicateStandingHelper"; import { getNightwaveSyndicateTag, getWorldState } from "./worldStateService"; import { generateNemesisProfile, INemesisProfile } from "../helpers/nemesisHelpers"; import { TAccountDocument } from "./loginService"; @@ -1202,8 +1202,10 @@ export const addStanding = ( inventory: TInventoryDatabaseDocument, syndicateTag: string, gainedStanding: number, - isMedallion: boolean = false -): IAffiliationMods => { + affiliationMods: IAffiliationMods[] = [], + isMedallion: boolean = false, + propagateAlignments: boolean = true +): void => { let syndicate = inventory.Affiliations.find(x => x.Tag == syndicateTag); const syndicateMeta = ExportSyndicates[syndicateTag]; @@ -1215,6 +1217,10 @@ export const addStanding = ( const max = getMaxStanding(syndicateMeta, syndicate.Title ?? 0); if (syndicate.Standing + gainedStanding > max) gainedStanding = max - syndicate.Standing; + if (syndicate.Title == -2 && syndicate.Standing + gainedStanding < -71000) { + gainedStanding = -71000 + syndicate.Standing; + } + if (!isMedallion || syndicateMeta.medallionsCappedByDailyLimit) { if (gainedStanding > getStandingLimit(inventory, syndicateMeta.dailyLimitBin)) { gainedStanding = getStandingLimit(inventory, syndicateMeta.dailyLimitBin); @@ -1223,10 +1229,27 @@ export const addStanding = ( } syndicate.Standing += gainedStanding; - return { + const affiliationMod: IAffiliationMods = { Tag: syndicateTag, Standing: gainedStanding }; + affiliationMods.push(affiliationMod); + + if (syndicateMeta.alignments) { + if (propagateAlignments) { + for (const [tag, factor] of Object.entries(syndicateMeta.alignments)) { + addStanding(inventory, tag, gainedStanding * factor, affiliationMods, isMedallion, false); + } + } else { + while (syndicate.Standing < getMinStanding(syndicateMeta, syndicate.Title ?? 0)) { + syndicate.Title ??= 0; + syndicate.Title -= 1; + affiliationMod.Title ??= 0; + affiliationMod.Title -= 1; + logger.debug(`${syndicateTag} is decreasing to title ${syndicate.Title} after applying alignment`); + } + } + } }; // TODO: AffiliationMods support (Nightwave). diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index 21aff3b1..3a50cfcf 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -1236,19 +1236,18 @@ export const addMissionRewards = async ( SyndicateXPItemReward = medallionAmount; } else { if (rewardInfo.JobTier! >= 0) { - AffiliationMods.push( - addStanding( - inventory, - syndicateEntry.Tag, - Math.floor(currentJob.xpAmounts[rewardInfo.JobStage] / (rewardInfo.Q ? 0.8 : 1)) - ) + addStanding( + inventory, + syndicateEntry.Tag, + Math.floor(currentJob.xpAmounts[rewardInfo.JobStage] / (rewardInfo.Q ? 0.8 : 1)), + AffiliationMods ); } else { if (jobType.endsWith("Heists/HeistProfitTakerBountyOne") && rewardInfo.JobStage === 2) { - AffiliationMods.push(addStanding(inventory, syndicateEntry.Tag, 1000)); + addStanding(inventory, syndicateEntry.Tag, 1000, AffiliationMods); } if (jobType.endsWith("Hunts/AllTeralystsHunt") && rewardInfo.JobStage === 2) { - AffiliationMods.push(addStanding(inventory, syndicateEntry.Tag, 5000)); + addStanding(inventory, syndicateEntry.Tag, 5000, AffiliationMods); } if ( [ @@ -1259,7 +1258,7 @@ export const addMissionRewards = async ( "Heists/HeistExploiterBountyOne" ].some(ending => jobType.endsWith(ending)) ) { - AffiliationMods.push(addStanding(inventory, syndicateEntry.Tag, 1000)); + addStanding(inventory, syndicateEntry.Tag, 1000, AffiliationMods); } } } @@ -1284,7 +1283,7 @@ export const addMissionRewards = async ( let standingAmount = (tier + 1) * 1000; if (tier > 5) standingAmount = 7500; // InfestedLichBounty if (isSteelPath) standingAmount *= 1.5; - AffiliationMods.push(addStanding(inventory, syndicateTag, standingAmount)); + addStanding(inventory, syndicateTag, standingAmount, AffiliationMods); } if (syndicateTag == "HexSyndicate" && chemistry && tier < 6) { const seed = getWorldState().SyndicateMissions.find(x => x.Tag == "HexSyndicate")!.Seed; From 12d09531b32e46de33b88127abf6a4762a9a8df2 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sat, 14 Jun 2025 12:58:26 -0700 Subject: [PATCH 006/153] feat: add dev.keepVendorsExpired config option (#2161) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2161 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- config.json.example | 4 ++++ src/controllers/api/getVendorInfoController.ts | 9 +++++++++ src/services/configService.ts | 5 ++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/config.json.example b/config.json.example index b9ebaab0..bf6eae91 100644 --- a/config.json.example +++ b/config.json.example @@ -50,6 +50,7 @@ "noDojoResearchTime": false, "fastClanAscension": false, "spoofMasteryRank": -1, + "nightwaveStandingMultiplier": 1, "worldState": { "creditBoost": false, "affinityBoost": false, @@ -58,5 +59,8 @@ "eidolonOverride": "", "vallisOverride": "", "nightwaveOverride": "" + }, + "dev": { + "keepVendorsExpired": false } } diff --git a/src/controllers/api/getVendorInfoController.ts b/src/controllers/api/getVendorInfoController.ts index 5f9d3292..15ec9287 100644 --- a/src/controllers/api/getVendorInfoController.ts +++ b/src/controllers/api/getVendorInfoController.ts @@ -2,6 +2,7 @@ import { RequestHandler } from "express"; import { applyStandingToVendorManifest, getVendorManifestByTypeName } from "@/src/services/serversideVendorsService"; import { getInventory } from "@/src/services/inventoryService"; import { getAccountIdForRequest } from "@/src/services/loginService"; +import { config } from "@/src/services/configService"; export const getVendorInfoController: RequestHandler = async (req, res) => { let manifest = getVendorManifestByTypeName(req.query.vendor as string); @@ -14,6 +15,14 @@ export const getVendorInfoController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); const inventory = await getInventory(accountId); manifest = applyStandingToVendorManifest(inventory, manifest); + if (config.dev?.keepVendorsExpired) { + manifest = { + VendorInfo: { + ...manifest.VendorInfo, + Expiry: { $date: { $numberLong: "0" } } + } + }; + } } res.json(manifest); diff --git a/src/services/configService.ts b/src/services/configService.ts index 1989330a..82cc4f31 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -56,6 +56,7 @@ interface IConfig { noDojoResearchTime?: boolean; fastClanAscension?: boolean; spoofMasteryRank?: number; + nightwaveStandingMultiplier?: number; worldState?: { creditBoost?: boolean; affinityBoost?: boolean; @@ -65,7 +66,9 @@ interface IConfig { vallisOverride?: string; nightwaveOverride?: string; }; - nightwaveStandingMultiplier?: number; + dev?: { + keepVendorsExpired?: boolean; + }; } export const configPath = path.join(repoDir, "config.json"); From c283d61399774dc75b4407495a51c8743d8d0b11 Mon Sep 17 00:00:00 2001 From: hxedcl Date: Sat, 14 Jun 2025 23:44:34 -0700 Subject: [PATCH 007/153] chore(webui): update to Spanish translation (#2165) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2165 Co-authored-by: hxedcl Co-committed-by: hxedcl --- static/webui/translations/es.js | 100 ++++++++++++++++---------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index bfe10139..907b9298 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -176,56 +176,56 @@ dict = { import_importNote: `Puedes proporcionar una respuesta de inventario completa o parcial (representación del cliente) aquí. Todos los campos compatibles con el importador serán sobrescritos en tu cuenta.`, import_submit: `Enviar`, - upgrade_Equilibrium: `[UNTRANSLATED] +|VAL|% Energy from Health pickups, +|VAL|% Health from Energy pickups`, - upgrade_MeleeCritDamage: `[UNTRANSLATED] +|VAL|% Melee Critical Damage`, - upgrade_PrimaryStatusChance: `[UNTRANSLATED] +|VAL|% Primary Status Chance`, - upgrade_SecondaryCritChance: `[UNTRANSLATED] +|VAL|% Secondary Critical Chance`, - upgrade_WarframeAbilityDuration: `[UNTRANSLATED] +|VAL|% Ability Duration`, - upgrade_WarframeAbilityStrength: `[UNTRANSLATED] +|VAL|% Ability Strength`, - upgrade_WarframeArmourMax: `[UNTRANSLATED] +|VAL| Armor`, - upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on inflicting Blast Status`, - upgrade_WarframeCastingSpeed: `[UNTRANSLATED] +|VAL|% Casting Speed`, - upgrade_WarframeCorrosiveDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Corrosion Status`, - upgrade_WarframeCorrosiveStack: `[UNTRANSLATED] Increase max stacks of Corrosion Status by +|VAL|`, - upgrade_WarframeCritDamageBoost: `[UNTRANSLATED] +|VAL|% Melee Critical Damage (Doubles over 500 Energy)`, - upgrade_WarframeElectricDamage: `[UNTRANSLATED] +|VAL1|% Primary Electricity Damage (+|VAL2|% per additional Shard)`, - upgrade_WarframeElectricDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Electricity Status`, - upgrade_WarframeEnergyMax: `[UNTRANSLATED] +|VAL| Energy Max`, - upgrade_WarframeGlobeEffectEnergy: `[UNTRANSLATED] +|VAL|% Energy Orb Effectiveness`, - upgrade_WarframeGlobeEffectHealth: `[UNTRANSLATED] +|VAL|% Health Orb Effectiveness`, - upgrade_WarframeHealthMax: `[UNTRANSLATED] +|VAL| Health`, - upgrade_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health per enemy killed with Blast Damage (Max |VAL2| Health)`, - upgrade_WarframeParkourVelocity: `[UNTRANSLATED] +|VAL|% Parkour Velocity`, - upgrade_WarframeRadiationDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Radiation Status`, - upgrade_WarframeRegen: `[UNTRANSLATED] +|VAL| Health Regen/s`, - upgrade_WarframeShieldMax: `[UNTRANSLATED] +|VAL| Shield`, - upgrade_WarframeStartingEnergy: `[UNTRANSLATED] +|VAL|% Energy on Spawn`, - upgrade_WarframeToxinDamage: `[UNTRANSLATED] +|VAL|% Toxin Status Effect Damage`, - upgrade_WarframeToxinHeal: `[UNTRANSLATED] +|VAL| Health on damaging enemies with Toxin Status`, - upgrade_WeaponCritBoostFromHeat: `[UNTRANSLATED] +|VAL1|% Secondary Critical Chance per Heat-affected enemy killed (Max |VAL2|%)`, - upgrade_AvatarAbilityRange: `[UNTRANSLATED] +7.5% Ability Range`, - upgrade_AvatarAbilityEfficiency: `[UNTRANSLATED] +5% Ability Efficiency`, - upgrade_AvatarEnergyRegen: `[UNTRANSLATED] +0.5 Energy Regen/s`, - upgrade_AvatarEnemyRadar: `[UNTRANSLATED] +5m Enemy Radar`, - upgrade_AvatarLootRadar: `[UNTRANSLATED] +7m Loot Radar`, - upgrade_WeaponAmmoMax: `[UNTRANSLATED] +15% Ammo Max`, - upgrade_EnemyArmorReductionAura: `[UNTRANSLATED] -3% Enemy Armor`, - upgrade_OnExecutionAmmo: `[UNTRANSLATED] 100% Primary and Secondary Magazine Refill on Mercy`, - upgrade_OnExecutionHealthDrop: `[UNTRANSLATED] 100% chance to drop a Health Orb on Mercy`, - upgrade_OnExecutionEnergyDrop: `[UNTRANSLATED] 50% chance to drop an Energy Orb on Mercy`, - upgrade_OnFailHackReset: `[UNTRANSLATED] +50% to retry on Hacking failure`, - upgrade_DamageReductionOnHack: `[UNTRANSLATED] 75% Damage Reduction while Hacking`, - upgrade_OnExecutionReviveCompanion: `[UNTRANSLATED] Mercy Kills reduce Companion Recovery by 15s`, - upgrade_OnExecutionParkourSpeed: `[UNTRANSLATED] +60% Parkour Speed after a Mercy for 15s`, - upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] s to Hacking`, - upgrade_ElectrifyOnHack: `[UNTRANSLATED] Shock enemies within 20m while Hacking`, - upgrade_OnExecutionTerrify: `[UNTRANSLATED] 50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`, - upgrade_OnHackLockers: `[UNTRANSLATED] Unlock 5 lockers within 20m after Hacking`, - upgrade_OnExecutionBlind: `[UNTRANSLATED] Blind enemies within 18m on Mercy`, - upgrade_OnExecutionDrainPower: `[UNTRANSLATED] 100% chance for next ability cast to gain +50% Ability Strength on Mercy`, - upgrade_OnHackSprintSpeed: `[UNTRANSLATED] +75% Sprint Speed for 15s after Hacking`, - upgrade_SwiftExecute: `[UNTRANSLATED] Speed of Mercy Kills increased by 50%`, - upgrade_OnHackInvis: `[UNTRANSLATED] Invisible for 15 seconds after hacking`, + upgrade_Equilibrium: `+|VAL|% de Energía al recoger salud, +|VAL|% de Salud al recoger energía`, + upgrade_MeleeCritDamage: `+|VAL|% de daño crítico cuerpo a cuerpo`, + upgrade_PrimaryStatusChance: `+|VAL|% de probabilidad de estado en armas primarias`, + upgrade_SecondaryCritChance: `+|VAL|% de probabilidad crítica en armas secundarias`, + upgrade_WarframeAbilityDuration: `+|VAL|% de duración de habilidades`, + upgrade_WarframeAbilityStrength: `+|VAL|% de fuerza de habilidades`, + upgrade_WarframeArmourMax: `+|VAL| de armadura`, + upgrade_WarframeBlastProc: `+|VAL| de escudos al infligir estado de explosión`, + upgrade_WarframeCastingSpeed: `+|VAL|% de velocidad de lanzamiento de habilidades`, + upgrade_WarframeCorrosiveDamageBoost: `+|VAL|% de daño de habilidades a enemigos con estado corrosivo`, + upgrade_WarframeCorrosiveStack: `Aumenta los acumuladores máximos de estado corrosivo en +|VAL|`, + upgrade_WarframeCritDamageBoost: `+|VAL|% de daño crítico cuerpo a cuerpo (se duplica con más de 500 de energía)`, + upgrade_WarframeElectricDamage: `+|VAL1|% de daño eléctrico en armas primarias (+|VAL2|% por fragmento adicional)`, + upgrade_WarframeElectricDamageBoost: `+|VAL|% de daño de habilidades a enemigos con estado eléctrico`, + upgrade_WarframeEnergyMax: `+|VAL| de energía máxima`, + upgrade_WarframeGlobeEffectEnergy: `+|VAL|% de efectividad de orbes de energía`, + upgrade_WarframeGlobeEffectHealth: `+|VAL|% de efectividad de orbes de salud`, + upgrade_WarframeHealthMax: `+|VAL| de salud máxima`, + upgrade_WarframeHPBoostFromImpact: `+|VAL1| de salud por enemigo eliminado con daño explosivo (máximo |VAL2| de salud)`, + upgrade_WarframeParkourVelocity: `+|VAL|% de velocidad de parkour`, + upgrade_WarframeRadiationDamageBoost: `+|VAL|% de daño de habilidades a enemigos con estado radiactivo`, + upgrade_WarframeRegen: `+|VAL| de regeneración de salud por segundo`, + upgrade_WarframeShieldMax: `+|VAL| de escudo`, + upgrade_WarframeStartingEnergy: `+|VAL|% de energía al reaparecer`, + upgrade_WarframeToxinDamage: `+|VAL|% de daño por efecto de estado tóxico`, + upgrade_WarframeToxinHeal: `+|VAL| de salud al dañar enemigos con estado tóxico`, + upgrade_WeaponCritBoostFromHeat: `+|VAL1|% de probabilidad crítica secundaria por enemigo afectado por calor eliminado (máx. |VAL2|%)`, + upgrade_AvatarAbilityRange: `+7.5% de alcance de habilidades`, + upgrade_AvatarAbilityEfficiency: `+5% de eficiencia de habilidades`, + upgrade_AvatarEnergyRegen: `+0.5 de regeneración de energía por segundo`, + upgrade_AvatarEnemyRadar: `+5m de radar de enemigos`, + upgrade_AvatarLootRadar: `+7m de radar de botín`, + upgrade_WeaponAmmoMax: `+15% de munición máxima`, + upgrade_EnemyArmorReductionAura: `-3% de armadura enemiga`, + upgrade_OnExecutionAmmo: `Recarga al 100% el cargador primario y secundario tras ejecución (Misericordia)`, + upgrade_OnExecutionHealthDrop: `100% de probabilidad de soltar un orbe de salud tras ejecución (Misericordia)`, + upgrade_OnExecutionEnergyDrop: `50% de probabilidad de soltar un orbe de energía tras ejecución (Misericordia)`, + upgrade_OnFailHackReset: `+50% de probabilidad de reintento al fallar un hackeo`, + upgrade_DamageReductionOnHack: `75% de reducción de daño al hackear`, + upgrade_OnExecutionReviveCompanion: `Las ejecuciones reducen el tiempo de recuperación del compañero en 15s`, + upgrade_OnExecutionParkourSpeed: `+60% de velocidad de parkour durante 15s tras una ejecución`, + upgrade_AvatarTimeLimitIncrease: `+|VAL|s al tiempo de hackeo`, + upgrade_ElectrifyOnHack: `Electrocuta a los enemigos en un radio de 20m al hackear`, + upgrade_OnExecutionTerrify: `50% de probabilidad de que enemigos en un radio de 15m entren en pánico por 8s tras una ejecución`, + upgrade_OnHackLockers: `Desbloquea 5 casilleros en un radio de 20m tras hackear`, + upgrade_OnExecutionBlind: `Ciega a los enemigos en un radio de 18m tras una ejecución`, + upgrade_OnExecutionDrainPower: `100% de probabilidad de que la siguiente habilidad tenga +50% de fuerza tras una ejecución`, + upgrade_OnHackSprintSpeed: `+75% de velocidad de carrera durante 15s después de hackear`, + upgrade_SwiftExecute: `Velocidad de ejecuciones aumentada en un 50%`, + upgrade_OnHackInvis: `Invisible durante 15 segundos después de hackear`, prettier_sucks_ass: `` }; From d00fbed46f6c66bf06e05ac51e7e8dd64d39a140 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sun, 15 Jun 2025 05:05:18 -0700 Subject: [PATCH 008/153] fix: treating chemstry delta as absolute value (#2163) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2163 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/saveDialogueController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/api/saveDialogueController.ts b/src/controllers/api/saveDialogueController.ts index 171538a7..fa5a89cb 100644 --- a/src/controllers/api/saveDialogueController.ts +++ b/src/controllers/api/saveDialogueController.ts @@ -24,7 +24,7 @@ export const saveDialogueController: RequestHandler = async (req, res) => { inventory.DialogueHistory.Dialogues ??= []; const dialogue = getDialogue(inventory, request.DialogueName); dialogue.Rank = request.Rank; - dialogue.Chemistry = request.Chemistry; + dialogue.Chemistry += request.Chemistry; dialogue.QueuedDialogues = request.QueuedDialogues; for (const bool of request.Booleans) { dialogue.Booleans.push(bool); From f448d0388019afffee0eb6e39bac12362ac36f99 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sun, 15 Jun 2025 05:05:35 -0700 Subject: [PATCH 009/153] fix: 1999 bounty chemistry (#2164) Closes #2162 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2164 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/missionInventoryUpdateService.ts | 54 +++++++++---------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index 3a50cfcf..3d1763a2 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -66,15 +66,7 @@ import { } from "@/src/helpers/nemesisHelpers"; import { Loadout } from "../models/inventoryModels/loadoutModel"; import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes"; -import { - getLiteSortie, - getSortie, - getWorldState, - idToBountyCycle, - idToDay, - idToWeek, - pushClassicBounties -} from "./worldStateService"; +import { getLiteSortie, getSortie, idToBountyCycle, idToDay, idToWeek, pushClassicBounties } from "./worldStateService"; import { config } from "./configService"; import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json"; import { ISyndicateMissionInfo } from "../types/worldStateTypes"; @@ -1266,9 +1258,9 @@ export const addMissionRewards = async ( } if (rewardInfo.challengeMissionId) { - const [syndicateTag, tierStr, chemistryStr] = rewardInfo.challengeMissionId.split("_"); + const [syndicateTag, tierStr, chemistryBuddyStr] = rewardInfo.challengeMissionId.split("_"); const tier = Number(tierStr); - const chemistry = Number(chemistryStr); + const chemistryBuddy = Number(chemistryBuddyStr); const isSteelPath = missions?.Tier; if (syndicateTag === "ZarimanSyndicate") { let medallionAmount = tier + 1; @@ -1285,22 +1277,19 @@ export const addMissionRewards = async ( if (isSteelPath) standingAmount *= 1.5; addStanding(inventory, syndicateTag, standingAmount, AffiliationMods); } - if (syndicateTag == "HexSyndicate" && chemistry && tier < 6) { - const seed = getWorldState().SyndicateMissions.find(x => x.Tag == "HexSyndicate")!.Seed; - const { nodes, buddies } = getHexBounties(seed); - const buddy = buddies[tier]; - logger.debug(`Hex seed is ${seed}, giving chemistry for ${buddy}`); - if (missions?.Tag != nodes[tier]) { - logger.warn( - `Uh-oh, tier ${tier} bounty should've been on ${nodes[tier]} but you were just on ${missions?.Tag}` - ); - } - const tomorrowAt0Utc = config.noKimCooldowns - ? Date.now() - : (Math.trunc(Date.now() / 86400_000) + 1) * 86400_000; + if (syndicateTag == "HexSyndicate" && tier < 6) { + const buddy = chemistryBuddies[chemistryBuddy]; const dialogue = getDialogue(inventory, buddy); - dialogue.Chemistry += chemistry; - dialogue.BountyChemExpiry = new Date(tomorrowAt0Utc); + if (Date.now() >= dialogue.BountyChemExpiry.getTime()) { + logger.debug(`Giving 20 chemistry for ${buddy}`); + const tomorrowAt0Utc = config.noKimCooldowns + ? Date.now() + : (Math.trunc(Date.now() / 86400_000) + 1) * 86400_000; + dialogue.Chemistry += 20; + dialogue.BountyChemExpiry = new Date(tomorrowAt0Utc); + } else { + logger.debug(`Already got today's chemistry for ${buddy}`); + } } if (isSteelPath) { await addItem(inventory, "/Lotus/Types/Items/MiscItems/SteelEssence", 1); @@ -1864,7 +1853,16 @@ const libraryPersonalTargetToAvatar: Record = { "/Lotus/Types/Enemies/Corpus/Spaceman/AIWeek/NullifySpacemanAvatar" }; -const node_excluded_buddies: Record = { +const chemistryBuddies: readonly string[] = [ + "/Lotus/Types/Gameplay/1999Wf/Dialogue/JabirDialogue_rom.dialogue", + "/Lotus/Types/Gameplay/1999Wf/Dialogue/AoiDialogue_rom.dialogue", + "/Lotus/Types/Gameplay/1999Wf/Dialogue/ArthurDialogue_rom.dialogue", + "/Lotus/Types/Gameplay/1999Wf/Dialogue/EleanorDialogue_rom.dialogue", + "/Lotus/Types/Gameplay/1999Wf/Dialogue/LettieDialogue_rom.dialogue", + "/Lotus/Types/Gameplay/1999Wf/Dialogue/QuincyDialogue_rom.dialogue" +]; + +/*const node_excluded_buddies: Record = { SolNode856: "/Lotus/Types/Gameplay/1999Wf/Dialogue/ArthurDialogue_rom.dialogue", SolNode852: "/Lotus/Types/Gameplay/1999Wf/Dialogue/LettieDialogue_rom.dialogue", SolNode851: "/Lotus/Types/Gameplay/1999Wf/Dialogue/JabirDialogue_rom.dialogue", @@ -1914,4 +1912,4 @@ const getHexBounties = (seed: number): { nodes: string[]; buddies: string[] } => } } return { nodes, buddies }; -}; +};*/ From 30531124287d0585a4846cd87364798a1ab31230 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sun, 15 Jun 2025 05:53:10 -0700 Subject: [PATCH 010/153] chore: auto-generate palladino's vendor manifest (#2160) A bit ugly, but having the self test forces correctness. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2160 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/serversideVendorsService.ts | 83 +++++++----- .../HubsIronwakeDondaVendorManifest.json | 125 ------------------ 2 files changed, 51 insertions(+), 157 deletions(-) delete mode 100644 static/fixed_responses/getVendorInfo/HubsIronwakeDondaVendorManifest.json diff --git a/src/services/serversideVendorsService.ts b/src/services/serversideVendorsService.ts index 0282c562..4ad670d2 100644 --- a/src/services/serversideVendorsService.ts +++ b/src/services/serversideVendorsService.ts @@ -6,7 +6,7 @@ import { mixSeeds, SRng } from "@/src/services/rngService"; import { IMongoDate } from "@/src/types/commonTypes"; import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes"; import { logger } from "@/src/utils/logger"; -import { ExportVendors, IRange, IVendor } from "warframe-public-export-plus"; +import { ExportVendors, IRange, IVendor, IVendorOffer } from "warframe-public-export-plus"; import ArchimedeanVendorManifest from "@/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json"; import DeimosEntratiFragmentVendorProductsManifest from "@/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json"; @@ -21,7 +21,6 @@ import DeimosProspectorVendorManifest from "@/static/fixed_responses/getVendorIn import DuviriAcrithisVendorManifest from "@/static/fixed_responses/getVendorInfo/DuviriAcrithisVendorManifest.json"; import EntratiLabsEntratiLabsCommisionsManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabsCommisionsManifest.json"; import EntratiLabsEntratiLabVendorManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabVendorManifest.json"; -import HubsIronwakeDondaVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsIronwakeDondaVendorManifest.json"; 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"; @@ -47,7 +46,6 @@ const rawVendorManifests: IVendorManifest[] = [ DuviriAcrithisVendorManifest, EntratiLabsEntratiLabsCommisionsManifest, EntratiLabsEntratiLabVendorManifest, - HubsIronwakeDondaVendorManifest, // uses preprocessing HubsRailjackCrewMemberVendorManifest, MaskSalesmanManifest, Nova1999ConquestShopManifest, @@ -83,10 +81,6 @@ const generatableVendors: IGeneratableVendorInfo[] = [ cycleOffset: 1744934400_000, cycleDuration: 4 * unixTimesInMs.day } - // { - // _id: { $oid: "5dbb4c41e966f7886c3ce939" }, - // TypeName: "/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest" - // } ]; const getVendorOid = (typeName: string): string => { @@ -261,13 +255,8 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani const cycleIndex = Math.trunc((Date.now() - cycleOffset) / cycleDuration); const rng = new SRng(mixSeeds(vendorSeed, cycleIndex)); const manifest = ExportVendors[vendorInfo.TypeName]; - const offersToAdd = []; - if ( - manifest.numItems && - (manifest.numItems.minValue != manifest.numItems.maxValue || - manifest.items.length != manifest.numItems.minValue) && - !manifest.isOneBinPerCycle - ) { + const offersToAdd: IVendorOffer[] = []; + if (!manifest.isOneBinPerCycle) { const remainingItemCapacity: Record = {}; for (const item of manifest.items) { remainingItemCapacity[item.storeItem] = 1 + item.duplicates; @@ -275,31 +264,48 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani for (const offer of info.ItemManifest) { remainingItemCapacity[offer.StoreItem] -= 1; } - const numItemsTarget = rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue); - while (info.ItemManifest.length + offersToAdd.length < numItemsTarget) { - // TODO: Consider per-bin item limits - // TODO: Consider item probability weightings - const item = rng.randomElement(manifest.items)!; - if (remainingItemCapacity[item.storeItem] != 0) { - remainingItemCapacity[item.storeItem] -= 1; - offersToAdd.push(item); + if (manifest.numItems && manifest.items.length != manifest.numItems.minValue) { + const numItemsTarget = rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue); + while (info.ItemManifest.length + offersToAdd.length < numItemsTarget) { + // TODO: Consider per-bin item limits + // TODO: Consider item probability weightings + const item = rng.randomElement(manifest.items)!; + if (remainingItemCapacity[item.storeItem] != 0) { + remainingItemCapacity[item.storeItem] -= 1; + offersToAdd.push(item); + } } + } else { + for (const item of manifest.items) { + if (!item.alwaysOffered && remainingItemCapacity[item.storeItem] != 0) { + remainingItemCapacity[item.storeItem] -= 1; + offersToAdd.push(item); + } + } + for (const e of Object.entries(remainingItemCapacity)) { + const item = manifest.items.find(x => x.storeItem == e[0])!; + if (!item.alwaysOffered) { + while (e[1] != 0) { + e[1] -= 1; + offersToAdd.push(item); + } + } + } + for (const item of manifest.items) { + if (item.alwaysOffered && remainingItemCapacity[item.storeItem] != 0) { + remainingItemCapacity[item.storeItem] -= 1; + offersToAdd.push(item); + } + } + offersToAdd.reverse(); } } else { - let binThisCycle; - if (manifest.isOneBinPerCycle) { - binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now. - } + const binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now. for (const rawItem of manifest.items) { - if (!manifest.isOneBinPerCycle || rawItem.bin == binThisCycle) { + if (rawItem.bin == binThisCycle) { offersToAdd.push(rawItem); } } - - // For most vendors, the offers seem to roughly be in reverse order from the manifest. Coda weapons are an odd exception. - if (!manifest.isOneBinPerCycle) { - offersToAdd.reverse(); - } } const cycleStart = cycleOffset + cycleIndex * cycleDuration; for (const rawItem of offersToAdd) { @@ -388,4 +394,17 @@ if (isDev) { ) { logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest`); } + + const pall = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest")! + .VendorInfo.ItemManifest; + if ( + pall.length != 5 || + pall[0].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/HarrowQuestKeyOrnament" || + pall[1].StoreItem != "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack" || + pall[2].StoreItem != "/Lotus/StoreItems/Types/StoreItems/CreditBundles/150000Credits" || + pall[3].StoreItem != "/Lotus/StoreItems/Types/Items/MiscItems/Kuva" || + pall[4].StoreItem != "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack" + ) { + logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest`); + } } diff --git a/static/fixed_responses/getVendorInfo/HubsIronwakeDondaVendorManifest.json b/static/fixed_responses/getVendorInfo/HubsIronwakeDondaVendorManifest.json deleted file mode 100644 index bec20cc1..00000000 --- a/static/fixed_responses/getVendorInfo/HubsIronwakeDondaVendorManifest.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "5dbb4c41e966f7886c3ce939" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/HarrowQuestKeyOrnament", - "ItemPrices": [ - { - "ItemCount": 25, - "ItemType": "/Lotus/Types/Items/MiscItems/PrimeBucks", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "604800000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e945f" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack", - "ItemPrices": [ - { - "ItemCount": 10, - "ItemType": "/Lotus/Types/Items/MiscItems/RivenFragment", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "604800000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e9468" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/StoreItems/CreditBundles/150000Credits", - "ItemPrices": [ - { - "ItemCount": 5, - "ItemType": "/Lotus/Types/Items/MiscItems/RivenFragment", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "604800000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e9469" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Kuva", - "ItemPrices": [ - { - "ItemCount": 10, - "ItemType": "/Lotus/Types/Items/MiscItems/RivenFragment", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 35000, - "Expiry": { - "$date": { - "$numberLong": "604800000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e946a" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack", - "ItemPrices": [ - { - "ItemCount": 10, - "ItemType": "/Lotus/Types/Items/MiscItems/RivenFragment", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "604800000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e946b" - } - } - ], - "PropertyTextHash": "62B64A8065B7C0FA345895D4BC234621", - "Expiry": { - "$date": { - "$numberLong": "604800000" - } - } - } -} From c6dd8bfb81274f9761c532a7e9cc06a4179f0fe6 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sun, 15 Jun 2025 18:51:32 -0700 Subject: [PATCH 011/153] chore: improve error reporting when config.json exists with invalid json (#2166) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2166 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/index.ts | 7 ++++++- src/services/configWatcherService.ts | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 80adc252..d4b0f810 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,14 @@ // First, init config. import { config, loadConfig } from "@/src/services/configService"; +import fs from "fs"; try { loadConfig(); } catch (e) { - console.log("ERROR: Failed to load config.json. You can copy config.json.example to create your config.json."); + if (fs.existsSync("config.json")) { + console.log("Failed to load config.json: " + (e as Error).message); + } else { + console.log("Failed to load config.json. You can copy config.json.example to create your config.json."); + } process.exit(1); } diff --git a/src/services/configWatcherService.ts b/src/services/configWatcherService.ts index 77c56d9a..88a5acdc 100644 --- a/src/services/configWatcherService.ts +++ b/src/services/configWatcherService.ts @@ -13,7 +13,7 @@ fs.watchFile(configPath, () => { try { loadConfig(); } catch (e) { - logger.error("Failed to reload config.json. Did you delete it?! Execution cannot continue."); + logger.error("FATAL ERROR: Config failed to be reloaded: " + (e as Error).message); process.exit(1); } validateConfig(); From 6023f1c113904c76199f4e3e72bb73907c6aceae Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sun, 15 Jun 2025 18:51:42 -0700 Subject: [PATCH 012/153] feat: autogenerate "today's special" at mining vendors (#2167) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2167 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- package-lock.json | 8 +-- package.json | 2 +- src/services/serversideVendorsService.ts | 8 +-- .../DeimosProspectorVendorManifest.json | 61 ------------------ .../OstronProspectorVendorManifest.json | 62 ------------------- .../SolarisProspectorVendorManifest.json | 61 ------------------ 6 files changed, 6 insertions(+), 196 deletions(-) delete mode 100644 static/fixed_responses/getVendorInfo/DeimosProspectorVendorManifest.json delete mode 100644 static/fixed_responses/getVendorInfo/OstronProspectorVendorManifest.json delete mode 100644 static/fixed_responses/getVendorInfo/SolarisProspectorVendorManifest.json diff --git a/package-lock.json b/package-lock.json index dc4968db..ceccfce4 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.66", + "warframe-public-export-plus": "^0.5.67", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0" @@ -3814,9 +3814,9 @@ } }, "node_modules/warframe-public-export-plus": { - "version": "0.5.66", - "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.66.tgz", - "integrity": "sha512-AU7XQA96OfYrLm2RioCwDjjdI3IrsmUiqebXyE+bpM0iST+4x/NHu8LTRT4Oygfo/2OBtDYhib7G6re0EeAe5g==" + "version": "0.5.67", + "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.67.tgz", + "integrity": "sha512-LsnZD2E5PTA+5MK9kDGvM/hFDtg8sb0EwQ4hKH5ILqrSgz30a9W8785v77RSsL1AEVF8dfb/lZcSTCJq1DZHzQ==" }, "node_modules/warframe-riven-info": { "version": "0.1.2", diff --git a/package.json b/package.json index 535dfa30..eaff654a 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.66", + "warframe-public-export-plus": "^0.5.67", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0" diff --git a/src/services/serversideVendorsService.ts b/src/services/serversideVendorsService.ts index 4ad670d2..2150e400 100644 --- a/src/services/serversideVendorsService.ts +++ b/src/services/serversideVendorsService.ts @@ -17,7 +17,6 @@ import DeimosHivemindCommisionsManifestTokenVendor from "@/static/fixed_response import DeimosHivemindCommisionsManifestWeaponsmith from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestWeaponsmith.json"; import DeimosHivemindTokenVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosHivemindTokenVendorManifest.json"; import DeimosPetVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosPetVendorManifest.json"; -import DeimosProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosProspectorVendorManifest.json"; import DuviriAcrithisVendorManifest from "@/static/fixed_responses/getVendorInfo/DuviriAcrithisVendorManifest.json"; import EntratiLabsEntratiLabsCommisionsManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabsCommisionsManifest.json"; import EntratiLabsEntratiLabVendorManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabVendorManifest.json"; @@ -25,9 +24,7 @@ import HubsRailjackCrewMemberVendorManifest from "@/static/fixed_responses/getVe import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json"; import Nova1999ConquestShopManifest from "@/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json"; import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json"; -import OstronProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronProspectorVendorManifest.json"; import SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.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"; import ZarimanCommisionsManifestArchimedean from "@/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json"; @@ -42,7 +39,6 @@ const rawVendorManifests: IVendorManifest[] = [ DeimosHivemindCommisionsManifestWeaponsmith, DeimosHivemindTokenVendorManifest, DeimosPetVendorManifest, - DeimosProspectorVendorManifest, DuviriAcrithisVendorManifest, EntratiLabsEntratiLabsCommisionsManifest, EntratiLabsEntratiLabVendorManifest, @@ -50,9 +46,7 @@ const rawVendorManifests: IVendorManifest[] = [ MaskSalesmanManifest, Nova1999ConquestShopManifest, OstronPetVendorManifest, - OstronProspectorVendorManifest, SolarisDebtTokenVendorRepossessionsManifest, - SolarisProspectorVendorManifest, Temple1999VendorManifest, TeshinHardModeVendorManifest, // uses preprocessing ZarimanCommisionsManifestArchimedean @@ -317,7 +311,7 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani StoreItem: rawItem.storeItem, ItemPrices: rawItem.itemPrices?.map(itemPrice => ({ ...itemPrice, ProductCategory: "MiscItems" })), Bin: "BIN_" + rawItem.bin, - QuantityMultiplier: 1, + QuantityMultiplier: rawItem.quantity, Expiry: { $date: { $numberLong: expiry.toString() } }, AllowMultipurchase: false, Id: { diff --git a/static/fixed_responses/getVendorInfo/DeimosProspectorVendorManifest.json b/static/fixed_responses/getVendorInfo/DeimosProspectorVendorManifest.json deleted file mode 100644 index 01158f13..00000000 --- a/static/fixed_responses/getVendorInfo/DeimosProspectorVendorManifest.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "5f456e00c96976e97d6b7fd7" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Deimos/ProspectorVendorManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Gems/Deimos/DeimosUncommonGemACutItem", - "PremiumPrice": [13, 13], - "Bin": "BIN_1", - "QuantityMultiplier": 10, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e93a8" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Gems/Deimos/DeimosCommonGemBCutItem", - "PremiumPrice": [7, 7], - "Bin": "BIN_0", - "QuantityMultiplier": 20, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e93a9" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Gems/Deimos/DeimosCommonGemACutItem", - "PremiumPrice": [7, 7], - "Bin": "BIN_0", - "QuantityMultiplier": 20, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e93aa" - } - } - ], - "PropertyTextHash": "2BBC116116C757F6AF4FBC3B9BF754C8", - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - } - } -} diff --git a/static/fixed_responses/getVendorInfo/OstronProspectorVendorManifest.json b/static/fixed_responses/getVendorInfo/OstronProspectorVendorManifest.json deleted file mode 100644 index 7c357351..00000000 --- a/static/fixed_responses/getVendorInfo/OstronProspectorVendorManifest.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "59dfe591314805ffe1d47c0a" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Ostron/ProspectorVendorManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Gems/Eidolon/RareGemACutAItem", - "PremiumPrice": [19, 19], - "Bin": "BIN_1", - "QuantityMultiplier": 5, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e98f0" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Gems/Eidolon/CommonGemBCutAItem", - "PremiumPrice": [8, 8], - "Bin": "BIN_0", - "QuantityMultiplier": 20, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e98f1" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Gems/Eidolon/CommonGemACutAItem", - "PremiumPrice": [5, 5], - "Bin": "BIN_0", - "QuantityMultiplier": 20, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e98f2" - } - } - ], - "MaxDailyPurchases": 0, - "PropertyTextHash": "773C6968D9A65506CD28DF28C768F0DA", - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - } - } -} diff --git a/static/fixed_responses/getVendorInfo/SolarisProspectorVendorManifest.json b/static/fixed_responses/getVendorInfo/SolarisProspectorVendorManifest.json deleted file mode 100644 index ce54b84e..00000000 --- a/static/fixed_responses/getVendorInfo/SolarisProspectorVendorManifest.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "5be4a159b144f3cdf1c22ebb" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Solaris/ProspectorVendorManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Gems/Solaris/SolarisRareGemACutItem", - "PremiumPrice": [20, 20], - "Bin": "BIN_1", - "QuantityMultiplier": 5, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9777" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Gems/Solaris/SolarisCommonGemBCutItem", - "PremiumPrice": [10, 10], - "Bin": "BIN_0", - "QuantityMultiplier": 20, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9778" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Gems/Solaris/SolarisCommonGemACutItem", - "PremiumPrice": [8, 8], - "Bin": "BIN_0", - "QuantityMultiplier": 20, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9779" - } - } - ], - "PropertyTextHash": "A5756A21991FF49CFA7D096B4026515B", - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - } - } -} From b98a88b7002d82c4515746278e108b2cc93be8e0 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sun, 15 Jun 2025 18:52:10 -0700 Subject: [PATCH 013/153] chore: retroactively populate vendor to hide that it was just generated (#2168) For example, debt-bonds at ticker always expire in at least 2 hours so visiting him, you'd never see an offer with an expiry less than that. The solution here is simply generating offers for the last few hours. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2168 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/serversideVendorsService.ts | 31 +++++++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/services/serversideVendorsService.ts b/src/services/serversideVendorsService.ts index 2150e400..701b85b4 100644 --- a/src/services/serversideVendorsService.ts +++ b/src/services/serversideVendorsService.ts @@ -216,6 +216,22 @@ const toRange = (value: IRange | number): IRange => { return value; }; +const getCycleDurationRange = (manifest: IVendor): IRange | undefined => { + const res: IRange = { minValue: Number.MAX_SAFE_INTEGER, maxValue: 0 }; + for (const offer of manifest.items) { + if (offer.durationHours) { + const range = toRange(offer.durationHours); + if (res.minValue > range.minValue) { + res.minValue = range.minValue; + } + if (res.maxValue < range.maxValue) { + res.maxValue = range.maxValue; + } + } + } + return res.maxValue != 0 ? res : undefined; +}; + const vendorManifestCache: Record = {}; const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifest => { @@ -232,10 +248,16 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani } const cacheEntry = vendorManifestCache[vendorInfo.TypeName]; const info = cacheEntry.VendorInfo; - if (Date.now() >= parseInt(info.Expiry.$date.$numberLong)) { + const manifest = ExportVendors[vendorInfo.TypeName]; + const cycleDurationRange = getCycleDurationRange(manifest); + let now = Date.now(); + if (cycleDurationRange && cycleDurationRange.minValue != cycleDurationRange.maxValue) { + now -= (cycleDurationRange.maxValue - 1) * unixTimesInMs.hour; + } + while (Date.now() >= parseInt(info.Expiry.$date.$numberLong)) { // Remove expired offers for (let i = 0; i != info.ItemManifest.length; ) { - if (Date.now() >= parseInt(info.ItemManifest[i].Expiry.$date.$numberLong)) { + if (now >= parseInt(info.ItemManifest[i].Expiry.$date.$numberLong)) { info.ItemManifest.splice(i, 1); } else { ++i; @@ -246,9 +268,8 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani const vendorSeed = parseInt(vendorInfo._id.$oid.substring(16), 16); const cycleOffset = vendorInfo.cycleOffset ?? 1734307200_000; const cycleDuration = vendorInfo.cycleDuration; - const cycleIndex = Math.trunc((Date.now() - cycleOffset) / cycleDuration); + const cycleIndex = Math.trunc((now - cycleOffset) / cycleDuration); const rng = new SRng(mixSeeds(vendorSeed, cycleIndex)); - const manifest = ExportVendors[vendorInfo.TypeName]; const offersToAdd: IVendorOffer[] = []; if (!manifest.isOneBinPerCycle) { const remainingItemCapacity: Record = {}; @@ -371,6 +392,8 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani } } info.Expiry.$date.$numberLong = soonestOfferExpiry.toString(); + + now += unixTimesInMs.hour; } return cacheEntry; }; From 9731004de6f8a362c2523eec5138a3454df25968 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 16 Jun 2025 14:51:47 -0700 Subject: [PATCH 014/153] feat: autogenerate railjack crew member vendor (#2170) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2170 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/serversideVendorsService.ts | 44 +++- .../HubsRailjackCrewMemberVendorManifest.json | 244 ------------------ 2 files changed, 40 insertions(+), 248 deletions(-) delete mode 100644 static/fixed_responses/getVendorInfo/HubsRailjackCrewMemberVendorManifest.json diff --git a/src/services/serversideVendorsService.ts b/src/services/serversideVendorsService.ts index 701b85b4..295759b2 100644 --- a/src/services/serversideVendorsService.ts +++ b/src/services/serversideVendorsService.ts @@ -20,7 +20,6 @@ import DeimosPetVendorManifest from "@/static/fixed_responses/getVendorInfo/Deim import DuviriAcrithisVendorManifest from "@/static/fixed_responses/getVendorInfo/DuviriAcrithisVendorManifest.json"; import EntratiLabsEntratiLabsCommisionsManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabsCommisionsManifest.json"; import EntratiLabsEntratiLabVendorManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabVendorManifest.json"; -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 OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json"; @@ -42,7 +41,6 @@ const rawVendorManifests: IVendorManifest[] = [ DuviriAcrithisVendorManifest, EntratiLabsEntratiLabsCommisionsManifest, EntratiLabsEntratiLabVendorManifest, - HubsRailjackCrewMemberVendorManifest, MaskSalesmanManifest, Nova1999ConquestShopManifest, OstronPetVendorManifest, @@ -273,20 +271,39 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani const offersToAdd: IVendorOffer[] = []; if (!manifest.isOneBinPerCycle) { const remainingItemCapacity: Record = {}; + const missingItemsPerBin: Record = {}; + let numOffersThatNeedToMatchABin = 0; + if (manifest.numItemsPerBin) { + for (let bin = 0; bin != manifest.numItemsPerBin.length; ++bin) { + missingItemsPerBin[bin] = manifest.numItemsPerBin[bin]; + numOffersThatNeedToMatchABin += manifest.numItemsPerBin[bin]; + } + } for (const item of manifest.items) { remainingItemCapacity[item.storeItem] = 1 + item.duplicates; } for (const offer of info.ItemManifest) { remainingItemCapacity[offer.StoreItem] -= 1; + const bin = parseInt(offer.Bin.substring(4)); + if (missingItemsPerBin[bin]) { + missingItemsPerBin[bin] -= 1; + numOffersThatNeedToMatchABin -= 1; + } } if (manifest.numItems && manifest.items.length != manifest.numItems.minValue) { const numItemsTarget = rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue); while (info.ItemManifest.length + offersToAdd.length < numItemsTarget) { - // TODO: Consider per-bin item limits // TODO: Consider item probability weightings const item = rng.randomElement(manifest.items)!; - if (remainingItemCapacity[item.storeItem] != 0) { + if ( + remainingItemCapacity[item.storeItem] != 0 && + (numOffersThatNeedToMatchABin == 0 || missingItemsPerBin[item.bin]) + ) { remainingItemCapacity[item.storeItem] -= 1; + if (missingItemsPerBin[item.bin]) { + missingItemsPerBin[item.bin] -= 1; + numOffersThatNeedToMatchABin -= 1; + } offersToAdd.push(item); } } @@ -383,6 +400,12 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani info.ItemManifest.push(item); } + info.ItemManifest.sort((a, b) => { + const aBin = parseInt(a.Bin.substring(4)); + const bBin = parseInt(b.Bin.substring(4)); + return aBin == bBin ? 0 : aBin < bBin ? +1 : -1; + }); + // Update vendor expiry let soonestOfferExpiry: number = Number.MAX_SAFE_INTEGER; for (const offer of info.ItemManifest) { @@ -424,4 +447,17 @@ if (isDev) { ) { logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest`); } + + const cms = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/RailjackCrewMemberVendorManifest")! + .VendorInfo.ItemManifest; + if ( + cms.length != 9 || + cms[0].Bin != "BIN_2" || + cms[8].Bin != "BIN_0" || + cms.reduce((a, x) => a + (x.Bin == "BIN_2" ? 1 : 0), 0) < 2 || + cms.reduce((a, x) => a + (x.Bin == "BIN_1" ? 1 : 0), 0) < 2 || + cms.reduce((a, x) => a + (x.Bin == "BIN_0" ? 1 : 0), 0) < 4 + ) { + logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/RailjackCrewMemberVendorManifest`); + } } diff --git a/static/fixed_responses/getVendorInfo/HubsRailjackCrewMemberVendorManifest.json b/static/fixed_responses/getVendorInfo/HubsRailjackCrewMemberVendorManifest.json deleted file mode 100644 index 16506360..00000000 --- a/static/fixed_responses/getVendorInfo/HubsRailjackCrewMemberVendorManifest.json +++ /dev/null @@ -1,244 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "5fb70313c96976e97d6be787" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Hubs/RailjackCrewMemberVendorManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/StoreItems/Types/Game/CrewShip/CrewMember/SteelMeridianCrewMemberGeneratorStrong", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/RailjackMiscItems/IsosRailjackItem", - "ItemCount": 2220, - "ProductCategory": "MiscItems" - } - ], - "RegularPrice": [2180000, 2180000], - "Bin": "BIN_2", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "Affiliation": "SteelMeridianSyndicate", - "MinAffiliationRank": 0, - "ReductionPerPositiveRank": 0.1, - "IncreasePerNegativeRank": 0.5, - "AllowMultipurchase": false, - "LocTagRandSeed": 4185144421, - "Id": { - "$oid": "670daf92d21f34757a5e73da" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Game/CrewShip/CrewMember/NewLokaCrewMemberGeneratorStrong", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/RailjackMiscItems/IsosRailjackItem", - "ItemCount": 2130, - "ProductCategory": "MiscItems" - } - ], - "RegularPrice": [1890000, 1890000], - "Bin": "BIN_2", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "Affiliation": "NewLokaSyndicate", - "MinAffiliationRank": 0, - "ReductionPerPositiveRank": 0.1, - "IncreasePerNegativeRank": 0.5, - "AllowMultipurchase": false, - "LocTagRandSeed": 496053258, - "Id": { - "$oid": "670daf92d21f34757a5e73db" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Game/CrewShip/CrewMember/SteelMeridianCrewMemberGeneratorMediumVersionTwo", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/RailjackMiscItems/IsosRailjackItem", - "ItemCount": 440, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "Affiliation": "SteelMeridianSyndicate", - "MinAffiliationRank": 0, - "ReductionPerPositiveRank": 0.1, - "IncreasePerNegativeRank": 0.5, - "AllowMultipurchase": false, - "LocTagRandSeed": 2078883475, - "Id": { - "$oid": "670daf92d21f34757a5e73dc" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Game/CrewShip/CrewMember/NewLokaCrewMemberGeneratorMediumVersionTwo", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/RailjackMiscItems/AsteriteRailjackItem", - "ItemCount": 730, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "Affiliation": "NewLokaSyndicate", - "MinAffiliationRank": 0, - "ReductionPerPositiveRank": 0.1, - "IncreasePerNegativeRank": 0.5, - "AllowMultipurchase": false, - "LocTagRandSeed": 3890380934, - "Id": { - "$oid": "670daf92d21f34757a5e73dd" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Game/CrewShip/CrewMember/CephalonSudaCrewMemberGeneratorMediumVersionTwo", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/RailjackMiscItems/AsteriteRailjackItem", - "ItemCount": 720, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "Affiliation": "CephalonSudaSyndicate", - "MinAffiliationRank": 0, - "ReductionPerPositiveRank": 0.1, - "IncreasePerNegativeRank": 0.5, - "AllowMultipurchase": false, - "LocTagRandSeed": 3425148044, - "Id": { - "$oid": "670daf92d21f34757a5e73de" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Game/CrewShip/CrewMember/ArbitersCrewMemberGeneratorMediumVersionTwo", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/RailjackMiscItems/CubicsRailjackItem", - "ItemCount": 6500, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "Affiliation": "ArbitersSyndicate", - "MinAffiliationRank": 0, - "ReductionPerPositiveRank": 0.1, - "IncreasePerNegativeRank": 0.5, - "AllowMultipurchase": false, - "LocTagRandSeed": 2472754512, - "Id": { - "$oid": "670daf92d21f34757a5e73df" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Game/CrewShip/CrewMember/PerrinCrewMemberGeneratorVersionTwo", - "RegularPrice": [105000, 105000], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "Affiliation": "PerrinSyndicate", - "MinAffiliationRank": 0, - "ReductionPerPositiveRank": 0.1, - "IncreasePerNegativeRank": 0.5, - "AllowMultipurchase": false, - "LocTagRandSeed": 966238763, - "Id": { - "$oid": "670daf92d21f34757a5e73e0" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Game/CrewShip/CrewMember/NewLokaCrewMemberGeneratorVersionTwo", - "RegularPrice": [120000, 120000], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "Affiliation": "NewLokaSyndicate", - "MinAffiliationRank": 0, - "ReductionPerPositiveRank": 0.1, - "IncreasePerNegativeRank": 0.5, - "AllowMultipurchase": false, - "LocTagRandSeed": 356717213, - "Id": { - "$oid": "670daf92d21f34757a5e73e1" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Game/CrewShip/CrewMember/ArbitersCrewMemberGeneratorVersionTwo", - "RegularPrice": [120000, 120000], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "Affiliation": "ArbitersSyndicate", - "MinAffiliationRank": 0, - "ReductionPerPositiveRank": 0.1, - "IncreasePerNegativeRank": 0.5, - "AllowMultipurchase": false, - "LocTagRandSeed": 1969797050, - "Id": { - "$oid": "670daf92d21f34757a5e73e2" - } - } - ], - "PropertyTextHash": "BE543CCC0A4F50A1D80CD2B523796EAE", - "RandomSeedType": "VRST_FLAVOUR_TEXT", - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - } - } -} From 3e99e069be857932bd4ff483ac16fa2b8e460f91 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 16 Jun 2025 14:55:35 -0700 Subject: [PATCH 015/153] feat: void storm rotation (#2171) Re #1512 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2171 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/worldStateService.ts | 69 +++++++++++++++++++ src/types/worldStateTypes.ts | 9 +++ .../worldState/worldState.json | 44 ------------ 3 files changed, 78 insertions(+), 44 deletions(-) diff --git a/src/services/worldStateService.ts b/src/services/worldStateService.ts index c5e30868..253bdc21 100644 --- a/src/services/worldStateService.ts +++ b/src/services/worldStateService.ts @@ -16,6 +16,7 @@ import { ISortie, ISortieMission, ISyndicateMissionInfo, + IVoidStorm, IWorldState } from "../types/worldStateTypes"; import { version_compare } from "../helpers/inventoryHelpers"; @@ -963,6 +964,61 @@ const getCalendarSeason = (week: number): ICalendarSeason => { }; }; +// Not very faithful, but to avoid the same node coming up back-to-back (which is not valid), I've split these into 2 arrays which we're alternating between. + +const voidStormMissionsA = { + VoidT1: ["CrewBattleNode519", "CrewBattleNode518", "CrewBattleNode515", "CrewBattleNode503"], + VoidT2: ["CrewBattleNode501", "CrewBattleNode534", "CrewBattleNode530"], + VoidT3: ["CrewBattleNode521", "CrewBattleNode516"], + VoidT4: [ + "CrewBattleNode555", + "CrewBattleNode553", + "CrewBattleNode554", + "CrewBattleNode539", + "CrewBattleNode531", + "CrewBattleNode527" + ] +}; + +const voidStormMissionsB = { + VoidT1: ["CrewBattleNode509", "CrewBattleNode522", "CrewBattleNode511", "CrewBattleNode512"], + VoidT2: ["CrewBattleNode535", "CrewBattleNode533"], + VoidT3: ["CrewBattleNode524", "CrewBattleNode525"], + VoidT4: [ + "CrewBattleNode542", + "CrewBattleNode538", + "CrewBattleNode543", + "CrewBattleNode536", + "CrewBattleNode550", + "CrewBattleNode529" + ] +}; + +const pushVoidStorms = (arr: IVoidStorm[], hour: number): void => { + const activation = hour * unixTimesInMs.hour + 40 * unixTimesInMs.minute; + const expiry = activation + 90 * unixTimesInMs.minute; + let accum = 0; + const rng = new SRng(new SRng(hour).randomInt(0, 100_000)); + const voidStormMissions = structuredClone(hour & 1 ? voidStormMissionsA : voidStormMissionsB); + for (const tier of ["VoidT1", "VoidT1", "VoidT2", "VoidT3", "VoidT4", "VoidT4"] as const) { + const idx = rng.randomInt(0, voidStormMissions[tier].length - 1); + const node = voidStormMissions[tier][idx]; + voidStormMissions[tier].splice(idx, 1); + arr.push({ + _id: { + $oid: + ((activation / 1000) & 0xffffffff).toString(16).padStart(8, "0") + + "0321e89b" + + (accum++).toString().padStart(8, "0") + }, + Node: node, + Activation: { $date: { $numberLong: activation.toString() } }, + Expiry: { $date: { $numberLong: expiry.toString() } }, + ActiveMissionTier: tier + }); + } +}; + const doesTimeSatsifyConstraints = (timeSecs: number): boolean => { if (config.worldState?.eidolonOverride) { const eidolonEpoch = 1391992660; @@ -1032,6 +1088,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => { Sorties: [], LiteSorties: [], GlobalUpgrades: [], + VoidStorms: [], EndlessXpChoices: [], KnownCalendarSeasons: [], ...staticWorldState, @@ -1228,6 +1285,18 @@ export const getWorldState = (buildLabel?: string): IWorldState => { worldState.KnownCalendarSeasons.push(getCalendarSeason(week + 1)); } + // Void Storms + const hour = Math.trunc(timeMs / unixTimesInMs.hour); + const overLastHourStormExpiry = hour * unixTimesInMs.hour + 10 * unixTimesInMs.minute; + const thisHourStormActivation = hour * unixTimesInMs.hour + 40 * unixTimesInMs.minute; + if (overLastHourStormExpiry > timeMs) { + pushVoidStorms(worldState.VoidStorms, hour - 2); + } + pushVoidStorms(worldState.VoidStorms, hour - 1); + if (isBeforeNextExpectedWorldStateRefresh(timeMs, thisHourStormActivation)) { + pushVoidStorms(worldState.VoidStorms, hour); + } + // Sentient Anomaly cycling every 30 minutes const halfHour = Math.trunc(timeMs / (unixTimesInMs.hour / 2)); const tmp = { diff --git a/src/types/worldStateTypes.ts b/src/types/worldStateTypes.ts index 78c1f330..880c5800 100644 --- a/src/types/worldStateTypes.ts +++ b/src/types/worldStateTypes.ts @@ -12,6 +12,7 @@ export interface IWorldState { GlobalUpgrades: IGlobalUpgrade[]; ActiveMissions: IFissure[]; NodeOverrides: INodeOverride[]; + VoidStorms: IVoidStorm[]; PVPChallengeInstances: IPVPChallengeInstance[]; EndlessXpChoices: IEndlessXpChoice[]; SeasonInfo?: { @@ -131,6 +132,14 @@ export interface ILiteSortie { }[]; } +export interface IVoidStorm { + _id: IOid; + Node: string; + Activation: IMongoDate; + Expiry: IMongoDate; + ActiveMissionTier: string; +} + export interface IPVPChallengeInstance { _id: IOid; challengeTypeRefID: string; diff --git a/static/fixed_responses/worldState/worldState.json b/static/fixed_responses/worldState/worldState.json index 95b5fde2..73d48ce1 100644 --- a/static/fixed_responses/worldState/worldState.json +++ b/static/fixed_responses/worldState/worldState.json @@ -2562,50 +2562,6 @@ ] } ], - "VoidStorms": [ - { - "_id": { "$oid": "663a7581ced28e18f694b550" }, - "Node": "CrewBattleNode519", - "Activation": { "$date": { "$numberLong": "1715109601821" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "ActiveMissionTier": "VoidT1" - }, - { - "_id": { "$oid": "663a7581ced28e18f694b551" }, - "Node": "CrewBattleNode515", - "Activation": { "$date": { "$numberLong": "1715109601825" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "ActiveMissionTier": "VoidT1" - }, - { - "_id": { "$oid": "663a7581ced28e18f694b554" }, - "Node": "CrewBattleNode536", - "Activation": { "$date": { "$numberLong": "1715109601832" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "ActiveMissionTier": "VoidT4" - }, - { - "_id": { "$oid": "663a7581ced28e18f694b555" }, - "Node": "CrewBattleNode539", - "Activation": { "$date": { "$numberLong": "1715109601834" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "ActiveMissionTier": "VoidT4" - }, - { - "_id": { "$oid": "663a7581ced28e18f694b553" }, - "Node": "CrewBattleNode521", - "Activation": { "$date": { "$numberLong": "1715109601829" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "ActiveMissionTier": "VoidT3" - }, - { - "_id": { "$oid": "663a7581ced28e18f694b552" }, - "Node": "CrewBattleNode535", - "Activation": { "$date": { "$numberLong": "1715109601827" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "ActiveMissionTier": "VoidT2" - } - ], "PrimeAccessAvailability": { "State": "PRIME1" }, "PrimeVaultAvailabilities": [false, false, false, false, false], "PrimeTokenAvailability": true, From 8c1147998d064ae7c71ac581dcd9862ee917dd13 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 16 Jun 2025 14:55:56 -0700 Subject: [PATCH 016/153] fix(webui): respond with 200 for successful shard operations (#2175) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2175 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/custom/popArchonCrystalUpgradeController.ts | 1 + src/controllers/custom/pushArchonCrystalUpgradeController.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/controllers/custom/popArchonCrystalUpgradeController.ts b/src/controllers/custom/popArchonCrystalUpgradeController.ts index 34e87ec6..d6ef86e0 100644 --- a/src/controllers/custom/popArchonCrystalUpgradeController.ts +++ b/src/controllers/custom/popArchonCrystalUpgradeController.ts @@ -12,6 +12,7 @@ export const popArchonCrystalUpgradeController: RequestHandler = async (req, res ); await inventory.save(); res.end(); + return; } res.status(400).end(); }; diff --git a/src/controllers/custom/pushArchonCrystalUpgradeController.ts b/src/controllers/custom/pushArchonCrystalUpgradeController.ts index 3a9286ee..0db365fb 100644 --- a/src/controllers/custom/pushArchonCrystalUpgradeController.ts +++ b/src/controllers/custom/pushArchonCrystalUpgradeController.ts @@ -15,6 +15,7 @@ export const pushArchonCrystalUpgradeController: RequestHandler = async (req, re } await inventory.save(); res.end(); + return; } } res.status(400).end(); From 4e832d3b2c9b8743cef25f8d3b9027c07143925d Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 16 Jun 2025 15:05:26 -0700 Subject: [PATCH 017/153] chore(webui): add login/register error messages to translation system (#2179) Closes #2178 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2179 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- static/webui/script.js | 2 +- static/webui/translations/de.js | 2 ++ static/webui/translations/en.js | 2 ++ static/webui/translations/es.js | 2 ++ static/webui/translations/fr.js | 2 ++ static/webui/translations/ru.js | 2 ++ static/webui/translations/zh.js | 2 ++ 7 files changed, 13 insertions(+), 1 deletion(-) diff --git a/static/webui/script.js b/static/webui/script.js index 5cc9feaf..b51648e1 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -29,7 +29,7 @@ function loginFromLocalStorage() { }, () => { logout(); - alert(isRegister ? "Registration failed. Account already exists?" : "Login failed"); + alert(loc(isRegister ? "code_regFail" : "code_loginFail")); } ); } diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index 4a3cd271..8b88151b 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -3,6 +3,8 @@ dict = { general_inventoryUpdateNote: `Hinweis: Änderungen, die hier vorgenommen werden, werden erst im Spiel angewendet, sobald das Inventar synchronisiert wird. Die Sternenkarte zu besuchen, sollte der einfachste Weg sein, dies auszulösen.`, general_addButton: `Hinzufügen`, general_bulkActions: `Massenaktionen`, + code_loginFail: `[UNTRANSLATED] Login failed. Double-check the email and password.`, + code_regFail: `[UNTRANSLATED] Registration failed. Account already exists?`, code_nonValidAuthz: `Deine Anmeldedaten sind nicht mehr gültig.`, code_changeNameConfirm: `In welchen Namen möchtest du deinen Account umbenennen?`, code_deleteAccountConfirm: `Bist du sicher, dass du deinen Account |DISPLAYNAME| (|EMAIL|) löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index 070dfa4c..437d5057 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -2,6 +2,8 @@ dict = { general_inventoryUpdateNote: `Note: Changes made here will only be applied in-game when the game syncs the inventory. Visiting the navigation should be the easiest way to trigger that.`, general_addButton: `Add`, general_bulkActions: `Bulk Actions`, + code_loginFail: `Login failed. Double-check the email and password.`, + code_regFail: `Registration failed. Account already exists?`, code_nonValidAuthz: `Your credentials are no longer valid.`, code_changeNameConfirm: `What would you like to change your account name to?`, code_deleteAccountConfirm: `Are you sure you want to delete your account |DISPLAYNAME| (|EMAIL|)? This action cannot be undone.`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index 907b9298..0b6b15ec 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -3,6 +3,8 @@ dict = { general_inventoryUpdateNote: `Nota: Los cambios realizados aquí se reflejarán en el juego cuando este sincronice el inventario. Usar la navegación debería ser la forma más sencilla de activar esto.`, general_addButton: `Agregar`, general_bulkActions: `Acciones masivas`, + code_loginFail: `[UNTRANSLATED] Login failed. Double-check the email and password.`, + code_regFail: `[UNTRANSLATED] Registration failed. Account already exists?`, code_nonValidAuthz: `Tus credenciales no son válidas.`, code_changeNameConfirm: `¿Qué nombre te gustaría ponerle a tu cuenta?`, code_deleteAccountConfirm: `¿Estás seguro de que deseas eliminar tu cuenta |DISPLAYNAME| (|EMAIL|)? Esta acción es permanente.`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index 22f2dfc8..8d4eceb2 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -3,6 +3,8 @@ dict = { general_inventoryUpdateNote: `Note : Les changements effectués ici seront appliqués lors de la syncrhonisation. Visiter la navigation appliquera les changements apportés à l'inventaire.`, general_addButton: `Ajouter`, general_bulkActions: `Action groupée`, + code_loginFail: `[UNTRANSLATED] Login failed. Double-check the email and password.`, + code_regFail: `[UNTRANSLATED] Registration failed. Account already exists?`, code_nonValidAuthz: `Informations de connexion invalides`, code_changeNameConfirm: `Nouveau nom du compte :`, code_deleteAccountConfirm: `Supprimer |DISPLAYNAME| (|EMAIL|) ? Cette action est irreversible.`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index 41dc15c7..7493099d 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -3,6 +3,8 @@ dict = { general_inventoryUpdateNote: `Примечание: изменения, внесенные здесь, отобразятся в игре только после повторной загрузки вашего инвентаря. Посещение навигации — самый простой способ этого добиться.`, general_addButton: `Добавить`, general_bulkActions: `Массовые действия`, + code_loginFail: `[UNTRANSLATED] Login failed. Double-check the email and password.`, + code_regFail: `[UNTRANSLATED] Registration failed. Account already exists?`, code_nonValidAuthz: `Ваши данные больше не действительны.`, code_changeNameConfirm: `Какое имя вы хотите установить для своей учетной записи?`, code_deleteAccountConfirm: `Вы уверены, что хотите удалить аккаунт |DISPLAYNAME| (|EMAIL|)? Это действие нельзя отменить.`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 9bd48150..08cb30d5 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -3,6 +3,8 @@ dict = { general_inventoryUpdateNote: `注意:此处所做的更改只有在游戏同步仓库后才会生效。您可以通过访问星图来触发仓库更新。`, general_addButton: `添加`, general_bulkActions: `批量操作`, + code_loginFail: `[UNTRANSLATED] Login failed. Double-check the email and password.`, + code_regFail: `[UNTRANSLATED] Registration failed. Account already exists?`, code_nonValidAuthz: `您的登录凭证已失效。`, code_changeNameConfirm: `您想将账户名称更改为什么?`, code_deleteAccountConfirm: `确定要删除账户 |DISPLAYNAME| (|EMAIL|) 吗?此操作不可撤销。`, From 53976378bbed3523986db127ee2a76465f458ffb Mon Sep 17 00:00:00 2001 From: qianlishun Date: Tue, 17 Jun 2025 03:28:49 -0700 Subject: [PATCH 018/153] chore(webui): update Chinese translation (#2184) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2184 Co-authored-by: qianlishun Co-committed-by: qianlishun --- static/webui/translations/zh.js | 110 ++++++++++++++++---------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 08cb30d5..722b709a 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -3,8 +3,8 @@ dict = { general_inventoryUpdateNote: `注意:此处所做的更改只有在游戏同步仓库后才会生效。您可以通过访问星图来触发仓库更新。`, general_addButton: `添加`, general_bulkActions: `批量操作`, - code_loginFail: `[UNTRANSLATED] Login failed. Double-check the email and password.`, - code_regFail: `[UNTRANSLATED] Registration failed. Account already exists?`, + code_loginFail: `登录失败。请检查邮箱和密码。`, + code_regFail: `注册失败。账号已存在。`, code_nonValidAuthz: `您的登录凭证已失效。`, code_changeNameConfirm: `您想将账户名称更改为什么?`, code_deleteAccountConfirm: `确定要删除账户 |DISPLAYNAME| (|EMAIL|) 吗?此操作不可撤销。`, @@ -27,7 +27,7 @@ dict = { code_renamePrompt: `输入新的自定义名称:`, code_remove: `移除`, code_addItemsConfirm: `确定要向账户添加 |COUNT| 件物品吗?`, - code_succRankUp: `[UNTRANSLATED] Successfully ranked up.`, + code_succRankUp: `等级已提升`, code_noEquipmentToRankUp: `没有可升级的装备。`, code_succAdded: `已成功添加。`, code_succRemoved: `已成功移除。`, @@ -36,8 +36,8 @@ dict = { code_rerollsNumber: `洗卡次数`, code_viewStats: `查看属性`, code_rank: `等级`, - code_rankUp: `[UNTRANSLATED] Rank up`, - code_rankDown: `[UNTRANSLATED] Rank down`, + code_rankUp: `等级提升`, + code_rankDown: `等级下降`, code_count: `数量`, code_focusAllUnlocked: `所有专精学派均已解锁。`, code_focusUnlocked: `已解锁 |COUNT| 个新专精学派!需要游戏内仓库更新才能生效,您可以通过访问星图来触发仓库更新。`, @@ -178,56 +178,56 @@ dict = { import_importNote: `您可以在此处提供完整或部分库存响应(客户端表示)。支持的所有字段将被覆盖到您的账户中。`, import_submit: `提交`, - upgrade_Equilibrium: `[UNTRANSLATED] +|VAL|% Energy from Health pickups, +|VAL|% Health from Energy pickups`, - upgrade_MeleeCritDamage: `[UNTRANSLATED] +|VAL|% Melee Critical Damage`, - upgrade_PrimaryStatusChance: `[UNTRANSLATED] +|VAL|% Primary Status Chance`, - upgrade_SecondaryCritChance: `[UNTRANSLATED] +|VAL|% Secondary Critical Chance`, - upgrade_WarframeAbilityDuration: `[UNTRANSLATED] +|VAL|% Ability Duration`, - upgrade_WarframeAbilityStrength: `[UNTRANSLATED] +|VAL|% Ability Strength`, - upgrade_WarframeArmourMax: `[UNTRANSLATED] +|VAL| Armor`, - upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on inflicting Blast Status`, - upgrade_WarframeCastingSpeed: `[UNTRANSLATED] +|VAL|% Casting Speed`, - upgrade_WarframeCorrosiveDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Corrosion Status`, - upgrade_WarframeCorrosiveStack: `[UNTRANSLATED] Increase max stacks of Corrosion Status by +|VAL|`, - upgrade_WarframeCritDamageBoost: `[UNTRANSLATED] +|VAL|% Melee Critical Damage (Doubles over 500 Energy)`, - upgrade_WarframeElectricDamage: `[UNTRANSLATED] +|VAL1|% Primary Electricity Damage (+|VAL2|% per additional Shard)`, - upgrade_WarframeElectricDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Electricity Status`, - upgrade_WarframeEnergyMax: `[UNTRANSLATED] +|VAL| Energy Max`, - upgrade_WarframeGlobeEffectEnergy: `[UNTRANSLATED] +|VAL|% Energy Orb Effectiveness`, - upgrade_WarframeGlobeEffectHealth: `[UNTRANSLATED] +|VAL|% Health Orb Effectiveness`, - upgrade_WarframeHealthMax: `[UNTRANSLATED] +|VAL| Health`, - upgrade_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health per enemy killed with Blast Damage (Max |VAL2| Health)`, - upgrade_WarframeParkourVelocity: `[UNTRANSLATED] +|VAL|% Parkour Velocity`, - upgrade_WarframeRadiationDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Radiation Status`, - upgrade_WarframeRegen: `[UNTRANSLATED] +|VAL| Health Regen/s`, - upgrade_WarframeShieldMax: `[UNTRANSLATED] +|VAL| Shield`, - upgrade_WarframeStartingEnergy: `[UNTRANSLATED] +|VAL|% Energy on Spawn`, - upgrade_WarframeToxinDamage: `[UNTRANSLATED] +|VAL|% Toxin Status Effect Damage`, - upgrade_WarframeToxinHeal: `[UNTRANSLATED] +|VAL| Health on damaging enemies with Toxin Status`, - upgrade_WeaponCritBoostFromHeat: `[UNTRANSLATED] +|VAL1|% Secondary Critical Chance per Heat-affected enemy killed (Max |VAL2|%)`, - upgrade_AvatarAbilityRange: `[UNTRANSLATED] +7.5% Ability Range`, - upgrade_AvatarAbilityEfficiency: `[UNTRANSLATED] +5% Ability Efficiency`, - upgrade_AvatarEnergyRegen: `[UNTRANSLATED] +0.5 Energy Regen/s`, - upgrade_AvatarEnemyRadar: `[UNTRANSLATED] +5m Enemy Radar`, - upgrade_AvatarLootRadar: `[UNTRANSLATED] +7m Loot Radar`, - upgrade_WeaponAmmoMax: `[UNTRANSLATED] +15% Ammo Max`, - upgrade_EnemyArmorReductionAura: `[UNTRANSLATED] -3% Enemy Armor`, - upgrade_OnExecutionAmmo: `[UNTRANSLATED] 100% Primary and Secondary Magazine Refill on Mercy`, - upgrade_OnExecutionHealthDrop: `[UNTRANSLATED] 100% chance to drop a Health Orb on Mercy`, - upgrade_OnExecutionEnergyDrop: `[UNTRANSLATED] 50% chance to drop an Energy Orb on Mercy`, - upgrade_OnFailHackReset: `[UNTRANSLATED] +50% to retry on Hacking failure`, - upgrade_DamageReductionOnHack: `[UNTRANSLATED] 75% Damage Reduction while Hacking`, - upgrade_OnExecutionReviveCompanion: `[UNTRANSLATED] Mercy Kills reduce Companion Recovery by 15s`, - upgrade_OnExecutionParkourSpeed: `[UNTRANSLATED] +60% Parkour Speed after a Mercy for 15s`, - upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] s to Hacking`, - upgrade_ElectrifyOnHack: `[UNTRANSLATED] Shock enemies within 20m while Hacking`, - upgrade_OnExecutionTerrify: `[UNTRANSLATED] 50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`, - upgrade_OnHackLockers: `[UNTRANSLATED] Unlock 5 lockers within 20m after Hacking`, - upgrade_OnExecutionBlind: `[UNTRANSLATED] Blind enemies within 18m on Mercy`, - upgrade_OnExecutionDrainPower: `[UNTRANSLATED] 100% chance for next ability cast to gain +50% Ability Strength on Mercy`, - upgrade_OnHackSprintSpeed: `[UNTRANSLATED] +75% Sprint Speed for 15s after Hacking`, - upgrade_SwiftExecute: `[UNTRANSLATED] Speed of Mercy Kills increased by 50%`, - upgrade_OnHackInvis: `[UNTRANSLATED] Invisible for 15 seconds after hacking`, + upgrade_Equilibrium: `+|VAL|% 能量 来自生命球, +|VAL|% 生命 来自能量球`, + upgrade_MeleeCritDamage: `+|VAL|% 近战暴击伤害`, + upgrade_PrimaryStatusChance: `+|VAL|% 主武器触发几率`, + upgrade_SecondaryCritChance: `+|VAL|% 次要武器暴击几率`, + upgrade_WarframeAbilityDuration: `+|VAL|% 技能持续时间`, + upgrade_WarframeAbilityStrength: `+|VAL|% 技能强度`, + upgrade_WarframeArmourMax: `+|VAL| 护甲`, + upgrade_WarframeBlastProc: `施加爆炸状态时,护盾 +|VAL|`, + upgrade_WarframeCastingSpeed: `+|VAL|% 施放速度`, + upgrade_WarframeCorrosiveDamageBoost: `对受腐蚀状态影响的敌人 +|VAL|% 技能伤害`, + upgrade_WarframeCorrosiveStack: `腐蚀状态最大堆叠数 +|VAL|`, + upgrade_WarframeCritDamageBoost: `+|VAL|% 近战暴击伤害 (500能量以上翻倍)`, + upgrade_WarframeElectricDamage: `+|VAL1|% 主武器伤害效果 (+|VAL2|% 每附加一个碎片)`, + upgrade_WarframeElectricDamageBoost: `对受电能状态影响的敌人 +|VAL|% 技能伤害`, + upgrade_WarframeEnergyMax: `+|VAL| 最大能量`, + upgrade_WarframeGlobeEffectEnergy: `+|VAL|% 能量球效果`, + upgrade_WarframeGlobeEffectHealth: `+|VAL|% 生命球效果`, + upgrade_WarframeHealthMax: `+|VAL| 生命`, + upgrade_WarframeHPBoostFromImpact: `每个被爆炸伤害击杀的敌人,补充 |VAL1|生命 (最大 |VAL2| 生命)`, + upgrade_WarframeParkourVelocity: `+|VAL|% 跑酷速度`, + upgrade_WarframeRadiationDamageBoost: `对受辐射状态影响的敌人 +|VAL|% 技能伤害`, + upgrade_WarframeRegen: `+|VAL| 生命再生/s`, + upgrade_WarframeShieldMax: `+|VAL| 护盾`, + upgrade_WarframeStartingEnergy: `+|VAL|% 能量出生时`, + upgrade_WarframeToxinDamage: `+|VAL|% 毒素伤害效果`, + upgrade_WarframeToxinHeal: `+|VAL| 生命 对毒素状态的敌人造成伤害时`, + upgrade_WeaponCritBoostFromHeat: `每个被火焰伤害杀死的敌人, 增加|VAL1|% 次要武器暴击几率 (最大 |VAL2|%)`, + upgrade_AvatarAbilityRange: `+7.5% 技能范围`, + upgrade_AvatarAbilityEfficiency: `+5% 技能效率`, + upgrade_AvatarEnergyRegen: `+0.5 能量再生/秒`, + upgrade_AvatarEnemyRadar: `+5米 敌方雷达`, + upgrade_AvatarLootRadar: `+7米 战利品雷达`, + upgrade_WeaponAmmoMax: `+15% 弹药最大容量`, + upgrade_EnemyArmorReductionAura: `-3% 敌方护甲`, + upgrade_OnExecutionAmmo: `怜悯之击 100% 补充主次要武器弹匣`, + upgrade_OnExecutionHealthDrop: `怜悯之击 100% 几率 掉落生命球`, + upgrade_OnExecutionEnergyDrop: `怜悯之击 50% 几率 掉落生命球`, + upgrade_OnFailHackReset: `+50% 在入侵失败时重试`, + upgrade_DamageReductionOnHack: `入侵时,+75% 伤害减免`, + upgrade_OnExecutionReviveCompanion: `怜悯之击 减少同伴复苏时间 15秒`, + upgrade_OnExecutionParkourSpeed: `怜悯之击 15秒内 +60% 跑酷速度`, + upgrade_AvatarTimeLimitIncrease: `增加入侵限制时间`, + upgrade_ElectrifyOnHack: `入侵时震慑20米之内的敌人`, + upgrade_OnExecutionTerrify: `怜悯之击 50% 几率让 15米 以内的敌人恐慌`, + upgrade_OnHackLockers: `入侵后解锁20米内的5个储物柜`, + upgrade_OnExecutionBlind: `怜悯之击 致盲18米之内的敌人`, + upgrade_OnExecutionDrainPower: `怜悯之击会使下一个技能有100%的机会获得+50%的技能强度`, + upgrade_OnHackSprintSpeed: `入侵后+75%冲刺速度,持续15秒`, + upgrade_SwiftExecute: `怜悯之击速度提升50%`, + upgrade_OnHackInvis: `入侵后隐身15秒`, prettier_sucks_ass: `` }; From 2e8fe799d74cfd0ac037cbe797845a8b636b5e0c Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Tue, 17 Jun 2025 05:01:21 -0700 Subject: [PATCH 019/153] feat: setSuitInfection (#2174) Closes #2172 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2174 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- .../api/setSuitInfectionController.ts | 22 +++++++++++++++++++ src/routes/api.ts | 2 ++ 2 files changed, 24 insertions(+) create mode 100644 src/controllers/api/setSuitInfectionController.ts diff --git a/src/controllers/api/setSuitInfectionController.ts b/src/controllers/api/setSuitInfectionController.ts new file mode 100644 index 00000000..b4a169d9 --- /dev/null +++ b/src/controllers/api/setSuitInfectionController.ts @@ -0,0 +1,22 @@ +import { fromMongoDate, fromOid } from "@/src/helpers/inventoryHelpers"; +import { getJSONfromString } from "@/src/helpers/stringHelpers"; +import { getInventory } from "@/src/services/inventoryService"; +import { getAccountIdForRequest } from "@/src/services/loginService"; +import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; +import { RequestHandler } from "express"; + +export const setSuitInfectionController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const inventory = await getInventory(accountId, "Suits"); + const payload = getJSONfromString(String(req.body)); + for (const clientSuit of payload.Suits) { + const dbSuit = inventory.Suits.id(fromOid(clientSuit.ItemId))!; + dbSuit.InfestationDate = fromMongoDate(clientSuit.InfestationDate!); + } + await inventory.save(); + res.end(); +}; + +interface ISetSuitInfectionRequest { + Suits: IEquipmentClient[]; +} diff --git a/src/routes/api.ts b/src/routes/api.ts index a12efbd0..55c2fd7b 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -132,6 +132,7 @@ import { setPlacedDecoInfoController } from "@/src/controllers/api/setPlacedDeco import { setShipCustomizationsController } from "@/src/controllers/api/setShipCustomizationsController"; import { setShipFavouriteLoadoutController } from "@/src/controllers/api/setShipFavouriteLoadoutController"; import { setShipVignetteController } from "@/src/controllers/api/setShipVignetteController"; +import { setSuitInfectionController } from "@/src/controllers/api/setSuitInfectionController"; import { setSupportedSyndicateController } from "@/src/controllers/api/setSupportedSyndicateController"; import { setWeaponSkillTreeController } from "@/src/controllers/api/setWeaponSkillTreeController"; import { shipDecorationsController } from "@/src/controllers/api/shipDecorationsController"; @@ -317,6 +318,7 @@ apiRouter.post("/setPlacedDecoInfo.php", setPlacedDecoInfoController); apiRouter.post("/setShipCustomizations.php", setShipCustomizationsController); apiRouter.post("/setShipFavouriteLoadout.php", setShipFavouriteLoadoutController); apiRouter.post("/setShipVignette.php", setShipVignetteController); +apiRouter.post("/setSuitInfection.php", setSuitInfectionController); apiRouter.post("/setWeaponSkillTree.php", setWeaponSkillTreeController); apiRouter.post("/shipDecorations.php", shipDecorationsController); apiRouter.post("/startCollectibleEntry.php", startCollectibleEntryController); From 01e490768c883184c34d10631ca779fba4cf1f7b Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Tue, 17 Jun 2025 05:01:55 -0700 Subject: [PATCH 020/153] fix: ensure helminth shard operations don't produce a null shard (#2176) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2176 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/infestedFoundryController.ts | 8 +++++--- src/models/inventoryModels/inventoryModel.ts | 11 +++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/controllers/api/infestedFoundryController.ts b/src/controllers/api/infestedFoundryController.ts index 4cc21061..495b4d8b 100644 --- a/src/controllers/api/infestedFoundryController.ts +++ b/src/controllers/api/infestedFoundryController.ts @@ -30,8 +30,9 @@ export const infestedFoundryController: RequestHandler = async (req, res) => { const request = getJSONfromString(String(req.body)); const inventory = await getInventory(account._id.toString()); const suit = inventory.Suits.id(request.SuitId.$oid)!; - if (!suit.ArchonCrystalUpgrades || suit.ArchonCrystalUpgrades.length != 5) { - suit.ArchonCrystalUpgrades = [{}, {}, {}, {}, {}]; + suit.ArchonCrystalUpgrades ??= []; + while (suit.ArchonCrystalUpgrades.length < request.Slot) { + suit.ArchonCrystalUpgrades.push({}); } suit.ArchonCrystalUpgrades[request.Slot] = { UpgradeType: request.UpgradeType, @@ -92,7 +93,8 @@ export const infestedFoundryController: RequestHandler = async (req, res) => { } // remove from suit - suit.ArchonCrystalUpgrades![request.Slot] = {}; + suit.ArchonCrystalUpgrades![request.Slot].UpgradeType = undefined; + suit.ArchonCrystalUpgrades![request.Slot].Color = undefined; await inventory.save(); diff --git a/src/models/inventoryModels/inventoryModel.ts b/src/models/inventoryModels/inventoryModel.ts index 2e4008dd..a9335fa7 100644 --- a/src/models/inventoryModels/inventoryModel.ts +++ b/src/models/inventoryModels/inventoryModel.ts @@ -251,12 +251,6 @@ const ArchonCrystalUpgradeSchema = new Schema( { _id: false } ); -ArchonCrystalUpgradeSchema.set("toJSON", { - transform(_document, returnedObject) { - delete returnedObject.__v; - } -}); - const boosterSchema = new Schema( { ExpiryDate: Number, @@ -1079,6 +1073,11 @@ EquipmentSchema.set("toJSON", { if (db.UmbraDate) { client.UmbraDate = toMongoDate(db.UmbraDate); } + + if (client.ArchonCrystalUpgrades) { + // For some reason, mongoose turns empty objects here into nulls, so we have to fix it. + client.ArchonCrystalUpgrades = client.ArchonCrystalUpgrades.map(x => (x as unknown) ?? {}); + } } }); From 6c2055a2465f8f84b7c1ca79e1e684ed1c080acd Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Tue, 17 Jun 2025 05:02:11 -0700 Subject: [PATCH 021/153] feat: echoes of umbra (#2177) Having this item in the inventory unlocks the helminth option which is helpfully called "remove cyst" to install and uninstall it on a frame. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2177 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/umbraController.ts | 27 ++++++++++++++++++++++++++ src/routes/api.ts | 2 ++ src/services/inventoryService.ts | 2 +- 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 src/controllers/api/umbraController.ts diff --git a/src/controllers/api/umbraController.ts b/src/controllers/api/umbraController.ts new file mode 100644 index 00000000..e89ca74d --- /dev/null +++ b/src/controllers/api/umbraController.ts @@ -0,0 +1,27 @@ +import { fromMongoDate, fromOid } from "@/src/helpers/inventoryHelpers"; +import { getJSONfromString } from "@/src/helpers/stringHelpers"; +import { addMiscItem, getInventory } from "@/src/services/inventoryService"; +import { getAccountIdForRequest } from "@/src/services/loginService"; +import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; +import { RequestHandler } from "express"; + +export const umbraController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const inventory = await getInventory(accountId, "Suits MiscItems"); + const payload = getJSONfromString(String(req.body)); + for (const clientSuit of payload.Suits) { + const dbSuit = inventory.Suits.id(fromOid(clientSuit.ItemId))!; + if (clientSuit.UmbraDate) { + addMiscItem(inventory, "/Lotus/Types/Items/MiscItems/UmbraEchoes", -1); + dbSuit.UmbraDate = fromMongoDate(clientSuit.UmbraDate); + } else { + dbSuit.UmbraDate = undefined; + } + } + await inventory.save(); + res.end(); +}; + +interface IUmbraRequest { + Suits: IEquipmentClient[]; +} diff --git a/src/routes/api.ts b/src/routes/api.ts index 55c2fd7b..8b0a12bc 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -148,6 +148,7 @@ import { syndicateStandingBonusController } from "@/src/controllers/api/syndicat import { tauntHistoryController } from "@/src/controllers/api/tauntHistoryController"; import { tradingController } from "@/src/controllers/api/tradingController"; import { trainingResultController } from "@/src/controllers/api/trainingResultController"; +import { umbraController } from "@/src/controllers/api/umbraController"; import { unlockShipFeatureController } from "@/src/controllers/api/unlockShipFeatureController"; import { updateAlignmentController } from "@/src/controllers/api/updateAlignmentController"; import { updateChallengeProgressController } from "@/src/controllers/api/updateChallengeProgressController"; @@ -329,6 +330,7 @@ apiRouter.post("/syndicateSacrifice.php", syndicateSacrificeController); apiRouter.post("/syndicateStandingBonus.php", syndicateStandingBonusController); apiRouter.post("/tauntHistory.php", tauntHistoryController); apiRouter.post("/trainingResult.php", trainingResultController); +apiRouter.post("/umbra.php", umbraController); apiRouter.post("/unlockShipFeature.php", unlockShipFeatureController); apiRouter.post("/updateAlignment.php", updateAlignmentController); apiRouter.post("/updateChallengeProgress.php", updateChallengeProgressController); diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index ad53cad5..d598cbb1 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -1580,7 +1580,7 @@ export const addMiscItem = ( inventory: TInventoryDatabaseDocument, type: string, count: number, - inventoryChanges: IInventoryChanges + inventoryChanges: IInventoryChanges = {} ): void => { const miscItemChanges: IMiscItem[] = [ { From 145d21e30e00c1a80ec8a58d8c5bf685db3b75f6 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Tue, 17 Jun 2025 05:02:34 -0700 Subject: [PATCH 022/153] fix: weaken infested lich (#2181) Closes #2180 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2181 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/nemesisController.ts | 34 +++++++++++++++--------- src/helpers/nemesisHelpers.ts | 2 +- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/controllers/api/nemesisController.ts b/src/controllers/api/nemesisController.ts index 8da8fe4d..fe536267 100644 --- a/src/controllers/api/nemesisController.ts +++ b/src/controllers/api/nemesisController.ts @@ -1,5 +1,6 @@ import { version_compare } from "@/src/helpers/inventoryHelpers"; import { + antivirusMods, consumeModCharge, decodeNemesisGuess, encodeNemesisGuess, @@ -134,34 +135,37 @@ export const nemesisController: RequestHandler = async (req, res) => { 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": + case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod": 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; + if (inventory.Nemesis!.HenchmenKilled >= 100) { + inventory.Nemesis!.HenchmenKilled = 100; + // Client doesn't seem to request mode=w for infested liches, so weakening it here. + inventory.Nemesis!.InfNodes = [ + { + Node: getNemesisManifest(inventory.Nemesis!.manifest).showdownNode, + Influence: 1 + } + ]; + inventory.Nemesis!.Weakened = true; + const upgrade = getKnifeUpgrade(inventory, dataknifeUpgrades, antivirusMods[passcode]); + consumeModCharge(response, inventory, upgrade, dataknifeUpgrades); + } } - if (inventory.Nemesis!.HenchmenKilled >= 100) { - inventory.Nemesis!.HenchmenKilled = 100; + if (inventory.Nemesis!.HenchmenKilled < 100) { + inventory.Nemesis!.InfNodes = getInfNodes(getNemesisManifest(inventory.Nemesis!.manifest), 0); } - inventory.Nemesis!.InfNodes = getInfNodes(getNemesisManifest(inventory.Nemesis!.manifest), 0); await inventory.save(); res.json(response); @@ -283,6 +287,10 @@ export const nemesisController: RequestHandler = async (req, res) => { ); //const body = getJSONfromString(String(req.body)); + if (inventory.Nemesis!.Weakened) { + logger.warn(`client is weakening an already-weakened nemesis?!`); + } + inventory.Nemesis!.InfNodes = [ { Node: getNemesisManifest(inventory.Nemesis!.manifest).showdownNode, diff --git a/src/helpers/nemesisHelpers.ts b/src/helpers/nemesisHelpers.ts index 8d0d8527..0c7e5bdd 100644 --- a/src/helpers/nemesisHelpers.ts +++ b/src/helpers/nemesisHelpers.ts @@ -248,7 +248,7 @@ const requiemMods: readonly string[] = [ "/Lotus/Upgrades/Mods/Immortal/ImmortalEightMod" ]; -const antivirusMods: readonly string[] = [ +export const antivirusMods: readonly string[] = [ "/Lotus/Upgrades/Mods/Immortal/AntivirusOneMod", "/Lotus/Upgrades/Mods/Immortal/AntivirusTwoMod", "/Lotus/Upgrades/Mods/Immortal/AntivirusThreeMod", From 1ead5817803df0714202faaec4afe2783bfa3745 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Tue, 17 Jun 2025 05:02:58 -0700 Subject: [PATCH 023/153] chore: improve typing of IFocusXp (#2182) Any given focus school can be undefined in this object due to importing. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2182 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/focusController.ts | 14 ++++++----- src/services/inventoryService.ts | 27 +++++++++++++++++----- src/types/inventoryTypes/inventoryTypes.ts | 10 ++++---- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/controllers/api/focusController.ts b/src/controllers/api/focusController.ts index 90b55a2e..c7f96a6d 100644 --- a/src/controllers/api/focusController.ts +++ b/src/controllers/api/focusController.ts @@ -43,7 +43,7 @@ export const focusController: RequestHandler = async (req, res) => { inventory.FocusAbility ??= focusType; inventory.FocusUpgrades.push({ ItemType: focusType }); if (inventory.FocusXP) { - inventory.FocusXP[focusPolarity] -= cost; + inventory.FocusXP[focusPolarity]! -= cost; } await inventory.save(); res.json({ @@ -78,7 +78,7 @@ export const focusController: RequestHandler = async (req, res) => { cost += ExportFocusUpgrades[focusType].baseFocusPointCost; inventory.FocusUpgrades.push({ ItemType: focusType, Level: 0 }); } - inventory.FocusXP![focusPolarity] -= cost; + inventory.FocusXP![focusPolarity]! -= cost; await inventory.save(); res.json({ FocusTypes: request.FocusTypes, @@ -96,7 +96,7 @@ export const focusController: RequestHandler = async (req, res) => { const focusUpgradeDb = inventory.FocusUpgrades.find(entry => entry.ItemType == focusUpgrade.ItemType)!; focusUpgradeDb.Level = focusUpgrade.Level; } - inventory.FocusXP![focusPolarity] -= cost; + inventory.FocusXP![focusPolarity]! -= cost; await inventory.save(); res.json({ FocusInfos: request.FocusInfos, @@ -123,7 +123,7 @@ export const focusController: RequestHandler = async (req, res) => { const request = JSON.parse(String(req.body)) as IUnbindUpgradeRequest; const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]); const inventory = await getInventory(accountId); - inventory.FocusXP![focusPolarity] -= 750_000 * request.FocusTypes.length; + inventory.FocusXP![focusPolarity]! -= 750_000 * request.FocusTypes.length; addMiscItems(inventory, [ { ItemType: "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem", @@ -168,8 +168,10 @@ export const focusController: RequestHandler = async (req, res) => { shard.ItemCount *= -1; } const inventory = await getInventory(accountId); - inventory.FocusXP ??= { AP_POWER: 0, AP_TACTIC: 0, AP_DEFENSE: 0, AP_ATTACK: 0, AP_WARD: 0 }; - inventory.FocusXP[request.Polarity] += xp; + const polarity = request.Polarity; + inventory.FocusXP ??= {}; + inventory.FocusXP[polarity] ??= 0; + inventory.FocusXP[polarity] += xp; addMiscItems(inventory, request.Shards); await inventory.save(); break; diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index d598cbb1..38c197f8 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -1731,12 +1731,27 @@ export const addFocusXpIncreases = (inventory: TInventoryDatabaseDocument, focus AP_ANY } - inventory.FocusXP ??= { AP_ATTACK: 0, AP_DEFENSE: 0, AP_TACTIC: 0, AP_POWER: 0, AP_WARD: 0 }; - inventory.FocusXP.AP_ATTACK += focusXpPlus[FocusType.AP_ATTACK]; - inventory.FocusXP.AP_DEFENSE += focusXpPlus[FocusType.AP_DEFENSE]; - inventory.FocusXP.AP_TACTIC += focusXpPlus[FocusType.AP_TACTIC]; - inventory.FocusXP.AP_POWER += focusXpPlus[FocusType.AP_POWER]; - inventory.FocusXP.AP_WARD += focusXpPlus[FocusType.AP_WARD]; + inventory.FocusXP ??= {}; + if (focusXpPlus[FocusType.AP_ATTACK]) { + inventory.FocusXP.AP_ATTACK ??= 0; + inventory.FocusXP.AP_ATTACK += focusXpPlus[FocusType.AP_ATTACK]; + } + if (focusXpPlus[FocusType.AP_DEFENSE]) { + inventory.FocusXP.AP_DEFENSE ??= 0; + inventory.FocusXP.AP_DEFENSE += focusXpPlus[FocusType.AP_DEFENSE]; + } + if (focusXpPlus[FocusType.AP_TACTIC]) { + inventory.FocusXP.AP_TACTIC ??= 0; + inventory.FocusXP.AP_TACTIC += focusXpPlus[FocusType.AP_TACTIC]; + } + if (focusXpPlus[FocusType.AP_POWER]) { + inventory.FocusXP.AP_POWER ??= 0; + inventory.FocusXP.AP_POWER += focusXpPlus[FocusType.AP_POWER]; + } + if (focusXpPlus[FocusType.AP_WARD]) { + inventory.FocusXP.AP_WARD ??= 0; + inventory.FocusXP.AP_WARD += focusXpPlus[FocusType.AP_WARD]; + } if (!config.noDailyFocusLimit) { inventory.DailyFocus -= focusXpPlus.reduce((a, b) => a + b, 0); diff --git a/src/types/inventoryTypes/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts index 997b50f4..94149f7c 100644 --- a/src/types/inventoryTypes/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -641,11 +641,11 @@ export interface IFocusUpgrade { } export interface IFocusXP { - AP_POWER: number; - AP_TACTIC: number; - AP_DEFENSE: number; - AP_ATTACK: number; - AP_WARD: number; + AP_POWER?: number; + AP_TACTIC?: number; + AP_DEFENSE?: number; + AP_ATTACK?: number; + AP_WARD?: number; } export type TFocusPolarity = keyof IFocusXP; From cccf6f04a549bbab9df8606d158461f4b6159a5a Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Tue, 17 Jun 2025 17:32:34 -0700 Subject: [PATCH 024/153] feat: xtra cheese rotation (#2191) This has a bug where the client shows a negative time for "Xtra cheese starts in ..." until it refreshes the world state. This is because we're only providing the new activation as soon as that time/date is reached. However, this is 100% faithful to live. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2191 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/worldStateService.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/services/worldStateService.ts b/src/services/worldStateService.ts index 253bdc21..a19abee5 100644 --- a/src/services/worldStateService.ts +++ b/src/services/worldStateService.ts @@ -1297,8 +1297,12 @@ export const getWorldState = (buildLabel?: string): IWorldState => { pushVoidStorms(worldState.VoidStorms, hour); } - // Sentient Anomaly cycling every 30 minutes + // Sentient Anomaly + Xtra Cheese cycles const halfHour = Math.trunc(timeMs / (unixTimesInMs.hour / 2)); + const hourInSeconds = 3600; + const cheeseInterval = hourInSeconds * 8; + const cheeseDuration = hourInSeconds * 2; + const cheeseIndex = Math.trunc(timeSecs / cheeseInterval); const tmp = { cavabegin: "1690761600", PurchasePlatformLockEnabled: true, @@ -1323,6 +1327,11 @@ export const getWorldState = (buildLabel?: string): IWorldState => { }, ennnd: true, mbrt: true, + fbst: { + a: cheeseIndex * cheeseInterval, // This has a bug where the client shows a negative time for "Xtra cheese starts in ..." until it refreshes the world state. This is because we're only providing the new activation as soon as that time/date is reached. However, this is 100% faithful to live. + e: cheeseIndex * cheeseInterval + cheeseDuration, + n: (cheeseIndex + 1) * hourInSeconds * 8 + }, sfn: [550, 553, 554, 555][halfHour % 4] }; worldState.Tmp = JSON.stringify(tmp); From a9f1368cb7dcc3156ee20bdc150fc33737ce27c7 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Tue, 17 Jun 2025 20:58:10 -0700 Subject: [PATCH 025/153] fix: add UpgradeType field to repaired railjack weapons (#2193) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2193 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/guildTechController.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/controllers/api/guildTechController.ts b/src/controllers/api/guildTechController.ts index 49d96b70..97abbecf 100644 --- a/src/controllers/api/guildTechController.ts +++ b/src/controllers/api/guildTechController.ts @@ -11,7 +11,7 @@ import { scaleRequiredCount, setGuildTechLogState } from "@/src/services/guildService"; -import { ExportDojoRecipes } from "warframe-public-export-plus"; +import { ExportDojoRecipes, ExportRailjackWeapons } from "warframe-public-export-plus"; import { getAccountIdForRequest } from "@/src/services/loginService"; import { addCrewShipWeaponSkin, @@ -442,6 +442,7 @@ const finishComponentRepair = ( ...(category == "CrewShipWeaponSkins" ? addCrewShipWeaponSkin(inventory, salvageItem.ItemType, salvageItem.UpgradeFingerprint) : addEquipment(inventory, category, salvageItem.ItemType, { + UpgradeType: ExportRailjackWeapons[salvageItem.ItemType].defaultUpgrades?.[0].ItemType, UpgradeFingerprint: salvageItem.UpgradeFingerprint })), ...occupySlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS, false) From ffeffe2796e193a2145b97374cb0c9733412cb96 Mon Sep 17 00:00:00 2001 From: hxedcl Date: Wed, 18 Jun 2025 05:18:02 -0700 Subject: [PATCH 026/153] chore(webui): update es (#2195) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2195 Co-authored-by: hxedcl Co-committed-by: hxedcl --- static/webui/translations/es.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index 0b6b15ec..e07199ca 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -3,8 +3,8 @@ dict = { general_inventoryUpdateNote: `Nota: Los cambios realizados aquí se reflejarán en el juego cuando este sincronice el inventario. Usar la navegación debería ser la forma más sencilla de activar esto.`, general_addButton: `Agregar`, general_bulkActions: `Acciones masivas`, - code_loginFail: `[UNTRANSLATED] Login failed. Double-check the email and password.`, - code_regFail: `[UNTRANSLATED] Registration failed. Account already exists?`, + code_loginFail: `Error al iniciar sesión. Verifica el correo electrónico y la contraseña.`, + code_regFail: `Error al registrar la cuenta. ¿Ya existe una cuenta con este correo?`, code_nonValidAuthz: `Tus credenciales no son válidas.`, code_changeNameConfirm: `¿Qué nombre te gustaría ponerle a tu cuenta?`, code_deleteAccountConfirm: `¿Estás seguro de que deseas eliminar tu cuenta |DISPLAYNAME| (|EMAIL|)? Esta acción es permanente.`, From 3dcd2663d3d01418018fe00f6c499c9cb956c527 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 18 Jun 2025 05:39:10 -0700 Subject: [PATCH 027/153] fix: weaken classic lich when getting all 3 mods correct (#2186) Fixes #2185 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2186 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/nemesisController.ts | 59 +++++++++++++++++------- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/src/controllers/api/nemesisController.ts b/src/controllers/api/nemesisController.ts index fe536267..56cc5562 100644 --- a/src/controllers/api/nemesisController.ts +++ b/src/controllers/api/nemesisController.ts @@ -17,6 +17,7 @@ import { IKnifeResponse } from "@/src/helpers/nemesisHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers"; +import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { Loadout } from "@/src/models/inventoryModels/loadoutModel"; import { freeUpSlot, getInventory } from "@/src/services/inventoryService"; import { getAccountForRequest } from "@/src/services/loginService"; @@ -202,16 +203,28 @@ export const nemesisController: RequestHandler = async (req, res) => { guess[body.position].result = correct ? GUESS_CORRECT : GUESS_INCORRECT; inventory.Nemesis!.GuessHistory[inventory.Nemesis!.GuessHistory.length - 1] = encodeNemesisGuess(guess); - // Increase rank if incorrect - let RankIncrease: number | undefined; - if (!correct) { - RankIncrease = 1; + const response: INemesisRequiemResponse = {}; + if (correct) { + if (body.position == 2) { + // That was all 3 guesses correct, nemesis is now weakened. + inventory.Nemesis!.InfNodes = [ + { + Node: getNemesisManifest(inventory.Nemesis!.manifest).showdownNode, + Influence: 1 + } + ]; + inventory.Nemesis!.Weakened = true; + await consumePasscodeModCharges(inventory, response); + } + } else { + // Guess was incorrect, increase rank + response.RankIncrease = 1; const manifest = getNemesisManifest(inventory.Nemesis!.manifest); inventory.Nemesis!.Rank = Math.min(inventory.Nemesis!.Rank + 1, manifest.systemIndexes.length - 1); inventory.Nemesis!.InfNodes = getInfNodes(manifest, inventory.Nemesis!.Rank); } await inventory.save(); - res.json({ RankIncrease }); + res.json(response); } } else if ((req.query.mode as string) == "rs") { // report spawn; POST but no application data in body @@ -299,20 +312,11 @@ export const nemesisController: RequestHandler = async (req, res) => { ]; inventory.Nemesis!.Weakened = true; - const response: IKnifeResponse & { target: INemesisClient } = { + const response: INemesisWeakenResponse = { 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 consumePasscodeModCharges(inventory, response); await inventory.save(); res.json(response); @@ -370,11 +374,19 @@ interface INemesisRequiemRequest { knife?: IKnife; } +interface INemesisRequiemResponse extends IKnifeResponse { + RankIncrease?: number; +} + // interface INemesisWeakenRequest { // target: INemesisClient; // knife: IKnife; // } +interface INemesisWeakenResponse extends IKnifeResponse { + target: INemesisClient; +} + interface IKnife { Item: IEquipmentClient; Skins: IWeaponSkinClient[]; @@ -383,3 +395,18 @@ interface IKnife { AttachedUpgrades: IUpgradeClient[]; HiddenWhenHolstered: boolean; } + +const consumePasscodeModCharges = async ( + inventory: TInventoryDatabaseDocument, + response: IKnifeResponse +): Promise => { + 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); + } +}; From 6691d4e402eeb5c033b08e741e74e07f2c0eb63a Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 18 Jun 2025 05:49:58 -0700 Subject: [PATCH 028/153] feat: autogenerate steel path honors vendor (#2187) No more "preprocessing" needed now. Some good progress for #1225, I'd say. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2187 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- package-lock.json | 8 +- package.json | 2 +- src/services/serversideVendorsService.ts | 143 +++-- .../TeshinHardModeVendorManifest.json | 603 ------------------ 4 files changed, 79 insertions(+), 677 deletions(-) delete mode 100644 static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json diff --git a/package-lock.json b/package-lock.json index ceccfce4..5d1f4a36 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.67", + "warframe-public-export-plus": "^0.5.68", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0" @@ -3814,9 +3814,9 @@ } }, "node_modules/warframe-public-export-plus": { - "version": "0.5.67", - "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.67.tgz", - "integrity": "sha512-LsnZD2E5PTA+5MK9kDGvM/hFDtg8sb0EwQ4hKH5ILqrSgz30a9W8785v77RSsL1AEVF8dfb/lZcSTCJq1DZHzQ==" + "version": "0.5.68", + "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.68.tgz", + "integrity": "sha512-KMmwCVeQ4k+EN73UZqxnM+qQdPsST8geWoJCP7US5LT6JcRxa8ptmqYXwCzaLtckBLZyVbamsxKZAxPPJckxsA==" }, "node_modules/warframe-riven-info": { "version": "0.1.2", diff --git a/package.json b/package.json index eaff654a..b8d00e39 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.67", + "warframe-public-export-plus": "^0.5.68", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0" diff --git a/src/services/serversideVendorsService.ts b/src/services/serversideVendorsService.ts index 295759b2..1c947197 100644 --- a/src/services/serversideVendorsService.ts +++ b/src/services/serversideVendorsService.ts @@ -3,7 +3,6 @@ import { isDev } from "@/src/helpers/pathHelper"; import { catBreadHash } from "@/src/helpers/stringHelpers"; import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { mixSeeds, SRng } from "@/src/services/rngService"; -import { IMongoDate } from "@/src/types/commonTypes"; import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes"; import { logger } from "@/src/utils/logger"; import { ExportVendors, IRange, IVendor, IVendorOffer } from "warframe-public-export-plus"; @@ -25,7 +24,6 @@ import Nova1999ConquestShopManifest from "@/static/fixed_responses/getVendorInfo import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json"; import SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json"; import Temple1999VendorManifest from "@/static/fixed_responses/getVendorInfo/Temple1999VendorManifest.json"; -import TeshinHardModeVendorManifest from "@/static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json"; import ZarimanCommisionsManifestArchimedean from "@/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json"; const rawVendorManifests: IVendorManifest[] = [ @@ -46,7 +44,6 @@ const rawVendorManifests: IVendorManifest[] = [ OstronPetVendorManifest, SolarisDebtTokenVendorRepossessionsManifest, Temple1999VendorManifest, - TeshinHardModeVendorManifest, // uses preprocessing ZarimanCommisionsManifestArchimedean ]; @@ -87,12 +84,16 @@ const gcd = (a: number, b: number): number => { const getCycleDuration = (manifest: IVendor): number => { let dur = 0; for (const item of manifest.items) { - if (typeof item.durationHours != "number") { + if (item.alwaysOffered) { + continue; + } + const durationHours = item.rotatedWeekly ? 168 : item.durationHours; + if (typeof durationHours != "number") { dur = 1; break; } - if (dur != item.durationHours) { - dur = gcd(dur, item.durationHours); + if (dur != durationHours) { + dur = gcd(dur, durationHours); } } return dur * unixTimesInMs.hour; @@ -101,7 +102,7 @@ const getCycleDuration = (manifest: IVendor): number => { export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | undefined => { for (const vendorManifest of rawVendorManifests) { if (vendorManifest.VendorInfo.TypeName == typeName) { - return preprocessVendorManifest(vendorManifest); + return vendorManifest; } } for (const vendorInfo of generatableVendors) { @@ -124,7 +125,7 @@ export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | export const getVendorManifestByOid = (oid: string): IVendorManifest | undefined => { for (const vendorManifest of rawVendorManifests) { if (vendorManifest.VendorInfo._id.$oid == oid) { - return preprocessVendorManifest(vendorManifest); + return vendorManifest; } } for (const vendorInfo of generatableVendors) { @@ -183,30 +184,6 @@ export const applyStandingToVendorManifest = ( }; }; -const preprocessVendorManifest = (originalManifest: IVendorManifest): IVendorManifest => { - if (Date.now() >= parseInt(originalManifest.VendorInfo.Expiry.$date.$numberLong)) { - const manifest = structuredClone(originalManifest); - const info = manifest.VendorInfo; - refreshExpiry(info.Expiry); - for (const offer of info.ItemManifest) { - refreshExpiry(offer.Expiry); - } - return manifest; - } - return originalManifest; -}; - -const refreshExpiry = (expiry: IMongoDate): void => { - const period = parseInt(expiry.$date.$numberLong); - if (Date.now() >= period) { - const epoch = 1734307200_000; // Monday (for weekly schedules) - const iteration = Math.trunc((Date.now() - epoch) / period); - const start = epoch + iteration * period; - const end = start + period; - expiry.$date.$numberLong = end.toString(); - } -}; - const toRange = (value: IRange | number): IRange => { if (typeof value == "number") { return { minValue: value, maxValue: value }; @@ -230,6 +207,18 @@ const getCycleDurationRange = (manifest: IVendor): IRange | undefined => { return res.maxValue != 0 ? res : undefined; }; +type TOfferId = string; + +const getOfferId = (offer: IVendorOffer | IItemManifest): TOfferId => { + if ("storeItem" in offer) { + // IVendorOffer + return offer.storeItem + "x" + offer.quantity; + } else { + // IItemManifest + return offer.StoreItem + "x" + offer.QuantityMultiplier; + } +}; + const vendorManifestCache: Record = {}; const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifest => { @@ -270,7 +259,8 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani const rng = new SRng(mixSeeds(vendorSeed, cycleIndex)); const offersToAdd: IVendorOffer[] = []; if (!manifest.isOneBinPerCycle) { - const remainingItemCapacity: Record = {}; + // Compute vendor requirements, subtracting existing offers + const remainingItemCapacity: Record = {}; const missingItemsPerBin: Record = {}; let numOffersThatNeedToMatchABin = 0; if (manifest.numItemsPerBin) { @@ -280,56 +270,59 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani } } for (const item of manifest.items) { - remainingItemCapacity[item.storeItem] = 1 + item.duplicates; + remainingItemCapacity[getOfferId(item)] = 1 + item.duplicates; } for (const offer of info.ItemManifest) { - remainingItemCapacity[offer.StoreItem] -= 1; + remainingItemCapacity[getOfferId(offer)] -= 1; const bin = parseInt(offer.Bin.substring(4)); if (missingItemsPerBin[bin]) { missingItemsPerBin[bin] -= 1; numOffersThatNeedToMatchABin -= 1; } } - if (manifest.numItems && manifest.items.length != manifest.numItems.minValue) { - const numItemsTarget = rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue); + + // Add permanent offers + let numUncountedOffers = 0; + let offset = 0; + for (const item of manifest.items) { + if (item.alwaysOffered || item.rotatedWeekly) { + ++numUncountedOffers; + const id = getOfferId(item); + if (remainingItemCapacity[id] != 0) { + remainingItemCapacity[id] -= 1; + offersToAdd.push(item); + ++offset; + } + } + } + + // Add counted offers + if (manifest.numItems) { + const useRng = manifest.numItems.minValue != manifest.numItems.maxValue; + const numItemsTarget = + numUncountedOffers + + (useRng + ? rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue) + : manifest.numItems.minValue); + let i = 0; while (info.ItemManifest.length + offersToAdd.length < numItemsTarget) { - // TODO: Consider item probability weightings - const item = rng.randomElement(manifest.items)!; + const item = useRng ? rng.randomElement(manifest.items)! : manifest.items[i++]; if ( - remainingItemCapacity[item.storeItem] != 0 && + !item.alwaysOffered && + remainingItemCapacity[getOfferId(item)] != 0 && (numOffersThatNeedToMatchABin == 0 || missingItemsPerBin[item.bin]) ) { - remainingItemCapacity[item.storeItem] -= 1; + remainingItemCapacity[getOfferId(item)] -= 1; if (missingItemsPerBin[item.bin]) { missingItemsPerBin[item.bin] -= 1; numOffersThatNeedToMatchABin -= 1; } - offersToAdd.push(item); + offersToAdd.splice(offset, 0, item); + } + if (i == manifest.items.length) { + i = 0; } } - } else { - for (const item of manifest.items) { - if (!item.alwaysOffered && remainingItemCapacity[item.storeItem] != 0) { - remainingItemCapacity[item.storeItem] -= 1; - offersToAdd.push(item); - } - } - for (const e of Object.entries(remainingItemCapacity)) { - const item = manifest.items.find(x => x.storeItem == e[0])!; - if (!item.alwaysOffered) { - while (e[1] != 0) { - e[1] -= 1; - offersToAdd.push(item); - } - } - } - for (const item of manifest.items) { - if (item.alwaysOffered && remainingItemCapacity[item.storeItem] != 0) { - remainingItemCapacity[item.storeItem] -= 1; - offersToAdd.push(item); - } - } - offersToAdd.reverse(); } } else { const binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now. @@ -342,16 +335,21 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani const cycleStart = cycleOffset + cycleIndex * cycleDuration; for (const rawItem of offersToAdd) { const durationHoursRange = toRange(rawItem.durationHours ?? cycleDuration); - const expiry = - cycleStart + - rng.randomInt(durationHoursRange.minValue, durationHoursRange.maxValue) * unixTimesInMs.hour; + const expiry = rawItem.alwaysOffered + ? 2051240400_000 + : cycleStart + + (rawItem.rotatedWeekly + ? unixTimesInMs.week + : rng.randomInt(durationHoursRange.minValue, durationHoursRange.maxValue) * unixTimesInMs.hour); const item: IItemManifest = { StoreItem: rawItem.storeItem, ItemPrices: rawItem.itemPrices?.map(itemPrice => ({ ...itemPrice, ProductCategory: "MiscItems" })), Bin: "BIN_" + rawItem.bin, QuantityMultiplier: rawItem.quantity, Expiry: { $date: { $numberLong: expiry.toString() } }, - AllowMultipurchase: false, + PurchaseQuantityLimit: rawItem.purchaseLimit, + RotatedWeekly: rawItem.rotatedWeekly, + AllowMultipurchase: rawItem.purchaseLimit !== 1, Id: { $oid: ((cycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + @@ -422,6 +420,13 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani }; if (isDev) { + if ( + getCycleDuration(ExportVendors["/Lotus/Types/Game/VendorManifests/Hubs/TeshinHardModeVendorManifest"]) != + unixTimesInMs.week + ) { + logger.warn(`getCycleDuration self test failed`); + } + const ads = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest")! .VendorInfo.ItemManifest; if ( diff --git a/static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json b/static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json deleted file mode 100644 index 7934f0a3..00000000 --- a/static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json +++ /dev/null @@ -1,603 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "63ed01efbdaa38891767bac9" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Hubs/TeshinHardModeVendorManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/OperatorArmour/HardMode/OperatorTeshinArmsBlueprint", - "ItemPrices": [ - { - "ItemCount": 15, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9947" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/OperatorArmour/HardMode/OperatorTeshinBodyBlueprint", - "ItemPrices": [ - { - "ItemCount": 25, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9948" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/OperatorArmour/HardMode/OperatorTeshinHeadBlueprint", - "ItemPrices": [ - { - "ItemCount": 20, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9949" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/OperatorArmour/HardMode/OperatorTeshinLegsBlueprint", - "ItemPrices": [ - { - "ItemCount": 25, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e994a" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/WeaponPrimaryArcaneUnlocker", - "ItemPrices": [ - { - "ItemCount": 15, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e994b" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/WeaponSecondaryArcaneUnlocker", - "ItemPrices": [ - { - "ItemCount": 15, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e994c" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/Components/FormaStanceBlueprint", - "ItemPrices": [ - { - "ItemCount": 10, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e994d" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Effects/OrbsEphemera", - "ItemPrices": [ - { - "ItemCount": 3, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e994e" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Effects/TatsuSkullEphemera", - "ItemPrices": [ - { - "ItemCount": 85, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e994f" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawShotgunRandomMod", - "ItemPrices": [ - { - "ItemCount": 75, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "PurchaseQuantityLimit": 1, - "RotatedWeekly": true, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e9950" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/Components/UmbraFormaBlueprint", - "ItemPrices": [ - { - "ItemCount": 150, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "PurchaseQuantityLimit": 1, - "RotatedWeekly": true, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e9951" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Kuva", - "ItemPrices": [ - { - "ItemCount": 55, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 50000, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "PurchaseQuantityLimit": 1, - "RotatedWeekly": true, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e9952" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawModularPistolRandomMod", - "ItemPrices": [ - { - "ItemCount": 75, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "PurchaseQuantityLimit": 1, - "RotatedWeekly": true, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e9953" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Forma", - "ItemPrices": [ - { - "ItemCount": 75, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 3, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "PurchaseQuantityLimit": 1, - "RotatedWeekly": true, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e9954" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawModularMeleeRandomMod", - "ItemPrices": [ - { - "ItemCount": 75, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "PurchaseQuantityLimit": 1, - "RotatedWeekly": true, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e9955" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/FusionBundles/EvergreenLoginRewardFusionBundle", - "ItemPrices": [ - { - "ItemCount": 150, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "PurchaseQuantityLimit": 1, - "RotatedWeekly": true, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e9956" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawRifleRandomMod", - "ItemPrices": [ - { - "ItemCount": 75, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "PurchaseQuantityLimit": 1, - "RotatedWeekly": true, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e9957" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/WeaponRecoilReductionMod", - "ItemPrices": [ - { - "ItemCount": 35, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9958" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/TeshinBobbleHead", - "ItemPrices": [ - { - "ItemCount": 35, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9959" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/ImageGaussVED", - "ItemPrices": [ - { - "ItemCount": 15, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e995a" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/ImageGrendelVED", - "ItemPrices": [ - { - "ItemCount": 15, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e995b" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageProteaAction", - "ItemPrices": [ - { - "ItemCount": 15, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e995c" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/TeaSet", - "ItemPrices": [ - { - "ItemCount": 15, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e995d" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageXakuAction", - "ItemPrices": [ - { - "ItemCount": 15, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e995e" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/RivenIdentifier", - "ItemPrices": [ - { - "ItemCount": 20, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "PurchaseQuantityLimit": 1, - "RotatedWeekly": true, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e995f" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/BoosterPacks/RandomSyndicateProjectionPack", - "ItemPrices": [ - { - "ItemCount": 15, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "604800000" - } - }, - "PurchaseQuantityLimit": 25, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e997c" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Kuva", - "ItemPrices": [ - { - "ItemCount": 15, - "ItemType": "/Lotus/Types/Items/MiscItems/SteelEssence", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 10000, - "Expiry": { - "$date": { - "$numberLong": "604800000" - } - }, - "PurchaseQuantityLimit": 25, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e997d" - } - } - ], - "PropertyTextHash": "0A0F20AFA748FBEE490510DBF5A33A0D", - "Expiry": { - "$date": { - "$numberLong": "604800000" - } - } - } -} From 16e80acb533e10665eaf8b334c559b9988c2ec6c Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 18 Jun 2025 05:50:43 -0700 Subject: [PATCH 029/153] chore: add return type to createMessage (#2188) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2188 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/inboxService.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/services/inboxService.ts b/src/services/inboxService.ts index 0c2d698d..ea837b01 100644 --- a/src/services/inboxService.ts +++ b/src/services/inboxService.ts @@ -50,14 +50,17 @@ export const createNewEventMessages = async (req: Request): Promise => { await account.save(); }; -export const createMessage = async (accountId: string | Types.ObjectId, messages: IMessageCreationTemplate[]) => { +export const createMessage = async ( + accountId: string | Types.ObjectId, + messages: IMessageCreationTemplate[] +): Promise[]> => { const ownerIdMessages = messages.map(m => ({ ...m, ownerId: accountId })); const savedMessages = await Inbox.insertMany(ownerIdMessages); - return savedMessages; + return savedMessages as HydratedDocument[]; }; export interface IMessageCreationTemplate extends Omit { From f8d0c9e0cbeb6db86fd105b071314e157b1b362c Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 18 Jun 2025 05:51:20 -0700 Subject: [PATCH 030/153] chore: use ChallengesFixVersion that client provides (#2190) It seems that now we're on version 7, so let's just not hard-code it. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2190 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/updateChallengeProgressController.ts | 6 +++++- src/models/inventoryModels/inventoryModel.ts | 2 +- src/types/inventoryTypes/inventoryTypes.ts | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/controllers/api/updateChallengeProgressController.ts b/src/controllers/api/updateChallengeProgressController.ts index b948bb79..51eb88c3 100644 --- a/src/controllers/api/updateChallengeProgressController.ts +++ b/src/controllers/api/updateChallengeProgressController.ts @@ -11,8 +11,11 @@ export const updateChallengeProgressController: RequestHandler = async (req, res const inventory = await getInventory( account._id.toString(), - "ChallengeProgress SeasonChallengeHistory Affiliations" + "ChallengesFixVersion ChallengeProgress SeasonChallengeHistory Affiliations" ); + if (challenges.ChallengesFixVersion !== undefined) { + inventory.ChallengesFixVersion = challenges.ChallengesFixVersion; + } let affiliationMods: IAffiliationMods[] = []; if (challenges.ChallengeProgress) { affiliationMods = addChallenges( @@ -40,6 +43,7 @@ export const updateChallengeProgressController: RequestHandler = async (req, res }; interface IUpdateChallengeProgressRequest { + ChallengesFixVersion?: number; ChallengeProgress?: IChallengeProgress[]; SeasonChallengeHistory?: ISeasonChallenge[]; SeasonChallengeCompletions?: ISeasonChallenge[]; diff --git a/src/models/inventoryModels/inventoryModel.ts b/src/models/inventoryModels/inventoryModel.ts index a9335fa7..0daeb2c1 100644 --- a/src/models/inventoryModels/inventoryModel.ts +++ b/src/models/inventoryModels/inventoryModel.ts @@ -1703,7 +1703,7 @@ const inventorySchema = new Schema( LastInventorySync: Schema.Types.ObjectId, Mailbox: MailboxSchema, HandlerPoints: Number, - ChallengesFixVersion: { type: Number, default: 6 }, + ChallengesFixVersion: Number, PlayedParkourTutorial: Boolean, //ActiveLandscapeTraps: [Schema.Types.Mixed], //RepVotes: [Schema.Types.Mixed], diff --git a/src/types/inventoryTypes/inventoryTypes.ts b/src/types/inventoryTypes/inventoryTypes.ts index 94149f7c..c3a8a7d1 100644 --- a/src/types/inventoryTypes/inventoryTypes.ts +++ b/src/types/inventoryTypes/inventoryTypes.ts @@ -234,7 +234,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu HandlerPoints: number; MiscItems: IMiscItem[]; HasOwnedVoidProjectionsPreviously?: boolean; - ChallengesFixVersion: number; + ChallengesFixVersion?: number; ChallengeProgress: IChallengeProgress[]; RawUpgrades: IRawUpgrade[]; ReceivedStartingGear: boolean; From 9af0e06b70b98d7c4edb1776871c63dee4fed85f Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 18 Jun 2025 05:51:56 -0700 Subject: [PATCH 031/153] feat: add worldState.circuitGameModes config option (#2192) Closes #749 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2192 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- README.md | 1 + config.json.example | 3 ++- src/services/configService.ts | 1 + src/services/worldStateService.ts | 12 ++++++--- src/types/worldStateTypes.ts | 45 +++++++++++++++++++++++++++++++ 5 files changed, 57 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e8d2b419..dc57e796 100644 --- a/README.md +++ b/README.md @@ -33,3 +33,4 @@ SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [confi - `RadioLegion2Syndicate` for The Emissary - `RadioLegionIntermissionSyndicate` for Intermission I - `RadioLegionSyndicate` for The Wolf of Saturn Six +- `worldState.circuitGameModes` can be provided with an array of valid game modes (`Survival`, `VoidFlood`, `Excavation`, `Defense`, `Exterminate`, `Assassination`, `Alchemy`) diff --git a/config.json.example b/config.json.example index bf6eae91..8f839751 100644 --- a/config.json.example +++ b/config.json.example @@ -58,7 +58,8 @@ "starDays": true, "eidolonOverride": "", "vallisOverride": "", - "nightwaveOverride": "" + "nightwaveOverride": "", + "circuitGameModes": null }, "dev": { "keepVendorsExpired": false diff --git a/src/services/configService.ts b/src/services/configService.ts index 82cc4f31..fbcda8af 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -65,6 +65,7 @@ interface IConfig { eidolonOverride?: string; vallisOverride?: string; nightwaveOverride?: string; + circuitGameModes?: string[]; }; dev?: { keepVendorsExpired?: boolean; diff --git a/src/services/worldStateService.ts b/src/services/worldStateService.ts index a19abee5..5f2df857 100644 --- a/src/services/worldStateService.ts +++ b/src/services/worldStateService.ts @@ -16,8 +16,10 @@ import { ISortie, ISortieMission, ISyndicateMissionInfo, + ITmp, IVoidStorm, - IWorldState + IWorldState, + TCircuitGameMode } from "../types/worldStateTypes"; import { version_compare } from "../helpers/inventoryHelpers"; import { logger } from "../utils/logger"; @@ -1303,10 +1305,9 @@ export const getWorldState = (buildLabel?: string): IWorldState => { const cheeseInterval = hourInSeconds * 8; const cheeseDuration = hourInSeconds * 2; const cheeseIndex = Math.trunc(timeSecs / cheeseInterval); - const tmp = { + const tmp: ITmp = { cavabegin: "1690761600", PurchasePlatformLockEnabled: true, - tcsn: true, pgr: { ts: "1732572900", en: "CUSTOM DECALS @ ZEVILA", @@ -1330,10 +1331,13 @@ export const getWorldState = (buildLabel?: string): IWorldState => { fbst: { a: cheeseIndex * cheeseInterval, // This has a bug where the client shows a negative time for "Xtra cheese starts in ..." until it refreshes the world state. This is because we're only providing the new activation as soon as that time/date is reached. However, this is 100% faithful to live. e: cheeseIndex * cheeseInterval + cheeseDuration, - n: (cheeseIndex + 1) * hourInSeconds * 8 + n: (cheeseIndex + 1) * cheeseInterval }, sfn: [550, 553, 554, 555][halfHour % 4] }; + if (Array.isArray(config.worldState?.circuitGameModes)) { + tmp.edg = config.worldState.circuitGameModes as TCircuitGameMode[]; + } worldState.Tmp = JSON.stringify(tmp); return worldState; diff --git a/src/types/worldStateTypes.ts b/src/types/worldStateTypes.ts index 880c5800..bd8ab138 100644 --- a/src/types/worldStateTypes.ts +++ b/src/types/worldStateTypes.ts @@ -191,3 +191,48 @@ export interface ICalendarEvent { dialogueName?: string; dialogueConvo?: string; } + +export type TCircuitGameMode = + | "Survival" + | "VoidFlood" + | "Excavation" + | "Defense" + | "Exterminate" + | "Assassination" + | "Alchemy"; + +export interface ITmp { + cavabegin: string; + PurchasePlatformLockEnabled: boolean; // Seems unused + pgr: IPgr; + ennnd?: boolean; // True if 1999 demo is available (no effect for >=38.6.0) + mbrt?: boolean; // Related to mobile app rating request + fbst: IFbst; + sfn: number; + edg?: TCircuitGameMode[]; // The Circuit game modes overwrite +} + +interface IPgr { + ts: string; + en: string; + fr: string; + it: string; + de: string; + es: string; + pt: string; + ru: string; + pl: string; + uk: string; + tr: string; + ja: string; + zh: string; + ko: string; + tc: string; + th: string; +} + +interface IFbst { + a: number; + e: number; + n: number; +} From b8b8b6a6c6975b2d1f745f98809b70065782e924 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 18 Jun 2025 05:52:16 -0700 Subject: [PATCH 032/153] feat: railjack valence fusion (#2194) Closes #1678 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2194 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- .../api/crewShipFusionController.ts | 107 ++++++++++++++++++ src/routes/api.ts | 2 + 2 files changed, 109 insertions(+) create mode 100644 src/controllers/api/crewShipFusionController.ts diff --git a/src/controllers/api/crewShipFusionController.ts b/src/controllers/api/crewShipFusionController.ts new file mode 100644 index 00000000..87cfd2ce --- /dev/null +++ b/src/controllers/api/crewShipFusionController.ts @@ -0,0 +1,107 @@ +import { getJSONfromString } from "@/src/helpers/stringHelpers"; +import { addMiscItems, freeUpSlot, getInventory, updateCurrency } from "@/src/services/inventoryService"; +import { getAccountIdForRequest } from "@/src/services/loginService"; +import { IOid } from "@/src/types/commonTypes"; +import { ICrewShipComponentFingerprint, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes"; +import { IInventoryChanges } from "@/src/types/purchaseTypes"; +import { RequestHandler } from "express"; +import { ExportCustoms, ExportDojoRecipes } from "warframe-public-export-plus"; + +export const crewShipFusionController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const inventory = await getInventory(accountId); + const payload = getJSONfromString(String(req.body)); + + const isWeapon = inventory.CrewShipWeapons.id(payload.PartA.$oid); + const itemA = isWeapon ?? inventory.CrewShipWeaponSkins.id(payload.PartA.$oid)!; + const category = isWeapon ? "CrewShipWeapons" : "CrewShipWeaponSkins"; + const salvageCategory = isWeapon ? "CrewShipSalvagedWeapons" : "CrewShipSalvagedWeaponSkins"; + const itemB = inventory[payload.SourceRecipe ? salvageCategory : category].id(payload.PartB.$oid)!; + const tierA = itemA.ItemType.charCodeAt(itemA.ItemType.length - 1) - 65; + const tierB = itemB.ItemType.charCodeAt(itemB.ItemType.length - 1) - 65; + + const inventoryChanges: IInventoryChanges = {}; + + // Charge partial repair cost if fusing with an identified but unrepaired part + if (payload.SourceRecipe) { + const recipe = ExportDojoRecipes.research[payload.SourceRecipe]; + updateCurrency(inventory, Math.round(recipe.price * 0.4), false, inventoryChanges); + const miscItemChanges = recipe.ingredients.map(x => ({ ...x, ItemCount: Math.round(x.ItemCount * -0.4) })); + addMiscItems(inventory, miscItemChanges); + inventoryChanges.MiscItems = miscItemChanges; + } + + // Remove inferior item + if (payload.SourceRecipe) { + inventory[salvageCategory].pull({ _id: payload.PartB.$oid }); + inventoryChanges.RemovedIdItems = [{ ItemId: payload.PartB }]; + } else { + const inferiorId = tierA < tierB ? payload.PartA : payload.PartB; + inventory[category].pull({ _id: inferiorId.$oid }); + inventoryChanges.RemovedIdItems = [{ ItemId: inferiorId }]; + freeUpSlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS); + inventoryChanges[InventorySlot.RJ_COMPONENT_AND_ARMAMENTS] = { count: -1, platinum: 0, Slots: 1 }; + } + + // Upgrade superior item + const superiorItem = tierA < tierB ? itemB : itemA; + const inferiorItem = tierA < tierB ? itemA : itemB; + const fingerprint: ICrewShipComponentFingerprint = JSON.parse( + superiorItem.UpgradeFingerprint! + ) as ICrewShipComponentFingerprint; + const inferiorFingerprint: ICrewShipComponentFingerprint = inferiorItem.UpgradeFingerprint + ? (JSON.parse(inferiorItem.UpgradeFingerprint) as ICrewShipComponentFingerprint) + : { compat: "", buffs: [] }; + if (isWeapon) { + for (let i = 0; i != fingerprint.buffs.length; ++i) { + const buffA = fingerprint.buffs[i]; + const buffB = i < inferiorFingerprint.buffs.length ? inferiorFingerprint.buffs[i] : undefined; + const fvalA = buffA.Value / 0x3fffffff; + const fvalB = (buffB?.Value ?? 0) / 0x3fffffff; + const percA = 0.3 + fvalA * (0.6 - 0.3); + const percB = 0.3 + fvalB * (0.6 - 0.3); + const newPerc = Math.min(0.6, Math.max(percA, percB) * FUSE_MULTIPLIERS[Math.abs(tierA - tierB)]); + const newFval = (newPerc - 0.3) / (0.6 - 0.3); + buffA.Value = Math.trunc(newFval * 0x3fffffff); + } + } else { + const superiorMeta = ExportCustoms[superiorItem.ItemType].randomisedUpgrades ?? []; + const inferiorMeta = ExportCustoms[inferiorItem.ItemType].randomisedUpgrades ?? []; + for (let i = 0; i != inferiorFingerprint.buffs.length; ++i) { + const buffA = fingerprint.buffs[i]; + const buffB = inferiorFingerprint.buffs[i]; + const fvalA = buffA.Value / 0x3fffffff; + const fvalB = buffB.Value / 0x3fffffff; + const rangeA = superiorMeta[i].range; + const rangeB = inferiorMeta[i].range; + const percA = rangeA[0] + fvalA * (rangeA[1] - rangeA[0]); + const percB = rangeB[0] + fvalB * (rangeB[1] - rangeB[0]); + const newPerc = Math.min(rangeA[1], Math.max(percA, percB) * FUSE_MULTIPLIERS[Math.abs(tierA - tierB)]); + const newFval = (newPerc - rangeA[0]) / (rangeA[1] - rangeA[0]); + buffA.Value = Math.trunc(newFval * 0x3fffffff); + } + if (inferiorFingerprint.SubroutineIndex) { + const useSuperiorSubroutine = tierA < tierB ? !payload.UseSubroutineA : payload.UseSubroutineA; + if (!useSuperiorSubroutine) { + fingerprint.SubroutineIndex = inferiorFingerprint.SubroutineIndex; + } + } + } + superiorItem.UpgradeFingerprint = JSON.stringify(fingerprint); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + inventoryChanges[category] = [superiorItem.toJSON() as any]; + + await inventory.save(); + res.json({ + InventoryChanges: inventoryChanges + }); +}; + +interface ICrewShipFusionRequest { + PartA: IOid; + PartB: IOid; + SourceRecipe: string; + UseSubroutineA: boolean; +} + +const FUSE_MULTIPLIERS = [1.1, 1.05, 1.02]; diff --git a/src/routes/api.ts b/src/routes/api.ts index 8b0a12bc..df9e8b59 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -33,6 +33,7 @@ import { createAllianceController } from "@/src/controllers/api/createAllianceCo import { createGuildController } from "@/src/controllers/api/createGuildController"; import { creditsController } from "@/src/controllers/api/creditsController"; import { crewMembersController } from "@/src/controllers/api/crewMembersController"; +import { crewShipFusionController } from "@/src/controllers/api/crewShipFusionController"; import { crewShipIdentifySalvageController } from "@/src/controllers/api/crewShipIdentifySalvageController"; import { customizeGuildRanksController } from "@/src/controllers/api/customizeGuildRanksController"; import { customObstacleCourseLeaderboardController } from "@/src/controllers/api/customObstacleCourseLeaderboardController"; @@ -247,6 +248,7 @@ apiRouter.post("/contributeToVault.php", contributeToVaultController); apiRouter.post("/createAlliance.php", createAllianceController); apiRouter.post("/createGuild.php", createGuildController); apiRouter.post("/crewMembers.php", crewMembersController); +apiRouter.post("/crewShipFusion.php", crewShipFusionController); apiRouter.post("/crewShipIdentifySalvage.php", crewShipIdentifySalvageController); apiRouter.post("/customizeGuildRanks.php", customizeGuildRanksController); apiRouter.post("/customObstacleCourseLeaderboard.php", customObstacleCourseLeaderboardController); From 7819d87bbeb777a8570f941d37078e3108bed12c Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 18 Jun 2025 10:23:39 -0700 Subject: [PATCH 033/153] chore: update nodejs version in Dockerfile (#2199) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2199 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index c36da824..b5b957e7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:18-alpine3.19 +FROM node:24-alpine3.21 ENV APP_MONGODB_URL=mongodb://mongodb:27017/openWF ENV APP_MY_ADDRESS=localhost From dabca46e884b605ee06cdd7cc04a0d08aa2559c1 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 18 Jun 2025 11:05:07 -0700 Subject: [PATCH 034/153] feat(webui): automatically commit toggle changes (#2198) Closes #2197 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2198 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- .../custom/updateConfigDataController.ts | 14 ++++-- src/services/configService.ts | 2 +- src/services/configWatcherService.ts | 6 --- static/webui/index.html | 23 +++++---- static/webui/script.js | 47 +++++++++---------- static/webui/translations/de.js | 2 +- static/webui/translations/en.js | 2 +- static/webui/translations/es.js | 2 +- static/webui/translations/fr.js | 2 +- static/webui/translations/ru.js | 2 +- static/webui/translations/zh.js | 2 +- 11 files changed, 52 insertions(+), 52 deletions(-) diff --git a/src/controllers/custom/updateConfigDataController.ts b/src/controllers/custom/updateConfigDataController.ts index 534dfe0f..7c87c372 100644 --- a/src/controllers/custom/updateConfigDataController.ts +++ b/src/controllers/custom/updateConfigDataController.ts @@ -1,15 +1,21 @@ import { RequestHandler } from "express"; -import { updateConfig } from "@/src/services/configWatcherService"; +import { saveConfig } from "@/src/services/configWatcherService"; import { getAccountForRequest, isAdministrator } from "@/src/services/loginService"; +import { config, IConfig } from "@/src/services/configService"; -const updateConfigDataController: RequestHandler = async (req, res) => { +export const updateConfigDataController: RequestHandler = async (req, res) => { const account = await getAccountForRequest(req); if (isAdministrator(account)) { - await updateConfig(String(req.body)); + const data = req.body as IUpdateConfigDataRequest; + config[data.key] = data.value; + await saveConfig(); res.end(); } else { res.status(401).end(); } }; -export { updateConfigDataController }; +interface IUpdateConfigDataRequest { + key: keyof IConfig; + value: never; +} diff --git a/src/services/configService.ts b/src/services/configService.ts index fbcda8af..aeabb306 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -2,7 +2,7 @@ import fs from "fs"; import path from "path"; import { repoDir } from "@/src/helpers/pathHelper"; -interface IConfig { +export interface IConfig { mongodbUrl: string; logger: { files: boolean; diff --git a/src/services/configWatcherService.ts b/src/services/configWatcherService.ts index 88a5acdc..544bd96c 100644 --- a/src/services/configWatcherService.ts +++ b/src/services/configWatcherService.ts @@ -46,12 +46,6 @@ export const validateConfig = (): void => { } }; -export const updateConfig = async (data: string): Promise => { - amnesia = true; - await fsPromises.writeFile(configPath, data); - Object.assign(config, JSON.parse(data)); -}; - export const saveConfig = async (): Promise => { amnesia = true; await fsPromises.writeFile(configPath, JSON.stringify(config, null, 2)); diff --git a/static/webui/index.html b/static/webui/index.html index 398eb647..9b9015a2 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -571,7 +571,7 @@

-
+
@@ -732,16 +732,21 @@
-
+ - -
-
+
+ + +
+ +
- -
- - +
+ + +
+ +
diff --git a/static/webui/script.js b/static/webui/script.js index b51648e1..bb7ec52a 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -1761,34 +1761,29 @@ function doAcquireMod() { const uiConfigs = [...$("#server-settings input[id]")].map(x => x.id); -function doChangeSettings() { - revalidateAuthz(() => { - fetch("/custom/config?" + window.authz) - .then(response => response.json()) - .then(json => { - for (const i of uiConfigs) { - var x = document.getElementById(i); - if (x != null) { - if (x.type == "checkbox") { - if (x.checked === true) { - json[i] = true; - } else { - json[i] = false; - } - } else if (x.type == "number") { - json[i] = parseInt(x.value); - } - } - } - $.post({ - url: "/custom/config?" + window.authz, - contentType: "text/plain", - data: JSON.stringify(json, null, 2) - }).then(() => { - // A few cheats affect the inventory response which in turn may change what values we need to show +for (const id of uiConfigs) { + const elm = document.getElementById(id); + if (elm.type == "checkbox") { + elm.onchange = function () { + $.post({ + url: "/custom/config?" + window.authz, + contentType: "application/json", + data: JSON.stringify({ key: id, value: this.checked }) + }).then(() => { + if (["infiniteCredits", "infinitePlatinum", "infiniteEndo", "infiniteRegalAya"].indexOf(id) != -1) { updateInventory(); - }); + } }); + }; + } +} + +function doSaveConfig(id) { + const elm = document.getElementById(id); + $.post({ + url: "/custom/config?" + window.authz, + contentType: "application/json", + data: JSON.stringify({ key: id, value: parseInt(elm.value) }) }); } diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index 8b88151b..a4771cf3 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -167,7 +167,7 @@ dict = { cheats_fastClanAscension: `Schneller Clan-Aufstieg`, cheats_spoofMasteryRank: `Gefälschter Meisterschaftsrang (-1 zum deaktivieren)`, cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`, - cheats_saveSettings: `Einstellungen speichern`, + cheats_save: `[UNTRANSLATED] Save`, cheats_account: `Account`, cheats_unlockAllFocusSchools: `Alle Fokus-Schulen freischalten`, cheats_helminthUnlockAll: `Helminth vollständig aufleveln`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index 437d5057..f17c2e0a 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -166,7 +166,7 @@ dict = { cheats_fastClanAscension: `Fast Clan Ascension`, cheats_spoofMasteryRank: `Spoofed Mastery Rank (-1 to disable)`, cheats_nightwaveStandingMultiplier: `Nightwave Standing Multiplier`, - cheats_saveSettings: `Save Settings`, + cheats_save: `Save`, cheats_account: `Account`, cheats_unlockAllFocusSchools: `Unlock All Focus Schools`, cheats_helminthUnlockAll: `Fully Level Up Helminth`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index e07199ca..4638debe 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -167,7 +167,7 @@ dict = { cheats_fastClanAscension: `Ascenso rápido del clan`, cheats_spoofMasteryRank: `Rango de maestría simulado (-1 para desactivar)`, cheats_nightwaveStandingMultiplier: `Multiplicador de Reputación de Onda Nocturna`, - cheats_saveSettings: `Guardar configuración`, + cheats_save: `[UNTRANSLATED] Save`, cheats_account: `Cuenta`, cheats_unlockAllFocusSchools: `Desbloquear todas las escuelas de enfoque`, cheats_helminthUnlockAll: `Subir al máximo el Helminto`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index 8d4eceb2..e4c99dd6 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -167,7 +167,7 @@ dict = { cheats_fastClanAscension: `Ascension de clan rapide`, cheats_spoofMasteryRank: `Rang de maîtrise personnalisé (-1 pour désactiver)`, cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`, - cheats_saveSettings: `Sauvegarder les paramètres`, + cheats_save: `[UNTRANSLATED] Save`, cheats_account: `Compte`, cheats_unlockAllFocusSchools: `Débloquer toutes les écoles de focus`, cheats_helminthUnlockAll: `Helminth niveau max`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index 7493099d..e0247b7e 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -167,7 +167,7 @@ dict = { cheats_fastClanAscension: `Мгновенное Вознесение Клана`, cheats_spoofMasteryRank: `Подделанный ранг мастерства (-1 для отключения)`, cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`, - cheats_saveSettings: `Сохранить настройки`, + cheats_save: `[UNTRANSLATED] Save`, cheats_account: `Аккаунт`, cheats_unlockAllFocusSchools: `Разблокировать все школы фокуса`, cheats_helminthUnlockAll: `Полностью улучшить Гельминта`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 722b709a..22bc33f9 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -167,7 +167,7 @@ dict = { cheats_fastClanAscension: `快速升级氏族`, cheats_spoofMasteryRank: `伪造精通段位(-1为禁用)`, cheats_nightwaveStandingMultiplier: `午夜电波声望倍率`, - cheats_saveSettings: `保存设置`, + cheats_save: `[UNTRANSLATED] Save`, cheats_account: `账户`, cheats_unlockAllFocusSchools: `解锁所有专精学派`, cheats_helminthUnlockAll: `完全升级Helminth`, From 0c4065619dd8570d2146fd63f81a40db66422f10 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 18 Jun 2025 11:34:12 -0700 Subject: [PATCH 035/153] chore: support config path being specified via command line argument (#2201) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2201 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/index.ts | 6 +++--- src/services/configService.ts | 2 +- src/services/configWatcherService.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index d4b0f810..de36b392 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,13 +1,13 @@ // First, init config. -import { config, loadConfig } from "@/src/services/configService"; +import { config, configPath, loadConfig } from "@/src/services/configService"; import fs from "fs"; try { loadConfig(); } catch (e) { if (fs.existsSync("config.json")) { - console.log("Failed to load config.json: " + (e as Error).message); + console.log("Failed to load " + configPath + ": " + (e as Error).message); } else { - console.log("Failed to load config.json. You can copy config.json.example to create your config.json."); + console.log("Failed to load " + configPath + ". You can copy config.json.example to create your config file."); } process.exit(1); } diff --git a/src/services/configService.ts b/src/services/configService.ts index aeabb306..0c09eb23 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -72,7 +72,7 @@ export interface IConfig { }; } -export const configPath = path.join(repoDir, "config.json"); +export const configPath = path.join(repoDir, process.argv[2] ?? "config.json"); export const config: IConfig = { mongodbUrl: "mongodb://127.0.0.1:27017/openWF", diff --git a/src/services/configWatcherService.ts b/src/services/configWatcherService.ts index 544bd96c..197a9567 100644 --- a/src/services/configWatcherService.ts +++ b/src/services/configWatcherService.ts @@ -9,7 +9,7 @@ fs.watchFile(configPath, () => { if (amnesia) { amnesia = false; } else { - logger.info("Detected a change to config.json, reloading its contents."); + logger.info("Detected a change to config file, reloading its contents."); try { loadConfig(); } catch (e) { @@ -41,7 +41,7 @@ export const validateConfig = (): void => { } } if (modified) { - logger.info(`Updating config.json to fix some issues with it.`); + logger.info(`Updating config file to fix some issues with it.`); void saveConfig(); } }; From 7c8e8fe049859711a62af5d3bc1bf5e48ceef206 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 18 Jun 2025 20:40:46 +0200 Subject: [PATCH 036/153] ci: only run on pushes to main Non-main branches are gonna have to open a PR anyway, so we don't need to run it twice. --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aed7014e..2f265a60 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,7 @@ name: Build on: - push: {} + push: + branches: ["main"] pull_request: {} jobs: build: From 2e9d3c33b69f556708fa2b75124d74d42c8d5d9a Mon Sep 17 00:00:00 2001 From: hxedcl Date: Wed, 18 Jun 2025 11:48:22 -0700 Subject: [PATCH 037/153] chore(webui): update to Spanish translation (#2203) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2203 Co-authored-by: hxedcl Co-committed-by: hxedcl --- static/webui/translations/es.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index 4638debe..c627dfa2 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -167,7 +167,7 @@ dict = { cheats_fastClanAscension: `Ascenso rápido del clan`, cheats_spoofMasteryRank: `Rango de maestría simulado (-1 para desactivar)`, cheats_nightwaveStandingMultiplier: `Multiplicador de Reputación de Onda Nocturna`, - cheats_save: `[UNTRANSLATED] Save`, + cheats_save: `Guardar`, cheats_account: `Cuenta`, cheats_unlockAllFocusSchools: `Desbloquear todas las escuelas de enfoque`, cheats_helminthUnlockAll: `Subir al máximo el Helminto`, From bf40155dd4c7979df4d92084fc05fcf752d0f48e Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 19 Jun 2025 04:22:54 -0700 Subject: [PATCH 038/153] chore: no-op nemesis mode=w (#2196) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2196 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/nemesisController.ts | 25 +++++------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/src/controllers/api/nemesisController.ts b/src/controllers/api/nemesisController.ts index 56cc5562..93ea4ee5 100644 --- a/src/controllers/api/nemesisController.ts +++ b/src/controllers/api/nemesisController.ts @@ -151,7 +151,8 @@ export const nemesisController: RequestHandler = async (req, res) => { inventory.Nemesis!.HenchmenKilled += antivirusGain; if (inventory.Nemesis!.HenchmenKilled >= 100) { inventory.Nemesis!.HenchmenKilled = 100; - // Client doesn't seem to request mode=w for infested liches, so weakening it here. + + // Weaken nemesis now. inventory.Nemesis!.InfNodes = [ { Node: getNemesisManifest(inventory.Nemesis!.manifest).showdownNode, @@ -294,31 +295,15 @@ export const nemesisController: RequestHandler = async (req, res) => { target: inventory.toJSON().Nemesis }); } else if ((req.query.mode as string) == "w") { - const inventory = await getInventory( - account._id.toString(), - "Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades" - ); + const inventory = await getInventory(account._id.toString(), "Nemesis"); //const body = getJSONfromString(String(req.body)); - if (inventory.Nemesis!.Weakened) { - logger.warn(`client is weakening an already-weakened nemesis?!`); - } - - inventory.Nemesis!.InfNodes = [ - { - Node: getNemesisManifest(inventory.Nemesis!.manifest).showdownNode, - Influence: 1 - } - ]; - inventory.Nemesis!.Weakened = true; + // As of 38.6.0, this request is no longer sent, instead mode=r already weakens the nemesis if appropriate. + // We always weaken the nemesis in mode=r so simply giving the client back the nemesis. const response: INemesisWeakenResponse = { target: inventory.toJSON().Nemesis! }; - - await consumePasscodeModCharges(inventory, response); - - await inventory.save(); res.json(response); } else { logger.debug(`data provided to ${req.path}: ${String(req.body)}`); From 4ca4990f89619d32aa848cf67ab6fd858e76a67d Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 19 Jun 2025 04:23:10 -0700 Subject: [PATCH 039/153] chore(docker): use file-based config & precompile code in image (#2202) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2202 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- Dockerfile | 50 +++--------------------------------- docker-compose.yml | 61 +++++++------------------------------------- docker-entrypoint.sh | 24 +++-------------- 3 files changed, 17 insertions(+), 118 deletions(-) diff --git a/Dockerfile b/Dockerfile index b5b957e7..ec346634 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,53 +1,11 @@ FROM node:24-alpine3.21 -ENV APP_MONGODB_URL=mongodb://mongodb:27017/openWF -ENV APP_MY_ADDRESS=localhost -ENV APP_HTTP_PORT=80 -ENV APP_HTTPS_PORT=443 -ENV APP_AUTO_CREATE_ACCOUNT=true -ENV APP_SKIP_TUTORIAL=false -ENV APP_SKIP_ALL_DIALOGUE=false -ENV APP_UNLOCK_ALL_SCANS=false -ENV APP_UNLOCK_ALL_MISSIONS=false -ENV APP_INFINITE_CREDITS=false -ENV APP_INFINITE_PLATINUM=false -ENV APP_INFINITE_ENDO=false -ENV APP_INFINITE_REGAL_AYA=false -ENV APP_INFINITE_HELMINTH_MATERIALS=false -ENV APP_CLAIMING_BLUEPRINT_REFUNDS_INGREDIENTS=false -ENV APP_DONT_SUBTRACT_VOIDTRACES=false -ENV APP_DONT_SUBTRACT_CONSUMABLES=false -ENV APP_UNLOCK_ALL_SHIP_FEATURES=false -ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=false -ENV APP_UNLOCK_ALL_FLAVOUR_ITEMS=false -ENV APP_UNLOCK_ALL_SKINS=false -ENV APP_UNLOCK_ALL_CAPTURA_SCENES=false -ENV APP_UNIVERSAL_POLARITY_EVERYWHERE=false -ENV APP_UNLOCK_DOUBLE_CAPACITY_POTATOES_EVERYWHERE=false -ENV APP_UNLOCK_EXILUS_EVERYWHERE=false -ENV APP_UNLOCK_ARCANES_EVERYWHERE=false -ENV APP_NO_DAILY_FOCUS_LIMIT=false -ENV APP_NO_ARGON_CRYSTAL_DECAY=false -ENV APP_NO_MASTERY_RANK_UP_COOLDOWN=false -ENV APP_NO_VENDOR_PURCHASE_LIMITS=true -ENV APP_NO_DEATH_MARKS=false -ENV APP_NO_KIM_COOLDOWNS=false -ENV APP_SYNDICATE_MISSIONS_REPEATABLE=false -ENV APP_INSTANT_FINISH_RIVEN_CHALLENGE=false -ENV APP_INSTANT_RESOURCE_EXTRACTOR_DRONES=false -ENV APP_NO_RESOURCE_EXTRACTOR_DRONES_DAMAGE=false -ENV APP_SKIP_CLAN_KEY_CRAFTING=false -ENV APP_NO_DOJO_ROOM_BUILD_STAGE=false -ENV APP_NO_DECO_BUILD_STAGE=false -ENV APP_FAST_DOJO_ROOM_DESTRUCTION=false -ENV APP_NO_DOJO_RESEARCH_COSTS=false -ENV APP_NO_DOJO_RESEARCH_TIME=false -ENV APP_FAST_CLAN_ASCENSION=false -ENV APP_SPOOF_MASTERY_RANK=-1 - -RUN apk add --no-cache bash sed wget jq +RUN apk add --no-cache bash jq COPY . /app WORKDIR /app +RUN npm i --omit=dev +RUN npm run build + ENTRYPOINT ["/app/docker-entrypoint.sh"] diff --git a/docker-compose.yml b/docker-compose.yml index 544dec95..aa328756 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,62 +1,18 @@ services: spaceninjaserver: - # build: . - image: openwf/spaceninjaserver:latest - environment: - APP_MONGODB_URL: mongodb://openwfagent:spaceninjaserver@mongodb:27017/ - - # Following environment variables are set to default image values. - # Uncomment to edit. - - # APP_MY_ADDRESS: localhost - # APP_HTTP_PORT: 80 - # APP_HTTPS_PORT: 443 - # APP_AUTO_CREATE_ACCOUNT: true - # APP_SKIP_TUTORIAL: false - # APP_SKIP_ALL_DIALOGUE: false - # APP_UNLOCK_ALL_SCANS: false - # APP_UNLOCK_ALL_MISSIONS: false - # APP_INFINITE_CREDITS: false - # APP_INFINITE_PLATINUM: false - # APP_INFINITE_ENDO: false - # APP_INFINITE_REGAL_AYA: false - # APP_INFINITE_HELMINTH_MATERIALS: false - # APP_CLAIMING_BLUEPRINT_REFUNDS_INGREDIENTS: false - # APP_DONT_SUBTRACT_VOIDTRACES: false - # APP_DONT_SUBTRACT_CONSUMABLES: false - # APP_UNLOCK_ALL_SHIP_FEATURES: false - # APP_UNLOCK_ALL_SHIP_DECORATIONS: false - # APP_UNLOCK_ALL_FLAVOUR_ITEMS: false - # APP_UNLOCK_ALL_SKINS: false - # APP_UNLOCK_ALL_CAPTURA_SCENES: false - # APP_UNIVERSAL_POLARITY_EVERYWHERE: false - # APP_UNLOCK_DOUBLE_CAPACITY_POTATOES_EVERYWHERE: false - # APP_UNLOCK_EXILUS_EVERYWHERE: false - # APP_UNLOCK_ARCANES_EVERYWHERE: false - # APP_NO_DAILY_FOCUS_LIMIT: false - # APP_NO_ARGON_CRYSTAL_DECAY: false - # APP_NO_MASTERY_RANK_UP_COOLDOWN: false - # APP_NO_VENDOR_PURCHASE_LIMITS: true - # APP_NO_DEATH_MARKS: false - # APP_NO_KIM_COOLDOWNS: false - # APP_SYNDICATE_MISSIONS_REPEATABLE: false - # APP_INSTANT_FINISH_RIVEN_CHALLENGE: false - # APP_INSTANT_RESOURCE_EXTRACTOR_DRONES: false - # APP_NO_RESOURCE_EXTRACTOR_DRONES_DAMAGE: false - # APP_SKIP_CLAN_KEY_CRAFTING: false - # APP_NO_DOJO_ROOM_BUILD_STAGE: false - # APP_NO_DECO_BUILD_STAGE: false - # APP_FAST_DOJO_ROOM_DESTRUCTION: false - # APP_NO_DOJO_RESEARCH_COSTS: false - # APP_NO_DOJO_RESEARCH_TIME: false - # APP_FAST_CLAN_ASCENSION: false - # APP_SPOOF_MASTERY_RANK: -1 volumes: - - ./docker-data/static:/app/static/data + - ./docker-data/conf:/app/conf + - ./docker-data/static-data:/app/static/data - ./docker-data/logs:/app/logs ports: - 80:80 - 443:443 + + # Normally, the image is fetched from Docker Hub, but you can use the local Dockerfile by swapping the following two fields. + # Works best when using `docker-compose up --force-recreate --build`. + image: openwf/spaceninjaserver:latest + #build: . + depends_on: - mongodb mongodb: @@ -66,3 +22,4 @@ services: MONGO_INITDB_ROOT_PASSWORD: spaceninjaserver volumes: - ./docker-data/database:/data/db + command: mongod --quiet --logpath /dev/null diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 13e70c33..457173d9 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -1,24 +1,8 @@ #!/bin/bash set -e -# Set up the configuration file using environment variables. -echo '{ - "logger": { - "files": true, - "level": "trace", - "__valid_levels": "fatal, error, warn, info, http, debug, trace" - } -} -' > config.json +if [ ! -f conf/config.json ]; then + jq --arg value "mongodb://openwfagent:spaceninjaserver@mongodb:27017/" '.mongodbUrl = $value' /app/config.json.example > /app/conf/config.json +fi -for config in $(env | grep "APP_") -do - var=$(echo "${config}" | tr '[:upper:]' '[:lower:]' | sed 's/app_//g' | sed -E 's/_([a-z])/\U\1/g' | sed 's/=.*//g') - val=$(echo "${config}" | sed 's/.*=//g') - jq --arg variable "$var" --arg value "$val" '.[$variable] += try [$value|fromjson][] catch $value' config.json > config.tmp - mv config.tmp config.json -done - -npm i --omit=dev -npm run build -exec npm run start +exec npm run start conf/config.json From d78ca91d6c60c748f8d883b7d59c7f3d9b611072 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 19 Jun 2025 04:23:33 -0700 Subject: [PATCH 040/153] fix(webui): properly handle renaming of pets (#2204) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2204 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/renamePetController.ts | 11 +++++-- static/webui/script.js | 34 ++++++++++++++++------ 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/controllers/api/renamePetController.ts b/src/controllers/api/renamePetController.ts index 6672d064..61212641 100644 --- a/src/controllers/api/renamePetController.ts +++ b/src/controllers/api/renamePetController.ts @@ -1,6 +1,7 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getInventory, updateCurrency } from "@/src/services/inventoryService"; import { getAccountIdForRequest } from "@/src/services/loginService"; +import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { RequestHandler } from "express"; export const renamePetController: RequestHandler = async (req, res) => { @@ -8,12 +9,18 @@ export const renamePetController: RequestHandler = async (req, res) => { const inventory = await getInventory(accountId, "KubrowPets PremiumCredits PremiumCreditsFree"); const data = getJSONfromString(String(req.body)); const details = inventory.KubrowPets.id(data.petId)!.Details!; + details.Name = data.name; - const currencyChanges = updateCurrency(inventory, 15, true); + + const inventoryChanges: IInventoryChanges = {}; + if (!("webui" in req.query)) { + updateCurrency(inventory, 15, true, inventoryChanges); + } + await inventory.save(); res.json({ ...data, - inventoryChanges: currencyChanges + inventoryChanges: inventoryChanges }); }; diff --git a/static/webui/script.js b/static/webui/script.js index bb7ec52a..bd32266f 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -546,6 +546,9 @@ function updateInventory() { td.textContent = item.ItemName + " (" + td.textContent + ")"; } } + if (item.Details?.Name) { + td.textContent = item.Details.Name + " (" + td.textContent + ")"; + } if (item.ModularParts && item.ModularParts.length) { td.textContent += " ["; item.ModularParts.forEach(part => { @@ -1506,15 +1509,28 @@ function sendBatchGearExp(data) { function renameGear(category, oid, name) { revalidateAuthz(() => { - $.post({ - url: "/api/nameWeapon.php?" + window.authz + "&Category=" + category + "&ItemId=" + oid + "&webui=1", - contentType: "text/plain", - data: JSON.stringify({ - ItemName: name - }) - }).done(function () { - updateInventory(); - }); + if (category == "KubrowPets") { + $.post({ + url: "/api/renamePet.php?" + window.authz + "&webui=1", + contentType: "text/plain", + data: JSON.stringify({ + petId: oid, + name: name + }) + }).done(function () { + updateInventory(); + }); + } else { + $.post({ + url: "/api/nameWeapon.php?" + window.authz + "&Category=" + category + "&ItemId=" + oid + "&webui=1", + contentType: "text/plain", + data: JSON.stringify({ + ItemName: name + }) + }).done(function () { + updateInventory(); + }); + } }); } From 61a8d01f64b1f8a9811666d14caeef20324eeb76 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 19 Jun 2025 13:47:04 +0200 Subject: [PATCH 041/153] ci: split docker amd64 & arm64 builds --- .github/workflows/docker.yml | 25 ++++++++++++++++++++++--- docker-compose.yml | 8 +++++--- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 55626376..4a97729c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,9 +4,9 @@ on: branches: - main jobs: - docker: + docker-amd64: if: github.repository == 'OpenWF/SpaceNinjaServer' - runs-on: ubuntu-latest + runs-on: amd64 steps: - name: Set up Docker buildx uses: docker/setup-buildx-action@v3 @@ -18,8 +18,27 @@ jobs: - name: Build and push uses: docker/build-push-action@v6 with: - platforms: linux/amd64,linux/arm64 + platforms: linux/amd64 push: true tags: | openwf/spaceninjaserver:latest openwf/spaceninjaserver:${{ github.sha }} + docker-arm64: + if: github.repository == 'OpenWF/SpaceNinjaServer' + runs-on: arm64 + steps: + - name: Set up Docker buildx + uses: docker/setup-buildx-action@v3 + - name: Log in to container registry + uses: docker/login-action@v3 + with: + username: openwf + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v6 + with: + platforms: linux/arm64 + push: true + tags: | + openwf/spaceninjaserver:latest-arm64 + openwf/spaceninjaserver:${{ github.sha }}-arm64 diff --git a/docker-compose.yml b/docker-compose.yml index aa328756..d9f89348 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,8 @@ services: spaceninjaserver: + # The image to use. If you have an ARM CPU, replace 'latest' with 'latest-arm64'. + image: openwf/spaceninjaserver:latest + volumes: - ./docker-data/conf:/app/conf - ./docker-data/static-data:/app/static/data @@ -8,10 +11,9 @@ services: - 80:80 - 443:443 - # Normally, the image is fetched from Docker Hub, but you can use the local Dockerfile by swapping the following two fields. - # Works best when using `docker-compose up --force-recreate --build`. - image: openwf/spaceninjaserver:latest + # Normally, the image is fetched from Docker Hub, but you can use the local Dockerfile by removing "image" above and adding this: #build: . + # Works best when using `docker-compose up --force-recreate --build`. depends_on: - mongodb From ad7b5fc05222202be24db36d5f7c7ecd029a4c46 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 19 Jun 2025 11:15:27 -0700 Subject: [PATCH 042/153] fix(webui): incorrect description of topaz shields for blast (#2211) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2211 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- static/webui/translations/de.js | 2 +- static/webui/translations/en.js | 2 +- static/webui/translations/es.js | 2 +- static/webui/translations/fr.js | 2 +- static/webui/translations/ru.js | 2 +- static/webui/translations/zh.js | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index a4771cf3..c42d3cf9 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -185,7 +185,7 @@ dict = { upgrade_WarframeAbilityDuration: `[UNTRANSLATED] +|VAL|% Ability Duration`, upgrade_WarframeAbilityStrength: `[UNTRANSLATED] +|VAL|% Ability Strength`, upgrade_WarframeArmourMax: `[UNTRANSLATED] +|VAL| Armor`, - upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on inflicting Blast Status`, + upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on kill with Blast Damage`, upgrade_WarframeCastingSpeed: `[UNTRANSLATED] +|VAL|% Casting Speed`, upgrade_WarframeCorrosiveDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Corrosion Status`, upgrade_WarframeCorrosiveStack: `[UNTRANSLATED] Increase max stacks of Corrosion Status by +|VAL|`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index f17c2e0a..aa35d2a9 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -184,7 +184,7 @@ dict = { upgrade_WarframeAbilityDuration: `+|VAL|% Ability Duration`, upgrade_WarframeAbilityStrength: `+|VAL|% Ability Strength`, upgrade_WarframeArmourMax: `+|VAL| Armor`, - upgrade_WarframeBlastProc: `+|VAL| Shields on inflicting Blast Status`, + upgrade_WarframeBlastProc: `+|VAL| Shields on kill with Blast Damage`, upgrade_WarframeCastingSpeed: `+|VAL|% Casting Speed`, upgrade_WarframeCorrosiveDamageBoost: `+|VAL|% Ability Damage on enemies affected by Corrosion Status`, upgrade_WarframeCorrosiveStack: `Increase max stacks of Corrosion Status by +|VAL|`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index c627dfa2..ad12b441 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -185,7 +185,7 @@ dict = { upgrade_WarframeAbilityDuration: `+|VAL|% de duración de habilidades`, upgrade_WarframeAbilityStrength: `+|VAL|% de fuerza de habilidades`, upgrade_WarframeArmourMax: `+|VAL| de armadura`, - upgrade_WarframeBlastProc: `+|VAL| de escudos al infligir estado de explosión`, + upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on kill with Blast Damage`, upgrade_WarframeCastingSpeed: `+|VAL|% de velocidad de lanzamiento de habilidades`, upgrade_WarframeCorrosiveDamageBoost: `+|VAL|% de daño de habilidades a enemigos con estado corrosivo`, upgrade_WarframeCorrosiveStack: `Aumenta los acumuladores máximos de estado corrosivo en +|VAL|`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index e4c99dd6..e4fb0f3f 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -185,7 +185,7 @@ dict = { upgrade_WarframeAbilityDuration: `[UNTRANSLATED] +|VAL|% Ability Duration`, upgrade_WarframeAbilityStrength: `[UNTRANSLATED] +|VAL|% Ability Strength`, upgrade_WarframeArmourMax: `[UNTRANSLATED] +|VAL| Armor`, - upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on inflicting Blast Status`, + upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on kill with Blast Damage`, upgrade_WarframeCastingSpeed: `[UNTRANSLATED] +|VAL|% Casting Speed`, upgrade_WarframeCorrosiveDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Corrosion Status`, upgrade_WarframeCorrosiveStack: `[UNTRANSLATED] Increase max stacks of Corrosion Status by +|VAL|`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index e0247b7e..e50f2959 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -185,7 +185,7 @@ dict = { upgrade_WarframeAbilityDuration: `[UNTRANSLATED] +|VAL|% Ability Duration`, upgrade_WarframeAbilityStrength: `[UNTRANSLATED] +|VAL|% Ability Strength`, upgrade_WarframeArmourMax: `[UNTRANSLATED] +|VAL| Armor`, - upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on inflicting Blast Status`, + upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on kill with Blast Damage`, upgrade_WarframeCastingSpeed: `[UNTRANSLATED] +|VAL|% Casting Speed`, upgrade_WarframeCorrosiveDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Corrosion Status`, upgrade_WarframeCorrosiveStack: `[UNTRANSLATED] Increase max stacks of Corrosion Status by +|VAL|`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 22bc33f9..4b763583 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -185,7 +185,7 @@ dict = { upgrade_WarframeAbilityDuration: `+|VAL|% 技能持续时间`, upgrade_WarframeAbilityStrength: `+|VAL|% 技能强度`, upgrade_WarframeArmourMax: `+|VAL| 护甲`, - upgrade_WarframeBlastProc: `施加爆炸状态时,护盾 +|VAL|`, + upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on kill with Blast Damage`, upgrade_WarframeCastingSpeed: `+|VAL|% 施放速度`, upgrade_WarframeCorrosiveDamageBoost: `对受腐蚀状态影响的敌人 +|VAL|% 技能伤害`, upgrade_WarframeCorrosiveStack: `腐蚀状态最大堆叠数 +|VAL|`, From e136c0494e2c913b6973adb15943850d057d813c Mon Sep 17 00:00:00 2001 From: hxedcl Date: Thu, 19 Jun 2025 13:38:00 -0700 Subject: [PATCH 043/153] chore(webui): update to Spanish translation (#2213) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2213 Co-authored-by: hxedcl Co-committed-by: hxedcl --- static/webui/translations/es.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index ad12b441..d3eb257b 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -185,7 +185,7 @@ dict = { upgrade_WarframeAbilityDuration: `+|VAL|% de duración de habilidades`, upgrade_WarframeAbilityStrength: `+|VAL|% de fuerza de habilidades`, upgrade_WarframeArmourMax: `+|VAL| de armadura`, - upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on kill with Blast Damage`, + upgrade_WarframeBlastProc: `+|VAL| de escudos al matar con daño de explosión`, upgrade_WarframeCastingSpeed: `+|VAL|% de velocidad de lanzamiento de habilidades`, upgrade_WarframeCorrosiveDamageBoost: `+|VAL|% de daño de habilidades a enemigos con estado corrosivo`, upgrade_WarframeCorrosiveStack: `Aumenta los acumuladores máximos de estado corrosivo en +|VAL|`, From 05382beaafbfd0c998922d8ef1fd1da29befa2eb Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 19 Jun 2025 16:18:17 -0700 Subject: [PATCH 044/153] feat: worldState.duviriOverride config (#2206) Closes #2205 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2206 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- README.md | 1 + config.json.example | 1 + src/services/configService.ts | 1 + src/services/worldStateService.ts | 21 +++++++++++++++++++++ 4 files changed, 24 insertions(+) diff --git a/README.md b/README.md index dc57e796..0bd7de22 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [confi - `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`. - `worldState.eidolonOverride` can be set to `day` or `night` to lock the time to day/fass and night/vome on Plains of Eidolon/Cambion Drift. - `worldState.vallisOverride` can be set to `warm` or `cold` to lock the temperature on Orb Vallis. +- `worldState.duviriOverride` can be set to `joy`, `anger`, `envy`, `sorrow`, or `fear` to lock the Duviri spiral. - `worldState.nightwaveOverride` will lock the nightwave season, assuming the client is new enough for it. Valid values: - `RadioLegionIntermission13Syndicate` for Nora's Mix Vol. 9 - `RadioLegionIntermission12Syndicate` for Nora's Mix Vol. 8 diff --git a/config.json.example b/config.json.example index 8f839751..4c5fafb5 100644 --- a/config.json.example +++ b/config.json.example @@ -58,6 +58,7 @@ "starDays": true, "eidolonOverride": "", "vallisOverride": "", + "duviriOverride": "", "nightwaveOverride": "", "circuitGameModes": null }, diff --git a/src/services/configService.ts b/src/services/configService.ts index 0c09eb23..a45645a2 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -64,6 +64,7 @@ export interface IConfig { starDays?: boolean; eidolonOverride?: string; vallisOverride?: string; + duviriOverride?: string; nightwaveOverride?: string; circuitGameModes?: string[]; }; diff --git a/src/services/worldStateService.ts b/src/services/worldStateService.ts index 5f2df857..8fa82eed 100644 --- a/src/services/worldStateService.ts +++ b/src/services/worldStateService.ts @@ -1068,6 +1068,27 @@ const doesTimeSatsifyConstraints = (timeSecs: number): boolean => { } } + if (config.worldState?.duviriOverride) { + const duviriMoods = ["sorrow", "fear", "joy", "anger", "envy"]; + const desiredMood = duviriMoods.indexOf(config.worldState.duviriOverride); + if (desiredMood == -1) { + logger.warn(`ignoring invalid config value for worldState.duviriOverride`, { + value: config.worldState.duviriOverride, + valid_values: duviriMoods + }); + } else { + const moodIndex = Math.trunc(timeSecs / 7200); + const moodStart = moodIndex * 7200; + const moodEnd = moodStart + 7200; + if ( + moodIndex % 5 != desiredMood || + isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, moodEnd * 1000) + ) { + return false; + } + } + } + return true; }; From 88d4ba63405a289614bf61f090caa844156d3647 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 19 Jun 2025 16:18:35 -0700 Subject: [PATCH 045/153] feat: dontSubtractVendor{Credit,Platinum,Item,Standing}Cost cheats (#2209) Closes #1586 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2209 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- config.json.example | 4 ++ src/services/configService.ts | 4 ++ src/services/purchaseService.ts | 90 +++++++++++++++++++-------------- static/webui/index.html | 20 +++++++- static/webui/translations/de.js | 6 ++- static/webui/translations/en.js | 6 ++- static/webui/translations/es.js | 4 ++ static/webui/translations/fr.js | 6 ++- static/webui/translations/ru.js | 6 ++- static/webui/translations/zh.js | 6 ++- 10 files changed, 106 insertions(+), 46 deletions(-) diff --git a/config.json.example b/config.json.example index 4c5fafb5..8dfdfd31 100644 --- a/config.json.example +++ b/config.json.example @@ -20,6 +20,10 @@ "infiniteRegalAya": false, "infiniteHelminthMaterials": false, "claimingBlueprintRefundsIngredients": false, + "dontSubtractPurchaseCreditCost": false, + "dontSubtractPurchasePlatinumCost": false, + "dontSubtractPurchaseItemCost": false, + "dontSubtractPurchaseStandingCost": false, "dontSubtractVoidTraces": false, "dontSubtractConsumables": false, "unlockAllShipFeatures": false, diff --git a/src/services/configService.ts b/src/services/configService.ts index a45645a2..b14ffb67 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -25,6 +25,10 @@ export interface IConfig { infiniteRegalAya?: boolean; infiniteHelminthMaterials?: boolean; claimingBlueprintRefundsIngredients?: boolean; + dontSubtractPurchaseCreditCost?: boolean; + dontSubtractPurchasePlatinumCost?: boolean; + dontSubtractPurchaseItemCost?: boolean; + dontSubtractPurchaseStandingCost?: boolean; dontSubtractVoidTraces?: boolean; dontSubtractConsumables?: boolean; unlockAllShipFeatures?: boolean; diff --git a/src/services/purchaseService.ts b/src/services/purchaseService.ts index 59a431c3..4fb5bb2d 100644 --- a/src/services/purchaseService.ts +++ b/src/services/purchaseService.ts @@ -67,25 +67,31 @@ export const handlePurchase = async ( if (!offer) { throw new Error(`unknown vendor offer: ${ItemId ? ItemId : purchaseRequest.PurchaseParams.StoreItem}`); } - if (offer.RegularPrice) { - combineInventoryChanges( - prePurchaseInventoryChanges, - updateCurrency(inventory, offer.RegularPrice[0], false) - ); + if (!config.dontSubtractPurchaseCreditCost) { + if (offer.RegularPrice) { + combineInventoryChanges( + prePurchaseInventoryChanges, + updateCurrency(inventory, offer.RegularPrice[0], false) + ); + } } - if (offer.PremiumPrice) { - combineInventoryChanges( - prePurchaseInventoryChanges, - updateCurrency(inventory, offer.PremiumPrice[0], true) - ); + if (!config.dontSubtractPurchasePlatinumCost) { + if (offer.PremiumPrice) { + combineInventoryChanges( + prePurchaseInventoryChanges, + updateCurrency(inventory, offer.PremiumPrice[0], true) + ); + } } - if (offer.ItemPrices) { - handleItemPrices( - inventory, - offer.ItemPrices, - purchaseRequest.PurchaseParams.Quantity, - prePurchaseInventoryChanges - ); + if (!config.dontSubtractPurchaseItemCost) { + if (offer.ItemPrices) { + handleItemPrices( + inventory, + offer.ItemPrices, + purchaseRequest.PurchaseParams.Quantity, + prePurchaseInventoryChanges + ); + } } if (offer.LocTagRandSeed !== undefined) { seed = BigInt(offer.LocTagRandSeed); @@ -179,21 +185,25 @@ export const handlePurchase = async ( x => x.ItemType == purchaseRequest.PurchaseParams.StoreItem ); if (offer) { - combineInventoryChanges( - purchaseResponse.InventoryChanges, - updateCurrency(inventory, offer.RegularPrice, false) - ); + if (!config.dontSubtractPurchaseCreditCost) { + combineInventoryChanges( + purchaseResponse.InventoryChanges, + updateCurrency(inventory, offer.RegularPrice, false) + ); + } if (purchaseRequest.PurchaseParams.ExpectedPrice) { throw new Error(`vendor purchase should not have an expected price`); } - const invItem: IMiscItem = { - ItemType: "/Lotus/Types/Items/MiscItems/PrimeBucks", - ItemCount: offer.PrimePrice * purchaseRequest.PurchaseParams.Quantity * -1 - }; - addMiscItems(inventory, [invItem]); - purchaseResponse.InventoryChanges.MiscItems ??= []; - purchaseResponse.InventoryChanges.MiscItems.push(invItem); + if (!config.dontSubtractPurchaseItemCost) { + const invItem: IMiscItem = { + ItemType: "/Lotus/Types/Items/MiscItems/PrimeBucks", + ItemCount: offer.PrimePrice * purchaseRequest.PurchaseParams.Quantity * -1 + }; + addMiscItems(inventory, [invItem]); + purchaseResponse.InventoryChanges.MiscItems ??= []; + purchaseResponse.InventoryChanges.MiscItems.push(invItem); + } } break; } @@ -211,7 +221,7 @@ export const handlePurchase = async ( Title: lastTitle } ]; - } else { + } else if (!config.dontSubtractPurchaseStandingCost) { const syndicate = ExportSyndicates[syndicateTag]; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (syndicate) { @@ -239,19 +249,19 @@ export const handlePurchase = async ( const vendor = ExportVendors[purchaseRequest.PurchaseParams.SourceId!]; const offer = vendor.items.find(x => x.storeItem == purchaseRequest.PurchaseParams.StoreItem); if (offer) { - if (typeof offer.credits == "number") { + if (typeof offer.credits == "number" && !config.dontSubtractPurchaseCreditCost) { combineInventoryChanges( purchaseResponse.InventoryChanges, updateCurrency(inventory, offer.credits, false) ); } - if (typeof offer.platinum == "number") { + if (typeof offer.platinum == "number" && !config.dontSubtractPurchasePlatinumCost) { combineInventoryChanges( purchaseResponse.InventoryChanges, updateCurrency(inventory, offer.platinum, true) ); } - if (offer.itemPrices) { + if (offer.itemPrices && !config.dontSubtractPurchaseItemCost) { handleItemPrices( inventory, offer.itemPrices, @@ -278,15 +288,17 @@ export const handlePurchase = async ( ); if (offer) { if (offer.RegularPrice) { - const invItem: IMiscItem = { - ItemType: "/Lotus/Types/Items/MiscItems/SchismKey", - ItemCount: offer.RegularPrice * purchaseRequest.PurchaseParams.Quantity * -1 - }; + if (!config.dontSubtractPurchaseItemCost) { + const invItem: IMiscItem = { + ItemType: "/Lotus/Types/Items/MiscItems/SchismKey", + ItemCount: offer.RegularPrice * purchaseRequest.PurchaseParams.Quantity * -1 + }; - addMiscItems(inventory, [invItem]); + addMiscItems(inventory, [invItem]); - purchaseResponse.InventoryChanges.MiscItems ??= []; - purchaseResponse.InventoryChanges.MiscItems.push(invItem); + purchaseResponse.InventoryChanges.MiscItems ??= []; + purchaseResponse.InventoryChanges.MiscItems.push(invItem); + } } else if (!config.infiniteRegalAya) { inventory.PrimeTokens -= offer.PrimePrice! * purchaseRequest.PurchaseParams.Quantity; diff --git a/static/webui/index.html b/static/webui/index.html index 9b9015a2..b6d87db4 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -604,13 +604,29 @@ +
+ + +
- - + + +
+
+ + +
+
+ + +
+
+ +
diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index c42d3cf9..6672d726 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -135,11 +135,15 @@ dict = { cheats_infiniteRegalAya: `Unendlich Reines Aya`, cheats_infiniteHelminthMaterials: `Unendlich Helminth-Materialien`, cheats_claimingBlueprintRefundsIngredients: `Fertige Blaupausen erstatten Ressourcen zurück`, + cheats_dontSubtractPurchaseCreditCost: `[UNTRANSLATED] Don't Subtract Purchase Credit Cost`, + cheats_dontSubtractPurchasePlatinumCost: `[UNTRANSLATED] Don't Subtract Purchase Platinum Cost`, + cheats_dontSubtractPurchaseItemCost: `[UNTRANSLATED] Don't Subtract Purchase Item Cost`, + cheats_dontSubtractPurchaseStandingCost: `[UNTRANSLATED] Don't Subtract Purchase Standing Cost`, cheats_dontSubtractVoidTraces: `Void-Spuren nicht verbrauchen`, 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`, + cheats_unlockAllFlavourItems: `Alle Sammlerstücke freischalten`, cheats_unlockAllSkins: `Alle Skins freischalten`, cheats_unlockAllCapturaScenes: `Alle Photora-Szenen freischalten`, cheats_unlockAllDecoRecipes: `Alle Dojo-Deko-Baupläne freischalten`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index aa35d2a9..f7869ac2 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -134,11 +134,15 @@ dict = { cheats_infiniteRegalAya: `Infinite Regal Aya`, cheats_infiniteHelminthMaterials: `Infinite Helminth Materials`, cheats_claimingBlueprintRefundsIngredients: `Claiming Blueprint Refunds Ingredients`, + cheats_dontSubtractPurchaseCreditCost: `Don't Subtract Purchase Credit Cost`, + cheats_dontSubtractPurchasePlatinumCost: `Don't Subtract Purchase Platinum Cost`, + cheats_dontSubtractPurchaseItemCost: `Don't Subtract Purchase Item Cost`, + cheats_dontSubtractPurchaseStandingCost: `Don't Subtract Purchase Standing Cost`, cheats_dontSubtractVoidTraces: `Don't Subtract Void Traces`, cheats_dontSubtractConsumables: `Don't Subtract Consumables`, cheats_unlockAllShipFeatures: `Unlock All Ship Features`, cheats_unlockAllShipDecorations: `Unlock All Ship Decorations`, - cheats_unlockAllFlavourItems: `Unlock All Flavor Items`, + cheats_unlockAllFlavourItems: `Unlock All Flavor Items`, cheats_unlockAllSkins: `Unlock All Skins`, cheats_unlockAllCapturaScenes: `Unlock All Captura Scenes`, cheats_unlockAllDecoRecipes: `Unlock All Dojo Deco Recipes`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index d3eb257b..79aedacd 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -135,6 +135,10 @@ dict = { cheats_infiniteRegalAya: `Aya Real infinita`, cheats_infiniteHelminthMaterials: `Materiales Helminto infinitos`, cheats_claimingBlueprintRefundsIngredients: `Reclamar ingredientes devueltos por planos`, + cheats_dontSubtractPurchaseCreditCost: `[UNTRANSLATED] Don't Subtract Purchase Credit Cost`, + cheats_dontSubtractPurchasePlatinumCost: `[UNTRANSLATED] Don't Subtract Purchase Platinum Cost`, + cheats_dontSubtractPurchaseItemCost: `[UNTRANSLATED] Don't Subtract Purchase Item Cost`, + cheats_dontSubtractPurchaseStandingCost: `[UNTRANSLATED] Don't Subtract Purchase Standing Cost`, cheats_dontSubtractVoidTraces: `No descontar vestigios del Vacío`, cheats_dontSubtractConsumables: `No restar consumibles`, cheats_unlockAllShipFeatures: `Desbloquear todas las funciones de nave`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index e4fb0f3f..c605fce8 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -135,11 +135,15 @@ dict = { cheats_infiniteRegalAya: `Aya Raffiné infini`, cheats_infiniteHelminthMaterials: `Ressources d'Helminth infinies`, cheats_claimingBlueprintRefundsIngredients: `Récupérer les items rend les ressources`, + cheats_dontSubtractPurchaseCreditCost: `[UNTRANSLATED] Don't Subtract Purchase Credit Cost`, + cheats_dontSubtractPurchasePlatinumCost: `[UNTRANSLATED] Don't Subtract Purchase Platinum Cost`, + cheats_dontSubtractPurchaseItemCost: `[UNTRANSLATED] Don't Subtract Purchase Item Cost`, + cheats_dontSubtractPurchaseStandingCost: `[UNTRANSLATED] Don't Subtract Purchase Standing Cost`, cheats_dontSubtractVoidTraces: `Ne pas consommer de Void Traces`, cheats_dontSubtractConsumables: `Ne pas retirer de consommables`, cheats_unlockAllShipFeatures: `Débloquer tous les segments du vaisseau`, cheats_unlockAllShipDecorations: `Débloquer toutes les décorations du vaisseau`, - cheats_unlockAllFlavourItems: `Débloquer tous les Flavor Items`, + cheats_unlockAllFlavourItems: `Débloquer tous les Flavor Items`, cheats_unlockAllSkins: `Débloquer tous les skins`, cheats_unlockAllCapturaScenes: `Débloquer toutes les scènes captura`, cheats_unlockAllDecoRecipes: `Débloquer toutes les recherches dojo`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index e50f2959..cb89fa7d 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -135,11 +135,15 @@ dict = { cheats_infiniteRegalAya: `Бесконечная Королевская Айя`, cheats_infiniteHelminthMaterials: `Бесконечные Выделения Гельминта`, cheats_claimingBlueprintRefundsIngredients: `[UNTRANSLATED] Claiming Blueprint Refunds Ingredients`, + cheats_dontSubtractPurchaseCreditCost: `[UNTRANSLATED] Don't Subtract Purchase Credit Cost`, + cheats_dontSubtractPurchasePlatinumCost: `[UNTRANSLATED] Don't Subtract Purchase Platinum Cost`, + cheats_dontSubtractPurchaseItemCost: `[UNTRANSLATED] Don't Subtract Purchase Item Cost`, + cheats_dontSubtractPurchaseStandingCost: `[UNTRANSLATED] Don't Subtract Purchase Standing Cost`, cheats_dontSubtractVoidTraces: `[UNTRANSLATED] Don't Subtract Void Traces`, cheats_dontSubtractConsumables: `Не уменьшать количество расходников`, cheats_unlockAllShipFeatures: `Разблокировать все функции корабля`, cheats_unlockAllShipDecorations: `Разблокировать все украшения корабля`, - cheats_unlockAllFlavourItems: `Разблокировать все уникальные предметы`, + cheats_unlockAllFlavourItems: `Разблокировать все уникальные предметы`, cheats_unlockAllSkins: `Разблокировать все скины`, cheats_unlockAllCapturaScenes: `Разблокировать все сцены Каптуры`, cheats_unlockAllDecoRecipes: `Разблокировать все рецепты декораций Дoдзё`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 4b763583..06b2ece9 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -135,11 +135,15 @@ dict = { cheats_infiniteRegalAya: `无限御品阿耶`, cheats_infiniteHelminthMaterials: `无限Helminth材料`, cheats_claimingBlueprintRefundsIngredients: `取消蓝图制造时返还材料`, + cheats_dontSubtractPurchaseCreditCost: `[UNTRANSLATED] Don't Subtract Purchase Credit Cost`, + cheats_dontSubtractPurchasePlatinumCost: `[UNTRANSLATED] Don't Subtract Purchase Platinum Cost`, + cheats_dontSubtractPurchaseItemCost: `[UNTRANSLATED] Don't Subtract Purchase Item Cost`, + cheats_dontSubtractPurchaseStandingCost: `[UNTRANSLATED] Don't Subtract Purchase Standing Cost`, cheats_dontSubtractVoidTraces: `虚空光体无消耗`, cheats_dontSubtractConsumables: `消耗物品使用时无损耗`, cheats_unlockAllShipFeatures: `解锁所有飞船功能`, cheats_unlockAllShipDecorations: `解锁所有飞船装饰`, - cheats_unlockAllFlavourItems: `解锁所有装饰物品`, + cheats_unlockAllFlavourItems: `解锁所有装饰物品`, cheats_unlockAllSkins: `解锁所有外观`, cheats_unlockAllCapturaScenes: `解锁所有Captura场景`, cheats_unlockAllDecoRecipes: `解锁所有道场配方`, From e686a2d028cfada2e11ff80afb73c9055bcbc7ba Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 19 Jun 2025 16:18:49 -0700 Subject: [PATCH 046/153] chore: report unknown fields in updateChallengeProgress payload (#2210) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2210 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- .../api/updateChallengeProgressController.ts | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/controllers/api/updateChallengeProgressController.ts b/src/controllers/api/updateChallengeProgressController.ts index 51eb88c3..5a7e6467 100644 --- a/src/controllers/api/updateChallengeProgressController.ts +++ b/src/controllers/api/updateChallengeProgressController.ts @@ -4,6 +4,8 @@ import { getAccountForRequest } from "@/src/services/loginService"; import { addChallenges, getInventory } from "@/src/services/inventoryService"; import { IChallengeProgress, ISeasonChallenge } from "@/src/types/inventoryTypes/inventoryTypes"; import { IAffiliationMods } from "@/src/types/purchaseTypes"; +import { getEntriesUnsafe } from "@/src/utils/ts-utils"; +import { logger } from "@/src/utils/logger"; export const updateChallengeProgressController: RequestHandler = async (req, res) => { const challenges = getJSONfromString(String(req.body)); @@ -13,9 +15,6 @@ export const updateChallengeProgressController: RequestHandler = async (req, res account._id.toString(), "ChallengesFixVersion ChallengeProgress SeasonChallengeHistory Affiliations" ); - if (challenges.ChallengesFixVersion !== undefined) { - inventory.ChallengesFixVersion = challenges.ChallengesFixVersion; - } let affiliationMods: IAffiliationMods[] = []; if (challenges.ChallengeProgress) { affiliationMods = addChallenges( @@ -25,15 +24,30 @@ export const updateChallengeProgressController: RequestHandler = async (req, res challenges.SeasonChallengeCompletions ); } - if (challenges.SeasonChallengeHistory) { - challenges.SeasonChallengeHistory.forEach(({ challenge, id }) => { - const itemIndex = inventory.SeasonChallengeHistory.findIndex(i => i.challenge === challenge); - if (itemIndex !== -1) { - inventory.SeasonChallengeHistory[itemIndex].id = id; - } else { - inventory.SeasonChallengeHistory.push({ challenge, id }); - } - }); + for (const [key, value] of getEntriesUnsafe(challenges)) { + switch (key) { + case "ChallengesFixVersion": + inventory.ChallengesFixVersion = value; + break; + + case "SeasonChallengeHistory": + value!.forEach(({ challenge, id }) => { + const itemIndex = inventory.SeasonChallengeHistory.findIndex(i => i.challenge === challenge); + if (itemIndex !== -1) { + inventory.SeasonChallengeHistory[itemIndex].id = id; + } else { + inventory.SeasonChallengeHistory.push({ challenge, id }); + } + }); + break; + + case "ChallengeProgress": + case "SeasonChallengeCompletions": + case "ChallengePTS": + break; + default: + logger.warn(`unknown challenge progress entry`, { key, value }); + } } await inventory.save(); @@ -43,6 +57,7 @@ export const updateChallengeProgressController: RequestHandler = async (req, res }; interface IUpdateChallengeProgressRequest { + ChallengePTS?: number; ChallengesFixVersion?: number; ChallengeProgress?: IChallengeProgress[]; SeasonChallengeHistory?: ISeasonChallenge[]; From 3186ffe164f100cd323320019bc23d376fe2c764 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 19 Jun 2025 19:39:50 -0700 Subject: [PATCH 047/153] feat: autogenerate temple & archimedean vendors (#2208) So the kuva offer is refreshed every week. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2208 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/serversideVendorsService.ts | 61 ++- .../ArchimedeanVendorManifest.json | 38 -- .../Temple1999VendorManifest.json | 459 ------------------ .../ZarimanCommisionsManifestArchimedean.json | 75 --- 4 files changed, 30 insertions(+), 603 deletions(-) delete mode 100644 static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json delete mode 100644 static/fixed_responses/getVendorInfo/Temple1999VendorManifest.json delete mode 100644 static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json diff --git a/src/services/serversideVendorsService.ts b/src/services/serversideVendorsService.ts index 1c947197..6205060f 100644 --- a/src/services/serversideVendorsService.ts +++ b/src/services/serversideVendorsService.ts @@ -7,7 +7,6 @@ import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorT import { logger } from "@/src/utils/logger"; import { ExportVendors, IRange, IVendor, IVendorOffer } from "warframe-public-export-plus"; -import ArchimedeanVendorManifest from "@/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json"; import DeimosEntratiFragmentVendorProductsManifest from "@/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json"; import DeimosHivemindCommisionsManifestFishmonger from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestFishmonger.json"; import DeimosHivemindCommisionsManifestPetVendor from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestPetVendor.json"; @@ -23,11 +22,8 @@ import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSal import Nova1999ConquestShopManifest from "@/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json"; import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json"; import SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json"; -import Temple1999VendorManifest from "@/static/fixed_responses/getVendorInfo/Temple1999VendorManifest.json"; -import ZarimanCommisionsManifestArchimedean from "@/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json"; const rawVendorManifests: IVendorManifest[] = [ - ArchimedeanVendorManifest, DeimosEntratiFragmentVendorProductsManifest, DeimosHivemindCommisionsManifestFishmonger, DeimosHivemindCommisionsManifestPetVendor, @@ -42,9 +38,7 @@ const rawVendorManifests: IVendorManifest[] = [ MaskSalesmanManifest, Nova1999ConquestShopManifest, OstronPetVendorManifest, - SolarisDebtTokenVendorRepossessionsManifest, - Temple1999VendorManifest, - ZarimanCommisionsManifestArchimedean + SolarisDebtTokenVendorRepossessionsManifest ]; interface IGeneratableVendorInfo extends Omit { @@ -297,31 +291,30 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani } // Add counted offers - if (manifest.numItems) { - const useRng = manifest.numItems.minValue != manifest.numItems.maxValue; - const numItemsTarget = - numUncountedOffers + - (useRng - ? rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue) - : manifest.numItems.minValue); - let i = 0; - while (info.ItemManifest.length + offersToAdd.length < numItemsTarget) { - const item = useRng ? rng.randomElement(manifest.items)! : manifest.items[i++]; - if ( - !item.alwaysOffered && - remainingItemCapacity[getOfferId(item)] != 0 && - (numOffersThatNeedToMatchABin == 0 || missingItemsPerBin[item.bin]) - ) { - remainingItemCapacity[getOfferId(item)] -= 1; - if (missingItemsPerBin[item.bin]) { - missingItemsPerBin[item.bin] -= 1; - numOffersThatNeedToMatchABin -= 1; - } - offersToAdd.splice(offset, 0, item); - } - if (i == manifest.items.length) { - i = 0; + const useRng = manifest.numItems && manifest.numItems.minValue != manifest.numItems.maxValue; + const numItemsTarget = manifest.numItems + ? numUncountedOffers + + (useRng + ? rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue) + : manifest.numItems.minValue) + : manifest.items.length; + let i = 0; + while (info.ItemManifest.length + offersToAdd.length < numItemsTarget) { + const item = useRng ? rng.randomElement(manifest.items)! : manifest.items[i++]; + if ( + !item.alwaysOffered && + remainingItemCapacity[getOfferId(item)] != 0 && + (numOffersThatNeedToMatchABin == 0 || missingItemsPerBin[item.bin]) + ) { + remainingItemCapacity[getOfferId(item)] -= 1; + if (missingItemsPerBin[item.bin]) { + missingItemsPerBin[item.bin] -= 1; + numOffersThatNeedToMatchABin -= 1; } + offersToAdd.splice(offset, 0, item); + } + if (i == manifest.items.length) { + i = 0; } } } else { @@ -465,4 +458,10 @@ if (isDev) { ) { logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/RailjackCrewMemberVendorManifest`); } + + const temple = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest")! + .VendorInfo.ItemManifest; + if (!temple.find(x => x.StoreItem == "/Lotus/StoreItems/Types/Items/MiscItems/Kuva")) { + logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest`); + } } diff --git a/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json b/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json deleted file mode 100644 index df3dc048..00000000 --- a/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "VendorInfo": { - "_id": { "$oid": "62695b0467e5d379750f9f75" }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Zariman/ArchimedeanVendorManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/VoidPlumeAOrnament", - "ItemPrices": [{ "ItemCount": 1, "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/VoidAngelItem", "ProductCategory": "MiscItems" }], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { "$date": { "$numberLong": "9999999000000" } }, - "AllowMultipurchase": true, - "Id": { "$oid": "63ed01ef4c37f93d0b797826" } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/VoidPlumeBOrnament", - "ItemPrices": [{ "ItemCount": 1, "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/VoidAngelItem", "ProductCategory": "MiscItems" }], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { "$date": { "$numberLong": "9999999000000" } }, - "AllowMultipurchase": true, - "Id": { "$oid": "63ed01ef4c37f93d0b797827" } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Kuva", - "ItemPrices": [{ "ItemCount": 5, "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/VoidAngelItem", "ProductCategory": "MiscItems" }], - "Bin": "BIN_0", - "QuantityMultiplier": 35000, - "Expiry": { "$date": { "$numberLong": "9999999000000" } }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { "$oid": "66664112af1177b5070ab882" } - } - ], - "PropertyTextHash": "DB7BF03C3FE6D0036A4DC30066A9A17E", - "Expiry": { "$date": { "$numberLong": "9999999000000" } } - } -} diff --git a/static/fixed_responses/getVendorInfo/Temple1999VendorManifest.json b/static/fixed_responses/getVendorInfo/Temple1999VendorManifest.json deleted file mode 100644 index 6309363f..00000000 --- a/static/fixed_responses/getVendorInfo/Temple1999VendorManifest.json +++ /dev/null @@ -1,459 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "67dadc30e4b6e0e5979c8d56" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TempleBlueprint", - "ItemPrices": [ - { - "ItemCount": 195, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c18c" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TempleSystemsBlueprint", - "ItemPrices": [ - { - "ItemCount": 65, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c18d" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TempleChassisBlueprint", - "ItemPrices": [ - { - "ItemCount": 65, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c18e" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TempleHelmetBlueprint", - "ItemPrices": [ - { - "ItemCount": 65, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c18f" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/1999EntHybridPistolBlueprint", - "ItemPrices": [ - { - "ItemCount": 120, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c190" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/1999EntHybridPistolBarrelBlueprint", - "ItemPrices": [ - { - "ItemCount": 60, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c191" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/1999EntHybridPistolReceiverBlueprint", - "ItemPrices": [ - { - "ItemCount": 60, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c192" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/1999EntHybridPistolStockBlueprint", - "ItemPrices": [ - { - "ItemCount": 60, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c193" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumDrumCoreKitA", - "ItemPrices": [ - { - "ItemCount": 30, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c194" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumDrumCymbalA", - "ItemPrices": [ - { - "ItemCount": 30, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c195" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumDrumFloorTomA", - "ItemPrices": [ - { - "ItemCount": 30, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c196" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumDrumSnareA", - "ItemPrices": [ - { - "ItemCount": 30, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c197" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumEquipmentCaseA", - "ItemPrices": [ - { - "ItemCount": 30, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c198" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumEquipmentCaseB", - "ItemPrices": [ - { - "ItemCount": 30, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c199" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumEquipmentCaseC", - "ItemPrices": [ - { - "ItemCount": 30, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c19a" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumEquipmentCaseD", - "ItemPrices": [ - { - "ItemCount": 30, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c19b" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumEquipmentCaseE", - "ItemPrices": [ - { - "ItemCount": 30, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c19c" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumEquipmentCaseF", - "ItemPrices": [ - { - "ItemCount": 30, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c19d" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumSynthKeyboardA", - "ItemPrices": [ - { - "ItemCount": 30, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c19e" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/PhotoBooth/Vania/PhotoboothTileVaniaObjTempleDefense", - "ItemPrices": [ - { - "ItemCount": 100, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": false, - "Id": { - "$oid": "67dadc30641da66dc5c1c19f" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Kuva", - "ItemPrices": [ - { - "ItemCount": 110, - "ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 6000, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "PurchaseQuantityLimit": 7, - "AllowMultipurchase": true, - "Id": { - "$oid": "67dadc30641da66dc5c1c1a5" - } - } - ], - "PropertyTextHash": "20B13D9EB78FEC80EA32D0687F5BA1AE", - "RequiredGoalTag": "", - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - } - } -} diff --git a/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json b/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json deleted file mode 100644 index ec1c5c22..00000000 --- a/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "VendorInfo": { - "_id": { "$oid": "62a20ba667e5d3797540d831" }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Zariman/ZarimanCommisionsManifestArchimedean", - "ItemManifest": [ - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Zariman/AchimedeanTaskE", - "ItemPrices": [ - { "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/ZarimanMiscItemB", "ItemCount": 4, "ProductCategory": "MiscItems" }, - { "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/ZarimanMiscItemA", "ItemCount": 6, "ProductCategory": "MiscItems" } - ], - "Bin": "BIN_4", - "QuantityMultiplier": 1, - "Expiry": { "$date": { "$numberLong": "9999999000000" } }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { "$oid": "6678b612aa3d8ee5c2597299" } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Zariman/AchimedeanTaskD", - "ItemPrices": [ - { "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/ZarimanMiscItemA", "ItemCount": 5, "ProductCategory": "MiscItems" }, - { "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/ZarimanMiscItemB", "ItemCount": 3, "ProductCategory": "MiscItems" } - ], - "Bin": "BIN_3", - "QuantityMultiplier": 1, - "Expiry": { "$date": { "$numberLong": "9999999000000" } }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { "$oid": "6678b612aa3d8ee5c259729a" } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Zariman/AchimedeanTaskC", - "ItemPrices": [ - { "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/VoidWraithItem", "ItemCount": 15, "ProductCategory": "MiscItems" }, - { "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/ZarimanDogTagUncommon", "ItemCount": 1, "ProductCategory": "MiscItems" } - ], - "Bin": "BIN_2", - "QuantityMultiplier": 1, - "Expiry": { "$date": { "$numberLong": "9999999000000" } }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { "$oid": "6678b612aa3d8ee5c259729b" } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Zariman/AchimedeanTaskB", - "ItemPrices": [ - { "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/VoidWraithItem", "ItemCount": 4, "ProductCategory": "MiscItems" }, - { "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/ZarimanMiscItemB", "ItemCount": 1, "ProductCategory": "MiscItems" } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { "$date": { "$numberLong": "9999999000000" } }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { "$oid": "6678b612aa3d8ee5c259729c" } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Zariman/AchimedeanTaskA", - "ItemPrices": [ - { "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/ZarimanMiscItemB", "ItemCount": 1, "ProductCategory": "MiscItems" }, - { "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/ZarimanMiscItemA", "ItemCount": 2, "ProductCategory": "MiscItems" } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { "$date": { "$numberLong": "9999999000000" } }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { "$oid": "6678b612aa3d8ee5c259729d" } - } - ], - "PropertyTextHash": "F43F0ED811985EEF856970A8342EF322", - "Expiry": { "$date": { "$numberLong": "9999999000000" } } - } -} From ce46fa14ac77dfb9bc2abbe35a165b9264620602 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 20 Jun 2025 05:04:24 +0200 Subject: [PATCH 048/153] chore: ignore crossPlaySetting in updateChallengeProgress --- src/controllers/api/updateChallengeProgressController.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/controllers/api/updateChallengeProgressController.ts b/src/controllers/api/updateChallengeProgressController.ts index 5a7e6467..9b7292e6 100644 --- a/src/controllers/api/updateChallengeProgressController.ts +++ b/src/controllers/api/updateChallengeProgressController.ts @@ -44,6 +44,7 @@ export const updateChallengeProgressController: RequestHandler = async (req, res case "ChallengeProgress": case "SeasonChallengeCompletions": case "ChallengePTS": + case "crossPlaySetting": break; default: logger.warn(`unknown challenge progress entry`, { key, value }); @@ -62,4 +63,5 @@ interface IUpdateChallengeProgressRequest { ChallengeProgress?: IChallengeProgress[]; SeasonChallengeHistory?: ISeasonChallenge[]; SeasonChallengeCompletions?: ISeasonChallenge[]; + crossPlaySetting?: string; } From c6c7a2966b22218ae84dcf4ee8a47a9810292126 Mon Sep 17 00:00:00 2001 From: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Date: Fri, 20 Jun 2025 04:40:27 -0700 Subject: [PATCH 049/153] fix: deimos vault bounty detection (#2207) Related to #388 this should fix incorect rewards for deimos filed bounties Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2207 Reviewed-by: Sainan Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> --- src/services/missionInventoryUpdateService.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index 3d1763a2..b8dc4c67 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -1204,8 +1204,10 @@ export const addMissionRewards = async ( if (syndicateEntry && syndicateEntry.Jobs) { let currentJob = syndicateEntry.Jobs[rewardInfo.JobTier!]; if (syndicateEntry.Tag === "EntratiSyndicate") { - const vault = syndicateEntry.Jobs.find(j => j.locationTag === locationTag); - if (vault) currentJob = vault; + if (jobType.endsWith("VaultBounty")) { + const vault = syndicateEntry.Jobs.find(j => j.locationTag === locationTag); + if (vault) currentJob = vault; + } let medallionAmount = Math.floor(currentJob.xpAmounts[rewardInfo.JobStage] / (rewardInfo.Q ? 0.8 : 1)); if ( @@ -1552,8 +1554,10 @@ function getRandomMissionDrops( let job = syndicateEntry.Jobs[RewardInfo.JobTier!]; if (syndicateEntry.Tag === "EntratiSyndicate") { - const vault = syndicateEntry.Jobs.find(j => j.locationTag === locationTag); - if (vault && locationTag) job = vault; + if (jobType.endsWith("VaultBounty")) { + const vault = syndicateEntry.Jobs.find(j => j.locationTag === locationTag); + if (vault) job = vault; + } // if ( // [ // "DeimosRuinsExterminateBounty", From 97064826b22d2ef4c123eff71cf1dcfba59d5db9 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 20 Jun 2025 04:41:13 -0700 Subject: [PATCH 050/153] feat: unlockAllProfitTakerStages cheat (#2215) Closes #2081 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2215 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- config.json.example | 1 + src/controllers/api/inventoryController.ts | 20 ++++++++++++++++++++ src/services/configService.ts | 1 + static/webui/index.html | 4 ++++ static/webui/translations/de.js | 1 + static/webui/translations/en.js | 1 + static/webui/translations/es.js | 1 + static/webui/translations/fr.js | 1 + static/webui/translations/ru.js | 1 + static/webui/translations/zh.js | 1 + 10 files changed, 32 insertions(+) diff --git a/config.json.example b/config.json.example index 8dfdfd31..e35b62df 100644 --- a/config.json.example +++ b/config.json.example @@ -43,6 +43,7 @@ "noDeathMarks": false, "noKimCooldowns": false, "syndicateMissionsRepeatable": false, + "unlockAllProfitTakerStages": false, "instantFinishRivenChallenge": false, "instantResourceExtractorDrones": false, "noResourceExtractorDronesDamage": false, diff --git a/src/controllers/api/inventoryController.ts b/src/controllers/api/inventoryController.ts index df04e9d4..cdaf8d4d 100644 --- a/src/controllers/api/inventoryController.ts +++ b/src/controllers/api/inventoryController.ts @@ -339,9 +339,29 @@ export const getInventoryResponse = async ( } } + if (config.unlockAllProfitTakerStages) { + inventoryResponse.CompletedJobChains ??= []; + const EudicoHeists = inventoryResponse.CompletedJobChains.find(x => x.LocationTag == "EudicoHeists"); + if (EudicoHeists) { + EudicoHeists.Jobs = allEudicoHeistJobs; + } else { + inventoryResponse.CompletedJobChains.push({ + LocationTag: "EudicoHeists", + Jobs: allEudicoHeistJobs + }); + } + } + return inventoryResponse; }; +const allEudicoHeistJobs = [ + "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyOne", + "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyTwo", + "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyThree", + "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyFour" +]; + const addString = (arr: string[], str: string): void => { if (arr.indexOf(str) == -1) { arr.push(str); diff --git a/src/services/configService.ts b/src/services/configService.ts index b14ffb67..20515ee2 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -49,6 +49,7 @@ export interface IConfig { noDeathMarks?: boolean; noKimCooldowns?: boolean; syndicateMissionsRepeatable?: boolean; + unlockAllProfitTakerStages?: boolean; instantFinishRivenChallenge?: boolean; instantResourceExtractorDrones?: boolean; noResourceExtractorDronesDamage?: boolean; diff --git a/static/webui/index.html b/static/webui/index.html index b6d87db4..3e0856bf 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -708,6 +708,10 @@
+
+ + +
diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index 6672d726..bb7ec5dc 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -159,6 +159,7 @@ dict = { cheats_noDeathMarks: `Keine Todesmarkierungen`, cheats_noKimCooldowns: `Keine Wartezeit bei KIM`, cheats_syndicateMissionsRepeatable: `Syndikat-Missionen wiederholbar`, + cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`, cheats_instantFinishRivenChallenge: `Riven-Mod Herausforderung sofort abschließen`, cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`, cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index f7869ac2..7a227acb 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -158,6 +158,7 @@ dict = { cheats_noDeathMarks: `No Death Marks`, cheats_noKimCooldowns: `No KIM Cooldowns`, cheats_syndicateMissionsRepeatable: `Syndicate Missions Repeatable`, + cheats_unlockAllProfitTakerStages: `Unlock All Profit Taker Stages`, cheats_instantFinishRivenChallenge: `Instant Finish Riven Challenge`, cheats_instantResourceExtractorDrones: `Instant Resource Extractor Drones`, cheats_noResourceExtractorDronesDamage: `No Resource Extractor Drones Damage`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index 79aedacd..5ee94df3 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -159,6 +159,7 @@ dict = { cheats_noDeathMarks: `Sin marcas de muerte`, cheats_noKimCooldowns: `Sin tiempo de espera para conversaciones KIM`, cheats_syndicateMissionsRepeatable: `Misiones de sindicato rejugables`, + cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`, cheats_instantFinishRivenChallenge: `Terminar desafío de agrietado inmediatamente`, cheats_instantResourceExtractorDrones: `Drones de extracción de recursos instantáneos`, cheats_noResourceExtractorDronesDamage: `Sin daño a los drones extractores de recursos`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index c605fce8..a4814e2f 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -159,6 +159,7 @@ dict = { cheats_noDeathMarks: `Aucune marque d'assassin`, cheats_noKimCooldowns: `Aucun cooldown sur le KIM`, cheats_syndicateMissionsRepeatable: `Mission syndicat répétables`, + cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`, cheats_instantFinishRivenChallenge: `Débloquer le challenge Riven instantanément`, cheats_instantResourceExtractorDrones: `Ressources de drones d'extraction instantannées`, cheats_noResourceExtractorDronesDamage: `Aucun dégâts aux drones d'extraction de resources`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index cb89fa7d..22277b35 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -159,6 +159,7 @@ dict = { cheats_noDeathMarks: `Без меток сметри`, cheats_noKimCooldowns: `Чаты KIM без кулдауна`, cheats_syndicateMissionsRepeatable: `[UNTRANSLATED] Syndicate Missions Repeatable`, + cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`, cheats_instantFinishRivenChallenge: `[UNTRANSLATED] Instant Finish Riven Challenge`, cheats_instantResourceExtractorDrones: `Мгновенные Экстракторы Ресурсов`, cheats_noResourceExtractorDronesDamage: `Без урона по дронам-сборщикам`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 06b2ece9..44917ae8 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -159,6 +159,7 @@ dict = { cheats_noDeathMarks: `无死亡标记(不会被 Stalker/Grustrag 三霸/Zanuka 猎人等标记)`, cheats_noKimCooldowns: `无 KIM 冷却时间`, cheats_syndicateMissionsRepeatable: `集团任务可重复`, + cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`, cheats_instantFinishRivenChallenge: `立即完成裂罅挑战`, cheats_instantResourceExtractorDrones: `即时资源采集无人机`, cheats_noResourceExtractorDronesDamage: `资源提取器不会损毁`, From 3619bdfdb50208e85ea5c3b61b07b82ae4794637 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 20 Jun 2025 04:41:47 -0700 Subject: [PATCH 051/153] feat: autogenerate mask vendor (#2216) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2216 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/serversideVendorsService.ts | 49 ++- .../getVendorInfo/MaskSalesmanManifest.json | 301 ------------------ 2 files changed, 37 insertions(+), 313 deletions(-) delete mode 100644 static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json diff --git a/src/services/serversideVendorsService.ts b/src/services/serversideVendorsService.ts index 6205060f..774180d6 100644 --- a/src/services/serversideVendorsService.ts +++ b/src/services/serversideVendorsService.ts @@ -18,7 +18,6 @@ import DeimosPetVendorManifest from "@/static/fixed_responses/getVendorInfo/Deim import DuviriAcrithisVendorManifest from "@/static/fixed_responses/getVendorInfo/DuviriAcrithisVendorManifest.json"; import EntratiLabsEntratiLabsCommisionsManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabsCommisionsManifest.json"; import EntratiLabsEntratiLabVendorManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabVendorManifest.json"; -import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json"; import Nova1999ConquestShopManifest from "@/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json"; import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json"; import SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json"; @@ -35,7 +34,6 @@ const rawVendorManifests: IVendorManifest[] = [ DuviriAcrithisVendorManifest, EntratiLabsEntratiLabsCommisionsManifest, EntratiLabsEntratiLabVendorManifest, - MaskSalesmanManifest, Nova1999ConquestShopManifest, OstronPetVendorManifest, SolarisDebtTokenVendorRepossessionsManifest @@ -277,6 +275,7 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani // Add permanent offers let numUncountedOffers = 0; + let numCountedOffers = 0; let offset = 0; for (const item of manifest.items) { if (item.alwaysOffered || item.rotatedWeekly) { @@ -287,11 +286,16 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani offersToAdd.push(item); ++offset; } + } else { + numCountedOffers += 1 + item.duplicates; } } // Add counted offers - const useRng = manifest.numItems && manifest.numItems.minValue != manifest.numItems.maxValue; + const useRng = + manifest.numItems && + (manifest.numItems.minValue != manifest.numItems.maxValue || + manifest.numItems.minValue != numCountedOffers); const numItemsTarget = manifest.numItems ? numUncountedOffers + (useRng @@ -299,10 +303,13 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani : manifest.numItems.minValue) : manifest.items.length; let i = 0; + const rollableOffers = manifest.items.filter(x => x.probability !== undefined) as (Omit< + IVendorOffer, + "probability" + > & { probability: number })[]; while (info.ItemManifest.length + offersToAdd.length < numItemsTarget) { - const item = useRng ? rng.randomElement(manifest.items)! : manifest.items[i++]; + const item = useRng ? rng.randomReward(rollableOffers)! : rollableOffers[i++]; if ( - !item.alwaysOffered && remainingItemCapacity[getOfferId(item)] != 0 && (numOffersThatNeedToMatchABin == 0 || missingItemsPerBin[item.bin]) ) { @@ -313,7 +320,7 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani } offersToAdd.splice(offset, 0, item); } - if (i == manifest.items.length) { + if (i == rollableOffers.length) { i = 0; } } @@ -351,7 +358,7 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani } }; if (rawItem.numRandomItemPrices) { - item.ItemPrices = []; + item.ItemPrices ??= []; for (let i = 0; i != rawItem.numRandomItemPrices; ++i) { let itemPrice: { type: string; count: IRange }; do { @@ -391,11 +398,13 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani info.ItemManifest.push(item); } - info.ItemManifest.sort((a, b) => { - const aBin = parseInt(a.Bin.substring(4)); - const bBin = parseInt(b.Bin.substring(4)); - return aBin == bBin ? 0 : aBin < bBin ? +1 : -1; - }); + if (manifest.numItemsPerBin) { + info.ItemManifest.sort((a, b) => { + const aBin = parseInt(a.Bin.substring(4)); + const bBin = parseInt(b.Bin.substring(4)); + return aBin == bBin ? 0 : aBin < bBin ? +1 : -1; + }); + } // Update vendor expiry let soonestOfferExpiry: number = Number.MAX_SAFE_INTEGER; @@ -464,4 +473,20 @@ if (isDev) { if (!temple.find(x => x.StoreItem == "/Lotus/StoreItems/Types/Items/MiscItems/Kuva")) { logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest`); } + + const nakak = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest")! + .VendorInfo.ItemManifest; + if ( + nakak.length != 10 || + nakak[0].StoreItem != "/Lotus/StoreItems/Upgrades/Skins/Ostron/RevenantMask" || + nakak[1].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyThumper" || + nakak[1].ItemPrices?.length != 4 || + nakak[2].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyThumperMedium" || + nakak[2].ItemPrices?.length != 4 || + nakak[3].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyThumperLarge" || + nakak[3].ItemPrices?.length != 4 + // The remaining offers should be computed by weighted RNG. + ) { + logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest`); + } } diff --git a/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json b/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json deleted file mode 100644 index 85aa7eac..00000000 --- a/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json +++ /dev/null @@ -1,301 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "598a090d9a4a313746fd1f24" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Ostron/RevenantMask", - "ItemPrices": [ - { - "ItemCount": 1, - "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/CetusWispItem", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "63ed01ef4c37f93d0b797674" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyThumper", - "ItemPrices": [ - { - "ItemCount": 2, - "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/CetusWispItem", - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothUncommonFishBPartItem", - "ItemCount": 10, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/NistlebrushItem", - "ItemCount": 10, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonOreAAlloyAItem", - "ItemCount": 32, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "63ed01ef4c37f93d0b797675" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyThumperMedium", - "ItemPrices": [ - { - "ItemCount": 4, - "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/CetusWispItem", - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonGemBCutAItem", - "ItemCount": 24, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothUncommonFishAPartItem", - "ItemCount": 18, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothUncommonFishBPartItem", - "ItemCount": 27, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "63ed01ef4c37f93d0b797676" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyThumperLarge", - "ItemPrices": [ - { - "ItemCount": 6, - "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/CetusWispItem", - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonGemACutAItem", - "ItemCount": 35, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothCommonFishAPartItem", - "ItemCount": 95, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/NistlebrushItem", - "ItemCount": 60, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "63ed01ef4c37f93d0b797677" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/SynthicatorRecipes/FlareBlueBlueprint", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothUncommonFishBPartItem", - "ItemCount": 10, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/DayCommonFishCPartItem", - "ItemCount": 10, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "6651291214e90115b91b50a1" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/SynthicatorRecipes/FlareRedBlueprint", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonOreAAlloyAItem", - "ItemCount": 37, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothUncommonFishAPartItem", - "ItemCount": 7, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "6651291214e90115b91b50a2" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Ostron/VoltMask", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonOreBAlloyBItem", - "ItemCount": 34, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/GrokdrulItem", - "ItemCount": 17, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "6651291214e90115b91b50a3" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Ostron/MagMask", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/DayCommonFishBPartItem", - "ItemCount": 16, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/ForestRodentPartItem", - "ItemCount": 5, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "6651291214e90115b91b50a4" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Ostron/ExcaliburMask", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/BirdOfPreyPartItem", - "ItemCount": 5, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/GrokdrulItem", - "ItemCount": 20, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "6651291214e90115b91b50a5" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Ostron/GrineerMask", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/DayCommonFishBPartItem", - "ItemCount": 20, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonOreAAlloyAItem", - "ItemCount": 31, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "6651291214e90115b91b50a6" - } - } - ], - "PropertyTextHash": "6AACA376DA34B35B5C16F1B40DBC017D", - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - } - } -} From 3c64f17e34fcdaaaeadb8c6f4cd95e80de9ec40a Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 20 Jun 2025 04:42:11 -0700 Subject: [PATCH 052/153] feat: missionsCanGiveAllRelics cheat (#2217) Closes #1060 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2217 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- config.json.example | 1 + src/services/configService.ts | 1 + src/services/missionInventoryUpdateService.ts | 18 ++++++++++++++++++ static/webui/index.html | 4 ++++ static/webui/translations/de.js | 1 + static/webui/translations/en.js | 1 + static/webui/translations/es.js | 1 + static/webui/translations/fr.js | 1 + static/webui/translations/ru.js | 1 + static/webui/translations/zh.js | 1 + 10 files changed, 30 insertions(+) diff --git a/config.json.example b/config.json.example index e35b62df..a92e3f8d 100644 --- a/config.json.example +++ b/config.json.example @@ -54,6 +54,7 @@ "noDojoResearchCosts": false, "noDojoResearchTime": false, "fastClanAscension": false, + "missionsCanGiveAllRelics": false, "spoofMasteryRank": -1, "nightwaveStandingMultiplier": 1, "worldState": { diff --git a/src/services/configService.ts b/src/services/configService.ts index 20515ee2..b4566096 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -60,6 +60,7 @@ export interface IConfig { noDojoResearchCosts?: boolean; noDojoResearchTime?: boolean; fastClanAscension?: boolean; + missionsCanGiveAllRelics?: boolean; spoofMasteryRank?: number; nightwaveStandingMultiplier?: number; worldState?: { diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index b8dc4c67..499115f9 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -2,6 +2,7 @@ import { ExportEnemies, ExportFusionBundles, ExportRegions, + ExportRelics, ExportRewards, IMissionReward as IMissionRewardExternal, IRegion, @@ -1805,6 +1806,23 @@ function getRandomMissionDrops( drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount }); } } + + if (config.missionsCanGiveAllRelics) { + for (const drop of drops) { + const itemType = fromStoreItem(drop.StoreItem); + if (itemType in ExportRelics) { + const relic = ExportRelics[itemType]; + const replacement = getRandomElement( + Object.entries(ExportRelics).filter( + arr => arr[1].era == relic.era && arr[1].quality == relic.quality + ) + )!; + logger.debug(`replacing ${relic.era} ${relic.category} with ${replacement[1].category}`); + drop.StoreItem = toStoreItem(replacement[0]); + } + } + } + return drops; } diff --git a/static/webui/index.html b/static/webui/index.html index 3e0856bf..e80d9fe7 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -752,6 +752,10 @@
+
+ + +
diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index bb7ec5dc..b84df0f2 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -170,6 +170,7 @@ dict = { cheats_noDojoResearchCosts: `Keine Dojo-Forschungskosten`, cheats_noDojoResearchTime: `Keine Dojo-Forschungszeit`, cheats_fastClanAscension: `Schneller Clan-Aufstieg`, + cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`, cheats_spoofMasteryRank: `Gefälschter Meisterschaftsrang (-1 zum deaktivieren)`, cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`, cheats_save: `[UNTRANSLATED] Save`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index 7a227acb..7f5b5aca 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -169,6 +169,7 @@ dict = { cheats_noDojoResearchCosts: `No Dojo Research Costs`, cheats_noDojoResearchTime: `No Dojo Research Time`, cheats_fastClanAscension: `Fast Clan Ascension`, + cheats_missionsCanGiveAllRelics: `Missions Can Give All Relics`, cheats_spoofMasteryRank: `Spoofed Mastery Rank (-1 to disable)`, cheats_nightwaveStandingMultiplier: `Nightwave Standing Multiplier`, cheats_save: `Save`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index 5ee94df3..da50fe7e 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -170,6 +170,7 @@ dict = { cheats_noDojoResearchCosts: `Sin costo de investigación del dojo`, cheats_noDojoResearchTime: `Sin tiempo de investigación del dojo`, cheats_fastClanAscension: `Ascenso rápido del clan`, + cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`, cheats_spoofMasteryRank: `Rango de maestría simulado (-1 para desactivar)`, cheats_nightwaveStandingMultiplier: `Multiplicador de Reputación de Onda Nocturna`, cheats_save: `Guardar`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index a4814e2f..4a8fae03 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -170,6 +170,7 @@ dict = { cheats_noDojoResearchCosts: `Aucun coût de recherche (Dojo)`, cheats_noDojoResearchTime: `Aucun temps de recherche (Dojo)`, cheats_fastClanAscension: `Ascension de clan rapide`, + cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`, cheats_spoofMasteryRank: `Rang de maîtrise personnalisé (-1 pour désactiver)`, cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`, cheats_save: `[UNTRANSLATED] Save`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index 22277b35..612555b3 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -170,6 +170,7 @@ dict = { cheats_noDojoResearchCosts: `Бесплатные Исследование Додзё`, cheats_noDojoResearchTime: `Мгновенные Исследование Додзё`, cheats_fastClanAscension: `Мгновенное Вознесение Клана`, + cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`, cheats_spoofMasteryRank: `Подделанный ранг мастерства (-1 для отключения)`, cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`, cheats_save: `[UNTRANSLATED] Save`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 44917ae8..6b1d4b7f 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -170,6 +170,7 @@ dict = { cheats_noDojoResearchCosts: `无视道场研究消耗`, cheats_noDojoResearchTime: `无视道场研究时间`, cheats_fastClanAscension: `快速升级氏族`, + cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`, cheats_spoofMasteryRank: `伪造精通段位(-1为禁用)`, cheats_nightwaveStandingMultiplier: `午夜电波声望倍率`, cheats_save: `[UNTRANSLATED] Save`, From 95136e6059b0d456d6e1174d6b2527c6944b70a6 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 20 Jun 2025 04:47:45 -0700 Subject: [PATCH 053/153] feat: dynamic void fissure missions (#2214) Closes #1512 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2214 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- .../dynamic/worldStateController.ts | 15 +- src/index.ts | 6 + src/models/worldStateModel.ts | 14 ++ src/services/worldStateService.ts | 91 ++++++++- src/types/worldStateTypes.ts | 10 +- .../worldState/fissureMissions.json | 154 ++++++++++++++ .../worldState/worldState.json | 189 ------------------ 7 files changed, 276 insertions(+), 203 deletions(-) create mode 100644 src/models/worldStateModel.ts create mode 100644 static/fixed_responses/worldState/fissureMissions.json diff --git a/src/controllers/dynamic/worldStateController.ts b/src/controllers/dynamic/worldStateController.ts index 89335497..75d78ff0 100644 --- a/src/controllers/dynamic/worldStateController.ts +++ b/src/controllers/dynamic/worldStateController.ts @@ -1,6 +1,15 @@ import { RequestHandler } from "express"; -import { getWorldState } from "@/src/services/worldStateService"; +import { getWorldState, populateFissures } from "@/src/services/worldStateService"; +import { version_compare } from "@/src/helpers/inventoryHelpers"; -export const worldStateController: RequestHandler = (req, res) => { - res.json(getWorldState(req.query.buildLabel as string | undefined)); +export const worldStateController: RequestHandler = async (req, res) => { + const buildLabel = req.query.buildLabel as string | undefined; + const worldState = getWorldState(buildLabel); + + // Omitting void fissures for versions prior to Dante Unbound to avoid script errors. + if (!buildLabel || version_compare(buildLabel, "2024.03.24.20.00") >= 0) { + await populateFissures(worldState); + } + + res.json(worldState); }; diff --git a/src/index.ts b/src/index.ts index de36b392..7afd9387 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,6 +22,7 @@ import { JSONStringify } from "json-with-bigint"; import { startWebServer } from "./services/webService"; import { validateConfig } from "@/src/services/configWatcherService"; +import { updateWorldStateCollections } from "./services/worldStateService"; // Patch JSON.stringify to work flawlessly with Bigints. JSON.stringify = JSONStringify; @@ -33,6 +34,11 @@ mongoose .then(() => { logger.info("Connected to MongoDB"); startWebServer(); + + void updateWorldStateCollections(); + setInterval(() => { + void updateWorldStateCollections(); + }, 60_000); }) .catch(error => { if (error instanceof Error) { diff --git a/src/models/worldStateModel.ts b/src/models/worldStateModel.ts new file mode 100644 index 00000000..37615d7e --- /dev/null +++ b/src/models/worldStateModel.ts @@ -0,0 +1,14 @@ +import { IFissureDatabase } from "@/src/types/worldStateTypes"; +import { model, Schema } from "mongoose"; + +const fissureSchema = new Schema({ + Activation: Date, + Expiry: Date, + Node: String, // must be unique + Modifier: String, + Hard: Boolean +}); + +fissureSchema.index({ Expiry: 1 }, { expireAfterSeconds: 0 }); // With this, MongoDB will automatically delete expired entries. + +export const Fissure = model("Fissure", fissureSchema); diff --git a/src/services/worldStateService.ts b/src/services/worldStateService.ts index 8fa82eed..ea48ef44 100644 --- a/src/services/worldStateService.ts +++ b/src/services/worldStateService.ts @@ -1,12 +1,13 @@ import staticWorldState from "@/static/fixed_responses/worldState/worldState.json"; +import fissureMissions from "@/static/fixed_responses/worldState/fissureMissions.json"; import sortieTilesets from "@/static/fixed_responses/worldState/sortieTilesets.json"; import sortieTilesetMissions from "@/static/fixed_responses/worldState/sortieTilesetMissions.json"; import syndicateMissions from "@/static/fixed_responses/worldState/syndicateMissions.json"; import { buildConfig } from "@/src/services/buildConfigService"; import { unixTimesInMs } from "@/src/constants/timeConstants"; import { config } from "@/src/services/configService"; -import { SRng } from "@/src/services/rngService"; -import { ExportRegions, ExportSyndicates, IRegion } from "warframe-public-export-plus"; +import { getRandomElement, getRandomInt, SRng } from "@/src/services/rngService"; +import { eMissionType, ExportRegions, ExportSyndicates, IRegion } from "warframe-public-export-plus"; import { ICalendarDay, ICalendarEvent, @@ -21,8 +22,9 @@ import { IWorldState, TCircuitGameMode } from "../types/worldStateTypes"; -import { version_compare } from "../helpers/inventoryHelpers"; +import { toMongoDate, toOid, version_compare } from "../helpers/inventoryHelpers"; import { logger } from "../utils/logger"; +import { Fissure } from "../models/worldStateModel"; const sortieBosses = [ "SORTIE_BOSS_HYENA", @@ -1110,6 +1112,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => { Alerts: [], Sorties: [], LiteSorties: [], + ActiveMissions: [], GlobalUpgrades: [], VoidStorms: [], EndlessXpChoices: [], @@ -1118,13 +1121,9 @@ export const getWorldState = (buildLabel?: string): IWorldState => { SyndicateMissions: [...staticWorldState.SyndicateMissions] }; - // Omit void fissures for versions prior to Dante Unbound to avoid script errors. - if (buildLabel && version_compare(buildLabel, "2024.03.24.20.00") < 0) { - worldState.ActiveMissions = []; - if (version_compare(buildLabel, "2017.10.12.17.04") < 0) { - // Old versions seem to really get hung up on not being able to load these. - worldState.PVPChallengeInstances = []; - } + // Old versions seem to really get hung up on not being able to load these. + if (buildLabel && version_compare(buildLabel, "2017.10.12.17.04") < 0) { + worldState.PVPChallengeInstances = []; } if (config.worldState?.starDays) { @@ -1364,6 +1363,24 @@ export const getWorldState = (buildLabel?: string): IWorldState => { return worldState; }; +export const populateFissures = async (worldState: IWorldState): Promise => { + const fissures = await Fissure.find({}); + for (const fissure of fissures) { + const meta = ExportRegions[fissure.Node]; + worldState.ActiveMissions.push({ + _id: toOid(fissure._id), + Region: meta.systemIndex + 1, + Seed: 1337, + Activation: toMongoDate(fissure.Activation), + Expiry: toMongoDate(fissure.Expiry), + Node: fissure.Node, + MissionType: eMissionType[meta.missionIndex].tag, + Modifier: fissure.Modifier, + Hard: fissure.Hard + }); + } +}; + export const idToBountyCycle = (id: string): number => { return Math.trunc((parseInt(id.substring(0, 8), 16) * 1000) / 9000_000); }; @@ -1491,3 +1508,57 @@ const nightwaveTagToSeason: Record = { RadioLegionIntermissionSyndicate: 1, // Intermission I RadioLegionSyndicate: 0 // The Wolf of Saturn Six }; + +export const updateWorldStateCollections = async (): Promise => { + const fissures = await Fissure.find(); + + const activeNodes = new Set(); + const tierToFurthestExpiry: Record = { + VoidT1: 0, + VoidT2: 0, + VoidT3: 0, + VoidT4: 0, + VoidT5: 0, + VoidT6: 0, + VoidT1Hard: 0, + VoidT2Hard: 0, + VoidT3Hard: 0, + VoidT4Hard: 0, + VoidT5Hard: 0, + VoidT6Hard: 0 + }; + for (const fissure of fissures) { + activeNodes.add(fissure.Node); + + const key = fissure.Modifier + (fissure.Hard ? "Hard" : ""); + tierToFurthestExpiry[key] = Math.max(tierToFurthestExpiry[key], fissure.Expiry.getTime()); + } + + const deadline = Date.now() - 6 * unixTimesInMs.minute; + for (const [tier, expiry] of Object.entries(tierToFurthestExpiry)) { + if (expiry < deadline) { + const numFissures = getRandomInt(1, 3); + for (let i = 0; i != numFissures; ++i) { + const modifier = tier.replace("Hard", "") as + | "VoidT1" + | "VoidT2" + | "VoidT3" + | "VoidT4" + | "VoidT5" + | "VoidT6"; + let node: string; + do { + node = getRandomElement(fissureMissions[modifier])!; + } while (activeNodes.has(node)); + activeNodes.add(node); + await Fissure.insertOne({ + Activation: new Date(), + Expiry: new Date(Date.now() + getRandomInt(60, 120) * unixTimesInMs.minute), + Node: node, + Modifier: modifier, + Hard: tier.indexOf("Hard") != -1 ? true : undefined + }); + } + } + } +}; diff --git a/src/types/worldStateTypes.ts b/src/types/worldStateTypes.ts index bd8ab138..73aa9d78 100644 --- a/src/types/worldStateTypes.ts +++ b/src/types/worldStateTypes.ts @@ -9,8 +9,8 @@ export interface IWorldState { Sorties: ISortie[]; LiteSorties: ILiteSortie[]; SyndicateMissions: ISyndicateMissionInfo[]; - GlobalUpgrades: IGlobalUpgrade[]; ActiveMissions: IFissure[]; + GlobalUpgrades: IGlobalUpgrade[]; NodeOverrides: INodeOverride[]; VoidStorms: IVoidStorm[]; PVPChallengeInstances: IPVPChallengeInstance[]; @@ -86,6 +86,14 @@ export interface IFissure { Hard?: boolean; } +export interface IFissureDatabase { + Activation: Date; + Expiry: Date; + Node: string; + Modifier: "VoidT1" | "VoidT2" | "VoidT3" | "VoidT4" | "VoidT5" | "VoidT6"; + Hard?: boolean; +} + export interface INodeOverride { _id: IOid; Activation?: IMongoDate; diff --git a/static/fixed_responses/worldState/fissureMissions.json b/static/fixed_responses/worldState/fissureMissions.json new file mode 100644 index 00000000..9b5c85e3 --- /dev/null +++ b/static/fixed_responses/worldState/fissureMissions.json @@ -0,0 +1,154 @@ +{ + "VoidT1": [ + "SolNode23", + "SolNode66", + "SolNode45", + "SolNode41", + "SolNode59", + "SolNode39", + "SolNode75", + "SolNode113", + "SolNode85", + "SolNode58", + "SolNode101", + "SolNode109", + "SolNode26", + "SolNode15", + "SolNode61", + "SolNode123", + "SolNode16", + "SolNode79", + "SolNode2", + "SolNode22", + "SolNode68", + "SolNode89", + "SolNode11", + "SolNode46", + "SolNode36", + "SolNode27", + "SolNode14", + "SolNode106", + "SolNode30", + "SolNode107", + "SolNode63", + "SolNode128" + ], + "VoidT2": [ + "SolNode141", + "SolNode149", + "SolNode10", + "SolNode93", + "SettlementNode11", + "SolNode137", + "SolNode132", + "SolNode73", + "SolNode82", + "SolNode25", + "SolNode88", + "SolNode126", + "SolNode135", + "SolNode74", + "SettlementNode15", + "SolNode147", + "SolNode67", + "SolNode20", + "SolNode42", + "SolNode18", + "SolNode31", + "SolNode139", + "SettlementNode12", + "SolNode100", + "SolNode140", + "SolNode70", + "SettlementNode1", + "SettlementNode14", + "SolNode50", + "SettlementNode2", + "SolNode146", + "SettlementNode3", + "SolNode97", + "SolNode125", + "SolNode19", + "SolNode121", + "SolNode96", + "SolNode131" + ], + "VoidT3": [ + "SolNode62", + "SolNode17", + "SolNode403", + "SolNode6", + "SolNode118", + "SolNode211", + "SolNode217", + "SolNode401", + "SolNode64", + "SolNode405", + "SolNode84", + "SolNode402", + "SolNode408", + "SolNode122", + "SolNode57", + "SolNode216", + "SolNode205", + "SolNode215", + "SolNode404", + "SolNode209", + "SolNode406", + "SolNode204", + "SolNode203", + "SolNode409", + "SolNode400", + "SolNode212", + "SolNode1", + "SolNode412", + "SolNode49", + "SolNode78", + "SolNode410", + "SolNode407", + "SolNode220" + ], + "VoidT4": [ + "SolNode188", + "SolNode403", + "SolNode189", + "SolNode21", + "SolNode102", + "SolNode171", + "SolNode196", + "SolNode184", + "SolNode185", + "SolNode76", + "SolNode195", + "SolNode164", + "SolNode401", + "SolNode405", + "SolNode56", + "SolNode402", + "SolNode408", + "SolNode4", + "SolNode181", + "SolNode406", + "SolNode162", + "SolNode72", + "SolNode407", + "SolNode177", + "SolNode404", + "SolNode400", + "SolNode409", + "SolNode43", + "SolNode166", + "SolNode172", + "SolNode412", + "SolNode187", + "SolNode38", + "SolNode175", + "SolNode81", + "SolNode48", + "SolNode410", + "SolNode153", + "SolNode173" + ], + "VoidT5": ["SolNode747", "SolNode743", "SolNode742", "SolNode744", "SolNode745", "SolNode748", "SolNode746", "SolNode741"], + "VoidT6": ["SolNode717", "SolNode309", "SolNode718", "SolNode232", "SolNode230", "SolNode310"] +} diff --git a/static/fixed_responses/worldState/worldState.json b/static/fixed_responses/worldState/worldState.json index 73d48ce1..f973d69a 100644 --- a/static/fixed_responses/worldState/worldState.json +++ b/static/fixed_responses/worldState/worldState.json @@ -327,195 +327,6 @@ "Nodes": [] } ], - "ActiveMissions": [ - { - "_id": { "$oid": "663a7509d93367863785932d" }, - "Region": 15, - "Seed": 80795, - "Activation": { "$date": { "$numberLong": "1715107081517" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode400", - "MissionType": "MT_EXTERMINATION", - "Modifier": "VoidT3", - "Hard": true - }, - { - "_id": { "$oid": "663a75f959a5964cadb39879" }, - "Region": 19, - "Seed": 32067, - "Activation": { "$date": { "$numberLong": "1715107321237" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode747", - "MissionType": "MT_INTEL", - "Modifier": "VoidT5", - "Hard": true - }, - { - "_id": { "$oid": "663a779d3e347839ff301814" }, - "Region": 7, - "Seed": 51739, - "Activation": { "$date": { "$numberLong": "1715107741454" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode64", - "MissionType": "MT_TERRITORY", - "Modifier": "VoidT3" - }, - { - "_id": { "$oid": "663a77d916c199f4644ee67d" }, - "Region": 17, - "Seed": 61179, - "Activation": { "$date": { "$numberLong": "1715107801647" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode718", - "MissionType": "MT_ALCHEMY", - "Modifier": "VoidT6" - }, - { - "_id": { "$oid": "663a78c98a609b49b8410726" }, - "Region": 3, - "Seed": 9520, - "Activation": { "$date": { "$numberLong": "1715108041501" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode79", - "MissionType": "MT_INTEL", - "Modifier": "VoidT1", - "Hard": true - }, - { - "_id": { "$oid": "663a7df15eeabaac79b0a061" }, - "Region": 6, - "Seed": 48861, - "Activation": { "$date": { "$numberLong": "1715109361974" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode67", - "MissionType": "MT_INTEL", - "Modifier": "VoidT2", - "Hard": true - }, - { - "_id": { "$oid": "663a7df25eeabaac79b0a062" }, - "Region": 5, - "Seed": 13550, - "Activation": { "$date": { "$numberLong": "1715109361974" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode10", - "MissionType": "MT_SABOTAGE", - "Modifier": "VoidT2", - "Hard": true - }, - { - "_id": { "$oid": "663a83cdec0d5181435f1324" }, - "Region": 19, - "Seed": 39392, - "Activation": { "$date": { "$numberLong": "1715110861506" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode742", - "MissionType": "MT_DEFENSE", - "Modifier": "VoidT5" - }, - { - "_id": { "$oid": "663a83cdec0d5181435f1325" }, - "Region": 19, - "Seed": 88668, - "Activation": { "$date": { "$numberLong": "1715110861506" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode743", - "MissionType": "MT_MOBILE_DEFENSE", - "Modifier": "VoidT5" - }, - { - "_id": { "$oid": "663a83cdec0d5181435f1326" }, - "Region": 19, - "Seed": 73823, - "Activation": { "$date": { "$numberLong": "1715110861506" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode741", - "MissionType": "MT_ASSAULT", - "Modifier": "VoidT5" - }, - { - "_id": { "$oid": "663a878d23d1514873170466" }, - "Region": 9, - "Seed": 88696, - "Activation": { "$date": { "$numberLong": "1715111821951" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode4", - "MissionType": "MT_EXTERMINATION", - "Modifier": "VoidT4", - "Hard": true - }, - { - "_id": { "$oid": "663a887d4903098c10992fe6" }, - "Region": 6, - "Seed": 66337, - "Activation": { "$date": { "$numberLong": "1715112061729" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode18", - "MissionType": "MT_TERRITORY", - "Modifier": "VoidT2" - }, - { - "_id": { "$oid": "663a887d4903098c10992fe7" }, - "Region": 10, - "Seed": 5135, - "Activation": { "$date": { "$numberLong": "1715112061729" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode149", - "MissionType": "MT_DEFENSE", - "Modifier": "VoidT2" - }, - { - "_id": { "$oid": "663a8931586c301b1fbe63d3" }, - "Region": 15, - "Seed": 32180, - "Activation": { "$date": { "$numberLong": "1715112241196" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode408", - "MissionType": "MT_DEFENSE", - "Modifier": "VoidT4" - }, - { - "_id": { "$oid": "663a8931586c301b1fbe63d4" }, - "Region": 12, - "Seed": 22521, - "Activation": { "$date": { "$numberLong": "1715112241196" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode181", - "MissionType": "MT_EXTERMINATION", - "Modifier": "VoidT4" - }, - { - "_id": { "$oid": "663a8931586c301b1fbe63d5" }, - "Region": 2, - "Seed": 28500, - "Activation": { "$date": { "$numberLong": "1715112241196" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode128", - "MissionType": "MT_EXTERMINATION", - "Modifier": "VoidT1" - }, - { - "_id": { "$oid": "663a8931586c301b1fbe63d6" }, - "Region": 3, - "Seed": 24747, - "Activation": { "$date": { "$numberLong": "1715112241196" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode26", - "MissionType": "MT_DEFENSE", - "Modifier": "VoidT1" - }, - { - "_id": { "$oid": "663a8931586c301b1fbe63d7" }, - "Region": 17, - "Seed": 63914, - "Activation": { "$date": { "$numberLong": "1715112241196" } }, - "Expiry": { "$date": { "$numberLong": "2000000000000" } }, - "Node": "SolNode717", - "MissionType": "MT_SURVIVAL", - "Modifier": "VoidT6", - "Hard": true - } - ], "NodeOverrides": [ { "_id": { "$oid": "549b18e9b029cef5991d6aec" }, "Node": "EuropaHUB", "Hide": true }, { "_id": { "$oid": "54a1737aeb658f6cbccf70ff" }, "Node": "ErisHUB", "Hide": true }, From f41377bb81d10b83eb634e825468ff3fb47c5bae Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 20 Jun 2025 09:40:53 -0700 Subject: [PATCH 054/153] chore: npm audit fix (#2220) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2220 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- package-lock.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5d1f4a36..ca339e31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -121,9 +121,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -171,9 +171,9 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -998,9 +998,9 @@ } }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1552,9 +1552,9 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -1983,9 +1983,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { From eadc9c4ecb2eeb429c7a4caad03b47ed64a06722 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 20 Jun 2025 14:00:39 -0700 Subject: [PATCH 055/153] feat(webui): max rank plexus (#2219) Closes #1740 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2219 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- static/webui/index.html | 3 ++ static/webui/script.js | 82 ++++++++++++++++++++++----------- static/webui/translations/de.js | 3 +- static/webui/translations/en.js | 3 +- static/webui/translations/es.js | 3 +- static/webui/translations/fr.js | 3 +- static/webui/translations/ru.js | 3 +- static/webui/translations/zh.js | 3 +- 8 files changed, 70 insertions(+), 33 deletions(-) diff --git a/static/webui/index.html b/static/webui/index.html index e80d9fe7..5b92f727 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -452,6 +452,9 @@
+
+ +
diff --git a/static/webui/script.js b/static/webui/script.js index bd32266f..f4c43175 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -594,8 +594,9 @@ function updateInventory() { a.onclick = function (event) { event.preventDefault(); revalidateAuthz(() => { + const promises = []; if (item.XP < maxXP) { - addGearExp(category, item.ItemId.$oid, maxXP - item.XP); + promises.push(addGearExp(category, item.ItemId.$oid, maxXP - item.XP)); } if ("exalted" in itemMap[item.ItemType]) { for (const exaltedType of itemMap[item.ItemType].exalted) { @@ -604,15 +605,20 @@ function updateInventory() { const exaltedCap = itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000; if (exaltedItem.XP < exaltedCap) { - addGearExp( - "SpecialItems", - exaltedItem.ItemId.$oid, - exaltedCap - exaltedItem.XP + promises.push( + addGearExp( + "SpecialItems", + exaltedItem.ItemId.$oid, + exaltedCap - exaltedItem.XP + ) ); } } } } + Promise.all(promises).then(() => { + updateInventory(); + }); }); }; a.title = loc("code_maxRank"); @@ -761,6 +767,13 @@ function updateInventory() { giveAllQEvolutionProgress.disabled = true; } + if (data.CrewShipHarnesses?.length) { + window.plexus = { + id: data.CrewShipHarnesses[0].ItemId.$oid, + xp: data.CrewShipHarnesses[0].XP + }; + } + // Populate quests route document.getElementById("QuestKeys-list").innerHTML = ""; data.QuestKeys.forEach(item => { @@ -1443,20 +1456,17 @@ function maxRankAllEquipment(categories) { XP: maxXP }); } - if (category === "Suits") { - if ("exalted" in itemMap[item.ItemType]) { - for (const exaltedType of itemMap[item.ItemType].exalted) { - const exaltedItem = data["SpecialItems"].find(x => x.ItemType == exaltedType); - if (exaltedItem) { - const exaltedCap = - itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000; - if (exaltedItem.XP < exaltedCap) { - batchData["SpecialItems"] ??= []; - batchData["SpecialItems"].push({ - ItemId: { $oid: exaltedItem.ItemId.$oid }, - XP: exaltedCap - }); - } + if (item.ItemType in itemMap && "exalted" in itemMap[item.ItemType]) { + for (const exaltedType of itemMap[item.ItemType].exalted) { + const exaltedItem = data["SpecialItems"].find(x => x.ItemType == exaltedType); + if (exaltedItem) { + const exaltedCap = itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000; + if (exaltedItem.XP < exaltedCap) { + batchData["SpecialItems"] ??= []; + batchData["SpecialItems"].push({ + ItemId: { $oid: exaltedItem.ItemId.$oid }, + XP: exaltedCap + }); } } } @@ -1483,14 +1493,14 @@ function addGearExp(category, oid, xp) { XP: xp } ]; - $.post({ - url: "/custom/addXp?" + window.authz, - contentType: "application/json", - data: JSON.stringify(data) - }).done(function () { - if (category != "SpecialItems") { - updateInventory(); - } + return new Promise((resolve, reject) => { + $.post({ + url: "/custom/addXp?" + window.authz, + contentType: "application/json", + data: JSON.stringify(data) + }) + .done(resolve) + .fail(reject); }); } @@ -2249,3 +2259,21 @@ function formatDatetime(fmt, date) { } }); } + +const calls_in_flight = new Set(); + +async function debounce(func, ...args) { + calls_in_flight.add(func); + await func(...args); + calls_in_flight.delete(func); +} + +async function doMaxPlexus() { + if ((window.plexus?.xp ?? 0) < 900_000) { + await addGearExp("CrewShipHarnesses", window.plexus.id, 900_000 - window.plexus.xp); + window.plexus.xp = 900_000; + toast(loc("code_succRankUp")); + } else { + toast(loc("code_noEquipmentToRankUp")); + } +} diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index b84df0f2..0401beb9 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -86,6 +86,7 @@ dict = { inventory_moaPets: `Moas`, inventory_kubrowPets: `Bestien`, inventory_evolutionProgress: `Incarnon-Entwicklungsfortschritte`, + inventory_Boosters: `[UNTRANSLATED] Boosters`, inventory_bulkAddSuits: `Fehlende Warframes hinzufügen`, inventory_bulkAddWeapons: `Fehlende Waffen hinzufügen`, inventory_bulkAddSpaceSuits: `Fehlende Archwings hinzufügen`, @@ -100,7 +101,7 @@ dict = { inventory_bulkRankUpSentinels: `Alle Wächter auf Max. Rang`, inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`, inventory_bulkRankUpEvolutionProgress: `Alle Incarnon-Entwicklungsfortschritte auf Max. Rang`, - inventory_Boosters: `[UNTRANSLATED] Boosters`, + inventory_maxPlexus: `[UNTRANSLATED] Max Rank Plexus`, quests_list: `Quests`, quests_completeAll: `Alle Quests abschließen`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index 7f5b5aca..28bd1e48 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -85,6 +85,7 @@ dict = { inventory_moaPets: `Moas`, inventory_kubrowPets: `Beasts`, inventory_evolutionProgress: `Incarnon Evolution Progress`, + inventory_Boosters: `Boosters`, inventory_bulkAddSuits: `Add Missing Warframes`, inventory_bulkAddWeapons: `Add Missing Weapons`, inventory_bulkAddSpaceSuits: `Add Missing Archwings`, @@ -99,7 +100,7 @@ dict = { inventory_bulkRankUpSentinels: `Max Rank All Sentinels`, inventory_bulkRankUpSentinelWeapons: `Max Rank All Sentinel Weapons`, inventory_bulkRankUpEvolutionProgress: `Max Rank All Incarnon Evolution Progress`, - inventory_Boosters: `Boosters`, + inventory_maxPlexus: `Max Rank Plexus`, quests_list: `Quests`, quests_completeAll: `Complete All Quests`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index da50fe7e..e46bcedd 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -86,6 +86,7 @@ dict = { inventory_moaPets: `Moas`, inventory_kubrowPets: `Bestias`, inventory_evolutionProgress: `Progreso de evolución Incarnon`, + inventory_Boosters: `Potenciadores`, inventory_bulkAddSuits: `Agregar Warframes faltantes`, inventory_bulkAddWeapons: `Agregar armas faltantes`, inventory_bulkAddSpaceSuits: `Agregar Archwings faltantes`, @@ -100,7 +101,7 @@ dict = { inventory_bulkRankUpSentinels: `Maximizar rango de todos los centinelas`, inventory_bulkRankUpSentinelWeapons: `Maximizar rango de todas las armas de centinela`, inventory_bulkRankUpEvolutionProgress: `Maximizar todo el progreso de evolución Incarnon`, - inventory_Boosters: `Potenciadores`, + inventory_maxPlexus: `[UNTRANSLATED] Max Rank Plexus`, quests_list: `Misiones`, quests_completeAll: `Completar todas las misiones`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index 4a8fae03..4958dd8e 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -86,6 +86,7 @@ dict = { inventory_moaPets: `Moas`, inventory_kubrowPets: `Bêtes`, inventory_evolutionProgress: `Progrès de l'évolution Incarnon`, + inventory_Boosters: `[UNTRANSLATED] Boosters`, inventory_bulkAddSuits: `Ajouter les Warframes manquantes`, inventory_bulkAddWeapons: `Ajouter les armes manquantes`, inventory_bulkAddSpaceSuits: `Ajouter les Archwings manquants`, @@ -100,7 +101,7 @@ dict = { inventory_bulkRankUpSentinels: `Toutes les Sentinelles au rang max`, inventory_bulkRankUpSentinelWeapons: `Toutes les armes de Sentinelles au rang max`, inventory_bulkRankUpEvolutionProgress: `Toutes les évolutions Incarnon au rang max`, - inventory_Boosters: `[UNTRANSLATED] Boosters`, + inventory_maxPlexus: `[UNTRANSLATED] Max Rank Plexus`, quests_list: `Quêtes`, quests_completeAll: `Compléter toutes les quêtes`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index 612555b3..d7369199 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -86,6 +86,7 @@ dict = { inventory_moaPets: `МОА`, inventory_kubrowPets: `Звери`, inventory_evolutionProgress: `Прогресс эволюции Инкарнонов`, + inventory_Boosters: `[UNTRANSLATED] Boosters`, inventory_bulkAddSuits: `Добавить отсутствующие варфреймы`, inventory_bulkAddWeapons: `Добавить отсутствующее оружие`, inventory_bulkAddSpaceSuits: `Добавить отсутствующие арчвинги`, @@ -100,7 +101,7 @@ dict = { inventory_bulkRankUpSentinels: `Максимальный ранг всех стражей`, inventory_bulkRankUpSentinelWeapons: `Максимальный ранг всего оружия стражей`, inventory_bulkRankUpEvolutionProgress: `Максимальный ранг всех эволюций Инкарнонов`, - inventory_Boosters: `[UNTRANSLATED] Boosters`, + inventory_maxPlexus: `[UNTRANSLATED] Max Rank Plexus`, quests_list: `Квесты`, quests_completeAll: `Завершить все квесты`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 6b1d4b7f..1bd23d49 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -86,6 +86,7 @@ dict = { inventory_moaPets: `恐鸟`, inventory_kubrowPets: `动物同伴`, inventory_evolutionProgress: `灵化之源进度`, + inventory_Boosters: `加成器`, inventory_bulkAddSuits: `添加缺失战甲`, inventory_bulkAddWeapons: `添加缺失武器`, inventory_bulkAddSpaceSuits: `添加缺失Archwing`, @@ -100,7 +101,7 @@ dict = { inventory_bulkRankUpSentinels: `所有守护升满级`, inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`, inventory_bulkRankUpEvolutionProgress: `所有灵化之源最大等级`, - inventory_Boosters: `加成器`, + inventory_maxPlexus: `[UNTRANSLATED] Max Rank Plexus`, quests_list: `任务`, quests_completeAll: `完成所有任务`, From 4cb0f8b167a6e10404ac4ef9eed26a4d9b78dfbf Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 20 Jun 2025 14:00:55 -0700 Subject: [PATCH 056/153] feat(webui): initial websocket integration to be more responsive (#2221) For now just handles changes to config.json but in the future might keep the inventory tabs up-to-date with in-game actions. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2221 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- package-lock.json | 34 +++++++++++++++++++- package.json | 4 ++- src/services/configWatcherService.ts | 8 ++++- src/services/webService.ts | 46 ++++++++++++++++++++++++++++ static/webui/script.js | 26 ++++++++++++++-- 5 files changed, 112 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index ca339e31..1708e273 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@types/express": "^5", "@types/morgan": "^1.9.9", + "@types/ws": "^8.18.1", "crc-32": "^1.2.2", "express": "^5", "json-with-bigint": "^3.4.4", @@ -21,7 +22,8 @@ "warframe-public-export-plus": "^0.5.68", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", - "winston-daily-rotate-file": "^5.0.0" + "winston-daily-rotate-file": "^5.0.0", + "ws": "^8.18.2" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^8.28.0", @@ -472,6 +474,15 @@ "@types/webidl-conversions": "*" } }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.32.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.0.tgz", @@ -3931,6 +3942,27 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index b8d00e39..c5a98e44 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "dependencies": { "@types/express": "^5", "@types/morgan": "^1.9.9", + "@types/ws": "^8.18.1", "crc-32": "^1.2.2", "express": "^5", "json-with-bigint": "^3.4.4", @@ -28,7 +29,8 @@ "warframe-public-export-plus": "^0.5.68", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", - "winston-daily-rotate-file": "^5.0.0" + "winston-daily-rotate-file": "^5.0.0", + "ws": "^8.18.2" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^8.28.0", diff --git a/src/services/configWatcherService.ts b/src/services/configWatcherService.ts index 197a9567..b6f8e83c 100644 --- a/src/services/configWatcherService.ts +++ b/src/services/configWatcherService.ts @@ -2,7 +2,7 @@ import fs from "fs"; import fsPromises from "fs/promises"; import { logger } from "../utils/logger"; import { config, configPath, loadConfig } from "./configService"; -import { getWebPorts, startWebServer, stopWebServer } from "./webService"; +import { getWebPorts, sendWsBroadcast, startWebServer, stopWebServer } from "./webService"; let amnesia = false; fs.watchFile(configPath, () => { @@ -21,7 +21,13 @@ fs.watchFile(configPath, () => { const webPorts = getWebPorts(); if (config.httpPort != webPorts.http || config.httpsPort != webPorts.https) { logger.info(`Restarting web server to apply port changes.`); + + // Tell webui clients to reload with new port + sendWsBroadcast({ ports: { http: config.httpPort, https: config.httpsPort } }); + void stopWebServer().then(startWebServer); + } else { + sendWsBroadcast({ config_reloaded: true }); } } }); diff --git a/src/services/webService.ts b/src/services/webService.ts index 77fe01fe..d4d1227c 100644 --- a/src/services/webService.ts +++ b/src/services/webService.ts @@ -5,9 +5,12 @@ import { config } from "./configService"; import { logger } from "../utils/logger"; import { app } from "../app"; import { AddressInfo } from "node:net"; +import ws from "ws"; let httpServer: http.Server | undefined; let httpsServer: https.Server | undefined; +let wsServer: ws.Server | undefined; +let wssServer: ws.Server | undefined; const tlsOptions = { key: fs.readFileSync("static/certs/key.pem"), @@ -21,10 +24,17 @@ export const startWebServer = (): void => { // eslint-disable-next-line @typescript-eslint/no-misused-promises httpServer = http.createServer(app); httpServer.listen(httpPort, () => { + wsServer = new ws.Server({ server: httpServer }); + //wsServer.on("connection", wsOnConnect); + logger.info("HTTP server started on port " + httpPort); + // eslint-disable-next-line @typescript-eslint/no-misused-promises httpsServer = https.createServer(tlsOptions, app); httpsServer.listen(httpsPort, () => { + wssServer = new ws.Server({ server: httpsServer }); + //wssServer.on("connection", wsOnConnect); + logger.info("HTTPS server started on port " + httpsPort); logger.info( @@ -61,5 +71,41 @@ export const stopWebServer = async (): Promise => { }) ); } + if (wsServer) { + promises.push( + new Promise(resolve => { + wsServer!.close(() => { + resolve(); + }); + }) + ); + } + if (wssServer) { + promises.push( + new Promise(resolve => { + wssServer!.close(() => { + resolve(); + }); + }) + ); + } await Promise.all(promises); }; + +/*const wsOnConnect = (ws: ws, _req: http.IncomingMessage): void => { + ws.on("message", console.log); +};*/ + +export const sendWsBroadcast = (data: T): void => { + const msg = JSON.stringify(data); + if (wsServer) { + for (const client of wsServer.clients) { + client.send(msg); + } + } + if (wssServer) { + for (const client of wssServer.clients) { + client.send(msg); + } + } +}; diff --git a/static/webui/script.js b/static/webui/script.js index f4c43175..dfe4e096 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -1,3 +1,23 @@ +function openWebSocket() { + window.ws = new WebSocket("/custom/ws"); + window.ws.onmessage = e => { + const msg = JSON.parse(e.data); + if ("ports" in msg) { + location.port = location.protocol == "https:" ? msg.ports.https : msg.ports.http; + } + if ("config_reloaded" in msg) { + //window.is_admin = undefined; + if (single.getCurrentPath() == "/webui/cheats") { + single.loadRoute("/webui/cheats"); + } + } + }; + window.ws.onclose = function () { + setTimeout(openWebSocket, 3000); + }; +} +openWebSocket(); + let loginOrRegisterPending = false; window.registerSubmit = false; @@ -1822,6 +1842,7 @@ single.getRoute("/webui/cheats").on("beforeload", function () { clearInterval(interval); fetch("/custom/config?" + window.authz).then(async res => { if (res.status == 200) { + //window.is_admin = true; $("#server-settings-no-perms").addClass("d-none"); $("#server-settings").removeClass("d-none"); res.json().then(json => @@ -1830,9 +1851,7 @@ single.getRoute("/webui/cheats").on("beforeload", function () { var x = document.getElementById(`${key}`); if (x != null) { if (x.type == "checkbox") { - if (value === true) { - x.setAttribute("checked", "checked"); - } + x.checked = value; } else if (x.type == "number") { x.setAttribute("value", `${value}`); } @@ -1847,6 +1866,7 @@ single.getRoute("/webui/cheats").on("beforeload", function () { } }); } else { + //window.is_admin = false; $("#server-settings-no-perms").removeClass("d-none"); $("#server-settings").addClass("d-none"); } From f84cc54c97d7040d11c94e8bf84318d7473e2e77 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Fri, 20 Jun 2025 18:30:52 -0700 Subject: [PATCH 057/153] chore: use build & start process for development as well (#2222) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2222 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- docker-entrypoint.sh | 2 +- package-lock.json | 538 +----------------- package.json | 8 +- scripts/dev.js | 49 ++ scripts/update-translations.js | 1 + .../webuiFileChangeDetectedController.ts | 11 + src/helpers/commandLineArguments.ts | 23 + src/helpers/pathHelper.ts | 3 +- src/routes/custom.ts | 4 +- src/routes/webui.ts | 21 +- src/services/configService.ts | 3 +- src/services/serversideVendorsService.ts | 4 +- static/webui/script.js | 17 +- 13 files changed, 138 insertions(+), 546 deletions(-) create mode 100644 scripts/dev.js create mode 100644 src/controllers/custom/webuiFileChangeDetectedController.ts create mode 100644 src/helpers/commandLineArguments.ts diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 457173d9..703b7eaf 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -5,4 +5,4 @@ if [ ! -f conf/config.json ]; then jq --arg value "mongodb://openwfagent:spaceninjaserver@mongodb:27017/" '.mongodbUrl = $value' /app/config.json.example > /app/conf/config.json fi -exec npm run start conf/config.json +exec npm run start -- --configPath conf/config.json diff --git a/package-lock.json b/package-lock.json index 1708e273..dc6eb284 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,11 +29,11 @@ "@typescript-eslint/eslint-plugin": "^8.28.0", "@typescript-eslint/parser": "^8.28.0", "@typescript/native-preview": "^7.0.0-dev.20250523.1", + "chokidar": "^4.0.3", "eslint": "^8", "eslint-plugin-prettier": "^5.2.5", "prettier": "^3.5.3", - "ts-node-dev": "^2.0.0", - "tsconfig-paths": "^4.2.0" + "tree-kill": "^1.2.2" } }, "node_modules/@colors/colors": { @@ -45,19 +45,6 @@ "node": ">=0.1.90" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@dabh/diagnostics": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", @@ -218,34 +205,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, "node_modules/@mongodb-js/saslprep": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.2.2.tgz", @@ -306,34 +265,6 @@ "url": "https://opencollective.com/pkgr" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -439,20 +370,6 @@ "@types/send": "*" } }, - "node_modules/@types/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/strip-json-comments": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", - "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", @@ -860,19 +777,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -916,27 +820,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -975,19 +858,6 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/body-parser": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", @@ -1040,13 +910,6 @@ "node": ">=16.20.1" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1113,41 +976,19 @@ } }, "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 14.16.0" }, "funding": { "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" } }, "node_modules/color": { @@ -1272,13 +1113,6 @@ "node": ">=0.8" } }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1327,16 +1161,6 @@ "node": ">= 0.8" } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -1364,16 +1188,6 @@ "node": ">= 0.4" } }, - "node_modules/dynamic-dedupe": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", - "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "xtend": "^4.0.0" - } - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1897,21 +1711,6 @@ "dev": true, "license": "ISC" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -2184,35 +1983,6 @@ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", "license": "MIT" }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2321,19 +2091,6 @@ "integrity": "sha512-AhpYAAaZsPjU7smaBomDt1SOQshi9rEm6BlTbfVwsG1vNmeHKtEedJi62sHZzJTyKNtwzmNnrsd55kjwJ7054A==", "license": "MIT" }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/kareem": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", @@ -2413,13 +2170,6 @@ "node": ">= 12.0.0" } }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -2517,29 +2267,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -2722,16 +2449,6 @@ "node": ">= 0.6" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object-hash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", @@ -2894,13 +2611,6 @@ "node": ">=8" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, "node_modules/path-to-regexp": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", @@ -3059,37 +2769,17 @@ } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" + "node": ">= 14.18.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/resolve-from": { @@ -3371,27 +3061,6 @@ "is-arrayish": "^0.3.1" } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, "node_modules/sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", @@ -3441,16 +3110,6 @@ "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3477,19 +3136,6 @@ "node": ">=8" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/synckit": { "version": "0.11.4", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.4.tgz", @@ -3586,137 +3232,6 @@ "typescript": ">=4.8.4" } }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node-dev": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz", - "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^3.5.1", - "dynamic-dedupe": "^0.3.0", - "minimist": "^1.2.6", - "mkdirp": "^1.0.4", - "resolve": "^1.0.0", - "rimraf": "^2.6.1", - "source-map-support": "^0.5.12", - "tree-kill": "^1.2.2", - "ts-node": "^10.4.0", - "tsconfig": "^7.0.0" - }, - "bin": { - "ts-node-dev": "lib/bin.js", - "tsnd": "lib/bin.js" - }, - "engines": { - "node": ">=0.8.0" - }, - "peerDependencies": { - "node-notifier": "*", - "typescript": "*" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/ts-node-dev/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/tsconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", - "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/strip-bom": "^3.0.0", - "@types/strip-json-comments": "0.0.30", - "strip-bom": "^3.0.0", - "strip-json-comments": "^2.0.0" - } - }, - "node_modules/tsconfig-paths": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tsconfig/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -3808,13 +3323,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT" - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -3963,26 +3471,6 @@ } } }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index c5a98e44..b5ca4fbd 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,10 @@ "main": "index.ts", "scripts": { "start": "node --enable-source-maps --import ./build/src/pathman.js build/src/index.js", - "dev": "ts-node-dev --openssl-legacy-provider -r tsconfig-paths/register src/index.ts ", "build": "tsc --incremental --sourceMap && ncp static/webui build/static/webui", + "build:dev": "tsc --incremental --sourceMap", + "build-and-start": "npm run build && npm run start", + "dev": "node scripts/dev.js", "verify": "tsgo --noEmit", "lint": "eslint --ext .ts .", "lint:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .", @@ -36,10 +38,10 @@ "@typescript-eslint/eslint-plugin": "^8.28.0", "@typescript-eslint/parser": "^8.28.0", "@typescript/native-preview": "^7.0.0-dev.20250523.1", + "chokidar": "^4.0.3", "eslint": "^8", "eslint-plugin-prettier": "^5.2.5", "prettier": "^3.5.3", - "ts-node-dev": "^2.0.0", - "tsconfig-paths": "^4.2.0" + "tree-kill": "^1.2.2" } } diff --git a/scripts/dev.js b/scripts/dev.js new file mode 100644 index 00000000..1852a131 --- /dev/null +++ b/scripts/dev.js @@ -0,0 +1,49 @@ +/* eslint-disable */ +const { spawn } = require("child_process"); +const chokidar = require("chokidar"); +const kill = require("tree-kill"); + +let secret = ""; +for (let i = 0; i != 10; ++i) { + secret += String.fromCharCode(Math.floor(Math.random() * 26) + 0x41); +} + +const args = [...process.argv].splice(2); +args.push("--dev"); +args.push("--secret"); +args.push(secret); + +let buildproc, runproc; +function run(changedFile) { + if (changedFile) { + console.log(`Change to ${changedFile} detected`); + } + + if (buildproc) { + kill(buildproc.pid); + buildproc = undefined; + } + if (runproc) { + kill(runproc.pid); + runproc = undefined; + } + + buildproc = spawn("npm", ["run", "build:dev"], { stdio: "inherit", shell: true }); + buildproc.on("exit", code => { + buildproc = undefined; + if (code === 0) { + runproc = spawn("npm", ["run", "start", "--", ...args], { stdio: "inherit", shell: true }); + runproc.on("exit", () => { + runproc = undefined; + }); + } + }); +} + +run(); +chokidar.watch("src").on("change", run); +chokidar.watch("static/fixed_responses").on("change", run); + +chokidar.watch("static/webui").on("change", () => { + fetch("http://localhost/custom/webuiFileChangeDetected?secret=" + secret); +}); diff --git a/scripts/update-translations.js b/scripts/update-translations.js index 5351afaa..0f1f56ec 100644 --- a/scripts/update-translations.js +++ b/scripts/update-translations.js @@ -1,6 +1,7 @@ // Based on https://onlyg.it/OpenWF/Translations/src/branch/main/update.php // Converted via ChatGPT-4o +/* eslint-disable */ const fs = require("fs"); function extractStrings(content) { diff --git a/src/controllers/custom/webuiFileChangeDetectedController.ts b/src/controllers/custom/webuiFileChangeDetectedController.ts new file mode 100644 index 00000000..3ec52a48 --- /dev/null +++ b/src/controllers/custom/webuiFileChangeDetectedController.ts @@ -0,0 +1,11 @@ +import { args } from "@/src/helpers/commandLineArguments"; +import { config } from "@/src/services/configService"; +import { sendWsBroadcast } from "@/src/services/webService"; +import { RequestHandler } from "express"; + +export const webuiFileChangeDetectedController: RequestHandler = (req, res) => { + if (args.dev && args.secret && req.query.secret == args.secret) { + sendWsBroadcast({ ports: { http: config.httpPort, https: config.httpsPort } }); + } + res.end(); +}; diff --git a/src/helpers/commandLineArguments.ts b/src/helpers/commandLineArguments.ts new file mode 100644 index 00000000..09af105d --- /dev/null +++ b/src/helpers/commandLineArguments.ts @@ -0,0 +1,23 @@ +interface IArguments { + configPath?: string; + dev?: boolean; + secret?: string; +} + +export const args: IArguments = {}; + +for (let i = 2; i < process.argv.length; ) { + switch (process.argv[i++]) { + case "--configPath": + args.configPath = process.argv[i++]; + break; + + case "--dev": + args.dev = true; + break; + + case "--secret": + args.secret = process.argv[i++]; + break; + } +} diff --git a/src/helpers/pathHelper.ts b/src/helpers/pathHelper.ts index 94a0f242..37e87dff 100644 --- a/src/helpers/pathHelper.ts +++ b/src/helpers/pathHelper.ts @@ -1,5 +1,4 @@ import path from "path"; export const rootDir = path.join(__dirname, "../.."); -export const isDev = path.basename(rootDir) != "build"; -export const repoDir = isDev ? rootDir : path.join(rootDir, ".."); +export const repoDir = path.basename(rootDir) != "build" ? rootDir : path.join(rootDir, ".."); diff --git a/src/routes/custom.ts b/src/routes/custom.ts index 7d8c7c82..75eb80a4 100644 --- a/src/routes/custom.ts +++ b/src/routes/custom.ts @@ -11,6 +11,7 @@ import { renameAccountController } from "@/src/controllers/custom/renameAccountC import { ircDroppedController } from "@/src/controllers/custom/ircDroppedController"; import { unlockAllIntrinsicsController } from "@/src/controllers/custom/unlockAllIntrinsicsController"; import { addMissingMaxRankModsController } from "@/src/controllers/custom/addMissingMaxRankModsController"; +import { webuiFileChangeDetectedController } from "@/src/controllers/custom/webuiFileChangeDetectedController"; import { createAccountController } from "@/src/controllers/custom/createAccountController"; import { createMessageController } from "@/src/controllers/custom/createMessageController"; @@ -20,10 +21,10 @@ import { addXpController } from "@/src/controllers/custom/addXpController"; import { importController } from "@/src/controllers/custom/importController"; import { manageQuestsController } from "@/src/controllers/custom/manageQuestsController"; import { setEvolutionProgressController } from "@/src/controllers/custom/setEvolutionProgressController"; +import { setBoosterController } from "@/src/controllers/custom/setBoosterController"; import { getConfigDataController } from "@/src/controllers/custom/getConfigDataController"; import { updateConfigDataController } from "@/src/controllers/custom/updateConfigDataController"; -import { setBoosterController } from "../controllers/custom/setBoosterController"; const customRouter = express.Router(); @@ -38,6 +39,7 @@ customRouter.get("/renameAccount", renameAccountController); customRouter.get("/ircDropped", ircDroppedController); customRouter.get("/unlockAllIntrinsics", unlockAllIntrinsicsController); customRouter.get("/addMissingMaxRankMods", addMissingMaxRankModsController); +customRouter.get("/webuiFileChangeDetected", webuiFileChangeDetectedController); customRouter.post("/createAccount", createAccountController); customRouter.post("/createMessage", createMessageController); diff --git a/src/routes/webui.ts b/src/routes/webui.ts index 02224903..535d68b1 100644 --- a/src/routes/webui.ts +++ b/src/routes/webui.ts @@ -1,6 +1,9 @@ import express from "express"; import path from "path"; import { repoDir, rootDir } from "@/src/helpers/pathHelper"; +import { args } from "@/src/helpers/commandLineArguments"; + +const baseDir = args.dev ? repoDir : rootDir; const webuiRouter = express.Router(); @@ -19,29 +22,29 @@ webuiRouter.use("/webui", (req, res, next) => { // Serve virtual routes webuiRouter.get("/webui/inventory", (_req, res) => { - res.sendFile(path.join(rootDir, "static/webui/index.html")); + res.sendFile(path.join(baseDir, "static/webui/index.html")); }); webuiRouter.get(/webui\/powersuit\/(.+)/, (_req, res) => { - res.sendFile(path.join(rootDir, "static/webui/index.html")); + res.sendFile(path.join(baseDir, "static/webui/index.html")); }); webuiRouter.get("/webui/mods", (_req, res) => { - res.sendFile(path.join(rootDir, "static/webui/index.html")); + res.sendFile(path.join(baseDir, "static/webui/index.html")); }); webuiRouter.get("/webui/settings", (_req, res) => { - res.sendFile(path.join(rootDir, "static/webui/index.html")); + res.sendFile(path.join(baseDir, "static/webui/index.html")); }); webuiRouter.get("/webui/quests", (_req, res) => { - res.sendFile(path.join(rootDir, "static/webui/index.html")); + res.sendFile(path.join(baseDir, "static/webui/index.html")); }); webuiRouter.get("/webui/cheats", (_req, res) => { - res.sendFile(path.join(rootDir, "static/webui/index.html")); + res.sendFile(path.join(baseDir, "static/webui/index.html")); }); webuiRouter.get("/webui/import", (_req, res) => { - res.sendFile(path.join(rootDir, "static/webui/index.html")); + res.sendFile(path.join(baseDir, "static/webui/index.html")); }); // Serve static files -webuiRouter.use("/webui", express.static(path.join(rootDir, "static/webui"))); +webuiRouter.use("/webui", express.static(path.join(baseDir, "static/webui"))); // Serve favicon webuiRouter.get("/favicon.ico", (_req, res) => { @@ -58,7 +61,7 @@ webuiRouter.get("/webui/riven-tool/RivenParser.js", (_req, res) => { // Serve translations webuiRouter.get("/translations/:file", (req, res) => { - res.sendFile(path.join(rootDir, `static/webui/translations/${req.params.file}`)); + res.sendFile(path.join(baseDir, `static/webui/translations/${req.params.file}`)); }); export { webuiRouter }; diff --git a/src/services/configService.ts b/src/services/configService.ts index b4566096..6ef2e96f 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -1,6 +1,7 @@ import fs from "fs"; import path from "path"; import { repoDir } from "@/src/helpers/pathHelper"; +import { args } from "@/src/helpers/commandLineArguments"; export interface IConfig { mongodbUrl: string; @@ -79,7 +80,7 @@ export interface IConfig { }; } -export const configPath = path.join(repoDir, process.argv[2] ?? "config.json"); +export const configPath = path.join(repoDir, args.configPath ?? "config.json"); export const config: IConfig = { mongodbUrl: "mongodb://127.0.0.1:27017/openWF", diff --git a/src/services/serversideVendorsService.ts b/src/services/serversideVendorsService.ts index 774180d6..0e4b3856 100644 --- a/src/services/serversideVendorsService.ts +++ b/src/services/serversideVendorsService.ts @@ -1,5 +1,5 @@ import { unixTimesInMs } from "@/src/constants/timeConstants"; -import { isDev } from "@/src/helpers/pathHelper"; +import { args } from "@/src/helpers/commandLineArguments"; import { catBreadHash } from "@/src/helpers/stringHelpers"; import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { mixSeeds, SRng } from "@/src/services/rngService"; @@ -421,7 +421,7 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani return cacheEntry; }; -if (isDev) { +if (args.dev) { if ( getCycleDuration(ExportVendors["/Lotus/Types/Game/VendorManifests/Hubs/TeshinHardModeVendorManifest"]) != unixTimesInMs.week diff --git a/static/webui/script.js b/static/webui/script.js index dfe4e096..6f2731c3 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -1,3 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-floating-promises */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable no-undef */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ + function openWebSocket() { window.ws = new WebSocket("/custom/ws"); window.ws.onmessage = e => { @@ -64,6 +74,7 @@ function doLoginRequest(succ_cb, fail_cb) { time: parseInt(new Date() / 1000), s: "W0RFXVN0ZXZlIGxpa2VzIGJpZyBidXR0cw==", // signature of some kind lang: "en", + // eslint-disable-next-line no-loss-of-precision date: 1501230947855458660, // ??? ClientType: registerSubmit ? "webui-register" : "webui", PS: "W0RFXVN0ZXZlIGxpa2VzIGJpZyBidXR0cw==" // anti-cheat data @@ -1697,7 +1708,9 @@ function doAcquireRiven() { if (typeof fingerprint !== "object") { fingerprint = JSON.parse(fingerprint); } - } catch (e) {} + } catch (e) { + /* empty */ + } if ( typeof fingerprint !== "object" || !("compat" in fingerprint) || @@ -1950,7 +1963,7 @@ function doAddAllMods() { const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1"); req.done(data => { for (const modOwned of data.RawUpgrades) { - if (modOwned.ItemCount ?? 1 > 0) { + if ((modOwned.ItemCount ?? 1) > 0) { modsAll.delete(modOwned.ItemType); } } From 5d5d0ee5607347d7ec1d4dabb67fc0db4b624186 Mon Sep 17 00:00:00 2001 From: CrazyZhang Date: Sat, 21 Jun 2025 07:25:48 -0700 Subject: [PATCH 058/153] chore(webui): update Chinese translation (#2227) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2227 Co-authored-by: CrazyZhang Co-committed-by: CrazyZhang --- static/webui/translations/zh.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 1bd23d49..0d4df1ce 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -101,7 +101,7 @@ dict = { inventory_bulkRankUpSentinels: `所有守护升满级`, inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`, inventory_bulkRankUpEvolutionProgress: `所有灵化之源最大等级`, - inventory_maxPlexus: `[UNTRANSLATED] Max Rank Plexus`, + inventory_maxPlexus: `最大深控等级`, quests_list: `任务`, quests_completeAll: `完成所有任务`, @@ -136,10 +136,10 @@ dict = { cheats_infiniteRegalAya: `无限御品阿耶`, cheats_infiniteHelminthMaterials: `无限Helminth材料`, cheats_claimingBlueprintRefundsIngredients: `取消蓝图制造时返还材料`, - cheats_dontSubtractPurchaseCreditCost: `[UNTRANSLATED] Don't Subtract Purchase Credit Cost`, - cheats_dontSubtractPurchasePlatinumCost: `[UNTRANSLATED] Don't Subtract Purchase Platinum Cost`, - cheats_dontSubtractPurchaseItemCost: `[UNTRANSLATED] Don't Subtract Purchase Item Cost`, - cheats_dontSubtractPurchaseStandingCost: `[UNTRANSLATED] Don't Subtract Purchase Standing Cost`, + cheats_dontSubtractPurchaseCreditCost: `不减少现金花费`, + cheats_dontSubtractPurchasePlatinumCost: `不减少白金花费`, + cheats_dontSubtractPurchaseItemCost: `不减少物品花费`, + cheats_dontSubtractPurchaseStandingCost: `不减少声望花费`, cheats_dontSubtractVoidTraces: `虚空光体无消耗`, cheats_dontSubtractConsumables: `消耗物品使用时无损耗`, cheats_unlockAllShipFeatures: `解锁所有飞船功能`, @@ -160,7 +160,7 @@ dict = { cheats_noDeathMarks: `无死亡标记(不会被 Stalker/Grustrag 三霸/Zanuka 猎人等标记)`, cheats_noKimCooldowns: `无 KIM 冷却时间`, cheats_syndicateMissionsRepeatable: `集团任务可重复`, - cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`, + cheats_unlockAllProfitTakerStages: `解锁利润收割者圆蛛所有阶段`, cheats_instantFinishRivenChallenge: `立即完成裂罅挑战`, cheats_instantResourceExtractorDrones: `即时资源采集无人机`, cheats_noResourceExtractorDronesDamage: `资源提取器不会损毁`, @@ -171,10 +171,10 @@ dict = { cheats_noDojoResearchCosts: `无视道场研究消耗`, cheats_noDojoResearchTime: `无视道场研究时间`, cheats_fastClanAscension: `快速升级氏族`, - cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`, + cheats_missionsCanGiveAllRelics: `任务可获取所有遗物`, cheats_spoofMasteryRank: `伪造精通段位(-1为禁用)`, cheats_nightwaveStandingMultiplier: `午夜电波声望倍率`, - cheats_save: `[UNTRANSLATED] Save`, + cheats_save: `保存`, cheats_account: `账户`, cheats_unlockAllFocusSchools: `解锁所有专精学派`, cheats_helminthUnlockAll: `完全升级Helminth`, @@ -192,7 +192,7 @@ dict = { upgrade_WarframeAbilityDuration: `+|VAL|% 技能持续时间`, upgrade_WarframeAbilityStrength: `+|VAL|% 技能强度`, upgrade_WarframeArmourMax: `+|VAL| 护甲`, - upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on kill with Blast Damage`, + upgrade_WarframeBlastProc: `+|VAL| 护盾在击杀时附带爆炸伤害`, upgrade_WarframeCastingSpeed: `+|VAL|% 施放速度`, upgrade_WarframeCorrosiveDamageBoost: `对受腐蚀状态影响的敌人 +|VAL|% 技能伤害`, upgrade_WarframeCorrosiveStack: `腐蚀状态最大堆叠数 +|VAL|`, From 93ef9a534847a8bb4553519cc95c32b9c08033bf Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sat, 21 Jun 2025 07:26:11 -0700 Subject: [PATCH 059/153] feat: autogenerate all vendors (#2225) I went through a few of the still-hardcoded vendors and they seemed to "just work" with autogeneration so I think it's time to say: Closes #1225 and disable the `noVendorPurchaseLimits` cheat by default. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2225 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- config.json.example | 2 +- src/services/serversideVendorsService.ts | 42 --- ...EntratiFragmentVendorProductsManifest.json | 300 ---------------- ...sHivemindCommisionsManifestFishmonger.json | 241 ------------- ...osHivemindCommisionsManifestPetVendor.json | 287 ---------------- ...sHivemindCommisionsManifestProspector.json | 312 ----------------- ...HivemindCommisionsManifestTokenVendor.json | 223 ------------ ...HivemindCommisionsManifestWeaponsmith.json | 254 -------------- .../DeimosHivemindTokenVendorManifest.json | 286 ---------------- .../DeimosPetVendorManifest.json | 136 -------- .../DuviriAcrithisVendorManifest.json | 321 ------------------ .../EntratiLabsEntratiLabVendorManifest.json | 245 ------------- ...ratiLabsEntratiLabsCommisionsManifest.json | 97 ------ .../Nova1999ConquestShopManifest.json | 188 ---------- .../OstronPetVendorManifest.json | 94 ----- ...sDebtTokenVendorRepossessionsManifest.json | 126 ------- 16 files changed, 1 insertion(+), 3153 deletions(-) delete mode 100644 static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json delete mode 100644 static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestFishmonger.json delete mode 100644 static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestPetVendor.json delete mode 100644 static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestProspector.json delete mode 100644 static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestTokenVendor.json delete mode 100644 static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestWeaponsmith.json delete mode 100644 static/fixed_responses/getVendorInfo/DeimosHivemindTokenVendorManifest.json delete mode 100644 static/fixed_responses/getVendorInfo/DeimosPetVendorManifest.json delete mode 100644 static/fixed_responses/getVendorInfo/DuviriAcrithisVendorManifest.json delete mode 100644 static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabVendorManifest.json delete mode 100644 static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabsCommisionsManifest.json delete mode 100644 static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json delete mode 100644 static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json delete mode 100644 static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json diff --git a/config.json.example b/config.json.example index a92e3f8d..9287091c 100644 --- a/config.json.example +++ b/config.json.example @@ -39,7 +39,7 @@ "noDailyFocusLimit": false, "noArgonCrystalDecay": false, "noMasteryRankUpCooldown": false, - "noVendorPurchaseLimits": true, + "noVendorPurchaseLimits": false, "noDeathMarks": false, "noKimCooldowns": false, "syndicateMissionsRepeatable": false, diff --git a/src/services/serversideVendorsService.ts b/src/services/serversideVendorsService.ts index 0e4b3856..c40c3cc6 100644 --- a/src/services/serversideVendorsService.ts +++ b/src/services/serversideVendorsService.ts @@ -7,38 +7,6 @@ import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorT import { logger } from "@/src/utils/logger"; import { ExportVendors, IRange, IVendor, IVendorOffer } from "warframe-public-export-plus"; -import DeimosEntratiFragmentVendorProductsManifest from "@/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.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"; -import DeimosHivemindCommisionsManifestTokenVendor from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestTokenVendor.json"; -import DeimosHivemindCommisionsManifestWeaponsmith from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestWeaponsmith.json"; -import DeimosHivemindTokenVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosHivemindTokenVendorManifest.json"; -import DeimosPetVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosPetVendorManifest.json"; -import DuviriAcrithisVendorManifest from "@/static/fixed_responses/getVendorInfo/DuviriAcrithisVendorManifest.json"; -import EntratiLabsEntratiLabsCommisionsManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabsCommisionsManifest.json"; -import EntratiLabsEntratiLabVendorManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabVendorManifest.json"; -import Nova1999ConquestShopManifest from "@/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json"; -import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json"; -import SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json"; - -const rawVendorManifests: IVendorManifest[] = [ - DeimosEntratiFragmentVendorProductsManifest, - DeimosHivemindCommisionsManifestFishmonger, - DeimosHivemindCommisionsManifestPetVendor, - DeimosHivemindCommisionsManifestProspector, - DeimosHivemindCommisionsManifestTokenVendor, - DeimosHivemindCommisionsManifestWeaponsmith, - DeimosHivemindTokenVendorManifest, - DeimosPetVendorManifest, - DuviriAcrithisVendorManifest, - EntratiLabsEntratiLabsCommisionsManifest, - EntratiLabsEntratiLabVendorManifest, - Nova1999ConquestShopManifest, - OstronPetVendorManifest, - SolarisDebtTokenVendorRepossessionsManifest -]; - interface IGeneratableVendorInfo extends Omit { cycleOffset?: number; cycleDuration: number; @@ -92,11 +60,6 @@ const getCycleDuration = (manifest: IVendor): number => { }; export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | undefined => { - for (const vendorManifest of rawVendorManifests) { - if (vendorManifest.VendorInfo.TypeName == typeName) { - return vendorManifest; - } - } for (const vendorInfo of generatableVendors) { if (vendorInfo.TypeName == typeName) { return generateVendorManifest(vendorInfo); @@ -115,11 +78,6 @@ export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | }; export const getVendorManifestByOid = (oid: string): IVendorManifest | undefined => { - for (const vendorManifest of rawVendorManifests) { - if (vendorManifest.VendorInfo._id.$oid == oid) { - return vendorManifest; - } - } for (const vendorInfo of generatableVendors) { if (vendorInfo._id.$oid == oid) { return generateVendorManifest(vendorInfo); diff --git a/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json b/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json deleted file mode 100644 index 07f94813..00000000 --- a/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json +++ /dev/null @@ -1,300 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "5f456e01c96976e97d6b802e" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Deimos/EntratiFragmentVendorProductsManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Deimos/SeriglassShard", - "ItemPrices": [ - { - "ItemCount": 20, - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentRareA", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b10ba592c4c95e8390" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileDeimosBouncy", - "ItemPrices": [ - { - "ItemCount": 50, - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentRareA", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b10ba592c4c95e8391" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileDeimosBreakthrough", - "ItemPrices": [ - { - "ItemCount": 50, - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentRareA", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b10ba592c4c95e8392" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileDeimosCatacombs", - "ItemPrices": [ - { - "ItemCount": 50, - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentRareA", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b10ba592c4c95e8393" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileDeimosDownfall", - "ItemPrices": [ - { - "ItemCount": 50, - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentRareA", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b10ba592c4c95e8394" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileDeimosObsession", - "ItemPrices": [ - { - "ItemCount": 50, - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentRareA", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b10ba592c4c95e8395" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileDeimosTunnels", - "ItemPrices": [ - { - "ItemCount": 50, - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentRareA", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b10ba592c4c95e8396" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Deimos/FatherTokenShipDeco", - "ItemPrices": [ - { - "ItemCount": 10, - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentUncommonA", - "ProductCategory": "MiscItems" - }, - { - "ItemCount": 5, - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentRareA", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b10ba592c4c95e83f1" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Deimos/LisetPropEntratiLamp", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentCommonB", - "ItemCount": 12, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b10ba592c4c95e83f2" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Deimos/LisetPropInfestedCrate", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentCommonA", - "ItemCount": 11, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b10ba592c4c95e83f3" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Deimos/LisetPropInfestedCystC", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentRareA", - "ItemCount": 10, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_2", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b10ba592c4c95e83f4" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/RequiemRisGlyph", - "ItemPrices": [ - { - "ItemCount": 15, - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentRareA", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b10ba592c4c95e83f5" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Deimos/MotherTokenShipDeco", - "ItemPrices": [ - { - "ItemCount": 10, - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentUncommonB", - "ProductCategory": "MiscItems" - }, - { - "ItemCount": 5, - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentRareA", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b10ba592c4c95e83f6" - } - } - ], - "PropertyTextHash": "DB953EE163A65B3BCC0552902321D791", - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - } - } -} diff --git a/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestFishmonger.json b/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestFishmonger.json deleted file mode 100644 index fd6a561b..00000000 --- a/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestFishmonger.json +++ /dev/null @@ -1,241 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "5f456e01c96976e97d6b8009" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Deimos/HivemindCommisionsManifestFishmonger", - "ItemManifest": [ - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Daughter/DaughterTaskC", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishFPartItem", - "ItemCount": 6, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishCPartItem", - "ItemCount": 16, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_2", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 63978959, - "Id": { - "$oid": "66fd60b10ba592c4c95e82cc" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Daughter/DaughterTaskB", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishBPartItem", - "ItemCount": 5, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishDPartItem", - "ItemCount": 4, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 3808064409, - "Id": { - "$oid": "66fd60b10ba592c4c95e82cd" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Daughter/DaughterTaskA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishEPartItem", - "ItemCount": 2, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishCPartItem", - "ItemCount": 2, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 3849710569, - "Id": { - "$oid": "66fd60b10ba592c4c95e82d0" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Daughter/DaughterTaskA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosGenericInfestedFishPartItem", - "ItemCount": 5, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishCPartItem", - "ItemCount": 4, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 1687111317, - "Id": { - "$oid": "66fd60b10ba592c4c95e82d1" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Daughter/DaughterTaskA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishAPartItem", - "ItemCount": 4, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishBPartItem", - "ItemCount": 4, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 2267414276, - "Id": { - "$oid": "66fd60b10ba592c4c95e82d2" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Daughter/DaughterTaskA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosGenericInfestedFishPartItem", - "ItemCount": 6, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosGenericSharedFishPartItem", - "ItemCount": 6, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 1497494256, - "Id": { - "$oid": "66fd60b10ba592c4c95e82d3" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Daughter/DaughterTaskA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosGenericInfestedFishPartItem", - "ItemCount": 5, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishCPartItem", - "ItemCount": 2, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 2883527039, - "Id": { - "$oid": "66fd60b10ba592c4c95e82d4" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Daughter/DaughterTaskC", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishGPartItem", - "ItemCount": 10, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishCPartItem", - "ItemCount": 12, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_2", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 4116691539, - "Id": { - "$oid": "66fd60b10ba592c4c95e82d5" - } - } - ], - "PropertyTextHash": "54B6992C6314367F8EEA74B7F1A1C352", - "RandomSeedType": "VRST_FLAVOUR_TEXT", - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - } - } -} diff --git a/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestPetVendor.json b/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestPetVendor.json deleted file mode 100644 index 44966e70..00000000 --- a/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestPetVendor.json +++ /dev/null @@ -1,287 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "5f456e03c96976e97d6b80a3" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Deimos/HivemindCommisionsManifestPetVendor", - "ItemManifest": [ - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Son/SonTaskB", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMaggotRare", - "ItemCount": 2, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMaggotCommon", - "ItemCount": 4, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 2707699975, - "Id": { - "$oid": "66fd60b10ba592c4c95e8897" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Son/SonTaskA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedPredatorCommon", - "ItemCount": 2, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMaggotCommon", - "ItemCount": 4, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 3610714639, - "Id": { - "$oid": "66fd60b10ba592c4c95e8898" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Son/SonTaskB", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedPredatorCommon", - "ItemCount": 2, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMaggotRare", - "ItemCount": 1, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 1782149988, - "Id": { - "$oid": "66fd60b10ba592c4c95e8899" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Son/SonTaskC", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMergooCommon", - "ItemCount": 3, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_2", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 2149416825, - "Id": { - "$oid": "66fd60b10ba592c4c95e889a" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Son/SonTaskB", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedCritterRare", - "ItemCount": 2, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedKdriveUncommon", - "ItemCount": 2, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 890863265, - "Id": { - "$oid": "66fd60b10ba592c4c95e889b" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Son/SonTaskA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedCritterCommon", - "ItemCount": 4, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedPredatorCommon", - "ItemCount": 4, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 2507606934, - "Id": { - "$oid": "66fd60b10ba592c4c95e889c" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Son/SonTaskC", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedPredatorRare", - "ItemCount": 5, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_2", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 1037784729, - "Id": { - "$oid": "66fd60b10ba592c4c95e889e" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Son/SonTaskA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMaggotCommon", - "ItemCount": 4, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedCritterCommon", - "ItemCount": 4, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 2048707501, - "Id": { - "$oid": "66fd60b10ba592c4c95e889f" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Son/SonTaskA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedKdriveRare", - "ItemCount": 2, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedPredatorCommon", - "ItemCount": 2, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 4038149313, - "Id": { - "$oid": "66fd60b10ba592c4c95e88a0" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Son/SonTaskD", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMergooCommon", - "ItemCount": 3, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedPredatorRare", - "ItemCount": 3, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_3", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 2155290001, - "Id": { - "$oid": "66fd60b10ba592c4c95e88a1" - } - } - ], - "PropertyTextHash": "61E66B4E9E5A121DD06A476AE2A81B24", - "RandomSeedType": "VRST_FLAVOUR_TEXT", - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - } - } -} diff --git a/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestProspector.json b/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestProspector.json deleted file mode 100644 index 7b1d1fad..00000000 --- a/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestProspector.json +++ /dev/null @@ -1,312 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "5f456e01c96976e97d6b7ff1" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Deimos/HivemindCommisionsManifestProspector", - "ItemManifest": [ - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Otak/OtakTaskA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonOreAItem", - "ItemCount": 10, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonGemBItem", - "ItemCount": 8, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 1370972414, - "Id": { - "$oid": "66fd60b20ba592c4c95e8ef8" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Otak/OtakTaskC", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonOreBItem", - "ItemCount": 20, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosUncommonGemAItem", - "ItemCount": 8, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosUncommonOreAItem", - "ItemCount": 20, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_2", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 2361790143, - "Id": { - "$oid": "66fd60b20ba592c4c95e8ef9" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Otak/OtakTaskA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonGemAItem", - "ItemCount": 6, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonOreAItem", - "ItemCount": 10, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 472210739, - "Id": { - "$oid": "66fd60b20ba592c4c95e8efb" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Otak/OtakTaskB", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonOreBItem", - "ItemCount": 12, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonOreAItem", - "ItemCount": 15, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 3072462886, - "Id": { - "$oid": "66fd60b20ba592c4c95e8efd" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Otak/OtakTaskA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonGemAItem", - "ItemCount": 8, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonOreAItem", - "ItemCount": 10, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 767765909, - "Id": { - "$oid": "66fd60b20ba592c4c95e8efe" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Otak/OtakTaskD", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosEidolonGemAItem", - "ItemCount": 5, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosEidolonGemBItem", - "ItemCount": 2, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosRareOreAItem", - "ItemCount": 22, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_3", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 512512880, - "Id": { - "$oid": "66fd60b20ba592c4c95e8eff" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Otak/OtakTaskA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonOreAItem", - "ItemCount": 10, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonGemAItem", - "ItemCount": 6, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 2037734419, - "Id": { - "$oid": "66fd60b20ba592c4c95e8f00" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Otak/OtakTaskB", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosUncommonOreAItem", - "ItemCount": 13, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonOreAItem", - "ItemCount": 8, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 1433662587, - "Id": { - "$oid": "66fd60b20ba592c4c95e8f01" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Otak/OtakTaskB", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonOreAItem", - "ItemCount": 12, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonGemAItem", - "ItemCount": 8, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 1618492734, - "Id": { - "$oid": "66fd60b20ba592c4c95e8f02" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Otak/OtakTaskD", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosUncommonGemAItem", - "ItemCount": 7, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonGemBItem", - "ItemCount": 10, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosRareOreAItem", - "ItemCount": 12, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_3", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 4032699594, - "Id": { - "$oid": "66fd60b20ba592c4c95e8f03" - } - } - ], - "PropertyTextHash": "0AC3C284471037011B36EC51238D13A9", - "RandomSeedType": "VRST_FLAVOUR_TEXT", - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - } - } -} diff --git a/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestTokenVendor.json b/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestTokenVendor.json deleted file mode 100644 index dcc45dc9..00000000 --- a/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestTokenVendor.json +++ /dev/null @@ -1,223 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "5f456e03c96976e97d6b80d2" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Deimos/HivemindCommisionsManifestTokenVendor", - "ItemManifest": [ - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Grandmother/GrandmotherTaskD", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentCommonC", - "ItemCount": 10, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentCommonA", - "ItemCount": 10, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentUncommonB", - "ItemCount": 10, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentUncommonA", - "ItemCount": 10, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 1415858946, - "Id": { - "$oid": "670a47b1872b2325705e746c" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Grandmother/GrandmotherTaskD", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentCommonB", - "ItemCount": 2, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentUncommonA", - "ItemCount": 10, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentCommonA", - "ItemCount": 10, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentUncommonB", - "ItemCount": 10, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 3178511462, - "Id": { - "$oid": "670a47b1872b2325705e746e" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Grandmother/GrandmotherTaskB", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentUncommonA", - "ItemCount": 10, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentCommonC", - "ItemCount": 10, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 3313207881, - "Id": { - "$oid": "670a47b1872b2325705e7471" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Grandmother/GrandmotherTaskA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentCommonB", - "ItemCount": 2, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 2241288767, - "Id": { - "$oid": "670a47b1872b2325705e7472" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Grandmother/GrandmotherTaskA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentUncommonB", - "ItemCount": 10, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 3395082536, - "Id": { - "$oid": "670a47b1872b2325705e7473" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Grandmother/GrandmotherTaskB", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentCommonC", - "ItemCount": 10, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentUncommonB", - "ItemCount": 10, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 493457277, - "Id": { - "$oid": "670a47b1872b2325705e7474" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Grandmother/GrandmotherTaskB", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentUncommonB", - "ItemCount": 10, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentCommonB", - "ItemCount": 2, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 4225814786, - "Id": { - "$oid": "670a47b1872b2325705e7475" - } - } - ], - "PropertyTextHash": "58884EC7ECE7D22AD4BD9E9B436C37A8", - "RandomSeedType": "VRST_FLAVOUR_TEXT", - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - } - } -} diff --git a/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestWeaponsmith.json b/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestWeaponsmith.json deleted file mode 100644 index 9a3c5805..00000000 --- a/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestWeaponsmith.json +++ /dev/null @@ -1,254 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "5f456e02c96976e97d6b8049" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Deimos/HivemindCommisionsManifestWeaponsmith", - "ItemManifest": [ - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Father/FatherTaskA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/InfGorgaricusSeedItem", - "ItemCount": 16, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 2438288725, - "Id": { - "$oid": "66fd60b00ba592c4c95e7caf" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Father/FatherTaskC", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Game/FishBait/Infested/OrokinFishBaitA", - "ItemCount": 6, - "ProductCategory": "Consumables" - }, - { - "ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/InfMapricoFruitItem", - "ItemCount": 21, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Game/FishBait/Infested/InfestedFishBaitA", - "ItemCount": 6, - "ProductCategory": "Consumables" - } - ], - "Bin": "BIN_2", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 2431016296, - "Id": { - "$oid": "66fd60b00ba592c4c95e7cb2" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Father/FatherTaskA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/InfMapricoFruitItem", - "ItemCount": 16, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 18484856, - "Id": { - "$oid": "66fd60b00ba592c4c95e7cb3" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Father/FatherTaskA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/InfMapricoFruitItem", - "ItemCount": 14, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 2278976516, - "Id": { - "$oid": "66fd60b00ba592c4c95e7cb4" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Father/FatherTaskC", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Game/FishBait/Infested/OrokinFishBaitA", - "ItemCount": 7, - "ProductCategory": "Consumables" - }, - { - "ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/OrbStoneItem", - "ItemCount": 25, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Game/FishBait/Infested/InfestedFishBaitA", - "ItemCount": 6, - "ProductCategory": "Consumables" - } - ], - "Bin": "BIN_2", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 3150323898, - "Id": { - "$oid": "66fd60b00ba592c4c95e7cb5" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Father/FatherTaskA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/InfMapricoFruitItem", - "ItemCount": 8, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 3971758486, - "Id": { - "$oid": "66fd60b00ba592c4c95e7cb6" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Father/FatherTaskB", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/InfGorgaricusSeedItem", - "ItemCount": 17, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/OrbStoneItem", - "ItemCount": 18, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 2512835718, - "Id": { - "$oid": "66fd60b00ba592c4c95e7cb7" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Father/FatherTaskA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/OrbStoneItem", - "ItemCount": 8, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 825411410, - "Id": { - "$oid": "66fd60b00ba592c4c95e7cb8" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Father/FatherTaskB", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/InfGorgaricusSeedItem", - "ItemCount": 22, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/OrbStoneItem", - "ItemCount": 12, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 2383349671, - "Id": { - "$oid": "66fd60b00ba592c4c95e7cb9" - } - } - ], - "PropertyTextHash": "CE9413585756FA39B793A9814E74E49F", - "RandomSeedType": "VRST_FLAVOUR_TEXT", - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - } - } -} diff --git a/static/fixed_responses/getVendorInfo/DeimosHivemindTokenVendorManifest.json b/static/fixed_responses/getVendorInfo/DeimosHivemindTokenVendorManifest.json deleted file mode 100644 index 1d165fc7..00000000 --- a/static/fixed_responses/getVendorInfo/DeimosHivemindTokenVendorManifest.json +++ /dev/null @@ -1,286 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "5fb70313c96976e97d6be6fe" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Deimos/HivemindTokenVendorManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Remedies/RemedySonB", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishBPartItem", - "ItemCount": 36, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishCPartItem", - "ItemCount": 20, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishEPartItem", - "ItemCount": 36, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_2", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 379215713, - "Id": { - "$oid": "66fd60b20ba592c4c95e9308" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Remedies/RemedyMotherB", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosGenericInfestedFishPartItem", - "ItemCount": 80, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/InfGorgaricusSeedItem", - "ItemCount": 32, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishCPartItem", - "ItemCount": 28, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 2534781881, - "Id": { - "$oid": "66fd60b20ba592c4c95e9309" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Remedies/RemedyDaughterB", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosUncommonGemAItem", - "ItemCount": 28, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishCPartItem", - "ItemCount": 32, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishAPartItem", - "ItemCount": 32, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 1507786123, - "Id": { - "$oid": "66fd60b20ba592c4c95e930a" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Remedies/RemedySonA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosRareGemAItem", - "ItemCount": 15, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonGemAItem", - "ItemCount": 30, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/InfGorgaricusSeedItem", - "ItemCount": 21, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 616241643, - "Id": { - "$oid": "66fd60b20ba592c4c95e930b" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Remedies/RemedyOtakA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishBPartItem", - "ItemCount": 21, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishCPartItem", - "ItemCount": 27, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishEPartItem", - "ItemCount": 27, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 2888479655, - "Id": { - "$oid": "66fd60b20ba592c4c95e930c" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Remedies/RemedyGrandmotherA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonGemBItem", - "ItemCount": 20, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishBPartItem", - "ItemCount": 28, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/InfGorgaricusSeedItem", - "ItemCount": 24, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonGemAItem", - "ItemCount": 20, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 2874726481, - "Id": { - "$oid": "66fd60b20ba592c4c95e930d" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Remedies/RemedyFatherA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosGenericSharedFishPartItem", - "ItemCount": 75, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosUncommonGemAItem", - "ItemCount": 27, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/OrbStoneItem", - "ItemCount": 30, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 274676857, - "Id": { - "$oid": "66fd60b20ba592c4c95e930e" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Remedies/RemedyDaughterA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/InfGorgaricusSeedItem", - "ItemCount": 24, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishBPartItem", - "ItemCount": 30, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosGenericSharedFishPartItem", - "ItemCount": 51, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "LocTagRandSeed": 2487943761, - "Id": { - "$oid": "66fd60b20ba592c4c95e930f" - } - } - ], - "PropertyTextHash": "C34BF0BEDEAF7CBB0EEBFFECDFD6646D", - "RandomSeedType": "VRST_FLAVOUR_TEXT", - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - } - } -} diff --git a/static/fixed_responses/getVendorInfo/DeimosPetVendorManifest.json b/static/fixed_responses/getVendorInfo/DeimosPetVendorManifest.json deleted file mode 100644 index 76d738ea..00000000 --- a/static/fixed_responses/getVendorInfo/DeimosPetVendorManifest.json +++ /dev/null @@ -1,136 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "5f456e02c96976e97d6b8080" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Deimos/PetVendorManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Deimos/AnimalTagInfestedNexiferaRare", - "PremiumPrice": [35, 35], - "Bin": "BIN_0", - "QuantityMultiplier": 5, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b10ba592c4c95e89f6" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Deimos/AnimalTagInfestedNexiferaUncommon", - "PremiumPrice": [22, 22], - "Bin": "BIN_0", - "QuantityMultiplier": 5, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b10ba592c4c95e89f7" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Deimos/AnimalTagInfestedMergooUncommon", - "PremiumPrice": [28, 28], - "Bin": "BIN_0", - "QuantityMultiplier": 5, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b10ba592c4c95e89f8" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Deimos/AnimalTagInfestedKdriveUncommon", - "PremiumPrice": [25, 25], - "Bin": "BIN_0", - "QuantityMultiplier": 5, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b10ba592c4c95e89f9" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Deimos/AnimalTagInfestedZongroCommon", - "PremiumPrice": [14, 14], - "Bin": "BIN_0", - "QuantityMultiplier": 5, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b10ba592c4c95e89fa" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Deimos/AnimalTagInfestedPredatorCommon", - "PremiumPrice": [12, 12], - "Bin": "BIN_0", - "QuantityMultiplier": 5, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b10ba592c4c95e89fb" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Deimos/AnimalTagInfestedMergooCommon", - "PremiumPrice": [13, 13], - "Bin": "BIN_0", - "QuantityMultiplier": 5, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b10ba592c4c95e89fc" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/Deimos/AnimalTagInfestedKdriveCommon", - "PremiumPrice": [14, 14], - "Bin": "BIN_0", - "QuantityMultiplier": 5, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b10ba592c4c95e89fd" - } - } - ], - "PropertyTextHash": "F14C6B6A61D7585A10537995661F5220", - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - } - } -} diff --git a/static/fixed_responses/getVendorInfo/DuviriAcrithisVendorManifest.json b/static/fixed_responses/getVendorInfo/DuviriAcrithisVendorManifest.json deleted file mode 100644 index 031db476..00000000 --- a/static/fixed_responses/getVendorInfo/DuviriAcrithisVendorManifest.json +++ /dev/null @@ -1,321 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "64493ca759e9b164c86a2e14" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Duviri/AcrithisVendorManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/StoreItems/Types/Items/DangerRoom/DangerRoomTileDuviriDragonArena", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem", - "ItemCount": 20, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_5", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b00ba592c4c95e7d88" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Recipes/Components/FormaBlueprint", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem", - "ItemCount": 10, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_2", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b00ba592c4c95e7deb" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/UtilityUnlocker", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem", - "ItemCount": 20, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_2", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b00ba592c4c95e7dec" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Kuva", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem", - "ItemCount": 10, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_2", - "QuantityMultiplier": 5000, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b00ba592c4c95e7ded" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/WeaponUtilityUnlocker", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem", - "ItemCount": 20, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_2", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b00ba592c4c95e7dee" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Kuva", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem", - "ItemCount": 10, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_2", - "QuantityMultiplier": 5000, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { - "$oid": "670c5e12576f461f1e5e739c" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Plastids", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriPlantItemD", - "ItemCount": 40, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": true, - "Id": { - "$oid": "6710c312fa0b2c5cd85e73c3" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Duviri/DUVxPlanterHangingPot", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriPlantItemE", - "ItemCount": 51, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "6710c312fa0b2c5cd85e73c6" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Duviri/DUVxPlanterPotB", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriRockItem", - "ItemCount": 44, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "6710c312fa0b2c5cd85e73c7" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/NeuralSensor", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriPlantItemA", - "ItemCount": 52, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 3, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": true, - "Id": { - "$oid": "6710c312fa0b2c5cd85e73c8" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/ControlModule", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriPlantItemF", - "ItemCount": 42, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 3, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": true, - "Id": { - "$oid": "6710c312fa0b2c5cd85e73c9" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileDuviriArenaOpera", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriProcessedItem", - "ItemCount": 240, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b00ba592c4c95e7ddd" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/CosmeticEnhancers/Utility/HealthWhileUsingChanneledAbilities", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem", - "ItemCount": 10, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_3", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b00ba592c4c95e7e01" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/Boons/DuviriVendorBoonItem", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriPlantItemG", - "ItemCount": 50, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_4", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": true, - "Id": { - "$oid": "6711a412ba1ba01e405e739c" - } - } - ], - "PropertyTextHash": "9EE40048EB685549ACA3D01AB1F65BF2", - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - } - } -} diff --git a/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabVendorManifest.json b/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabVendorManifest.json deleted file mode 100644 index 624e2d33..00000000 --- a/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabVendorManifest.json +++ /dev/null @@ -1,245 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "6579d82b553a20c6fc0067ca" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/EntratiLabs/EntratiLabVendorManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/StoreItems/Types/Gameplay/NarmerSorties/ArchonCrystalAmar", - "Bin": "BIN_3", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "RotatedWeekly": true, - "Affiliation": "EntratiLabSyndicate", - "MinAffiliationRank": 5, - "ReductionPerPositiveRank": 0, - "IncreasePerNegativeRank": 0, - "StandingCost": 30000, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e920d" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Gameplay/NarmerSorties/ArchonCrystalBoreal", - "Bin": "BIN_3", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "RotatedWeekly": true, - "Affiliation": "EntratiLabSyndicate", - "MinAffiliationRank": 5, - "ReductionPerPositiveRank": 0, - "IncreasePerNegativeRank": 0, - "StandingCost": 30000, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e920e" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Gameplay/NarmerSorties/ArchonCrystalNira", - "Bin": "BIN_3", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "RotatedWeekly": true, - "Affiliation": "EntratiLabSyndicate", - "MinAffiliationRank": 5, - "ReductionPerPositiveRank": 0, - "IncreasePerNegativeRank": 0, - "StandingCost": 30000, - "AllowMultipurchase": false, - "Id": { - "$oid": "66fd60b20ba592c4c95e920f" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/EntratiLabs/ORKxLabStool", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLabDogTagUncommon", - "ItemCount": 2, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLabMiscItemA", - "ItemCount": 22, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/MurmurItem", - "ItemCount": 18, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9270" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/EntratiLabs/ORKxLabChairA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/MurmurItem", - "ItemCount": 15, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLabMiscItemA", - "ItemCount": 19, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLabMiscItemB", - "ItemCount": 19, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9271" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/EntratiLabs/ORKxLabLightWallCandleA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/MurmurItem", - "ItemCount": 12, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLabDogTagCommon", - "ItemCount": 3, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9272" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/EntratiLabs/ORKxLabLightChandelierD", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLabMiscItemB", - "ItemCount": 8, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLabDogTagCommon", - "ItemCount": 3, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9273" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/EntratiLabs/ORKxLabLightChandelierB", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLabMiscItemA", - "ItemCount": 12, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLabDogTagCommon", - "ItemCount": 2, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9274" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/EntratiLabs/ORKxLabLightChandelierA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/MurmurItem", - "ItemCount": 15, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLabMiscItemA", - "ItemCount": 13, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9275" - } - } - ], - "PropertyTextHash": "44DA3839E6F7BDB32ACED53F2B0BE14E", - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - } - } -} diff --git a/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabsCommisionsManifest.json b/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabsCommisionsManifest.json deleted file mode 100644 index 41a9454e..00000000 --- a/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabsCommisionsManifest.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "6579d82b553a20c6fc0067ae" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/EntratiLabs/EntratiLabsCommisionsManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/EntratiLabs/LoidTaskC", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLabMiscItemB", - "ItemCount": 17, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/MurmurItem", - "ItemCount": 30, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_2", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { - "$oid": "670a2b928ac7854ac55e73d3" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/EntratiLabs/LoidTaskB", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/MurmurItem", - "ItemCount": 20, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLabMiscItemA", - "ItemCount": 228, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { - "$oid": "670a2b928ac7854ac55e73d4" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/EntratiLabs/LoidTaskA", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/MurmurItem", - "ItemCount": 15, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/ZarimanMiscItemB", - "ItemCount": 1, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { - "$oid": "670a2b928ac7854ac55e73d5" - } - } - ], - "PropertyTextHash": "60C4D85A8DE5E6538AD23CDDFEEF0422", - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - } - } -} diff --git a/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json b/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json deleted file mode 100644 index 59afcd65..00000000 --- a/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json +++ /dev/null @@ -1,188 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "67dadc30e4b6e0e5979c8d6a" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/TheHex/Nova1999ConquestShopManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/StoreItems/Types/BoosterPacks/1999StickersPackEchoesArchimedea", - "ItemPrices": [ - { - "ItemCount": 10, - "ItemType": "/Lotus/Types/Items/MiscItems/1999ConquestBucks", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67db32b983b2ad79a9c1c18c" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/BoosterPacks/1999StickersPackEchoesArchimedeaFree", - "ItemPrices": [ - { - "ItemCount": 1, - "ItemType": "/Lotus/Types/Items/MiscItems/1999FreeStickersPack", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67db32b983b2ad79a9c1c18d" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/BoosterPacks/1999StickersPackEchoesArchimedeaFixed", - "ItemPrices": [ - { - "ItemCount": 1, - "ItemType": "/Lotus/Types/Items/MiscItems/1999FixedStickersPack", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67db32b983b2ad79a9c1c18e" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/SyndicateVosforPack", - "ItemPrices": [ - { - "ItemCount": 6, - "ItemType": "/Lotus/Types/Items/MiscItems/1999ConquestBucks", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67db32b983b2ad79a9c1c18f" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/StickerPictureFrame", - "ItemPrices": [ - { - "ItemCount": 10, - "ItemType": "/Lotus/Types/Items/MiscItems/1999ConquestBucks", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "67db32b983b2ad79a9c1c190" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/CosmeticEnhancers/Utility/AbilityRadiationProcsCreateUniversalOrbsOnKill", - "ItemPrices": [ - { - "ItemCount": 5, - "ItemType": "/Lotus/Types/Items/MiscItems/1999ConquestBucks", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { - "$oid": "67db32b983b2ad79a9c1c191" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/CosmeticEnhancers/Offensive/AbilityHeatProcsGiveCritChance", - "ItemPrices": [ - { - "ItemCount": 5, - "ItemType": "/Lotus/Types/Items/MiscItems/1999ConquestBucks", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { - "$oid": "67db32b983b2ad79a9c1c192" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Upgrades/CosmeticEnhancers/Defensive/InvulnerabilityOnDeathOnMercyKill", - "ItemPrices": [ - { - "ItemCount": 5, - "ItemType": "/Lotus/Types/Items/MiscItems/1999ConquestBucks", - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - }, - "PurchaseQuantityLimit": 1, - "AllowMultipurchase": false, - "Id": { - "$oid": "67db32b983b2ad79a9c1c193" - } - } - ], - "PropertyTextHash": "CB7D0E807FD5E2BCD059195201D963B9", - "RequiredGoalTag": "", - "Expiry": { - "$date": { - "$numberLong": "2051240400000" - } - } - } -} diff --git a/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json b/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json deleted file mode 100644 index 521260cf..00000000 --- a/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "5991d5e6bcc718474ee90c15" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Ostron/PetVendorManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropOstBirdCage", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Gems/Eidolon/UncommonOreAAlloyAItem", - "ItemCount": 10, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/DayUncommonFishBPartItem", - "ItemCount": 8, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9a8e" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/KubrowColorPackDrahk", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/DayCommonFishBPartItem", - "ItemCount": 14, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothCommonFishBPartItem", - "ItemCount": 13, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9a8f" - } - }, - { - "StoreItem": "/Lotus/Types/StoreItems/Packages/KubrowColorPackFeral", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothCommonFishAPartItem", - "ItemCount": 19, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonOreBAlloyBItem", - "ItemCount": 34, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "66fd60b20ba592c4c95e9a90" - } - } - ], - "PropertyTextHash": "3D85F1A0A2B62734AE90370DEC214C26", - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - } - } -} diff --git a/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json b/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json deleted file mode 100644 index 67b234a2..00000000 --- a/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json +++ /dev/null @@ -1,126 +0,0 @@ -{ - "VendorInfo": { - "_id": { - "$oid": "5be4a159b144f3cdf1c22edf" - }, - "TypeName": "/Lotus/Types/Game/VendorManifests/Solaris/DebtTokenVendorRepossessionsManifest", - "ItemManifest": [ - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Venus/SUToolBox", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Solaris/DebtTokenB", - "ItemCount": 6, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "6711a412ba1ba01e405e739d" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Venus/SUBookAOpen", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Solaris/DebtTokenC", - "ItemCount": 6, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "6711a412ba1ba01e405e739e" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Venus/SUFoodCans", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Solaris/DebtTokenC", - "ItemCount": 7, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "6711a412ba1ba01e405e739f" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Venus/SUTechToolD", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Solaris/DebtTokenD", - "ItemCount": 5, - "ProductCategory": "MiscItems" - }, - { - "ItemType": "/Lotus/Types/Items/Solaris/DebtTokenA", - "ItemCount": 15, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_1", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "6711a412ba1ba01e405e73a0" - } - }, - { - "StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Venus/SUContainerCrate", - "ItemPrices": [ - { - "ItemType": "/Lotus/Types/Items/Solaris/DebtTokenA", - "ItemCount": 9, - "ProductCategory": "MiscItems" - } - ], - "Bin": "BIN_0", - "QuantityMultiplier": 1, - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - }, - "AllowMultipurchase": true, - "Id": { - "$oid": "6711a412ba1ba01e405e73a1" - } - } - ], - "PropertyTextHash": "E0E83157D73468DC578403CB9EBA9DA6", - "Expiry": { - "$date": { - "$numberLong": "9999999000000" - } - } - } -} From 2fa6dcc7edb34c9382c31739d8b61a84803d69c2 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Sat, 21 Jun 2025 07:26:43 -0700 Subject: [PATCH 060/153] feat(webui): handle auth via websocket (#2226) Now when logging in and out of the game, the webui is notified so it can refresh the nonce, removing the need for constant login requests to revalidate it. Closes #2223 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2226 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/loginController.ts | 59 +++------- src/controllers/api/logoutController.ts | 7 +- src/models/loginModel.ts | 8 +- src/services/loginService.ts | 17 +++ src/services/webService.ts | 114 +++++++++++++++++- src/types/loginTypes.ts | 2 +- static/webui/index.html | 2 +- static/webui/script.js | 147 +++++++++++++----------- 8 files changed, 236 insertions(+), 120 deletions(-) diff --git a/src/controllers/api/loginController.ts b/src/controllers/api/loginController.ts index 3d510f26..c7b5c16d 100644 --- a/src/controllers/api/loginController.ts +++ b/src/controllers/api/loginController.ts @@ -4,16 +4,16 @@ import { config } from "@/src/services/configService"; import { buildConfig } from "@/src/services/buildConfigService"; import { Account } from "@/src/models/loginModel"; -import { createAccount, isCorrectPassword, isNameTaken } from "@/src/services/loginService"; +import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "@/src/services/loginService"; import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes"; import { logger } from "@/src/utils/logger"; import { version_compare } from "@/src/helpers/inventoryHelpers"; +import { sendWsBroadcastTo } from "@/src/services/webService"; export const loginController: RequestHandler = async (request, response) => { const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object const account = await Account.findOne({ email: loginRequest.email }); - const nonce = Math.round(Math.random() * Number.MAX_SAFE_INTEGER); const buildLabel: string = typeof request.query.buildLabel == "string" @@ -42,26 +42,14 @@ export const loginController: RequestHandler = async (request, response) => { loginRequest.ClientType == "webui-register") ) { try { - const nameFromEmail = loginRequest.email.substring(0, loginRequest.email.indexOf("@")); - let name = nameFromEmail || loginRequest.email.substring(1) || "SpaceNinja"; - if (await isNameTaken(name)) { - let suffix = 0; - do { - ++suffix; - name = nameFromEmail + suffix; - } while (await isNameTaken(name)); - } + const name = await getUsernameFromEmail(loginRequest.email); const newAccount = await createAccount({ email: loginRequest.email, password: loginRequest.password, DisplayName: name, CountryCode: loginRequest.lang?.toUpperCase() ?? "EN", - ClientType: loginRequest.ClientType == "webui-register" ? "webui" : loginRequest.ClientType, - CrossPlatformAllowed: true, - ForceLogoutVersion: 0, - ConsentNeeded: false, - TrackedSettings: [], - Nonce: nonce, + ClientType: loginRequest.ClientType, + Nonce: createNonce(), BuildLabel: buildLabel, LastLogin: new Date() }); @@ -80,38 +68,29 @@ export const loginController: RequestHandler = async (request, response) => { return; } - if (loginRequest.ClientType == "webui-register") { - response.status(400).json({ error: "account already exists" }); - return; - } - if (!isCorrectPassword(loginRequest.password, account.password)) { response.status(400).json({ error: "incorrect login data" }); return; } - if (loginRequest.ClientType == "webui") { - if (!account.Nonce) { - account.ClientType = "webui"; - account.Nonce = nonce; + if (account.Nonce && account.ClientType != "webui" && !account.Dropped && !loginRequest.kick) { + // U17 seems to handle "nonce still set" like a login failure. + if (version_compare(buildLabel, "2015.12.05.18.07") >= 0) { + response.status(400).send({ error: "nonce still set" }); + return; } - } else { - if (account.Nonce && account.ClientType != "webui" && !account.Dropped && !loginRequest.kick) { - // U17 seems to handle "nonce still set" like a login failure. - if (version_compare(buildLabel, "2015.12.05.18.07") >= 0) { - response.status(400).send({ error: "nonce still set" }); - return; - } - } - - account.ClientType = loginRequest.ClientType; - account.Nonce = nonce; - account.CountryCode = loginRequest.lang?.toUpperCase() ?? "EN"; - account.BuildLabel = buildLabel; - account.LastLogin = new Date(); } + + account.ClientType = loginRequest.ClientType; + account.Nonce = createNonce(); + account.CountryCode = loginRequest.lang?.toUpperCase() ?? "EN"; + account.BuildLabel = buildLabel; + account.LastLogin = new Date(); await account.save(); + // Tell WebUI its nonce has been invalidated + sendWsBroadcastTo(account._id.toString(), { logged_out: true }); + response.json(createLoginResponse(myAddress, myUrlBase, account.toJSON(), buildLabel)); }; diff --git a/src/controllers/api/logoutController.ts b/src/controllers/api/logoutController.ts index 889e7d78..e2074f76 100644 --- a/src/controllers/api/logoutController.ts +++ b/src/controllers/api/logoutController.ts @@ -1,5 +1,6 @@ import { RequestHandler } from "express"; import { Account } from "@/src/models/loginModel"; +import { sendWsBroadcastTo } from "@/src/services/webService"; export const logoutController: RequestHandler = async (req, res) => { if (!req.query.accountId) { @@ -10,7 +11,7 @@ export const logoutController: RequestHandler = async (req, res) => { throw new Error("Request is missing nonce parameter"); } - await Account.updateOne( + const stat = await Account.updateOne( { _id: req.query.accountId, Nonce: nonce @@ -19,6 +20,10 @@ export const logoutController: RequestHandler = async (req, res) => { Nonce: 0 } ); + if (stat.modifiedCount) { + // Tell WebUI its nonce has been invalidated + sendWsBroadcastTo(req.query.accountId as string, { logged_out: true }); + } res.writeHead(200, { "Content-Type": "text/html", diff --git a/src/models/loginModel.ts b/src/models/loginModel.ts index 0f83c0cb..44dab113 100644 --- a/src/models/loginModel.ts +++ b/src/models/loginModel.ts @@ -11,13 +11,13 @@ const databaseAccountSchema = new Schema( email: { type: String, required: true, unique: true }, password: { type: String, required: true }, DisplayName: { type: String, required: true, unique: true }, - CountryCode: { type: String, required: true }, + CountryCode: { type: String, default: "" }, ClientType: { type: String }, - CrossPlatformAllowed: { type: Boolean, required: true }, - ForceLogoutVersion: { type: Number, required: true }, + CrossPlatformAllowed: { type: Boolean, default: true }, + ForceLogoutVersion: { type: Number, default: 0 }, AmazonAuthToken: { type: String }, AmazonRefreshToken: { type: String }, - ConsentNeeded: { type: Boolean, required: true }, + ConsentNeeded: { type: Boolean, default: false }, TrackedSettings: { type: [String], default: [] }, Nonce: { type: Number, default: 0 }, BuildLabel: String, diff --git a/src/services/loginService.ts b/src/services/loginService.ts index ccf5b958..44366f5c 100644 --- a/src/services/loginService.ts +++ b/src/services/loginService.ts @@ -18,6 +18,23 @@ export const isNameTaken = async (name: string): Promise => { return !!(await Account.findOne({ DisplayName: name })); }; +export const createNonce = (): number => { + return Math.round(Math.random() * Number.MAX_SAFE_INTEGER); +}; + +export const getUsernameFromEmail = async (email: string): Promise => { + const nameFromEmail = email.substring(0, email.indexOf("@")); + let name = nameFromEmail || email.substring(1) || "SpaceNinja"; + if (await isNameTaken(name)) { + let suffix = 0; + do { + ++suffix; + name = nameFromEmail + suffix; + } while (await isNameTaken(name)); + } + return nameFromEmail; +}; + export const createAccount = async (accountData: IDatabaseAccountRequiredFields): Promise => { const account = new Account(accountData); try { diff --git a/src/services/webService.ts b/src/services/webService.ts index d4d1227c..8beda45c 100644 --- a/src/services/webService.ts +++ b/src/services/webService.ts @@ -6,6 +6,10 @@ import { logger } from "../utils/logger"; import { app } from "../app"; import { AddressInfo } from "node:net"; import ws from "ws"; +import { Account } from "../models/loginModel"; +import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "./loginService"; +import { IDatabaseAccountJson } from "../types/loginTypes"; +import { HydratedDocument } from "mongoose"; let httpServer: http.Server | undefined; let httpsServer: https.Server | undefined; @@ -25,7 +29,7 @@ export const startWebServer = (): void => { httpServer = http.createServer(app); httpServer.listen(httpPort, () => { wsServer = new ws.Server({ server: httpServer }); - //wsServer.on("connection", wsOnConnect); + wsServer.on("connection", wsOnConnect); logger.info("HTTP server started on port " + httpPort); @@ -33,7 +37,7 @@ export const startWebServer = (): void => { httpsServer = https.createServer(tlsOptions, app); httpsServer.listen(httpsPort, () => { wssServer = new ws.Server({ server: httpsServer }); - //wssServer.on("connection", wsOnConnect); + wssServer.on("connection", wsOnConnect); logger.info("HTTPS server started on port " + httpsPort); @@ -92,11 +96,91 @@ export const stopWebServer = async (): Promise => { await Promise.all(promises); }; -/*const wsOnConnect = (ws: ws, _req: http.IncomingMessage): void => { - ws.on("message", console.log); -};*/ +interface IWsCustomData extends ws { + accountId?: string; +} -export const sendWsBroadcast = (data: T): void => { +interface IWsMsgFromClient { + auth?: { + email: string; + password: string; + isRegister: boolean; + }; + logout?: boolean; +} + +interface IWsMsgToClient { + ports?: { + http: number | undefined; + https: number | undefined; + }; + config_reloaded?: boolean; + auth_succ?: { + id: string; + DisplayName: string; + Nonce: number; + }; + auth_fail?: { + isRegister: boolean; + }; + logged_out?: boolean; +} + +const wsOnConnect = (ws: ws, _req: http.IncomingMessage): void => { + // eslint-disable-next-line @typescript-eslint/no-misused-promises + ws.on("message", async msg => { + const data = JSON.parse(String(msg)) as IWsMsgFromClient; + if (data.auth) { + let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email }); + if (account) { + if (isCorrectPassword(data.auth.password, account.password)) { + if (!account.Nonce) { + account.ClientType = "webui"; + account.Nonce = createNonce(); + await (account as HydratedDocument).save(); + } + } else { + account = null; + } + } else if (data.auth.isRegister) { + const name = await getUsernameFromEmail(data.auth.email); + account = await createAccount({ + email: data.auth.email, + password: data.auth.password, + ClientType: "webui", + LastLogin: new Date(), + DisplayName: name, + Nonce: createNonce() + }); + } + if (account) { + (ws as IWsCustomData).accountId = account.id; + ws.send( + JSON.stringify({ + auth_succ: { + id: account.id, + DisplayName: account.DisplayName, + Nonce: account.Nonce + } + } satisfies IWsMsgToClient) + ); + } else { + ws.send( + JSON.stringify({ + auth_fail: { + isRegister: data.auth.isRegister + } + } satisfies IWsMsgToClient) + ); + } + } + if (data.logout) { + (ws as IWsCustomData).accountId = undefined; + } + }); +}; + +export const sendWsBroadcast = (data: IWsMsgToClient): void => { const msg = JSON.stringify(data); if (wsServer) { for (const client of wsServer.clients) { @@ -109,3 +193,21 @@ export const sendWsBroadcast = (data: T): void => { } } }; + +export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void => { + const msg = JSON.stringify(data); + if (wsServer) { + for (const client of wsServer.clients) { + if ((client as IWsCustomData).accountId == accountId) { + client.send(msg); + } + } + } + if (wssServer) { + for (const client of wssServer.clients) { + if ((client as IWsCustomData).accountId == accountId) { + client.send(msg); + } + } + } +}; diff --git a/src/types/loginTypes.ts b/src/types/loginTypes.ts index 159d39e9..ef280188 100644 --- a/src/types/loginTypes.ts +++ b/src/types/loginTypes.ts @@ -2,7 +2,7 @@ import { Types } from "mongoose"; export interface IAccountAndLoginResponseCommons { DisplayName: string; - CountryCode: string; + CountryCode?: string; ClientType?: string; CrossPlatformAllowed?: boolean; ForceLogoutVersion?: number; diff --git a/static/webui/index.html b/static/webui/index.html index 5b92f727..1a748968 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -37,7 +37,7 @@