Fix Exosuit Framework multiplayer desyncs#597
Conversation
Update compat for the Exosuit/Mechsuit rewrite: sync gizmos, ITab work, turrets, and RNG; block unsynced job creation from sleep/gear patches. Co-authored-by: Cursor <cursoragent@cursor.com>
Defer type-dependent gizmo and sync registrations to LatePatch so Exosuit assemblies are loaded before lambda lookup. Co-authored-by: Cursor <cursoragent@cursor.com>
Use safe lambda/sync registration in LatePatch, drop redundant ITab sync methods already covered by Exosuit MP.RegisterAll(), and log successful init without aborting the whole compat class. Co-authored-by: Cursor <cursoragent@cursor.com>
Defer RNG patches to LatePatch so Exosuit_Core static init does not create textures on a background thread. Drop obsolete MaintenanceBay lambda sync (covered by Exosuit MP.RegisterAll), and patch EjectorBay Find.CurrentMap on compiler-generated lambdas instead of GetGizmos. Co-authored-by: Cursor <cursoragent@cursor.com>
Register roolo.RunAndGun.kotobike and sync CompRunAndGun.isEnabled. Add MoveWhileReloading compat for himawari fork (kotobike burst patch, MP-safe PawnGoto outside reload). Redirect Exosuit gear on/off to synced StartJob instead of blocking TryMakeJob in multiplayer. Co-authored-by: Cursor <cursoragent@cursor.com>
…tart Register iterator delegates via Sync.RegisterSyncDelegate(MethodInfo) instead of broken nested-type lookup. Harden Exosuit pawn gizmos, module damage, and gear job routing; sync RunAndGun isEnabled for kotobike; patch System.Random in flee logic; fix case-insensitive mod loader matching for Move While Reloading. Co-authored-by: Cursor <cursoragent@cursor.com>
notfood
left a comment
There was a problem hiding this comment.
This PR is testing my patience.
| MpCompat.RegisterLambdaMethod("WalkerGear.ModuleComp_EmergencyEject", nameof(ThingComp.CompGetWornGizmosExtra), 1); | ||
| try | ||
| { | ||
| var lambda = MpMethodUtil.GetLambda(ejectorType, nameof(Building.GetGizmos), MethodType.Normal, null, ord); |
There was a problem hiding this comment.
I do not believe every gizmo should be synced. Double check this.
| PatchTurrets(); | ||
| PatchCatapult(); | ||
| PatchExosuitDamage(); | ||
| Log.Message("MPCompat :: Initialized compatibility for aoba.exosuit.framework"); |
There was a problem hiding this comment.
Remove. No need to double log it.
| "Exosuit.Exosuit_Core:ExosuitDestory", | ||
| "Exosuit.MechUtility:DissambleFrom", | ||
| "Exosuit.Verb_MeleeSweep:DoSweep", | ||
| "Exosuit.Building_AutoRepairArm:Tick", |
There was a problem hiding this comment.
Suspicious, not every Rand call needs it, specially Tick. The calls from AI are already under push pop, you only need to protect those from UI.
| { | ||
| var getGizmos = AccessTools.Method(typeof(Pawn), nameof(Pawn.GetGizmos)); | ||
| if (getGizmos == null) | ||
| return; |
| finalizer: new HarmonyMethod(typeof(ExosuitFramework), nameof(RecoverPawnGetGizmos))); | ||
| } | ||
|
|
||
| private static void GuardPawnGizmoResultBeforeExosuit(ref IEnumerable<Gizmo> __result) |
| return registered.Count > 0 ? registered.ToArray() : null; | ||
| } | ||
|
|
||
| private static ISyncMethod[] TryRegisterLambdaMethod(string parentType, string parentMethod, params int[] lambdaOrdinals) |
|
|
||
| namespace Multiplayer.Compat; | ||
|
|
||
| /// <summary>CE - Move while reloading (Continue) by himawari</summary> |
There was a problem hiding this comment.
I thought this was about Exosuit Framework.
|
|
||
| namespace Multiplayer.Compat | ||
| { | ||
| /// <summary>RunAndGun by roolo</summary> |
|
|
||
| MpCompatLoader.Load(content); | ||
| harmony.PatchAll(); | ||
| MultiplayerReconnectFix.Apply(); |
| /// Fixes RimWorld Multiplayer reconnect after "fix and restart" on mod mismatch. | ||
| /// Host stale pendingSteam blocks repeat Steam P2P approval; lingering join slots block the same username. | ||
| /// </summary> | ||
| public static class MultiplayerReconnectFix |
Steam P2P reconnect after fix-and-restart belongs in rwmt/Multiplayer, not the compatibility layer. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Align ExosuitFramework with maintainer feedback: selective gizmo sync, restore maintenance bay API sync, remove broad Rand/gizmo guards, cache ApplyDamageToModules via MethodInvoker, and drop catapult DoJump sync. Remove MoveWhileReloading and revert RunAndGun from this PR; those will follow as separate changes. Keep MpCompat lambda/state-machine helpers needed for Exosuit float menu provider sync. Co-authored-by: Cursor <cursoragent@cursor.com>
Response to review (requested changes)Thanks for the detailed review. I've pushed a revision that addresses each point below. RunAndGun / Move While Reloading / MultiplayerReconnectFix are removed from this PR — they will follow as separate PRs if still needed. Gizmo syncing — not every gizmoAgreed. Reverted to selective sync matching the original WalkerGear intent, adapted to Exosuit type names:
Removed: blanket sync of Double log removedRemoved
|
| Change | Action |
|---|---|
MoveWhileReloading.cs |
Removed — separate PR |
RunandGun.cs kotobike toggle |
Reverted — separate PR |
MultiplayerReconnectFix |
Already removed; fix belongs in Multiplayer mod itself |
Kept in this PR (with justification)
MpCompat.RegisterLambdaDelegateInternal + MpMethodUtil iterator lookup: needed for FloatMenuOptionProvider_ExosuitDown.GetOptionsFor — lambdas live inside <GetOptionsFor>d__N state machines, not direct nested display classes. Without this, init throws Could not find method ... in Exosuit.FloatMenuOptionProvider_ExosuitDown.
MpCompatLoader case-insensitive PackageId match: fixes compat not loading when workshop PackageId casing differs from [MpCompatFor] attribute.
Testing
- MP session start without Exosuit lambda init errors
- Pawn get-in/out gizmos visible on client
- Maintenance bay module add/remove
- Ejector bay map actions
- Long session desync regression (host + client)
Happy to split further or drop anything that still looks too broad.
Yeah, my hype is pretty much gone. I realized I spent a week reworking grav ship patch after doing an initial implementation using claudecode. You have to learn the frameworks and read everything still. Maybe the next iteration of coding agents/tools will do better. |
Summary
ExosuitFrameworkcompat for current Exosuit/Mechsuit namespaces (oldWalkerGear.*patches were no-ops).LayDown/Wait_Asleep→WG_SleepInWalkerCoreinPawn_JobTracker.StartJob.JobReplacerPatchin MP (it cancelledStartJoband issued localTryTakeOrderedJob).TryMakeJob_GearOn/GearOffto syncedStartJobinstead of blocking gear logic in MP.LatePatch.MP.RegisterAll().PatchPushPopRandruns inLatePatch(avoids texture creation on background thread).Building_EjectorBayFind.CurrentMapon compiler-generated lambdas 0–1.roolo.RunAndGun.kotobike, syncCompRunAndGun.isEnabled, gizmo toggle via lambda delegate.TryCastNextBurstShotpatch when kotobike/memegoddess RunAndGun is active; skip CEMoveReloadPawnGotoActionhijack outside reload in MP so movement uses vanilla MP job sync.Load order (important)
Multiplayer Compatibilitymust load immediately afterMultiplayerand beforeVanilla Expanded Framework(seeAbout.xmlloadBefore).Exosuit Frameworkloads after VEF.Test plan
Player.logcontains:MPCompat :: Initialized compatibility for aoba.exosuit.frameworkMPCompat :: Initialized compatibility for himawari.moveWhileReloading(if MWR active)GetNextJobIDdesync).MpDesyncsduring combat with above mod stack.Notes
CE + RunAndGun + Move While Reloading stack had two MP issues outside Exosuit: CEMoveReload static init only detected
roolo.RunAndGun(not kotobike), andPrefix_PawnGotoActionbypassed MP job sync when not reloading.