Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into fix-comp-obelisk-ab…
Browse files Browse the repository at this point in the history
…ductor

# Conflicts:
#	Source/Client/Patches/Determinism.cs
  • Loading branch information
SokyranTheDragon committed Jul 17, 2024
2 parents 454829e + 49b6f5a commit ada70ec
Show file tree
Hide file tree
Showing 20 changed files with 359 additions and 249 deletions.
Binary file added About/ModIcon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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.4104-beta" />
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.5.4104" />
<PackageReference Include="RestSharp" Version="106.12.0" />
<PackageReference Include="RimWorld.MultiplayerAPI" Version="0.5.0" />
</ItemGroup>
Expand Down
3 changes: 2 additions & 1 deletion Source/Client/MultiplayerStatic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,8 @@ void TryPatch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod po
var canEverSpectate = typeof(RitualRoleAssignments).GetMethod(nameof(RitualRoleAssignments.CanEverSpectate));

var effectMethods = new MethodBase[] { subSustainerStart, sampleCtor, subSoundPlay, effecterTick, effecterTrigger, effecterCleanup, randomBoltMesh, drawTrackerCtor, randomHair };
var moteMethods = typeof(MoteMaker).GetMethods(BindingFlags.Static | BindingFlags.Public);
var moteMethods = typeof(MoteMaker).GetMethods(BindingFlags.Static | BindingFlags.Public)
.Concat(typeof(CompCableConnection.Cable).GetMethod(nameof(CompCableConnection.Cable.Tick)));
var fleckMethods = typeof(FleckMaker).GetMethods(BindingFlags.Static | BindingFlags.Public)
.Where(m => m.ReturnType == typeof(void))
.Concat(typeof(FleckManager).GetMethods() // FleckStatic uses Rand in Setup method, FleckThrown uses RandomInRange in TimeInterval. May as well catch all in case mods do the same.
Expand Down
67 changes: 65 additions & 2 deletions Source/Client/Patches/Determinism.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,19 @@

namespace Multiplayer.Client.Patches
{
[EarlyPatch]
[HarmonyPatch(typeof(PawnTweener))]
[HarmonyPatch(nameof(PawnTweener.TweenedPos), MethodType.Getter)]
static class DrawPosPatch
{
static bool Prefix() => Multiplayer.Client == null || Multiplayer.InInterface;
public static bool returnTruePosition = false;

static bool Prefix() => Multiplayer.Client == null || Multiplayer.InInterface || returnTruePosition;

// Give the root position during ticking
static void Postfix(PawnTweener __instance, ref Vector3 __result)
{
if (Multiplayer.Client == null || Multiplayer.InInterface) return;
if (Multiplayer.Client == null || Multiplayer.InInterface || returnTruePosition) return;
__result = __instance.TweenedPosRoot();
}
}
Expand Down Expand Up @@ -524,6 +527,66 @@ static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> inst
}
}

[HarmonyPatch(typeof(UndercaveMapComponent), nameof(UndercaveMapComponent.MapComponentTick))]
static class DeterministicUndercaveRockCollapse
{
static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instr)
{
var target = MethodOf.Lambda(Rand.MTBEventOccurs);

foreach (var ci in instr)
{
yield return ci;

// Add "& false" to any call to Rand.MTBEventOccurs.
// We'll handle those calls in our postfix.
if (ci.Calls(target))
{
yield return new CodeInstruction(OpCodes.Ldc_I4_0);
yield return new CodeInstruction(OpCodes.And);
}
}
}

static void Prefix() => Rand.PushState();

static void Postfix(UndercaveMapComponent __instance)
{
// Pop the RNG state from the prefix
Rand.PopState();

// Make sure the pit gate is collapsing
if (__instance.pitGate is not { IsCollapsing: true })
return;

// Check if the rocks should collapse
var mtb = UndercaveMapComponent.HoursToShakeMTBTicksCurve.Evaluate(__instance.pitGate.TicksUntilCollapse / 2500f);
if (!Rand.MTBEventOccurs(mtb, 1, 1))
return;

// Since the number of RNG calls will depend on numDustEffecters argument, we need to push/pop the RNG state.
// The RNG calls related to simulation will happen first, followed by the one determined by amount of
// effecters - it would not be MP safe, but since it happens last it will be fine once we pop the state.
Rand.PushState();

// If not looking at the map, trigger the collapse without shake/effecters (since it's not needed for current player).
// The call to play a sound is handled by RW itself, since it targets a specific map already.
if (Find.CurrentMap != __instance.map)
{
// Progress the RNG state, matching the RandomInRange call in other two cases
Rand.RangeInclusive(0, 100);
__instance.TriggerCollapseFX(0, 0);
}
// Else, follow vanilla shake/effecter rules
else if (__instance.pitGate.CollapseStage == 1)
__instance.TriggerCollapseFX(UndercaveMapComponent.StageOneShakeAmount, UndercaveMapComponent.StageOneNumCollapseEffects.RandomInRange);
else
__instance.TriggerCollapseFX(UndercaveMapComponent.StageTwoShakeAmount, UndercaveMapComponent.StageTwoNumCollapseEffects.RandomInRange);

Rand.PopState();
}
}

[HarmonyPatch(typeof(CompObelisk_Abductor), nameof(CompObelisk_Abductor.GenerateLabyrinth))]
static class SynchronousAbductorObelisk
{
Expand Down
9 changes: 9 additions & 0 deletions Source/Client/Patches/Patches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Reflection.Emit;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using Multiplayer.Client.Patches;
using UnityEngine;
using Verse;
using Verse.AI;
Expand Down Expand Up @@ -581,4 +582,12 @@ static void FixStorage(IStoreSettingsParent __instance, StorageSettings ___allow
___allowedNutritionSettings.owner ??= __instance;
}
}

[HarmonyPatch(typeof(MoteAttachLink), nameof(MoteAttachLink.UpdateDrawPos))]
static class MoteAttachLinkUsesTruePosition
{
static void Prefix() => DrawPosPatch.returnTruePosition = true;

static void Finalizer() => DrawPosPatch.returnTruePosition = false;
}
}
5 changes: 3 additions & 2 deletions Source/Client/Patches/Seeds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,12 @@ static void Prefix(ref Action action)
{
if (Multiplayer.Client != null && (Multiplayer.Ticking || Multiplayer.ExecutingCmds))
{
action = PushState + action + Rand.PopState;
var seed = Rand.Int;
action = (() => PushState(seed)) + action + Rand.PopState;
}
}

static void PushState() => Rand.PushState(4);
static void PushState(int seed) => Rand.PushState(seed);
}

// Seed the rotation random
Expand Down
11 changes: 7 additions & 4 deletions Source/Client/Patches/UniqueIds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@ public static class UniqueIdsPatch
// Start at -2 because -1 is sometimes used as the uninitialized marker
private static int localIds = -2;

public static bool useLocalIdsOverride;

private static bool UseLocalIds =>
Multiplayer.Client != null && (useLocalIdsOverride || Multiplayer.InInterface || Current.ProgramState == ProgramState.Entry);

static bool Prefix()
{
return Multiplayer.Client == null || (!Multiplayer.InInterface && Current.ProgramState != ProgramState.Entry);
return !UseLocalIds;
}

static void Postfix(ref int __result)
{
if (Multiplayer.Client == null) return;

if (Multiplayer.InInterface || Current.ProgramState == ProgramState.Entry)
if (UseLocalIds)
__result = localIds--;
}
}
Expand Down
65 changes: 61 additions & 4 deletions Source/Client/Persistent/CaravanFormingPatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using RimWorld;
using RimWorld.Planet;
using System;
using Multiplayer.API;
using Multiplayer.Client.Patches;
using UnityEngine;
using Verse;
using static Verse.Widgets;
Expand Down Expand Up @@ -147,29 +149,84 @@ static class CancelDialogFormCaravan
{
static bool Prefix(Window window)
{
if (Multiplayer.MapContext != null && window.GetType() == typeof(Dialog_FormCaravan))
if (Multiplayer.Client != null && window.GetType() == typeof(Dialog_FormCaravan))
return false;

return true;
}
}

[HarmonyPatch(typeof(Dialog_FormCaravan), MethodType.Constructor)]
[HarmonyPatch(new[] { typeof(Map), typeof(bool), typeof(Action), typeof(bool), typeof(IntVec3) })]
[HarmonyPatch(new[] { typeof(Map), typeof(bool), typeof(Action), typeof(bool), typeof(IntVec3?) })]
static class DialogFormCaravanCtorPatch
{
static void Prefix(Dialog_FormCaravan __instance, Map map, bool reform, Action onClosed, bool mapAboutToBeRemoved)
static void Prefix(Dialog_FormCaravan __instance, Map map, bool reform, Action onClosed, bool mapAboutToBeRemoved, IntVec3? designatedMeetingPoint)
{
if (Multiplayer.Client == null)
return;

if (__instance.GetType() != typeof(Dialog_FormCaravan))
return;

// Handles showing the dialog from TimedForcedExit.CompTick -> TimedForcedExit.ForceReform
// (note TimedForcedExit is obsolete)
if (Multiplayer.ExecutingCmds || Multiplayer.Ticking)
{
var comp = map.MpComp();
if (comp.sessionManager.GetFirstOfType<CaravanFormingSession>() == null)
comp.CreateCaravanFormingSession(reform, onClosed, mapAboutToBeRemoved);
comp.CreateCaravanFormingSession(reform, onClosed, mapAboutToBeRemoved, designatedMeetingPoint);
}
else // Handles opening from the interface: forming gizmos, reforming gizmos and caravan hitching spots
{
StartFormingCaravan(map, reform, designatedMeetingPoint);
}
}

[SyncMethod]
internal static void StartFormingCaravan(Map map, bool reform = false, IntVec3? designatedMeetingPoint = null, int? routePlannerWaypoint = null)
{
var comp = map.MpComp();
var session = comp.CreateCaravanFormingSession(reform, null, false, designatedMeetingPoint);

if (TickPatch.currentExecutingCmdIssuedBySelf)
{
var dialog = session.OpenWindow();
if (routePlannerWaypoint is { } tile)
{
try
{
UniqueIdsPatch.useLocalIdsOverride = true;

// Just to be safe
// RNG shouldn't be invoked but TryAddWaypoint is quite complex and calls pathfinding
Rand.PushState();

var worldRoutePlanner = Find.WorldRoutePlanner;
worldRoutePlanner.Start(dialog);
worldRoutePlanner.TryAddWaypoint(tile);
}
finally
{
Rand.PopState();
UniqueIdsPatch.useLocalIdsOverride = false;
}
}
}
}
}

[HarmonyPatch(typeof(FormCaravanGizmoUtility), nameof(FormCaravanGizmoUtility.DialogFromToSettlement))]
static class HandleFormCaravanShowRoutePlanner
{
static bool Prefix(Map origin, int tile)
{
if (Multiplayer.Client == null)
return true;

// Override behavior in multiplayer
DialogFormCaravanCtorPatch.StartFormingCaravan(origin, routePlannerWaypoint: tile);

return false;
}
}

Expand Down
5 changes: 4 additions & 1 deletion Source/Client/Persistent/CaravanFormingProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ public class CaravanFormingProxy : Dialog_FormCaravan, ISwitchToMap

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

public CaravanFormingProxy(Map map, bool reform = false, Action onClosed = null, bool mapAboutToBeRemoved = false, IntVec3? meetingSpot = null) : base(map, reform, onClosed, mapAboutToBeRemoved, meetingSpot)
public int originalSessionId;

public CaravanFormingProxy(int originalSessionId, Map map, bool reform = false, Action onClosed = null, bool mapAboutToBeRemoved = false, IntVec3? meetingSpot = null) : base(map, reform, onClosed, mapAboutToBeRemoved, meetingSpot)
{
this.originalSessionId = originalSessionId;
}

public override void DoWindowContents(Rect inRect)
Expand Down
14 changes: 8 additions & 6 deletions Source/Client/Persistent/CaravanFormingSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,15 @@ public CaravanFormingSession(Map map, bool reform, Action onClosed, bool mapAbou

private void AddItems()
{
var dialog = new CaravanFormingProxy(map, reform, null, mapAboutToBeRemoved, meetingSpot)
var dialog = new CaravanFormingProxy(sessionId, map, reform, null, mapAboutToBeRemoved, meetingSpot)
{
autoSelectTravelSupplies = autoSelectTravelSupplies
};
dialog.CalculateAndRecacheTransferables();
transferables = dialog.transferables;
}

public void OpenWindow(bool sound = true)
public CaravanFormingProxy OpenWindow(bool sound = true)
{
var dialog = PrepareDummyDialog();
if (!sound)
Expand All @@ -72,13 +72,14 @@ public void OpenWindow(bool sound = true)
);

dialog.Notify_TransferablesChanged();

Find.WindowStack.Add(dialog);

return dialog;
}

private CaravanFormingProxy PrepareDummyDialog()
{
var dialog = new CaravanFormingProxy(map, reform, null, mapAboutToBeRemoved, meetingSpot)
var dialog = new CaravanFormingProxy(sessionId, map, reform, null, mapAboutToBeRemoved, meetingSpot)
{
transferables = transferables,
startingTile = startingTile,
Expand Down Expand Up @@ -142,7 +143,9 @@ public void Cancel()
private void Remove()
{
map.MpComp().sessionManager.RemoveSession(this);
Find.WorldRoutePlanner.Stop();

if (Find.WorldRoutePlanner.currentFormCaravanDialog is CaravanFormingProxy proxy && proxy.originalSessionId == sessionId)
Find.WorldRoutePlanner.Stop();
}

[SyncMethod]
Expand Down Expand Up @@ -186,7 +189,6 @@ public override FloatMenuOption GetBlockingWindowOptions(ColonistBar.Entry entry
{
return new FloatMenuOption("MpCaravanFormingSession".Translate(), () =>
{
SwitchToMapOrWorld(entry.map);
OpenWindow();
});
}
Expand Down
Loading

0 comments on commit ada70ec

Please sign in to comment.