Skip to content

Fix RegisterSyncField(Type, string) to preserve caller targetType (#880)#881

Open
sviyh wants to merge 1 commit intorwmt:devfrom
sviyh:fix/registersyncfield-targettype
Open

Fix RegisterSyncField(Type, string) to preserve caller targetType (#880)#881
sviyh wants to merge 1 commit intorwmt:devfrom
sviyh:fix/registersyncfield-targettype

Conversation

@sviyh
Copy link
Copy Markdown

@sviyh sviyh commented Apr 12, 2026

Summary

Fixes #880.

MP.RegisterSyncField(Type, string) currently routes through RegisterSyncField(FieldInfo), which keys everything off FieldInfo.ReflectedType. Because AccessTools.Field walks the inheritance chain via Type.GetField, the returned FieldInfo for a private field declared on a base class comes back with ReflectedType collapsed to the base type — silently discarding the concrete Type the caller passed in.

Downstream, SyncField stores that base targetType (SyncField.cs L24-30), and DoSync serializes target as the base type (L47-52). Any SyncWorker registered against the concrete subclass then fails to match, because the wire format now references the base.

Concrete case that surfaced this: Gizmo_Slider.targetValuePct is declared on the base Gizmo_Slider. A compat patch calling MP.RegisterSyncField(concreteSubclass, \"targetValuePct\") ended up registered against Gizmo_Slider instead, breaking the subclass SyncWorker lookup. The Vanilla Gravship Expanded compat patch currently works around this with a reflection SetValue on the private targetType field (rwmt/Multiplayer-Compatibility#577); that workaround can be removed once this ships.

Fix

  • RegisterSyncField(Type, string) now looks up the FieldInfo only to validate existence and branch on static, but passes the caller's targetType straight through to Sync.Field and the memberPath key. The internal Sync.Field(Type, string, string) path already accepted an arbitrary Type — it just wasn't reachable from the public compat API.
  • RegisterSyncField(FieldInfo) is collapsed into a one-line delegate to (field.ReflectedType, field.Name), preserving its prior behavior exactly and eliminating the duplicated switch.
  • Style matches the surrounding file: explicit FieldInfo declaration, ?? throw new Exception(...) as in Sync.Method, K&R braces.

No behavior change for callers whose targetType equals the declaring type (the common case), or for any caller of the FieldInfo overload.

Test plan

  • dotnet build Source/Client/Multiplayer.csproj -c Release — 0 errors
  • In-game: VGE oxygen regulator slider syncs across clients without the compat-side SetValue workaround
  • Regression spot-check of existing RegisterSyncField(Type, string) callers in Multiplayer-Compatibility (Pharmacist, AnimalTab, SRTS Expanded, VEF autocast) — where targetType already equals the declaring type, behavior is unchanged
  • Regression spot-check of RegisterSyncField(FieldInfo) callers (CommonSense, VanillaHairExpanded) — routes through the same path as before via ReflectedType

AccessTools.Field walks the inheritance chain, so the returned
FieldInfo.ReflectedType collapses to the base type that declared the
field. Routing the (Type, string) overload through RegisterSyncField(
FieldInfo) silently discarded the caller's concrete Type, causing
SyncField to store the base targetType and breaking SyncWorker lookups
registered on the concrete subclass (rwmt#880).

Keep the caller's Type for the instance path; use the FieldInfo only
for existence validation and the static branch. Collapse the
RegisterSyncField(FieldInfo) overload into a one-line delegate through
(field.ReflectedType, field.Name), preserving its prior behavior.
@notfood notfood changed the base branch from master to dev April 13, 2026 16:01
@notfood notfood added enhancement New feature or request. 1.6 Fixes or bugs relating to 1.6 (Not Odyssey). labels Apr 13, 2026
@notfood notfood moved this to In review in 1.6 and Odyssey Apr 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

1.6 Fixes or bugs relating to 1.6 (Not Odyssey). enhancement New feature or request.

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

Registering sync field thhrough API doesn't allow for specifying the exact type of the target

2 participants