feat(coop): permanent soldier ownership transfer + host-authoritative saves#22
Conversation
… saves Adds a co-op feature to permanently hand a soldier to the other player, backed by a rework of how co-op games are persisted. Transfer: - New "Give unit" dialog (TransferSoldierMenu) reachable from the soldier list, craft-soldier list and soldier-info screens, plus in battle. - Guest-soldier model: a soldier's object lives in its owner's save, tagged with the station base's coop id; transferring serializes it, removes it from the giver and recreates it in the receiver while keeping the same station base, so it stays where it was and shows up for the new owner. - In battle only the control flags flip immediately; the physical move is queued and applied once the mission ends. - Duplicate-delivery guard keyed on a sender-unique xfer_id (roster-based checks are unreliable: two fresh saves both number soldiers from 1 and can roll identical names). - On-screen transfer notice (TransferNoticeState), geoscape-legible, with correct player names and instant visibility during peer base visits. Save-file rework (single authority): - The host's .sav embeds the latest client-world blob, so loading a host save atomically restores BOTH rosters; the client re-fetches its world on reconnect. Loading rolls both sides' transfers back together, making trades rollback-safe. Session transfer state is reset on load. Squashed from the feature branch; test harness intentionally excluded. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
[EDIT: FIXED] Bug: Battlescape transfers do not persist, leading to the soldier being lost to both players. |
|
[EDIT: FIXED] Bug: Transferred soldier's equipped craft not persisted after first mission post-transfer |
…tle transfer Fixes two bugs in the coop soldier-transfer flow. Bug 1 (guest unassigned from host craft): the client's guest -> host-craft assignment was written only into the "basehost" blob (its copy of the host world). The mission-end own-world reload (GeoscapeState) reads the client's own-world blob, which never saw the assignment, so CoopCraft reverted to its stale value and the soldier came back unassigned. Add connectionTCP::syncOwnWorldGuestCraft() to durably mirror each guest's CoopCraft/CoopCraftType into the own-world blob, wired from SoldiersState::btnOkClick after the basehost save. Bug 2 (soldier transferred mid-battle lost entirely): at mission end the client's live save is still the host's throwaway battle world, so the deferred physical hand-off was applied there (coopBase cleared, soldier deleted by the post-battle cleanup) and pushProgressToHostSilently() then uploaded that host world as the client's own-world blob, destroying the roster. Defer soldier_yaml transfers that arrive during the mission-end / active-battle window and replay them once the own world is restored; guard pushProgressToHostSilently() and the incoming-transfer replay against the swapped-world window (mission-end, savedBattle, or a pending LoadGameState). Also, in processPendingSoldierTransfers(): auto-keep an in-battle-transferred soldier on the craft it was deployed on (unless wounded) via the guest CoopCraft mechanism, and reset a fallen transfer to a plain own-soldier so the giver's memorial records it correctly. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
sackSoldier's base-palette colors are illegible under the battle palette. In
battle, match the coop lobby window: setInterface("geoscape", true, battle) +
saveMenus element colors + applyBattlescapeTheme("saveMenus") for the uniform
battlescape theme color, high contrast and TAC00 background. Out-of-battle
behavior is unchanged (sackSoldier over basescape, palette-adopt for the notice).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Insane feature and very glad to see it. These 2 especially 👌
so as long this implementation is clean, it's so worth it 🚀 For no misunderstandings when talking about this feature, just like other games, it is good to seperate the word
Why would a soldier in the base of P1, be removed from the savefile of P1 and exist in the save of P2? Also, having everything in the base of P1, exist only in the savefile of P1 is good for simplicity/clarity and by extension, debugging. Aside of debugging crashes and bugs being made harder, one more thing it makes harder is the planned implementation of 4 players. If their save files are intertwined together, debugging them will be very hard.
Could you update this part, because with the latest commits, there is no |
Summary
Adds a co-op feature to permanently transfer soldier ownership between the
$${\color{red}NOTE:\ This\ is\ very\ likely\ a\ breaking\ change\ for\ saves.}$$
two players, backed by a rework of how co-op games are saved so those
transfers survive save/load and rollbacks.
This is squashed from the feature development branch into a single commit.
Soldier ownership transfer
TransferSoldierMenu) reachable from the soldierlist, the craft-soldier list and the soldier-info screen, plus in battle.
tagged with the station base's coop id. Transferring serialises the soldier,
removes it from the giver's save and recreates it in the receiver's save,
keeping the same station base — so it stays "in" the base it was in and shows
up when the new owner views that base.
queued and applied once the mission ends.
xfer_id. Roster-basedchecks are unreliable: two fresh saves both number soldiers from 1 and can
roll identical names, which made a genuine transfer look like a duplicate.
TransferNoticeState), geoscape-legible, withthe correct player names and instant visibility during peer base visits.
Save-file rework (single authority)
.savembeds the latest client-world blob, so loading a hostsave atomically restores both players' rosters; the client re-fetches its
world from the host on reconnect.
back in lockstep, making trades rollback-safe.
so stale in-memory state never outlives the save that is now the authority.
Notes
(
OpenXcom.exelinks, 0 errors).