diff --git a/src/controllers/api/missionInventoryUpdateController.ts b/src/controllers/api/missionInventoryUpdateController.ts index 1a8e6318..738002fd 100644 --- a/src/controllers/api/missionInventoryUpdateController.ts +++ b/src/controllers/api/missionInventoryUpdateController.ts @@ -55,8 +55,10 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res) const inventory = await getInventory(accountId); const inventoryUpdates = await addMissionInventoryUpdates(inventory, missionReport); - // skip mission rewards if not GS_SUCCESS and not a bounty (by presence of jobId, as there's a reward every stage but only the last stage has GS_SUCCESS) - if (missionReport.MissionStatus !== "GS_SUCCESS" && !missionReport.RewardInfo?.jobId) { + if ( + missionReport.MissionStatus !== "GS_SUCCESS" && + !(missionReport.RewardInfo?.jobId || missionReport.RewardInfo?.challengeMissionId) + ) { await inventory.save(); const inventoryResponse = await getInventoryResponse(inventory, true); res.json({ @@ -66,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); @@ -78,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 b52b09d7..bccfe08d 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"; @@ -529,6 +530,8 @@ interface AddMissionRewardsReturnType { MissionRewards: IMissionReward[]; inventoryChanges?: IInventoryChanges; credits?: IMissionCredits; + AffiliationMods?: IAffiliationMods[]; + SyndicateXPItemReward?: number; } //TODO: return type of partial missioninventoryupdate response @@ -555,6 +558,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 @@ -718,7 +723,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 6e732f83..494e23f4 100644 --- a/src/types/requestTypes.ts +++ b/src/types/requestTypes.ts @@ -150,6 +150,7 @@ export interface IRewardInfo { JobStage?: number; Q?: boolean; // likely indicates that the bonus objective for this stage was completed 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";