diff --git a/Source/Client/Factions/FactionsWindow.cs b/Source/Client/Factions/FactionsWindow.cs new file mode 100644 index 00000000..301980b3 --- /dev/null +++ b/Source/Client/Factions/FactionsWindow.cs @@ -0,0 +1,95 @@ +using System; +using System.Linq; +using Multiplayer.Client.Util; +using Multiplayer.Common; +using RimWorld; +using UnityEngine; +using Verse; + +namespace Multiplayer.Client.Factions; + +public class FactionsWindow : Window +{ + public override Vector2 InitialSize { get; } = new(700, 600); + + private static Vector2 scroll; + + public FactionsWindow() + { + doCloseX = true; + } + + public override void DoWindowContents(Rect inRect) + { + var group = DragAndDropWidget.NewGroup(); + + Layouter.BeginArea(inRect); + Layouter.BeginScroll(ref scroll, spacing: 0f); + + var factions = Find.FactionManager.AllFactions.Where(f => f.IsPlayer).ToList(); + + void DrawFactionInLastRect(Faction faction) + { + DragAndDropWidget.DropArea(group, Layouter.LastRect(), playerId => + { + Multiplayer.Client.Send(Packets.Client_SetFaction, (int)playerId, faction.loadID); + }, null); + + Layouter.BeginVerticalInLastRect(spacing: 1f); + + Layouter.BeginHorizontal(); + { + Layouter.BeginVertical(spacing: 0f, false); + Layouter.Rect(0f, 5f); + + if (Layouter.Button(">", 20f, 20f)) + Multiplayer.Client.Send(Packets.Client_SetFaction, Multiplayer.session.playerId, faction.loadID); + + TooltipHandler.TipRegion(Layouter.LastRect(), "Switch faction"); + + Layouter.EndVertical(); + + using (MpStyle.Set(GameFont.Medium)) + Layouter.Label(faction.Name); + } + Layouter.EndHorizontal(); + + foreach (var p in Multiplayer.session.players) + { + if (p.factionId != faction.loadID) + continue; + + var rect = Layouter.ContentRect(p.username); + + if (Multiplayer.LocalServer != null && DragAndDropWidget.Draggable(group, rect, p.id)) + Widgets.Label(rect with { position = Event.current.mousePosition }, p.username); + else + Widgets.Label(rect, p.username); + } + + Layouter.EndVertical(); + } + + for (int i = 0; i < factions.Count; i++) + { + Layouter.BeginHorizontal(); + + float height = 23 * Multiplayer.session.players.Count(p => p.factionId == factions[i].loadID) + 30f; + if (i + 1 < factions.Count) + height = Math.Max(23 * Multiplayer.session.players.Count(p => p.factionId == factions[i+1].loadID) + 30f, height); + height = Math.Max(200, height); + + Layouter.FixedHeight(height); + DrawFactionInLastRect(factions[i]); + i++; + + Layouter.FixedHeight(height); + if (i < factions.Count) DrawFactionInLastRect(factions[i]); + + Layouter.EndHorizontal(); + } + + Layouter.EndScroll(); + Layouter.EndArea(); + } +} diff --git a/Source/Client/Factions/MultifactionPatches.cs b/Source/Client/Factions/MultifactionPatches.cs index ea5281ed..94abfdca 100644 --- a/Source/Client/Factions/MultifactionPatches.cs +++ b/Source/Client/Factions/MultifactionPatches.cs @@ -10,6 +10,7 @@ using UnityEngine; using Verse; using Verse.AI; +using Verse.Sound; namespace Multiplayer.Client.Patches; @@ -332,6 +333,32 @@ static void Postfix(LetterStack __instance, Letter let) } } +[HarmonyPatch(typeof(LetterStack), nameof(LetterStack.ReceiveLetter), typeof(Letter), typeof(string))] +static class LetterStackReceiveSoundOnlyMyFaction +{ + private static MethodInfo PlayOneShotOnCamera = + typeof(SoundStarter).GetMethod(nameof(SoundStarter.PlayOneShotOnCamera)); + + static IEnumerable Transpiler(IEnumerable insts) + { + foreach (var inst in insts) + { + if (inst.operand == PlayOneShotOnCamera) + yield return new CodeInstruction( + OpCodes.Call, + SymbolExtensions.GetMethodInfo((SoundDef s, Map m) => PlaySoundReplacement(s, m))); + else + yield return inst; + } + } + + static void PlaySoundReplacement(SoundDef sound, Map map) + { + if (Multiplayer.RealPlayerFaction == Faction.OfPlayer) + sound.PlayOneShotOnCamera(map); + } +} + [HarmonyPatch] static class DontClearDialogBeginRitualCache { diff --git a/Source/Client/MultiplayerStatic.cs b/Source/Client/MultiplayerStatic.cs index 5f54dc6d..550864a4 100644 --- a/Source/Client/MultiplayerStatic.cs +++ b/Source/Client/MultiplayerStatic.cs @@ -12,6 +12,7 @@ using Multiplayer.Client.Util; using Multiplayer.Common; using RimWorld; +using RimWorld.Planet; using Steamworks; using UnityEngine; using Verse; @@ -403,6 +404,35 @@ void LogError(string str) } } + // Set FactionContext in common WorldObject methods + { + var prefix = new HarmonyMethod(typeof(WorldObjectMethodPatches).GetMethod(nameof(WorldObjectMethodPatches.Prefix))); + var finalizer = new HarmonyMethod(typeof(WorldObjectMethodPatches).GetMethod(nameof(WorldObjectMethodPatches.Finalizer))); + + var thingMethods = new[] + { + ("SpawnSetup", Type.EmptyTypes), + ("Tick", Type.EmptyTypes) + }; + + foreach (Type t in typeof(WorldObject).AllSubtypesAndSelf()) + { + foreach ((string m, Type[] args) in thingMethods) + { + MethodInfo method = t.GetMethod(m, BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly, null, args, null); + if (method != null) + { + try + { + harmony.PatchMeasure(method, prefix, finalizer: finalizer); + } catch (Exception e) { + LogError($"FAIL: {method.DeclaringType.FullName}:{method.Name} with {e}"); + } + } + } + } + } + // Full precision floating point saving { var doubleSavePrefix = new HarmonyMethod(typeof(ValueSavePatch).GetMethod(nameof(ValueSavePatch.DoubleSave_Prefix))); diff --git a/Source/Client/Patches/WorldObjectMethodPatches.cs b/Source/Client/Patches/WorldObjectMethodPatches.cs new file mode 100644 index 00000000..43621dd0 --- /dev/null +++ b/Source/Client/Patches/WorldObjectMethodPatches.cs @@ -0,0 +1,25 @@ +using HarmonyLib; +using RimWorld.Planet; + +namespace Multiplayer.Client.Patches; + +public static class WorldObjectMethodPatches +{ + [HarmonyPriority(MpPriority.MpFirst)] + public static void Prefix(WorldObject __instance) + { + if (Multiplayer.Client == null) return; + + if (__instance.def.canHaveFaction) + FactionContext.Push(__instance.Faction); + } + + [HarmonyPriority(MpPriority.MpLast)] + public static void Finalizer(WorldObject __instance) + { + if (Multiplayer.Client == null) return; + + if (__instance.def.canHaveFaction) + FactionContext.Pop(); + } +} diff --git a/Source/Client/UI/Layouter.cs b/Source/Client/UI/Layouter.cs index 59738080..58213e92 100644 --- a/Source/Client/UI/Layouter.cs +++ b/Source/Client/UI/Layouter.cs @@ -105,14 +105,13 @@ private static El GetNextChild() #region Groups private static void PushGroup(El el) { - var parent = currentGroup; - currentGroup = el; - - if (parent != null) + if (currentGroup != null) { - currentGroup.parent = parent; - parent.children.Add(currentGroup); + el.parent = currentGroup; + currentGroup.children.Add(el); } + + currentGroup = el; } private static void PopGroup() @@ -184,6 +183,18 @@ public static void EndHorizontal() PopGroup(); } + public static void BeginHorizontalCenter() + { + BeginHorizontal(); + FlexibleWidth(); + } + + public static void EndHorizontalCenter() + { + FlexibleWidth(); + EndHorizontal(); + } + public static void BeginVertical(float spacing = 10f, bool stretch = true) { if (Event.current.type == EventType.Layout) @@ -192,6 +203,19 @@ public static void BeginVertical(float spacing = 10f, bool stretch = true) currentGroup = GetNextChild(); } + public static void BeginVerticalInLastRect(float spacing = 10f) + { + if (Event.current.type == EventType.Layout) + { + LastEl().parent = currentGroup; + currentGroup = LastEl(); + currentGroup.spacing = spacing; + } else + { + currentGroup = LastEl(); + } + } + public static void EndVertical() { PopGroup(); @@ -204,15 +228,19 @@ public static void BeginScroll(ref Vector2 scrollPos, float spacing = 10f) var outRect = currentGroup!.rect; currentGroup.scroll = true; - Widgets.BeginScrollView( - outRect, - ref scrollPos, - new Rect(0, 0, outRect.width - currentGroup.paddingRight, currentGroup.childrenHeight)); + var viewRect = new Rect(outRect.x, outRect.y, outRect.width - currentGroup.paddingRight, currentGroup.childrenHeight); + + if (currentGroup.paddingRight != 0f) + Widgets.BeginScrollView( + outRect, + ref scrollPos, + viewRect); } public static void EndScroll() { - Widgets.EndScrollView(); + if (currentGroup.paddingRight != 0f) + Widgets.EndScrollView(); EndVertical(); } #endregion @@ -304,12 +332,29 @@ public static Rect FixedWidth(float width) return GetNextChild().rect; } - public static Rect LastRect() + public static Rect FixedHeight(float height) + { + if (Event.current.type == EventType.Layout) + { + currentGroup!.children.Add(new El() + { rect = new Rect(0, 0, 0, height), widthMode = DimensionMode.Stretch, heightMode = DimensionMode.Fixed }); + return DummyRect; + } + + return GetNextChild().rect; + } + + private static El LastEl() { return Event.current.type == EventType.Layout ? - currentGroup!.children.Last().rect : - currentGroup!.children[currentGroup.currentChild - 1].rect; + currentGroup!.children.Last() : + currentGroup!.children[currentGroup.currentChild - 1]; + } + + public static Rect LastRect() + { + return LastEl().rect; } public static Rect GroupRect() @@ -318,6 +363,18 @@ public static Rect GroupRect() } #endregion + #region UI elements + public static void Label(string text, bool inheritHeight = false) + { + GUI.Label(inheritHeight ? FlexibleWidth() : ContentRect(text), text, Text.CurFontStyle); + } + + public static bool Button(string text, float width, float height = 35f) + { + return Widgets.ButtonText(Rect(width, height), text); + } + #endregion + #region Algorithm private static void Layout() { diff --git a/Source/Client/Windows/ChatWindow.cs b/Source/Client/Windows/ChatWindow.cs index c8ed0802..ecf83f6e 100644 --- a/Source/Client/Windows/ChatWindow.cs +++ b/Source/Client/Windows/ChatWindow.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Text; +using Multiplayer.Client.Factions; using Multiplayer.Client.Util; using UnityEngine; using Verse; @@ -91,7 +92,9 @@ public override void DoWindowContents(Rect inRect) DrawChat(chat); GUI.BeginGroup(new Rect(chat.xMax + 10f, chat.y, infoWidth, inRect.height)); - DrawInfo(new Rect(0, 0, infoWidth, inRect.height)); + DrawInfo(new Rect(0, 0, infoWidth, inRect.height - 30f)); + if (Widgets.ButtonText(new Rect(50f, inRect.height - 25f, infoWidth - 50f, 25f), "Factions")) + Find.WindowStack.Add(new FactionsWindow()); GUI.EndGroup(); if (KeyBindingDefOf.Cancel.KeyDownEvent && Find.WindowStack.focusedWindow == this) diff --git a/Source/Common/Networking/State/ServerPlayingState.cs b/Source/Common/Networking/State/ServerPlayingState.cs index 73599a13..d12da74a 100644 --- a/Source/Common/Networking/State/ServerPlayingState.cs +++ b/Source/Common/Networking/State/ServerPlayingState.cs @@ -261,6 +261,7 @@ public void HandleSetFaction(ByteReader data) var player = Server.GetPlayer(playerId); if (player == null) return; + if (player.FactionId == factionId) return; player.FactionId = factionId; Server.SendToPlaying(Packets.Server_SetFaction, new object[] { playerId, factionId }); diff --git a/Source/Common/Version.cs b/Source/Common/Version.cs index e009484c..45b33221 100644 --- a/Source/Common/Version.cs +++ b/Source/Common/Version.cs @@ -2,8 +2,8 @@ namespace Multiplayer.Common { public static class MpVersion { - public const string Version = "0.9.5"; - public const int Protocol = 38; + public const string Version = "0.9.6"; + public const int Protocol = 39; public const string ApiAssemblyName = "0MultiplayerAPI";