diff --git a/Dalamud/Fools/FoolsManager.cs b/Dalamud/Fools/FoolsManager.cs index 0a2a8c99c..1509acb4e 100644 --- a/Dalamud/Fools/FoolsManager.cs +++ b/Dalamud/Fools/FoolsManager.cs @@ -81,6 +81,7 @@ internal class FoolsManager : IDisposable, IServiceType new("DailyLifeDuty", "DailyLifeDutyPlugin", "Easily Track Daily and Weekly tasks... in real life", "MidoriKami", typeof(DailyLifeDutyPlugin)), new("Oops, Maybe Lalafells!", "OopsMaybeLalafellsPlugin", "Turn everyone into Lalafells? Maybe. We haven't quite tested it yet.", "Chrip", typeof(OopsMaybeLalafells)), new("Screensaver", "ScreensaverPlugin", "Prevent burn-in on loading screens.", "NotNite", typeof(ScreensaverPlugin)), + new("Cat Bubbles", "CatBubblesPlugin", "Enables in-game sdfgasdfgkljewriogdfkjghahfvcxbnmlqpwoeiruty", "Chirp's Cat, Sir Fluffington III", typeof(CatBubblesPlugin)) }; } diff --git a/Dalamud/Fools/Plugins/CatBubblesPlugin.cs b/Dalamud/Fools/Plugins/CatBubblesPlugin.cs new file mode 100644 index 000000000..3016a465e --- /dev/null +++ b/Dalamud/Fools/Plugins/CatBubblesPlugin.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Timers; + +using Dalamud.Game; +using Dalamud.Game.ClientState; +using Dalamud.Hooking; + +using FFXIVClientStructs.FFXIV.Client.Game; + +namespace Dalamud.Fools.Plugins; + +public class CatBubblesPlugin : IFoolsPlugin +{ + // Plugin + + private ClientState ClientState; + + public CatBubblesPlugin() + { + ClientState = Service.Get(); + + var sigscanner = Service.Get(); + + var openAddr = sigscanner.ScanText("E8 ?? ?? ?? ?? C7 43 ?? ?? ?? ?? ?? 48 8B 0D ?? ?? ?? ?? E8 ?? ?? ?? ??"); + BalloonOpen = Marshal.GetDelegateForFunctionPointer(openAddr); + + var updateAddr = sigscanner.ScanText("48 85 D2 0F 84 ?? ?? ?? ?? 48 89 5C 24 ?? 57 48 83 EC 20 8B 41 0C"); + BalloonUpdateHook = Hook.FromAddress(updateAddr, BalloonUpdateDetour); + BalloonUpdateHook.Enable(); + + Timer.Elapsed += OnTimerElapsed; + Timer.Interval = Rng.Next(3, 8) * 1000; + Timer.Start(); + } + + public void Dispose() + { + Timer.Elapsed -= OnTimerElapsed; + Timer.Stop(); + + BalloonUpdateHook.Disable(); + BalloonUpdateHook.Dispose(); + } + + private void OnTimerElapsed(object sender, object e) + { + EngageCatMode = true; + Timer.Interval = Rng.Next(35, 150) * 1000; + } + + // meow :3 + + private bool EngageCatMode = false; + + private readonly Timer Timer = new(); + private readonly Random Rng = new(); + + private readonly List strs1 = new() { "mrrp", "nya", "mew", "meow", "mraow", "purr" }; + private readonly List strs2 = new() { ":3", ":3c", "=^-^=" }; + private readonly List strs3 = new() { "zxcvbnm,./`-=", "qweasdzxc", "fghjkl;mnbvcxz", "plokmijnuhkjgs" }; + + private string GetRandStr(List list) + { + var x = Rng.Next(list.Count); + return list[x]; + } + + private string GenerateCatSpeak() + { + var items = new List(); + + var itemCt = Rng.Next(1, 10) + 1; + + int lastGen = -1; + bool hasEmoted = false; + for (var i = 0; i < itemCt; i++) + { + var isLast = i == itemCt - 1; + + var r = i == 0 ? 0 : Rng.Next(0, 3); + switch (r) + { + case 0: + items.Add(GetRandStr(strs1)); + break; + case 1: + if (hasEmoted && !isLast) goto case default; + var item = GetRandStr(strs2); + if (lastGen == 0) item = ' ' + item; + if (!isLast) item += ' '; + items.Add(item); + hasEmoted = true; + break; + case 2: + if (isLast && lastGen != 1) goto case 1; + if (lastGen != 0) goto case default; + items.Add(" "); + break; + default: + items.Add(GetRandStr(strs3)); + break; + } + + lastGen = r; + } + + return string.Join("", items); + } + + private delegate nint BalloonOpenDelegate(nint a1, nint a2, string a3, bool a4); + private BalloonOpenDelegate BalloonOpen; + + private delegate nint BalloonUpdateDelegate(nint a1, nint a2, nint a3, nint a4); + private Hook BalloonUpdateHook = null!; + + private unsafe nint BalloonUpdateDetour(nint a1, nint a2, nint a3, nint a4) + { + var balloon = (Balloon*)a1; + if (EngageCatMode && a2 == ClientState.LocalPlayer?.Address && balloon->State == BalloonState.Inactive) + { + var text = GenerateCatSpeak(); + balloon->Text.SetString(text); + balloon->State = BalloonState.Active; + balloon->Type = BalloonType.Timer; + balloon->PlayTimer = 5f; + BalloonOpen(a1, a2, text, balloon->UnkBool == 1); + + EngageCatMode = false; + } + + return BalloonUpdateHook.Original(a1, a2, a3, a4); + } +} diff --git a/Dalamud/Fools/Plugins/OopsMaybeLalafellsPlugin.cs b/Dalamud/Fools/Plugins/OopsMaybeLalafellsPlugin.cs index b8f5f4f6e..29982ba65 100644 --- a/Dalamud/Fools/Plugins/OopsMaybeLalafellsPlugin.cs +++ b/Dalamud/Fools/Plugins/OopsMaybeLalafellsPlugin.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using Dalamud.Game; using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Hooking; +using Dalamud.Logging; using FFXIVClientStructs.FFXIV.Client.Game.Object; @@ -27,6 +29,7 @@ public class OopsMaybeLalafells : IFoolsPlugin { SetupCharacterHook.Disable(); SetupCharacterHook.Dispose(); + RedrawAll(); } @@ -36,7 +39,7 @@ public class OopsMaybeLalafells : IFoolsPlugin var objects = Service.Get(); foreach (var obj in objects) { - if (obj.ObjectIndex > 241) break; + if (obj.ObjectIndex > 241 && obj.ObjectIndex < 301) continue; var csObject = (GameObject*)obj.Address; if (csObject == null) continue; @@ -54,30 +57,51 @@ public class OopsMaybeLalafells : IFoolsPlugin private readonly List ReplaceIDs = new() { 84, 85, 86, 87, 88, 89, 90, 91, 257, 258, 581, 597, 744 }; private const string SetupCharacterSig = "E8 ?? ?? ?? ?? 48 8B 0D ?? ?? ?? ?? 48 8B D7 E8 ?? ?? ?? ?? 48 8B CF E8 ?? ?? ?? ?? 48 8B C7"; - private delegate char SetupCharacterDelegate(nint a1, nint a2); private Hook SetupCharacterHook = null!; - private unsafe char SetupCharacterDetour(nint a1, nint a2) + private char SetupCharacterDetour(nint a1, nint a2) { - // Roll the dice - if (Rng.Next(0, 4) == 0) + try { - var customize = (byte*)a2; - customize[(int)CustomizeIndex.Race] = 3; - - var face = customize + (int)CustomizeIndex.FaceType; - *face = (byte)(1 + ((*face - 1) % 4)); + var custom = Marshal.PtrToStructure(a2); - var equipTar = (ushort)(customize[(int)CustomizeIndex.Gender] == 0 ? 92 : 93); - for (var i = 1; i < 5; i++) + // Roll the dice + if (custom.Race != 3 && Rng.Next(0, 4) == 0) { - var equip = (ushort*)(a2 + 28 + (i * 4)); - if (ReplaceIDs.Contains(*equip)) - *equip = equipTar; + custom.Race = 3; + custom.Tribe = (byte)(((custom.Race * 2) - 1) + 1 - (custom.Tribe % 2)); + custom.FaceType = (byte)(1 + ((custom.FaceType - 1) % 4)); + custom.ModelType %= 2; + Marshal.StructureToPtr(custom, a2, true); + + var equipTar = (ushort)(custom.Gender == 0 ? 92 : 93); + for (var i = 1; i < 5; i++) + { + var ofs = a2 + 28 + (i * 4); + var equip = (ushort)Marshal.ReadInt16(ofs); + if (ReplaceIDs.Contains(equip)) + Marshal.WriteInt16(ofs, (short)equipTar); + } } } + catch (Exception e) + { + PluginLog.Error(e.ToString(), e); + } return SetupCharacterHook.Original(a1, a2); } + + // Customize shit + + [StructLayout(LayoutKind.Explicit)] + private struct CustomizeData + { + [FieldOffset((int)CustomizeIndex.FaceType)] public byte FaceType; + [FieldOffset((int)CustomizeIndex.ModelType)] public byte ModelType; + [FieldOffset((int)CustomizeIndex.Race)] public byte Race; + [FieldOffset((int)CustomizeIndex.Tribe)] public byte Tribe; + [FieldOffset((int)CustomizeIndex.Gender)] public byte Gender; + } }