Skip to content

Commit

Permalink
Version 0.10.3, update to RW 1.5.4104
Browse files Browse the repository at this point in the history
Add notice about current 1.5/DLC compatibility
Fix errors caused by changes to Building_Bed in the latest RW version
Fix desyncs caused by a cache in ListerHaulables.cellCycleIndices
Use finalizers for RandPatches and SetMapTime
Rename AllImplementationsOrdered to AllInterfaceImplementationsOrdered
Move DontClearDialogBeginRitualCache patch to Rituals.cs
Remove red coloring from text in About.xml (some people mistook it for an error)
Further attempts at fixing rituals (WIP)
  • Loading branch information
Zetrith committed May 24, 2024
1 parent 0cf13ed commit 55dae5f
Show file tree
Hide file tree
Showing 24 changed files with 169 additions and 101 deletions.
4 changes: 2 additions & 2 deletions About/About.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
</supportedVersions>
<author>RimWorld Multiplayer Team</author>
<url>https://github.com/rwmt/Multiplayer</url>
<description>&lt;color=red&gt;&lt;b&gt;Important: &lt;/b&gt; This mod should be placed right below Core and expansions in the mod list to work properly!
Requires Rimworld >= v1.5.4034&lt;/color&gt;\n
<description>&lt;b&gt;Important: This mod should be placed right below Core and expansions in the mod list to work properly!
Requires RimWorld >= 1.5.4104&lt;/b&gt;\n
Multiplayer mod for RimWorld.

FAQ - https://hackmd.io/@rimworldmultiplayer/docs/
Expand Down
2 changes: 1 addition & 1 deletion Source/Client/AsyncTime/SetMapTime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ internal static void Prefix(ref TimeSnapshot? __state)
}

[HarmonyPriority(MpPriority.MpLast)]
internal static void Postfix(TimeSnapshot? __state) => __state?.Set();
internal static void Finalizer(TimeSnapshot? __state) => __state?.Set();
}

[HarmonyPatch]
Expand Down
2 changes: 1 addition & 1 deletion Source/Client/EarlyInit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ internal static void EarlyPatches(Harmony harmony)
harmony.PatchMeasure(
AccessTools.Constructor(typeof(Def), Type.EmptyTypes),
new HarmonyMethod(typeof(RandPatches), nameof(RandPatches.Prefix)),
new HarmonyMethod(typeof(RandPatches), nameof(RandPatches.Postfix))
finalizer: new HarmonyMethod(typeof(RandPatches), nameof(RandPatches.Finalizer))
);

Assembly.GetCallingAssembly().GetTypes().Do(type =>
Expand Down
37 changes: 0 additions & 37 deletions Source/Client/Factions/MultifactionPatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -359,43 +359,6 @@ static void PlaySoundReplacement(SoundDef sound, Map map)
}
}

[HarmonyPatch]
static class DontClearDialogBeginRitualCache
{
private static MethodInfo listClear = AccessTools.Method(typeof(List<Precept_Role>), "Clear");

static IEnumerable<MethodBase> TargetMethods()
{
yield return typeof(Dialog_BeginRitual).GetConstructors(BindingFlags.Public | BindingFlags.Instance).First();
}

static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> insts, ILGenerator gen)
{
var list = insts.ToList();
var brLabel = gen.DefineLabel();

foreach (var inst in list)
{
if (inst.operand == listClear)
{
yield return new CodeInstruction(OpCodes.Call,
AccessTools.Method(typeof(DontClearDialogBeginRitualCache), nameof(ShouldCancelCacheClear)));
yield return new CodeInstruction(OpCodes.Brfalse, brLabel);
yield return new CodeInstruction(OpCodes.Pop);
yield return new CodeInstruction(OpCodes.Ret);
yield return new CodeInstruction(OpCodes.Nop) { labels = { brLabel } };
}

yield return inst;
}
}

static bool ShouldCancelCacheClear()
{
return Multiplayer.Ticking || Multiplayer.ExecutingCmds;
}
}

[HarmonyPatch(typeof(Apparel), nameof(Apparel.WornGraphicPath), MethodType.Getter)]
static class ApparelWornGraphicPathGetterPatch
{
Expand Down
2 changes: 1 addition & 1 deletion Source/Client/Multiplayer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Lib.Harmony" Version="2.3.3" ExcludeAssets="runtime" />
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.5.4062" />
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.5.4104-beta" />
<PackageReference Include="RestSharp" Version="106.12.0" />
<PackageReference Include="RimWorld.MultiplayerAPI" Version="0.5.0" />
</ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions Source/Client/MultiplayerGame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public MultiplayerGame()
District.nextDistrictID = 1;
Region.nextId = 1;
ListerHaulables.groupCycleIndex = 0;
ListerHaulables.cellCycleIndices.Clear();

ZoneColorUtility.nextGrowingZoneColorIndex = 0;
ZoneColorUtility.nextStorageZoneColorIndex = 0;
Expand Down
10 changes: 5 additions & 5 deletions Source/Client/MultiplayerStatic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ void TryPatch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod po
// Remove side effects from methods which are non-deterministic during ticking (e.g. camera dependent motes and sound effects)
{
var randPatchPrefix = new HarmonyMethod(typeof(RandPatches), nameof(RandPatches.Prefix));
var randPatchPostfix = new HarmonyMethod(typeof(RandPatches), nameof(RandPatches.Postfix));
var randPatchFinalizer = new HarmonyMethod(typeof(RandPatches), nameof(RandPatches.Finalizer));

var subSustainerStart = MpMethodUtil.GetLambda(typeof(SubSustainer), parentMethodType: MethodType.Constructor, parentArgs: new[] { typeof(Sustainer), typeof(SubSoundDef) });
var sampleCtor = typeof(Sample).GetConstructor(new[] { typeof(SubSoundDef) });
Expand All @@ -362,7 +362,7 @@ void TryPatch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod po
var ritualMethods = new[] { canEverSpectate };

foreach (MethodBase m in effectMethods.Concat(moteMethods).Concat(fleckMethods).Concat(ritualMethods))
TryPatch(m, randPatchPrefix, randPatchPostfix);
TryPatch(m, randPatchPrefix, finalizer: randPatchFinalizer);
}

SetCategory("Non-deterministic patches 2");
Expand Down Expand Up @@ -435,17 +435,17 @@ void TryPatch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod po
// Set the map time for GUI methods depending on it
{
var setMapTimePrefix = new HarmonyMethod(AccessTools.Method(typeof(SetMapTimeForUI), nameof(SetMapTimeForUI.Prefix)));
var setMapTimePostfix = new HarmonyMethod(AccessTools.Method(typeof(SetMapTimeForUI), nameof(SetMapTimeForUI.Postfix)));
var setMapTimeFinalizer = new HarmonyMethod(AccessTools.Method(typeof(SetMapTimeForUI), nameof(SetMapTimeForUI.Finalizer)));

var windowMethods = new[] { "DoWindowContents", "WindowUpdate" };
foreach (string m in windowMethods)
TryPatch(typeof(MainTabWindow_Inspect).GetMethod(m), setMapTimePrefix, setMapTimePostfix);
TryPatch(typeof(MainTabWindow_Inspect).GetMethod(m), setMapTimePrefix, finalizer: setMapTimeFinalizer);

foreach (var t in typeof(InspectTabBase).AllSubtypesAndSelf())
{
var method = t.GetMethod("FillTab", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly, null, Type.EmptyTypes, null);
if (method != null && !method.IsAbstract)
TryPatch(method, setMapTimePrefix, setMapTimePostfix);
TryPatch(method, setMapTimePrefix, finalizer: setMapTimeFinalizer);
}
}
}
Expand Down
11 changes: 10 additions & 1 deletion Source/Client/Patches/Patches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ static void Postfix(WorldRoutePlanner __instance, ref bool __result)
{
if (Multiplayer.Client == null) return;

// Ignore pause
// Ignore unpausing
if (__result && __instance.active && WorldRendererUtility.WorldRenderedNow)
__result = false;
}
Expand Down Expand Up @@ -586,4 +586,13 @@ static void FixStorage(IStoreSettingsParent __instance, StorageSettings ___allow
___allowedNutritionSettings.owner ??= __instance;
}
}

[HarmonyPatch(typeof(PawnRoleSelectionWidgetBase<ILordJobRole>), nameof(PawnRoleSelectionWidgetBase<ILordJobRole>.TryAssign))]
static class Patc
{
static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> insts)
{
return insts;
}
}
}
2 changes: 1 addition & 1 deletion Source/Client/Patches/Seeds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ public static void Prefix(ref bool __state)
}

[HarmonyPriority(MpPriority.MpLast)]
public static void Postfix(bool __state)
public static void Finalizer(bool __state)
{
if (__state)
Rand.PopState();
Expand Down
3 changes: 2 additions & 1 deletion Source/Client/Persistent/CaravanFormingPatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ static void Postfix(bool __state, ref bool __result)
GUI.color = Color.white;
if (__result)
{
CaravanFormingProxy.drawing.Session?.Remove();
CaravanFormingProxy.drawing.Session?.Cancel();
__result = false;
}
}
Expand Down Expand Up @@ -163,6 +163,7 @@ static void Prefix(Dialog_FormCaravan __instance, Map map, bool reform, Action o
if (__instance.GetType() != typeof(Dialog_FormCaravan))
return;

// Handles showing the dialog from TimedForcedExit.CompTick -> TimedForcedExit.ForceReform
if (Multiplayer.ExecutingCmds || Multiplayer.Ticking)
{
var comp = map.MpComp();
Expand Down
7 changes: 6 additions & 1 deletion Source/Client/Persistent/CaravanFormingSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,12 @@ public void Reset()
}

[SyncMethod]
public void Remove()
public void Cancel()
{
Remove();
}

private void Remove()
{
map.MpComp().sessionManager.RemoveSession(this);
Find.WorldRoutePlanner.Stop();
Expand Down
112 changes: 89 additions & 23 deletions Source/Client/Persistent/Rituals.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
using HarmonyLib;
using Multiplayer.API;
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Multiplayer.Client.Util;
using System.Reflection.Emit;
using UnityEngine;
using Verse;
using static Verse.Widgets;
Expand Down Expand Up @@ -46,6 +45,7 @@ public void Start()
public void OpenWindow(bool sound = true)
{
var dialog = new BeginRitualProxy(
data.assignments,
data.ritualLabel,
data.ritual,
data.target,
Expand All @@ -60,10 +60,7 @@ public void OpenWindow(bool sound = true)
data.outcome,
data.extraInfos,
null
)
{
assignments = data.assignments
};
);

if (!sound)
dialog.soundAppear = null;
Expand Down Expand Up @@ -107,12 +104,43 @@ public class BeginRitualProxy : Dialog_BeginRitual, ISwitchToMap

public RitualSession Session => map.MpComp().sessionManager.GetFirstOfType<RitualSession>();

public BeginRitualProxy(string ritualLabel, Precept_Ritual ritual, TargetInfo target, Map map, ActionCallback action, Pawn organizer, RitualObligation obligation, PawnFilter filter = null, string okButtonText = null, List<Pawn> requiredPawns = null, Dictionary<string, Pawn> forcedForRole = null, RitualOutcomeEffectDef outcome = null, List<string> extraInfoText = null, Pawn selectedPawn = null) :
base(ritualLabel, ritual, target, map, action, organizer, obligation, filter, okButtonText, requiredPawns, forcedForRole, outcome, extraInfoText, selectedPawn)
// In the base type, the unused fields are used to create RitualRoleAssignments which already exist in an MP session
// They are here to help keep the base constructor in sync with this one
public BeginRitualProxy(
RitualRoleAssignments assignments,
string ritualLabel,
Precept_Ritual ritual,
TargetInfo target,
Map map,
ActionCallback action,
Pawn organizer,
RitualObligation obligation,
PawnFilter filter = null,
string okButtonText = null,
// ReSharper disable once UnusedParameter.Local
List<Pawn> requiredPawns = null,
// ReSharper disable once UnusedParameter.Local
Dictionary<string, Pawn> forcedForRole = null,
RitualOutcomeEffectDef outcome = null,
List<string> extraInfoText = null,
// ReSharper disable once UnusedParameter.Local
Pawn selectedPawn = null) :
base(assignments, ritual, target, ritual?.outcomeEffect?.def ?? outcome)
{
this.obligation = obligation;
this.filter = filter;
this.organizer = organizer;
this.map = map;
this.action = action;
ritualExplanation = ritual?.ritualExplanation;
this.ritualLabel = ritualLabel;
this.okButtonText = okButtonText ?? "OK".Translate();
extraInfos = extraInfoText;

soundClose = SoundDefOf.TabClose;

// This gets cancelled in the base constructor if called from ticking/cmd in DontClearDialogBeginRitualCache
// Note that: cachedRoles is a static field, cachedRoles is only used for UI drawing
cachedRoles.Clear();
if (ritual is { ideo: not null })
{
Expand Down Expand Up @@ -199,15 +227,15 @@ static bool Prefix(Window window)
&& window.GetType() == typeof(Dialog_BeginRitual) // Doesn't let BeginRitualProxy through
&& (Multiplayer.ExecutingCmds || Multiplayer.Ticking))
{
var dialog = (Dialog_BeginRitual)window;
dialog.PostOpen(); // Completes initialization
var tempDialog = (Dialog_BeginRitual)window;
tempDialog.PostOpen(); // Completes initialization

var comp = dialog.map.MpComp();
var comp = tempDialog.map.MpComp();
var session = comp.sessionManager.GetFirstOfType<RitualSession>();

if (session != null &&
(session.data.ritual != dialog.ritual ||
session.data.outcome != dialog.outcome))
(session.data.ritual != tempDialog.ritual ||
session.data.outcome != tempDialog.outcome))
{
Messages.Message("MpAnotherRitualInProgress".Translate(), MessageTypeDefOf.RejectInput, false);
return false;
Expand All @@ -217,16 +245,16 @@ static bool Prefix(Window window)
{
var data = new RitualData
{
ritual = dialog.ritual,
target = dialog.target,
obligation = dialog.obligation,
outcome = dialog.outcome,
extraInfos = dialog.extraInfos,
action = dialog.action,
ritualLabel = dialog.ritualLabel,
confirmText = dialog.confirmText,
organizer = dialog.organizer,
assignments = MpUtil.ShallowCopy(dialog.assignments, new MpRitualAssignments())
ritual = tempDialog.ritual,
target = tempDialog.target,
obligation = tempDialog.obligation,
outcome = tempDialog.outcome,
extraInfos = tempDialog.extraInfos,
action = tempDialog.action,
ritualLabel = tempDialog.ritualLabel,
confirmText = tempDialog.confirmText,
organizer = tempDialog.organizer,
assignments = MpUtil.ShallowCopy(tempDialog.assignments, new MpRitualAssignments())
};

session = comp.CreateRitualSession(data);
Expand All @@ -242,6 +270,44 @@ static bool Prefix(Window window)
}
}

// Note that cachedRoles is a static field and is only used for UI drawing
[HarmonyPatch]
static class DontClearDialogBeginRitualCache
{
private static MethodInfo listClear = AccessTools.Method(typeof(List<Precept_Role>), "Clear");

static IEnumerable<MethodBase> TargetMethods()
{
yield return typeof(Dialog_BeginRitual).GetConstructors(BindingFlags.Public | BindingFlags.Instance).First();
}

static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> insts, ILGenerator gen)
{
var list = insts.ToList();
var brLabel = gen.DefineLabel();

foreach (var inst in list)
{
if (inst.operand == listClear)
{
yield return new CodeInstruction(OpCodes.Call,
AccessTools.Method(typeof(DontClearDialogBeginRitualCache), nameof(ShouldCancelCacheClear)));
yield return new CodeInstruction(OpCodes.Brfalse, brLabel);
yield return new CodeInstruction(OpCodes.Pop);
yield return new CodeInstruction(OpCodes.Ret);
yield return new CodeInstruction(OpCodes.Nop) { labels = { brLabel } };
}

yield return inst;
}
}

static bool ShouldCancelCacheClear()
{
return Multiplayer.Ticking || Multiplayer.ExecutingCmds;
}
}

// We do not sync Ability.QueueCastingJob - instead syncing the delegate method queueing the job.
// We do this to avoid confirmation dialogs from some of the abilities, like neuroquake and a few ones added in Biotech.
// This causes an issue with abilities like throne speech or leader speech - the ritual dialog itself is treated as the confirmation dialog.
Expand Down
4 changes: 2 additions & 2 deletions Source/Client/Syncing/ApiSerialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public static class ApiSerialization

public static void Init()
{
syncSimples = TypeCache.AllImplementationsOrdered(typeof(ISyncSimple));
sessions = TypeCache.AllImplementationsOrdered(typeof(Session));
syncSimples = TypeCache.AllInterfaceImplementationsOrdered(typeof(ISyncSimple));
sessions = TypeCache.AllSubclassesNonAbstractOrdered(typeof(Session));
}
}
Loading

0 comments on commit 55dae5f

Please sign in to comment.