Skip to content

Commit

Permalink
Sync VEF graphic customization and Vanilla Persona Weapons Expanded (#…
Browse files Browse the repository at this point in the history
…466)

Updated graphic customization component from Vanilla Expanded Framework. This includes sync worker for the dialog and a couple of types used by it, syncing confirmation button, and closing the dialog/stopping synced methods if the dialog data is no longer valid.

Vanilla Persona Weapons Expanded was also synced due to being based on graphic customization from VEF. It wasn't as straightforward as graphic customization was. The sync worker for the dialog reuses part of the code from VEF graphic customization one. It also required quite a bit more patches, for example to ensure the letter is not removed too early in MP, as well as some bug fixes for the mod itself (when selecting persona weapons without customization).
  • Loading branch information
SokyranTheDragon authored Aug 30, 2024
1 parent 537c31a commit 7faabda
Show file tree
Hide file tree
Showing 2 changed files with 398 additions and 0 deletions.
177 changes: 177 additions & 0 deletions Source/Mods/VanillaExpandedFramework.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Serialization;
using HarmonyLib;
using Multiplayer.API;
using RimWorld;
Expand Down Expand Up @@ -48,6 +49,7 @@ public VanillaExpandedFramework(ModContentPack mod)
(PatchWorkGiverDeliverResources, "Building stuff requiring non-construction skill", false),
(PatchExpandableProjectile, "Expandable projectile", false),
(PatchStaticCaches, "Static caches", false),
(PatchGraphicCustomizationDialog, "Graphic Customization Dialog", true),
];

foreach (var (patchMethod, componentName, latePatch) in patches)
Expand Down Expand Up @@ -1521,5 +1523,180 @@ private static bool NeverTimeoutDueToRealTime(ref bool __result, ref int ___Upda
}

#endregion

#region Graphic Customization Dialog

// Dialog_GraphicCustomization
private static Type graphicCustomizationDialogType;
internal static AccessTools.FieldRef<Window, ThingComp> graphicCustomizationCompField;
private static AccessTools.FieldRef<Window, ThingComp> graphicCustomizationGeneratedNamesCompField;
private static AccessTools.FieldRef<Window, Pawn> graphicCustomizationPawnField;
private static AccessTools.FieldRef<Window, IList> graphicCustomizationCurrentVariantsField;
private static AccessTools.FieldRef<Window, string> graphicCustomizationCurrentNameField;

// TextureVariant
private static SyncType variantsListType;
private static AccessTools.FieldRef<object, string> textureVariantTexNameField;
private static AccessTools.FieldRef<object, string> textureVariantTextureField;
private static AccessTools.FieldRef<object, string> textureVariantOutlineField;
private static AccessTools.FieldRef<object, object> textureVariantTextureVariantOverrideField;
private static AccessTools.FieldRef<object, float> textureVariantChanceOverrideField;

// TextureVariantOverride
private static SyncType textureVariantOverrideType;
private static AccessTools.FieldRef<object, float> textureVariantOverrideChanceField;
private static AccessTools.FieldRef<object, string> textureVariantOverrideGroupNameField;
private static AccessTools.FieldRef<object, string> textureVariantOverrideTexNameField;

private static void PatchGraphicCustomizationDialog()
{
var type = graphicCustomizationDialogType = AccessTools.TypeByName("GraphicCustomization.Dialog_GraphicCustomization");
graphicCustomizationCompField = AccessTools.FieldRefAccess<ThingComp>(type, "comp");
graphicCustomizationGeneratedNamesCompField = AccessTools.FieldRefAccess<ThingComp>(type, "compGeneratedName");
graphicCustomizationPawnField = AccessTools.FieldRefAccess<Pawn>(type, "pawn");
graphicCustomizationCurrentVariantsField = AccessTools.FieldRefAccess<IList>(type, "currentVariants");
graphicCustomizationCurrentNameField = AccessTools.FieldRefAccess<string>(type, "currentName");
// Accept customization
var method = MpMethodUtil.GetLambda(type, nameof(Window.DoWindowContents), 0);
MP.RegisterSyncMethod(method);
MpCompat.RegisterLambdaMethod(type, nameof(Window.DoWindowContents), 0);
MP.RegisterSyncWorker<Window>(SyncGraphicCustomizationDialog, type, true);
MpCompat.harmony.Patch(AccessTools.DeclaredMethod(type, "DrawConfirmButton"),
prefix: new HarmonyMethod(CloseGraphicCustomizationDialogOnAccept));
MpCompat.harmony.Patch(AccessTools.DeclaredMethod(type, nameof(Window.DoWindowContents)),
prefix: new HarmonyMethod(CloseGraphicCustomizationWhenNoLongerValid));

type = AccessTools.TypeByName("GraphicCustomization.TextureVariant");
variantsListType = typeof(List<>).MakeGenericType(type);
textureVariantTexNameField = AccessTools.FieldRefAccess<string>(type, "texName");
textureVariantTextureField = AccessTools.FieldRefAccess<string>(type, "texture");
textureVariantOutlineField = AccessTools.FieldRefAccess<string>(type, "outline");
textureVariantChanceOverrideField = AccessTools.FieldRefAccess<float>(type, "chanceOverride");
textureVariantTextureVariantOverrideField = AccessTools.FieldRefAccess<object>(type, "textureVariantOverride");
MP.RegisterSyncWorker<object>(SyncTextureVariant, type, shouldConstruct: true);

textureVariantOverrideType = type = AccessTools.TypeByName("GraphicCustomization.TextureVariantOverride");
textureVariantOverrideChanceField = AccessTools.FieldRefAccess<float>(type, "chance");
textureVariantOverrideGroupNameField = AccessTools.FieldRefAccess<string>(type, "groupName");
textureVariantOverrideTexNameField = AccessTools.FieldRefAccess<string>(type, "texName");
MP.RegisterSyncWorker<object>(SyncTextureVariantOverride, type, shouldConstruct: true);
}

private static void CloseGraphicCustomizationDialogOnAccept(Window __instance, ref Action action)
{
// The action is a synced method, and it handles closing the dialog. Since
// the method ends up being synced the closing does not happen. This will
// ensure that the dialog is closed when the accept button is pressed
// and the proper method will be synced as well.
var nonRefAction = action;
action = (() => __instance.Close()) + nonRefAction;
}

private static void CloseGraphicCustomizationWhenNoLongerValid(Window __instance, ThingComp ___comp, Pawn ___pawn)
{
// Since the dialog doesn't pause, ensure we close it if the comp's parent
// or the pawn ever get destroyed, despawned, or end up on different maps.
if (MP.IsInMultiplayer && !IsGraphicCustomizationDialogValid(___comp, ___pawn))
__instance.Close();
}

private static bool CancelGraphicCustomizationExecutionIfNoLongerValid(ThingComp ___comp, Pawn ___pawn)
=> !MP.IsInMultiplayer || IsGraphicCustomizationDialogValid(___comp, ___pawn);

private static bool IsGraphicCustomizationDialogValid(ThingComp comp, Pawn pawn)
=> comp.parent != null && pawn != null &&
comp.parent.Spawned && pawn.Spawned &&
!comp.parent.Destroyed && !pawn.Destroyed &&
!pawn.Dead && comp.parent.Map == pawn.Map;

private static void SyncGraphicCustomizationDialog(SyncWorker sync, ref Window dialog)
{
ThingComp comp = null;

if (sync.isWriting)
{
sync.Write(graphicCustomizationCompField(dialog));
}
else
{
// Skip constructor since it has a bunch of initialization we don't care about.
dialog = (Window)FormatterServices.GetUninitializedObject(graphicCustomizationDialogType);

comp = sync.Read<ThingComp>();
graphicCustomizationCompField(dialog) = comp;
}

SyncGraphicCustomizationDialog(sync, ref dialog, comp);
}

internal static void SyncGraphicCustomizationDialog(SyncWorker sync, ref Window dialog, ThingComp graphicCustomizationComp)
{
if (sync.isWriting)
{
sync.Write(graphicCustomizationPawnField(dialog));
sync.Write(graphicCustomizationCurrentVariantsField(dialog), variantsListType);
sync.Write(graphicCustomizationCurrentNameField(dialog));
}
else
{
graphicCustomizationGeneratedNamesCompField(dialog) = graphicCustomizationComp?.parent.GetComp<CompGeneratedNames>();
graphicCustomizationPawnField(dialog) = sync.Read<Pawn>();
graphicCustomizationCurrentVariantsField(dialog) = sync.Read<IList>(variantsListType);
graphicCustomizationCurrentNameField(dialog) = sync.Read<string>();
}
}

private static void SyncTextureVariant(SyncWorker sync, ref object variant)
{
if (sync.isWriting)
{
if (variant == null)
{
sync.Write(false);
return;
}

sync.Write(true);

sync.Write(textureVariantTexNameField(variant));
sync.Write(textureVariantTextureField(variant));
sync.Write(textureVariantOutlineField(variant));
sync.Write(textureVariantTextureVariantOverrideField(variant), textureVariantOverrideType);
sync.Write(textureVariantChanceOverrideField(variant));
}
else if (sync.Read<bool>())
{
textureVariantTexNameField(variant) = sync.Read<string>();
textureVariantTextureField(variant) = sync.Read<string>();
textureVariantOutlineField(variant) = sync.Read<string>();
textureVariantTextureVariantOverrideField(variant) = sync.Read<object>(textureVariantOverrideType);
textureVariantChanceOverrideField(variant) = sync.Read<float>();
}
}

private static void SyncTextureVariantOverride(SyncWorker sync, ref object variantOverride)
{
if (sync.isWriting)
{
if (variantOverride == null)
{
sync.Write(false);
return;
}

sync.Write(true);
sync.Write(textureVariantOverrideChanceField(variantOverride));
sync.Write(textureVariantOverrideGroupNameField(variantOverride));
sync.Write(textureVariantOverrideTexNameField(variantOverride));
}
else if (sync.Read<bool>())
{
textureVariantOverrideChanceField(variantOverride) = sync.Read<float>();
textureVariantOverrideGroupNameField(variantOverride) = sync.Read<string>();
textureVariantOverrideTexNameField(variantOverride) = sync.Read<string>();
}
}

#endregion
}
}
Loading

0 comments on commit 7faabda

Please sign in to comment.