From 9a37b9ed852a334ae43564a71c2fcefd013a3e59 Mon Sep 17 00:00:00 2001 From: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Date: Fri, 11 Apr 2025 02:22:32 +0200 Subject: [PATCH 1/3] feat: bounty standing reward Re #388 I think this only missing `Field Bounties` and `Arcana Isolation Vault` --- .../api/missionInventoryUpdateController.ts | 12 ++- src/services/inventoryService.ts | 29 +++++ src/services/missionInventoryUpdateService.ts | 101 +++++++++++++++++- src/types/requestTypes.ts | 5 +- src/types/worldStateTypes.ts | 15 ++- 5 files changed, 153 insertions(+), 9 deletions(-) diff --git a/src/controllers/api/missionInventoryUpdateController.ts b/src/controllers/api/missionInventoryUpdateController.ts index 4dc5bd66..589285ae 100644 --- a/src/controllers/api/missionInventoryUpdateController.ts +++ b/src/controllers/api/missionInventoryUpdateController.ts @@ -55,7 +55,10 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res) const inventory = await getInventory(accountId); const inventoryUpdates = await addMissionInventoryUpdates(inventory, missionReport); - if (missionReport.MissionStatus !== "GS_SUCCESS") { + if ( + missionReport.MissionStatus === "GS_SUCCESS" || + (missionReport.RewardInfo && (missionReport.RewardInfo.jobId || missionReport.RewardInfo.challengeMissionId)) + ) { await inventory.save(); const inventoryResponse = await getInventoryResponse(inventory, true); res.json({ @@ -65,7 +68,8 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res) return; } - const { MissionRewards, inventoryChanges, credits } = await addMissionRewards(inventory, missionReport); + const { MissionRewards, inventoryChanges, credits, AffiliationMods, SyndicateXPItemReward } = + await addMissionRewards(inventory, missionReport); await inventory.save(); const inventoryResponse = await getInventoryResponse(inventory, true); @@ -77,7 +81,9 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res) MissionRewards, ...credits, ...inventoryUpdates, - FusionPoints: inventoryChanges?.FusionPoints + FusionPoints: inventoryChanges?.FusionPoints, + SyndicateXPItemReward, + AffiliationMods }); }; diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index 078e7cd2..60eb054e 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -65,6 +65,7 @@ import { handleBundleAcqusition } from "./purchaseService"; import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json"; import { getRandomElement, getRandomInt, SRng } from "./rngService"; import { createMessage } from "./inboxService"; +import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper"; export const createInventory = async ( accountOwnerId: Types.ObjectId, @@ -947,6 +948,34 @@ export const updateStandingLimit = ( } }; +export const addStanding = ( + inventory: TInventoryDatabaseDocument, + syndicateTag: string, + gainedStanding: number +): IAffiliationMods => { + let syndicate = inventory.Affiliations.find(x => x.Tag == syndicateTag); + const syndicateMeta = ExportSyndicates[syndicateTag]; + + if (!syndicate) { + syndicate = + inventory.Affiliations[inventory.Affiliations.push({ Tag: syndicateTag, Standing: 0, Title: 0 }) - 1]; + } + + const max = getMaxStanding(syndicateMeta, syndicate.Title ?? 0); + if (syndicate.Standing + gainedStanding > max) gainedStanding = max - syndicate.Standing; + + if (gainedStanding > getStandingLimit(inventory, syndicateMeta.dailyLimitBin)) { + gainedStanding = getStandingLimit(inventory, syndicateMeta.dailyLimitBin); + } + updateStandingLimit(inventory, syndicateMeta.dailyLimitBin, gainedStanding); + + syndicate.Standing += gainedStanding; + return { + Tag: syndicateTag, + Standing: gainedStanding + }; +}; + // TODO: AffiliationMods support (Nightwave). export const updateGeneric = async (data: IGenericUpdate, accountId: string): Promise => { const inventory = await getInventory(accountId, "NodeIntrosCompleted MiscItems"); diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index 9ca788af..42ef9b51 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -28,13 +28,14 @@ import { addMods, addRecipes, addShipDecorations, + addStanding, combineInventoryChanges, updateCurrency, updateSyndicate } from "@/src/services/inventoryService"; import { updateQuestKey } from "@/src/services/questService"; import { Types } from "mongoose"; -import { IInventoryChanges } from "@/src/types/purchaseTypes"; +import { IAffiliationMods, IInventoryChanges } from "@/src/types/purchaseTypes"; import { getLevelKeyRewards, toStoreItem } from "@/src/services/itemDataService"; import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { getEntriesUnsafe } from "@/src/utils/ts-utils"; @@ -50,6 +51,7 @@ import conservationAnimals from "@/static/fixed_responses/conservationAnimals.js import { getInfNodes } from "@/src/helpers/nemesisHelpers"; import { Loadout } from "../models/inventoryModels/loadoutModel"; import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes"; +import { getWorldState } from "./worldStateService"; const getRotations = (rotationCount: number, tierOverride: number | undefined): number[] => { if (rotationCount === 0) return [0]; @@ -515,6 +517,8 @@ interface AddMissionRewardsReturnType { MissionRewards: IMissionReward[]; inventoryChanges?: IInventoryChanges; credits?: IMissionCredits; + AffiliationMods?: IAffiliationMods[]; + SyndicateXPItemReward?: number; } //TODO: return type of partial missioninventoryupdate response @@ -541,6 +545,8 @@ export const addMissionRewards = async ( const MissionRewards: IMissionReward[] = getRandomMissionDrops(rewardInfo, wagerTier); logger.debug("random mission drops:", MissionRewards); const inventoryChanges: IInventoryChanges = {}; + const AffiliationMods: IAffiliationMods[] = []; + let SyndicateXPItemReward; let missionCompletionCredits = 0; //inventory change is what the client has not rewarded itself, also the client needs to know the credit changes for display @@ -704,7 +710,98 @@ export const addMissionRewards = async ( inventoryChanges.Nemesis.InfNodes = inventory.Nemesis.InfNodes; } } - return { inventoryChanges, MissionRewards, credits }; + + if (rewardInfo.JobStage != undefined && rewardInfo.jobId) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [jobType, tierStr, hubNode, syndicateId, locationTag] = rewardInfo.jobId.split("_"); + const tier = Number(tierStr); + + const worldState = getWorldState(); + let syndicateEntry = worldState.SyndicateMissions.find(m => m._id.$oid === syndicateId); + if (!syndicateEntry) syndicateEntry = worldState.SyndicateMissions.find(m => m.Tag === syndicateId); // Sometimes syndicateId can be tag + if (syndicateEntry && syndicateEntry.Jobs) { + let currentJob = syndicateEntry.Jobs[tier]; + if (syndicateEntry.Tag === "EntratiSyndicate") { + const vault = syndicateEntry.Jobs.find(j => j.locationTag === locationTag); + if (vault) currentJob = vault; + let medallionAmount = currentJob.xpAmounts[rewardInfo.JobStage]; + + if ( + ["DeimosEndlessAreaDefenseBounty", "DeimosEndlessExcavateBounty", "DeimosEndlessPurifyBounty"].some( + ending => jobType.endsWith(ending) + ) + ) { + const endlessJob = syndicateEntry.Jobs.find(j => j.endless); + if (endlessJob) { + currentJob = endlessJob; + const index = rewardInfo.JobStage % currentJob.xpAmounts.length; + const excess = Math.floor(rewardInfo.JobStage / currentJob.xpAmounts.length); + medallionAmount = Math.floor(currentJob.xpAmounts[index] * (1 + 0.15000001 * excess)); + } + } + await addItem(inventory, "/Lotus/Types/Items/Deimos/EntratiFragmentUncommonB", medallionAmount); + MissionRewards.push({ + StoreItem: "/Lotus/StoreItems/Types/Items/Deimos/EntratiFragmentUncommonB", + ItemCount: medallionAmount + }); + SyndicateXPItemReward = medallionAmount; + } else { + if (tier >= 0) { + AffiliationMods.push( + addStanding(inventory, syndicateEntry.Tag, currentJob.xpAmounts[rewardInfo.JobStage]) + ); + } else { + if ( + [ + "Hunts/TeralystHunt", + "Heists/HeistProfitTakerBountyOne", + "Heists/HeistProfitTakerBountyTwo", + "Heists/HeistProfitTakerBountyThree", + "Heists/HeistProfitTakerBountyFour", + "Heists/HeistExploiterBountyOne" + ].some(ending => jobType.endsWith(ending)) + ) { + AffiliationMods.push(addStanding(inventory, syndicateEntry.Tag, 1000)); + } + if (jobType.endsWith("Hunts/AllTeralystsHunt")) { + AffiliationMods.push(addStanding(inventory, syndicateEntry.Tag, 5000)); + } + } + } + } + } + + if (rewardInfo.challengeMissionId) { + const [syndicateTag, tierStr] = rewardInfo.challengeMissionId.split("_"); // TODO: third part in HexSyndicate jobs - Chemistry points + const tier = Number(tierStr); + const isSteelPath = missions?.Tier; + if (syndicateTag === "ZarimanSyndicate") { + let medallionAmount = tier + 1; + if (isSteelPath) medallionAmount = Math.round(medallionAmount * 1.5); + await addItem(inventory, "/Lotus/Types/Gameplay/Zariman/Resources/ZarimanDogTagBounty", medallionAmount); + MissionRewards.push({ + StoreItem: "/Lotus/StoreItems/Types/Gameplay/Zariman/Resources/ZarimanDogTagBounty", + ItemCount: medallionAmount + }); + SyndicateXPItemReward = medallionAmount; + } else { + let standingAmount = (tier + 1) * 1000; + if (tier > 5) standingAmount = 7500; // InfestedLichBounty + if (isSteelPath) standingAmount *= 1.5; + const affiliationMod = addStanding(inventory, syndicateTag, standingAmount); + + AffiliationMods.push(affiliationMod); + } + if (isSteelPath) { + await addItem(inventory, "/Lotus/Types/Items/MiscItems/SteelEssence", 1); + MissionRewards.push({ + StoreItem: "/Lotus/StoreItems/Types/Items/MiscItems/SteelEssence", + ItemCount: 1 + }); + } + } + + return { inventoryChanges, MissionRewards, credits, AffiliationMods, SyndicateXPItemReward }; }; interface IMissionCredits { diff --git a/src/types/requestTypes.ts b/src/types/requestTypes.ts index 1f00fa45..3b66a0b5 100644 --- a/src/types/requestTypes.ts +++ b/src/types/requestTypes.ts @@ -145,11 +145,12 @@ export interface IRewardInfo { periodicMissionTag?: string; // for bounties, only EOM_AFK and node are given from above, plus: - JobTier?: string; + JobTier?: number; jobId?: string; - JobStage?: string; + JobStage?: number; Q?: boolean; // always false? CheckpointCounter?: number; // starts at 1, is incremented with each job stage upload, and does not reset when starting a new job + challengeMissionId?: string; } export type IMissionStatus = "GS_SUCCESS" | "GS_FAILURE" | "GS_DUMPED" | "GS_QUIT" | "GS_INTERRUPTED"; diff --git a/src/types/worldStateTypes.ts b/src/types/worldStateTypes.ts index a90f2134..a3b6efac 100644 --- a/src/types/worldStateTypes.ts +++ b/src/types/worldStateTypes.ts @@ -5,7 +5,7 @@ export interface IWorldState { BuildLabel: string; Time: number; Goals: IGoal[]; - SyndicateMissions: ISyndicateMission[]; + SyndicateMissions: ISyndicateMissionInfo[]; GlobalUpgrades: IGlobalUpgrade[]; Sorties: ISortie[]; LiteSorties: ILiteSortie[]; @@ -39,13 +39,24 @@ export interface IGoal { Node: string; } -export interface ISyndicateMission { +export interface ISyndicateMissionInfo { _id: IOid; Activation: IMongoDate; Expiry: IMongoDate; Tag: string; Seed: number; Nodes: string[]; + Jobs?: { + jobType?: string; + rewards: string; + masteryReq: number; + minEnemyLevel: number; + maxEnemyLevel: number; + xpAmounts: number[]; + endless?: boolean; + locationTag?: string; + isVault?: boolean; + }[]; } export interface IGlobalUpgrade { From 3bcf719c81c7edfae5e42d91f64ea7e1e930460d Mon Sep 17 00:00:00 2001 From: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Date: Fri, 11 Apr 2025 02:39:40 +0200 Subject: [PATCH 2/3] Update missionInventoryUpdateService.ts --- src/services/missionInventoryUpdateService.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index 42ef9b51..cf996fe6 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -788,9 +788,7 @@ export const addMissionRewards = async ( let standingAmount = (tier + 1) * 1000; if (tier > 5) standingAmount = 7500; // InfestedLichBounty if (isSteelPath) standingAmount *= 1.5; - const affiliationMod = addStanding(inventory, syndicateTag, standingAmount); - - AffiliationMods.push(affiliationMod); + AffiliationMods.push(addStanding(inventory, syndicateTag, standingAmount)); } if (isSteelPath) { await addItem(inventory, "/Lotus/Types/Items/MiscItems/SteelEssence", 1); From ed3d836417529dde7854a5e1183af1c75633e8dc Mon Sep 17 00:00:00 2001 From: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Date: Fri, 11 Apr 2025 03:40:14 +0200 Subject: [PATCH 3/3] `Heists/HeistProfitTakerBountyOne`: give standing only on last stage --- src/controllers/api/missionInventoryUpdateController.ts | 3 ++- src/services/missionInventoryUpdateService.ts | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/controllers/api/missionInventoryUpdateController.ts b/src/controllers/api/missionInventoryUpdateController.ts index 589285ae..fb1bf652 100644 --- a/src/controllers/api/missionInventoryUpdateController.ts +++ b/src/controllers/api/missionInventoryUpdateController.ts @@ -6,6 +6,7 @@ import { addMissionInventoryUpdates, addMissionRewards } from "@/src/services/mi import { getInventory } from "@/src/services/inventoryService"; import { getInventoryResponse } from "./inventoryController"; import { logger } from "@/src/utils/logger"; +import { ExportRegions } from "warframe-public-export-plus"; /* **** INPUT **** @@ -57,7 +58,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res) if ( missionReport.MissionStatus === "GS_SUCCESS" || - (missionReport.RewardInfo && (missionReport.RewardInfo.jobId || missionReport.RewardInfo.challengeMissionId)) + ExportRegions[missionReport.Missions!.Tag].missionIndex === 28 ) { await inventory.save(); const inventoryResponse = await getInventoryResponse(inventory, true); diff --git a/src/services/missionInventoryUpdateService.ts b/src/services/missionInventoryUpdateService.ts index cf996fe6..1716d147 100644 --- a/src/services/missionInventoryUpdateService.ts +++ b/src/services/missionInventoryUpdateService.ts @@ -751,10 +751,12 @@ export const addMissionRewards = async ( addStanding(inventory, syndicateEntry.Tag, currentJob.xpAmounts[rewardInfo.JobStage]) ); } else { + if (jobType.endsWith("Heists/HeistProfitTakerBountyOne") && rewardInfo.JobStage === 2) { + AffiliationMods.push(addStanding(inventory, syndicateEntry.Tag, 1000)); + } if ( [ "Hunts/TeralystHunt", - "Heists/HeistProfitTakerBountyOne", "Heists/HeistProfitTakerBountyTwo", "Heists/HeistProfitTakerBountyThree", "Heists/HeistProfitTakerBountyFour",