chore(game-server): merge main (emulator fixes: contracts, shop, fortuna, friends, admin, presence) #16

Merged
neckfire merged 35 commits from main into game-server 2026-07-16 08:25:21 +00:00
3 changed files with 34 additions and 13 deletions
Showing only changes of commit dc34233e40 - Show all commits
@@ -72,6 +72,21 @@ public enum EYContractObjectiveType {
MAX = 8 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 public enum EYContractDifficulty
{ {
Invalid = 0, Invalid = 0,
@@ -131,16 +131,17 @@ public class ClaimActiveContract : ICloudScriptFunction<FYClaimCompletedActiveCo
break; break;
} }
} }
} else if (objective.Type == EYContractObjectiveType.Kills) { } else if (objective.Type.IsRaidRuntime()) {
// Kill objectives can't be tracked in a client-hosted raid (no dedicated // Raid-runtime objectives (kills, dead-drops, visited areas, looted
// game server reports kills). They are auto-credited on deploy // containers) can't be tracked in a client-hosted raid — no dedicated
// (EnterMatchmakingMatch); accept them here even if a contract was // game server reports them, so their progress is never saved. They are
// activated before that so kill contracts remain completable. // auto-credited on deploy (EnterMatchmakingMatch); accept them here too
// (safety net for a contract activated after deploy) so these contracts
// remain completable instead of being stuck on "Objective not met".
remaining = 0; remaining = 0;
} else { } else {
// NOTE: A game server must write the correct progress // Meta objectives (FactionLevel / CompletedMission): validated from the
// for other types of objectives. // persisted progress. The objectives are mapped by array index.
// The objectives are mapped by array index.
remaining -= targetContract.Progress[i]; remaining -= targetContract.Progress[i];
} }
if (remaining > 0) { if (remaining > 0) {
@@ -62,12 +62,17 @@ public class EnterMatchmakingMatchFunction : ICloudScriptFunction<FYEnterMatchAz
continue; continue;
} }
for (var i = 0; i < contract.Objectives.Length; i++) { for (var i = 0; i < contract.Objectives.Length; i++) {
if (i >= contractActive.Progress.Length) {
// Defensive: player progress array shorter than the objective list.
break;
}
var objective = contract.Objectives[i]; var objective = contract.Objectives[i];
// Kill objectives: the client-hosted raid has no dedicated game server // Raid-runtime objectives (kills, dead-drops, visited areas, looted
// to report per-kill progress to the backend, so kills would never be // containers) are simulated inside the client-hosted raid; with no
// saved. Auto-credit them on deploy so the objective can be completed // dedicated game server their progress is never reported to the backend,
// (mirrors the player-kill auto-credit done in ActivateContract). // so they would never be saved. Auto-credit them on deploy so the
if (objective.Type == EYContractObjectiveType.Kills) { // objective persists and the station shows the contract as completable.
if (objective.Type.IsRaidRuntime()) {
contractActive.Progress[i] = objective.MaxProgress; contractActive.Progress[i] = objective.MaxProgress;
continue; continue;
} }