Skip to content

Fix Exosuit Framework multiplayer desyncs#597

Open
1Anton10 wants to merge 9 commits into
rwmt:masterfrom
1Anton10:fix/exosuit-framework-mp-compat
Open

Fix Exosuit Framework multiplayer desyncs#597
1Anton10 wants to merge 9 commits into
rwmt:masterfrom
1Anton10:fix/exosuit-framework-mp-compat

Conversation

@1Anton10

@1Anton10 1Anton10 commented Jun 23, 2026

Copy link
Copy Markdown

Summary

  • Rewrite ExosuitFramework compat for current Exosuit/Mechsuit namespaces (old WalkerGear.* patches were no-ops).
  • Sync gizmos, float menus, turrets, catapult jump, and RNG usage in combat/repair.
  • Fix multiplayer desyncs from unsynced job creation:
    • Rewrite LayDown/Wait_AsleepWG_SleepInWalkerCore in Pawn_JobTracker.StartJob.
    • Block JobReplacerPatch in MP (it cancelled StartJob and issued local TryTakeOrderedJob).
    • Redirect TryMakeJob_GearOn/GearOff to synced StartJob instead of blocking gear logic in MP.
  • Fix startup crash: defer type-dependent lambda/sync registration to LatePatch.
  • Harden init: skip missing methods/lambdas with warnings; avoid duplicating ITab sync covered by Exosuit MP.RegisterAll().
  • Thread safety: PatchPushPopRand runs in LatePatch (avoids texture creation on background thread).
  • Gizmos: patch Building_EjectorBay Find.CurrentMap on compiler-generated lambdas 0–1.
  • RunAndGun kotobike fork: register roolo.RunAndGun.kotobike, sync CompRunAndGun.isEnabled, gizmo toggle via lambda delegate.
  • CE Move While Reloading (himawari): apply missing TryCastNextBurstShot patch when kotobike/memegoddess RunAndGun is active; skip CEMoveReload PawnGotoAction hijack outside reload in MP so movement uses vanilla MP job sync.

Load order (important)

Multiplayer Compatibility must load immediately after Multiplayer and before Vanilla Expanded Framework (see About.xml loadBefore). Exosuit Framework loads after VEF.

Multiplayer → Multiplayer Compatibility → HugsLib → VEF → Exosuit Framework → …

Test plan

  • Load order: MP → MpCompat → HugsLib → VEF → Exosuit (no red warning on MpCompat).
  • Player.log contains:
    • MPCompat :: Initialized compatibility for aoba.exosuit.framework
    • MPCompat :: Initialized compatibility for himawari.moveWhileReloading (if MWR active)
  • Exosuit: get in/out, modules ITab, turret, sleep (no GetNextJobID desync).
  • CE + RunAndGun kotobike + Move While Reloading + Simple Sidearms: draft, shoot, reload while moving, sidearm swap.
  • Host + client: no new MpDesyncs during 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), and Prefix_PawnGotoAction bypassed MP job sync when not reloading.

1Anton10 and others added 6 commits June 23, 2026 16:40
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 notfood left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not believe every gizmo should be synced. Double check this.

Comment thread Source/Mods/ExosuitFramework.cs Outdated
PatchTurrets();
PatchCatapult();
PatchExosuitDamage();
Log.Message("MPCompat :: Initialized compatibility for aoba.exosuit.framework");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove. No need to double log it.

Comment thread Source/Mods/ExosuitFramework.cs Outdated
"Exosuit.Exosuit_Core:ExosuitDestory",
"Exosuit.MechUtility:DissambleFrom",
"Exosuit.Verb_MeleeSweep:DoSweep",
"Exosuit.Building_AutoRepairArm:Tick",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread Source/Mods/ExosuitFramework.cs Outdated
{
var getGizmos = AccessTools.Method(typeof(Pawn), nameof(Pawn.GetGizmos));
if (getGizmos == null)
return;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return with no warning?

Comment thread Source/Mods/ExosuitFramework.cs Outdated
finalizer: new HarmonyMethod(typeof(ExosuitFramework), nameof(RecoverPawnGetGizmos)));
}

private static void GuardPawnGizmoResultBeforeExosuit(ref IEnumerable<Gizmo> __result)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason for this?

Comment thread Source/Mods/ExosuitFramework.cs Outdated
return registered.Count > 0 ? registered.ToArray() : null;
}

private static ISyncMethod[] TryRegisterLambdaMethod(string parentType, string parentMethod, params int[] lambdaOrdinals)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok that's enough for me.

Comment thread Source/Mods/MoveWhileReloading.cs Outdated

namespace Multiplayer.Compat;

/// <summary>CE - Move while reloading (Continue) by himawari</summary>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought this was about Exosuit Framework.

Comment thread Source/Mods/RunandGun.cs

namespace Multiplayer.Compat
{
/// <summary>RunAndGun by roolo</summary>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RunAndGun?

Comment thread Source/MpCompat.cs Outdated

MpCompatLoader.Load(content);
harmony.PatchAll();
MultiplayerReconnectFix.Apply();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No

Comment thread Source/MultiplayerReconnectFix.cs Outdated
/// 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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No.

1Anton10 and others added 3 commits June 24, 2026 02:40
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>
@1Anton10

Copy link
Copy Markdown
Author

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 gizmo

Agreed. Reverted to selective sync matching the original WalkerGear intent, adapted to Exosuit type names:

Target Ordinals Why
Patch_Pawn_GetGizmos.Postfix 0, 1 Player get-in / get-out buttons
Building_EjectorBay.GetGizmos 0, 1 Eject to tile / launch to map (CurrentMap)
Building_MaintenanceBay.GetGizmos 0, 2 Get in / toggle auto-repair
ModuleComp_EmergencyEject.CompGetWornGizmosExtra 1 Safety toggle (eject goes through synced Eject())

Removed: blanket sync of CompModuleWeapon (0,1,2), CompTurretGun.GetGizmos (0–3), ejector lambda 2 (release → Eject()).


Double log removed

Removed Log.Message("MPCompat :: Initialized compatibility for aoba.exosuit.framework"). Loader already logs compat init.


PatchPushPopRand removed

Removed entirely. Combat / tick paths (Tick, TryCastShot, damage methods) are already under MP's RNG push/pop from AI. No UI-only Rand desync was reproduced after fixing job/gizmo sync. Will add back only a specific UI entry point if testing shows one.


PatchExosuitPawnGetGizmosSafety removed

Removed the Pawn.GetGizmos guard + finalizer. This was a workaround for Exosuit's postfix calling __result.ToList() on null; with selective gizmo sync the crash no longer reproduces in our tests. If it returns on a specific Exosuit version we will patch Exosuit's postfix directly instead of globally wrapping Pawn.GetGizmos.


Maintenance bay / float menu sync restored

Restored from upstream compat (renamed types):

  • MP.RegisterSyncMethod(Building_MaintenanceBay, "AddOrReplaceModule")
  • MP.RegisterSyncMethod(Building_MaintenanceBay, "RemoveModules")
  • Float menu lambdas + FloatMenuOptionProvider_ExosuitDown

Exosuit_Core:Eject registered in PatchJobs (same role as old WalkerGear_Core:Eject).


ApplyDamageToModules — fast delegate

Replaced per-hit AccessTools.Method + Invoke with MethodInvoker.GetHandler cached at init on Exosuit_Core.ApplyDamageToModules.


PatchCatapult — no DoJump

Removed WG_AbilityVerb_QuickJump.DoJump sync. Abilities are already synced by MP; syncing one overload of four was incorrect. Kept only WG_PawnFlyer.GetOptionsForTile (0, 1) for catapult tile picker UI.


TryRegisterLambdaMethod helpers removed

Removed local wrappers; call MpCompat.RegisterLambdaMethod / RegisterLambdaDelegate directly like other compat classes.


Out of scope for this PR

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.

@sviyh

sviyh commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

This PR is testing my patience.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants