From dc34233e40544b12f58a24cb1c0371aeabf47841 Mon Sep 17 00:00:00 2001 From: neckfire Date: Wed, 15 Jul 2026 01:54:25 +0200 Subject: [PATCH] fix(contracts): persist raid-zone objective progress (client-hosted raids) Symptom: missions done in raid zones weren't saved -> contracts stuck on 'Objective not met', no claim possible. Root cause: client-hosted raids have no dedicated game server, so UpdatePlayerActiveContracts is never called (confirmed: 0 calls in prod logs). Only Kills and OwnNumOfItem objectives were handled; DeadDrop/VisitArea/ LootContainer had Progress[i]=0 forever -> unclaimable and not shown at station. Fix: EYContractObjectiveType.IsRaidRuntime() groups the objectives that can only be observed in a live raid (Kills/DeadDrop/VisitArea/LootContainer). Auto-credit them on deploy (EnterMatchmakingMatch, persisted) so the station shows them complete, and accept them in ClaimActiveContract. OwnNumOfItem still validated against the real stash; FactionLevel/CompletedMission unchanged. Added a defensive guard against a Progress array shorter than the objective list. Co-Authored-By: Claude Opus 4.8 --- .../Models/Title/ContractInfo.cs | 15 +++++++++++++++ .../Functions/ClaimActiveContract.cs | 17 +++++++++-------- .../Functions/EnterMatchmakingMatch.cs | 15 ++++++++++----- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/Prospect.Server.Api/Models/Title/ContractInfo.cs b/src/Prospect.Server.Api/Models/Title/ContractInfo.cs index 8417b9e..6e47db2 100644 --- a/src/Prospect.Server.Api/Models/Title/ContractInfo.cs +++ b/src/Prospect.Server.Api/Models/Title/ContractInfo.cs @@ -72,6 +72,21 @@ public enum EYContractObjectiveType { MAX = 8 } +public static class ContractObjectiveTypeExtensions { + // Objectives that can only be observed while a raid is running (kills, dead-drops, + // visiting an area, looting a container). The raid is hosted by the player's own + // client and there is no dedicated game server, so their progress is never reported + // to the backend (UpdatePlayerActiveContracts is never called). They are therefore + // auto-credited server-side, otherwise these contracts can never be completed. + // OwnNumOfItem is excluded (validated against the real stash) and so are the meta + // objectives FactionLevel / CompletedMission (validated from persisted player data). + public static bool IsRaidRuntime(this EYContractObjectiveType type) => + type is EYContractObjectiveType.Kills + or EYContractObjectiveType.DeadDrop + or EYContractObjectiveType.VisitArea + or EYContractObjectiveType.LootContainer; +} + public enum EYContractDifficulty { Invalid = 0, diff --git a/src/Prospect.Server.Api/Services/CloudScript/Functions/ClaimActiveContract.cs b/src/Prospect.Server.Api/Services/CloudScript/Functions/ClaimActiveContract.cs index 5796102..5aa031e 100644 --- a/src/Prospect.Server.Api/Services/CloudScript/Functions/ClaimActiveContract.cs +++ b/src/Prospect.Server.Api/Services/CloudScript/Functions/ClaimActiveContract.cs @@ -131,16 +131,17 @@ public class ClaimActiveContract : ICloudScriptFunction 0) { diff --git a/src/Prospect.Server.Api/Services/CloudScript/Functions/EnterMatchmakingMatch.cs b/src/Prospect.Server.Api/Services/CloudScript/Functions/EnterMatchmakingMatch.cs index 5bf6316..1e2967b 100644 --- a/src/Prospect.Server.Api/Services/CloudScript/Functions/EnterMatchmakingMatch.cs +++ b/src/Prospect.Server.Api/Services/CloudScript/Functions/EnterMatchmakingMatch.cs @@ -62,12 +62,17 @@ public class EnterMatchmakingMatchFunction : ICloudScriptFunction= contractActive.Progress.Length) { + // Defensive: player progress array shorter than the objective list. + break; + } var objective = contract.Objectives[i]; - // Kill objectives: the client-hosted raid has no dedicated game server - // to report per-kill progress to the backend, so kills would never be - // saved. Auto-credit them on deploy so the objective can be completed - // (mirrors the player-kill auto-credit done in ActivateContract). - if (objective.Type == EYContractObjectiveType.Kills) { + // Raid-runtime objectives (kills, dead-drops, visited areas, looted + // containers) are simulated inside the client-hosted raid; with no + // dedicated game server their progress is never reported to the backend, + // so they would never be saved. Auto-credit them on deploy so the + // objective persists and the station shows the contract as completable. + if (objective.Type.IsRaidRuntime()) { contractActive.Progress[i] = objective.MaxProgress; continue; }