diff --git a/Source/Client/Persistent/MapPortalPatches.cs b/Source/Client/Persistent/MapPortalPatches.cs new file mode 100644 index 00000000..1cdfa246 --- /dev/null +++ b/Source/Client/Persistent/MapPortalPatches.cs @@ -0,0 +1,99 @@ +using HarmonyLib; +using RimWorld; +using UnityEngine; +using Verse; + +namespace Multiplayer.Client.Persistent; + +[HarmonyPatch(typeof(Widgets), nameof(Widgets.ButtonTextWorker))] +static class MakeMapPortalCancelButtonRed +{ + static void Prefix(string label, ref bool __state) + { + if (MapPortalProxy.drawing == null) return; + if (label != "CancelButton".Translate()) return; + + GUI.color = new Color(1f, 0.3f, 0.35f); + __state = true; + } + + static void Postfix(bool __state, ref Widgets.DraggableResult __result) + { + if (!__state) return; + + GUI.color = Color.white; + if (__result.AnyPressed()) + { + MapPortalProxy.drawing.Session?.Remove(); + __result = Widgets.DraggableResult.Idle; + } + } +} + +[HarmonyPatch(typeof(Widgets), nameof(Widgets.ButtonTextWorker))] +static class MapPortalHandleReset +{ + static void Prefix(string label, ref bool __state) + { + if (MapPortalProxy.drawing == null) return; + if (label != "ResetButton".Translate()) return; + + __state = true; + } + + static void Postfix(bool __state, ref Widgets.DraggableResult __result) + { + if (!__state) return; + + if (__result.AnyPressed()) + { + MapPortalProxy.drawing.Session?.Reset(); + __result = Widgets.DraggableResult.Idle; + } + } +} + +[HarmonyPatch(typeof(Dialog_EnterPortal), nameof(Dialog_EnterPortal.TryAccept))] +static class TryAcceptMapPortal +{ + static bool Prefix(Dialog_EnterPortal __instance) + { + if (Multiplayer.InInterface && __instance is MapPortalProxy mapPortal) + { + mapPortal.Session?.TryAccept(); + return false; + } + + return true; + } +} + +[HarmonyPatch(typeof(Dialog_EnterPortal), nameof(Dialog_EnterPortal.AddToTransferables))] +static class CancelMapPortalAddItems +{ + static bool Prefix(Dialog_EnterPortal __instance) + { + if (__instance is MapPortalProxy { itemsReady: true } mp) + { + // Sets the transferables list back to the session list + // as it gets reset in CalculateAndRecacheTransferables + mp.transferables = mp.Session.transferables; + return false; + } + + return true; + } +} + +static class OpenMapPortalSessionDialog +{ + [MpPrefix(typeof(MapPortal), nameof(MapPortal.GetGizmos), 0)] + static bool Prefix(MapPortal __instance) + { + if (Multiplayer.Client == null) + return true; + + MapPortalSession.OpenOrCreateSession(__instance); + return false; + } +} diff --git a/Source/Client/Persistent/MapPortalProxy.cs b/Source/Client/Persistent/MapPortalProxy.cs new file mode 100644 index 00000000..0dfb7fc7 --- /dev/null +++ b/Source/Client/Persistent/MapPortalProxy.cs @@ -0,0 +1,32 @@ +using RimWorld; +using UnityEngine; + +namespace Multiplayer.Client.Persistent; + +public class MapPortalProxy(MapPortal portal) : Dialog_EnterPortal(portal), ISwitchToMap +{ + public static MapPortalProxy drawing; + public bool itemsReady = false; + + public MapPortalSession Session => portal.Map.MpComp().sessionManager.GetFirstOfType(); + + public override void DoWindowContents(Rect inRect) + { + drawing = this; + var session = Session; + SyncSessionWithTransferablesMarker.DrawnSessionWithTransferables = session; + + try + { + if (session == null) + Close(); + + base.DoWindowContents(inRect); + } + finally + { + drawing = null; + SyncSessionWithTransferablesMarker.DrawnSessionWithTransferables = null; + } + } +} diff --git a/Source/Client/Persistent/MapPortalSession.cs b/Source/Client/Persistent/MapPortalSession.cs new file mode 100644 index 00000000..4e64623c --- /dev/null +++ b/Source/Client/Persistent/MapPortalSession.cs @@ -0,0 +1,127 @@ +using System.Collections.Generic; +using System.Linq; +using Multiplayer.API; +using RimWorld; +using Verse; + +namespace Multiplayer.Client.Persistent; + +public class MapPortalSession : ExposableSession, ISessionWithTransferables, ISessionWithCreationRestrictions +{ + public MapPortal portal; + public List transferables; + + public override Map Map => portal.Map; + + public MapPortalSession(Map _) : base(null) + { + // Mandatory constructor + } + + public MapPortalSession(MapPortal portal) : base(null) + { + this.portal = portal; + + AddItems(); + } + + private void AddItems() + { + var dialog = new MapPortalProxy(portal); + + // Init code taken from Dialog_EnterPortal.PostOpen + dialog.CalculateAndRecacheTransferables(); + + transferables = dialog.transferables; + } + + public override bool IsCurrentlyPausing(Map map) => Map == map; + + public override FloatMenuOption GetBlockingWindowOptions(ColonistBar.Entry entry) + { + if (entry.map != Map) + return null; + + return new FloatMenuOption("MpMapPortalSession".Translate(portal?.Label), () => + { + SwitchToMapOrWorld(Map); + OpenWindow(); + }); + } + + public static void OpenOrCreateSession(MapPortal portal) + { + var session = portal.Map.MpComp().sessionManager.AllSessions + .OfType() + .FirstOrDefault(s => s.portal == portal); + if (session == null) + CreateSession(portal); + else + session.OpenWindow(); + } + + [SyncMethod] + private static void CreateSession(MapPortal portal) + { + var map = portal.Map; + var manager = map.MpComp().sessionManager; + var session = manager.GetOrAddSession(new MapPortalSession(portal)); + + // Shouldn't happen and is here for safety. + if (session == null) + Log.Error($"Failed creating session of type {nameof(MapPortalSession)}."); + else if (MP.IsExecutingSyncCommandIssuedBySelf) + session.OpenWindow(); + } + + [SyncMethod] + public void TryAccept() + { + // There's not a single situation where TryAccept would return false. + // However, it will likely be used by prefixes added by mods. + if (PrepareDummyDialog().TryAccept()) + Remove(); + } + + [SyncMethod] + public void Reset() => transferables.ForEach(t => t.CountToTransfer = 0); + + [SyncMethod] + public void Remove() => Map.MpComp().sessionManager.RemoveSession(this); + + public void OpenWindow(bool sound = true) + { + var dialog = PrepareDummyDialog(); + if (!sound) + dialog.soundAppear = null; + + Find.WindowStack.Add(dialog); + } + + private MapPortalProxy PrepareDummyDialog() + { + return new MapPortalProxy(portal) + { + itemsReady = true, + transferables = transferables, + }; + } + + public override void ExposeData() + { + base.ExposeData(); + + Scribe_Collections.Look(ref transferables, "transferables", LookMode.Deep); + Scribe_References.Look(ref portal, "portal"); + } + + public Transferable GetTransferableByThingId(int thingId) + => transferables.Find(tr => tr.things.Any(t => t.thingIDNumber == thingId)); + + public void Notify_CountChanged(Transferable tr) + { + // There should not really be a need to clear caches, as this dialog does not really have any. + } + + public bool CanExistWith(Session other) => other is not MapPortalSession portalSession || portalSession.portal != portal; +}