Merge pull request #1169 from goaaats/fools23

This commit is contained in:
goat 2023-03-31 21:05:38 +02:00 committed by GitHub
commit 5e4e530cfe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 2227 additions and 167 deletions

View file

@ -376,6 +376,8 @@ internal sealed class DalamudConfiguration : IServiceType
/// </summary>
public double UiBuilderHitch { get; set; } = 100;
public bool HasSeenFools23 { get; set; } = false;
/// <summary>
/// Load a configuration from the provided path.
/// </summary>

View file

@ -85,6 +85,7 @@
<PackageReference Include="System.Reactive" Version="5.0.0" />
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="7.0.0" />
<PackageReference Include="System.Resources.Extensions" Version="7.0.0" />
<PackageReference Include="System.Speech" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Dalamud.Interface\Dalamud.Interface.csproj" />

View file

@ -0,0 +1,257 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Dalamud.Configuration.Internal;
using Dalamud.Fools.Plugins;
using Dalamud.Game.ClientState;
using Dalamud.Interface;
using Dalamud.Interface.Internal;
using Dalamud.Logging.Internal;
namespace Dalamud.Fools;
// . ///*,.. .,*/((#######((((((((((((#(((########(*,****,,....,**///
// . (//*,,. ./(#%%%%%%%##((((/////(((((###(#####%#(/**,......,,**/(
// (*((/*,,,/###%%%%%%%%%%%#((///((((##%%%%%%%%####%#(//,. ..,,*/(*
// #((/*/(((((#%%%%&&%%%%%##(/(((((#%%%%&%%%%%%%##(((((/,...,,*/(#%
// %,&%#######%%%%%%%%%%%%%%%%###(####%%%%%%&&&%%%%%%#((((((((/**/(#&%
// ,,#&%%####%%%%%(//##%%%%%%%%%%#%%%%%%%%%%%%%%%%%%%%%%%%%###(####((#%#
// ,.%&%%%%%%%%%#/. *#%%%&&&&&&&&&%&&&%%%%%%%%%#*...*(##%%####((##%%%%
// ..%&&%%%%%%%%#(* ,%&&&&&&&&&&&&&&&&&&&%%%(/, . *(#%%#######%%%
// /#&&&%%%%%%%%%%#, *#&&&&&&&&&&&&&&&&&&&&&&&&&&%(, , ,(####%%%%####
// %&&&&%%%%&%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&%*. /#%%%%%#%%%%%%
// (&&%%%%%&&&&&&&&&&&&&%%%%%%%%%%%%##%%&&&&&&&&&&&&&&%%%%###%%%%%%%%%%%%%###
// *(@&%%%%%&&&&&%%%%%#(#############%%%%%%%&&&&&&&&&&&&&&&%%&%&&&%%%%%%%%%%%%%
// */&&%%%#((//******/********/(((((((/*/(#%%&&&&&&&&&&&&&&&&&&&&&%%%%%%%%%%%%%
// ((&%/,................,,,,,,,,*,,,,,*,,,,*(%%%%&&&&&&&&&&&&&&&&&%%%%%%%%%%%%
// . , ...............,,*(%%&&&&&&&&&&&&&&&&&&%%%%%%%%%%
// ., .,,,**/(#%%%&&&&&&&&&&&&&&%%%%%%%%%
// . ..,,,**/#%%%&&&&&&&&&&&&&&%%%%%%%%
// , .,*/#%%%%&&&&&&&&&&&&&%%%%%%%%
// ,,. ./(#%%%&&&&&&&&%&&&%%##%%%%%
// ((%&# ,(#%%%%&&&&&&&%%%&%%%%%%%%%
// #,%%/ .*#%%%%&&&&&&&&%&%%###%%%%%
// @(%%( .,(%%%%%%%&&&&&&&%%#%%%%%%%%
// /%%%* ./#%%%%%%%%%&&&&&%##%%%%%%%(
// #*&%%#. ..,(#%%%%%%%%%%&&&&&%%%%%%%#((,
// *(%%#/. .,**(#####%%%%%%%%%%%%%%%%%#**..
// #((#%##/. ,,*(####%%%%%%%%%%%&&%%%#(,
// **###((*. ,(*///((####%%%%%%%&&&%(/.,
// .,(###(//*,. ..,*//(((((##%%%%%%%%%%%%#%.
// . (#((/**,,.. ..,*//*/////(##%%%%%%%%%(#(*
// */(((/////,.. .....,,,,***///####%%%%%%%#.
// //*((((*,.. ...,...,,,.,//((((##%%%%%%%,/
// * //(/******,,,.,,..,,,****/*///((((#(###(/##(,
//
// April fools! <3
/// <summary>
/// Manager for all the IFoolsPlugin instances.
/// </summary>
[ServiceManager.BlockingEarlyLoadedService]
internal class FoolsManager : IDisposable, IServiceType
{
public readonly List<FoolsPluginMetadata> FoolsPlugins = new();
public readonly Dictionary<string, IFoolsPlugin> ActivatedPlugins = new();
private static readonly ModuleLog Log = new("FOOLS");
private UiBuilder uiBuilder;
private ClientState clientState;
[ServiceManager.ServiceConstructor]
private FoolsManager()
{
this.uiBuilder = new UiBuilder("fools");
this.uiBuilder.Draw += this.DrawUi;
this.clientState = Service<ClientState>.Get();
this.clientState.Login += this.ClientStateOnLogin;
// reflect over all IFoolsPlugin implementations sometime(?)
this.FoolsPlugins = new List<FoolsPluginMetadata>
{
new()
{
Name = "Pixel Imperfect",
InternalName = "PixelImperfectPlugin",
Description = "Whoops... we messed up the math on that one.",
Author = "Halpo",
RealAuthor = "NotNite",
Type = typeof(PixelImperfectPlugin),
},
new()
{
Name = "DailyLifeDuty",
InternalName = "DailyLifeDutyPlugin",
Description = "We were just informed there are these things called \"chores\" outside the game. No worries, though, we can track them!",
Author = "MidoriKami",
RealAuthor = "Berna",
Type = typeof(DailyLifeDutyPlugin),
},
new()
{
Name = "Oops, Maybe Lalafells?",
InternalName = "OopsMaybeLalafellsPlugin",
Description = "Turn everyone into Lalafells? Maybe. We haven't quite tested it yet.",
Author = "Chirpopo Chirpo",
RealAuthor = "Chirp",
Type = typeof(OopsMaybeLalafells),
},
new()
{
Name = "Screensaver",
InternalName = "ScreensaverPlugin",
Description = "Prevent burn-in on loading screens.",
Author = "NotNite",
RealAuthor = "NotNite",
Type = typeof(ScreensaverPlugin),
},
new()
{
Name = "Cat Bubbles",
InternalName = "CatBubblesPlugin",
Description = "Enables in-game sdfgasdfgkljewriogdfkjghahfvcxbnmlqpwoeiruty",
Author = "Chirp's Cat, Sir Fluffington III",
RealAuthor = "Chirp",
Type = typeof(CatBubblesPlugin),
},
/*
new()
{
Name = "YesSoliciting",
InternalName = "YesSolicitingPlugin",
Description = "Summon annoying shout messages from beyond the rift.",
Author = "Anna",
RealAuthor = "NotNite",
Type = typeof(YesSolicitingPlugin),
},
*/
new()
{
Name = "GoodVibes",
InternalName = "GoodVibesPlugin",
Description = "Shake things up with this vibe plugin!",
Author = "C h i r p",
RealAuthor = "Chirp",
Type = typeof(GoodVibesPlugin),
},
new()
{
Name = "YesHealMe",
InternalName = "YesHealMePlugin",
Description = "As the saying goes: it's the first missing HP that matters. And the second. And the third...",
Author = "MidoriKami",
RealAuthor = "Berna",
Type = typeof(YesHealMePlugin),
},
new()
{
Name = "Complicated Tweaks",
InternalName = "ComplicatedTweaksPlugin",
Description = "As complicated as it gets!",
Author = "Caraxi",
RealAuthor = "NotNite",
Type = typeof(ComplicatedTweaksPlugin),
},
new()
{
Name = "Hey Dalamud!",
InternalName = "HeyDalamudPlugin",
Description = "Scientists have unearthed advanced Allagan Voice Recognition Technology from before the Calamity, then they used it in a Dalamud plugin. Was it a good idea? That's for you to decide.\nVoice recognition is performed locally, it only listens after \"Hey, Dalamud!\" is detected(a sound will play) and none of your prompts will be stored.",
Author = "snake",
RealAuthor = "Avaflow",
Type = typeof(HeyDalamudPlugin),
},
};
}
public bool CheckIsApplicableAprilFoolsTime()
{
var now = DateTime.Now;
return now is { Year: 2023, Month: 4, Day: 1 };
}
public void ActivatePlugin(string plugin)
{
if (this.ActivatedPlugins.ContainsKey(plugin))
{
Log.Warning("Trying to activate plugin {0} that is already activated", plugin);
return;
}
var pluginMetadata = this.FoolsPlugins.FirstOrDefault(x => x.InternalName == plugin);
if (pluginMetadata == null)
{
Log.Warning("Trying to activate plugin {0} that does not exist", plugin);
return;
}
var pluginInstance = (IFoolsPlugin)Activator.CreateInstance(pluginMetadata.Type);
this.ActivatedPlugins.Add(plugin, pluginInstance);
}
public void Dispose()
{
foreach (var plugin in this.ActivatedPlugins.Values)
{
plugin.Dispose();
}
this.ActivatedPlugins.Clear();
((IDisposable)this.uiBuilder).Dispose();
this.clientState.Login -= this.ClientStateOnLogin;
}
public bool IsPluginActivated(string plugin)
{
return this.ActivatedPlugins.ContainsKey(plugin);
}
public void DeactivatePlugin(string plugin)
{
if (!this.ActivatedPlugins.ContainsKey(plugin))
{
Log.Warning("Trying to deactivate plugin {0} that is not activated", plugin);
return;
}
var pluginInstance = this.ActivatedPlugins[plugin];
pluginInstance.Dispose();
this.ActivatedPlugins.Remove(plugin);
}
private void DrawUi()
{
foreach (var plugin in this.ActivatedPlugins.Values)
{
plugin.DrawUi();
}
}
private void ClientStateOnLogin(object? o, EventArgs e)
{
var dalamudConfig = Service<DalamudConfiguration>.Get();
#if !DEBUG
if (DateTime.Now is not { Month: 4, Day: 1 })
{
return;
}
#endif
if (dalamudConfig.HasSeenFools23)
{
return;
}
/*
var di = Service<DalamudInterface>.Get();
di.OpenFoolsWindow();
dalamudConfig.HasSeenFools23 = true;
dalamudConfig.QueueSave();
*/
}
}

View file

@ -0,0 +1,18 @@
using System;
namespace Dalamud.Fools;
public class FoolsPluginMetadata
{
public string Name { get; init; }
public string InternalName { get; init; }
public string Description { get; init; }
public string Author { get; init; }
public string RealAuthor { get; init; }
public Type Type { get; init; }
}

View file

@ -0,0 +1,61 @@
using System;
using System.IO;
using System.Numerics;
using Dalamud.Interface;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Windowing;
using ImGuiNET;
using ImGuiScene;
namespace Dalamud.Fools;
// Stolen from ChangelogWindow
public class FoolsWindow : Window, IDisposable {
private readonly TextureWrap logoTexture;
public FoolsWindow()
: base("Introducing Alternate Reality Dalamud", ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoResize)
{
this.Namespace = "FoolsWindow";
this.Size = new Vector2(885, 463);
this.SizeCondition = ImGuiCond.Appearing;
var interfaceManager = Service<InterfaceManager>.Get();
var dalamud = Service<Dalamud>.Get();
this.logoTexture =
interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "logo.png"))!;
}
public override void Draw()
{
var imgCursor = ImGui.GetCursorPos();
ImGui.TextWrapped(@"
A team of scientists and plugin developers have collaborated to create a new
version of Dalamud that includes plugins from other realities.
With our high tech systems, the plugin installer will now offer new, unique
plugins with endless possibilities. Open the ""Alternate Reality"" tab in the
plugin installer to see what's available.
We hope you enjoy this new version of Dalamud, and we look forward to your feedback.
".Trim());
if (ImGui.Button("Open the plugin installer"))
{
var di = Service<DalamudInterface>.Get();
di.OpenPluginInstallerFools();
}
imgCursor.X += 500;
ImGui.SetCursorPos(imgCursor);
ImGui.Image(this.logoTexture.ImGuiHandle, new Vector2(100));
}
public void Dispose()
{
this.logoTexture.Dispose();
}
}

View file

@ -0,0 +1,32 @@
using Dalamud.Game.Gui;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
namespace Dalamud.Fools.Helper;
// Copied from KamiLib's Chat
// https://github.com/MidoriKami/KamiLib/blob/master/ChatCommands/Chat.cs
internal static class Chat
{
public static void Print(string pluginName, string tag, string message) => Service<ChatGui>.Get().Print(GetBaseString(pluginName, tag, message).BuiltString);
private static SeStringBuilder GetBaseString(string pluginName, string tag, string message, DalamudLinkPayload? payload = null)
{
if (payload is null)
{
return new SeStringBuilder()
.AddUiForeground($"[{pluginName}] ", 45)
.AddUiForeground($"[{tag}] ", 62)
.AddText(message);
}
else
{
return new SeStringBuilder()
.AddUiForeground($"[{pluginName}] ", 45)
.AddUiForeground($"[{tag}] ", 62)
.Add(payload)
.AddUiForeground(message, 35)
.Add(RawPayload.LinkTerminator);
}
}
}

View file

@ -0,0 +1,9 @@
using System.Numerics;
namespace Dalamud.Fools.Helper.YesHealMe;
public static class Colors
{
public static readonly Vector4 White = new(1.0f, 1.0f, 1.0f, 1.0f);
public static readonly Vector4 Black = new(0.0f, 0.0f, 0.0f, 1.0f);
}

View file

@ -0,0 +1,101 @@
using System;
using System.Numerics;
using ImGuiNET;
namespace Dalamud.Fools.Helper.YesHealMe;
internal static class DrawUtilities
{
public static void TextOutlined(
FontManager fontManager, Vector2 startingPosition, string text, float scale, Vector4 color)
{
startingPosition = startingPosition.Ceil();
var outlineThickness = (int)MathF.Ceiling(1 * scale);
for (var x = -outlineThickness; x <= outlineThickness; ++x)
{
for (var y = -outlineThickness; y <= outlineThickness; ++y)
{
if (x == 0 && y == 0)
{
continue;
}
DrawText(fontManager, startingPosition + new Vector2(x, y), text, Colors.Black, scale);
}
}
DrawText(fontManager, startingPosition, text, color, scale);
}
public static void DrawIconWithName(
FontManager fontManager, Vector2 drawPosition, uint iconID, string name, float iconScale, float textScale)
{
if (!fontManager.GameFont.Available)
{
return;
}
var icon = IconCache.Instance.GetIcon(iconID);
if (icon != null)
{
var drawList = ImGui.GetBackgroundDrawList();
var imagePadding = new Vector2(20.0f, 10.0f) * iconScale;
var imageSize = new Vector2(50.0f, 50.0f) * iconScale;
drawPosition += imagePadding;
drawList.AddImage(icon.ImGuiHandle, drawPosition, drawPosition + imageSize);
drawPosition.X += imageSize.X / 2.0f;
drawPosition.Y += imageSize.Y + (2.0f * iconScale);
var textSize = CalculateTextSize(fontManager, name, textScale / 2.75f);
var textOffset = new Vector2(0.0f, 5.0f) * iconScale;
drawPosition.X -= textSize.X / 2.0f;
TextOutlined(fontManager, drawPosition + textOffset, name, textScale / 2.75f, Colors.White);
}
}
public static Vector2 CalculateTextSize(FontManager fontManager, string text, float scale)
{
if (!fontManager.GameFont.Available)
{
return Vector2.Zero;
}
var fontSize = fontManager.GameFont.ImFont.FontSize;
var textSize = ImGui.CalcTextSize(text);
var fontScalar = 62.0f / textSize.Y;
var textWidth = textSize.X * fontScalar;
return new Vector2(textWidth, fontSize) * scale;
}
private static void DrawText(FontManager fontManager, Vector2 drawPosition, string text, Vector4 color, float scale)
{
if (!fontManager.GameFont.Available)
{
return;
}
var font = fontManager.GameFont.ImFont;
var drawList = ImGui.GetBackgroundDrawList();
drawList.AddText(font, font.FontSize * scale, drawPosition, ImGui.GetColorU32(color), text);
}
}
public static class VectorExtensions
{
public static Vector2 Ceil(this Vector2 data)
{
return new Vector2(MathF.Ceiling(data.X), MathF.Ceiling(data.Y));
}
}

View file

@ -0,0 +1,20 @@
using System;
using Dalamud.Interface;
using Dalamud.Interface.GameFonts;
namespace Dalamud.Fools.Helper.YesHealMe;
public class FontManager : IDisposable
{
public FontManager(UiBuilder uiBuilder)
{
this.GameFont = uiBuilder.GetGameFontHandle(new GameFontStyle(GameFontFamily.Axis, 52.0f));
}
public GameFontHandle GameFont { get; }
public void Dispose()
{
this.GameFont.Dispose();
}
}

View file

@ -0,0 +1,35 @@
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.SubKinds;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
namespace Dalamud.Fools.Helper.YesHealMe;
public static unsafe class HudHelper
{
private static AgentHUD* AgentHud => AgentModule.Instance()->GetAgentHUD();
public static PlayerCharacter? GetPlayerCharacter(int index)
{
// Sorta temporary, waiting for ClientStructs to merge a fixed size array for this element
var partyMemberList = AgentHud->PartyMemberList;
var targetOffset = index * sizeof(HudPartyMember);
var targetAddress = partyMemberList + targetOffset;
var hudData = (HudPartyMember*)targetAddress;
var targetPlayer = hudData->ObjectId;
return GetPlayer(targetPlayer);
}
private static PlayerCharacter? GetPlayer(uint objectId)
{
var result = Service<ObjectTable>.Get().SearchById(objectId);
if (result?.GetType() == typeof(PlayerCharacter))
{
return result as PlayerCharacter;
}
return null;
}
}

View file

@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Dalamud.Data;
using Dalamud.Logging;
using Dalamud.Utility;
using ImGuiScene;
namespace Dalamud.Fools.Helper.YesHealMe;
public class IconCache : IDisposable
{
private const string IconFilePath = "ui/icon/{0:D3}000/{1:D6}_hr1.tex";
private static IconCache? internalInstance;
private readonly Dictionary<uint, TextureWrap?> iconTextures = new();
public static IconCache Instance => internalInstance ??= new IconCache();
public void Dispose()
{
foreach (var texture in this.iconTextures.Values)
{
texture?.Dispose();
}
this.iconTextures.Clear();
}
public static void Cleanup()
{
internalInstance?.Dispose();
}
private void LoadIconTexture(uint iconId)
{
Task.Run(() =>
{
try
{
var path = IconFilePath.Format(iconId / 1000, iconId);
var tex = Service<DataManager>.Get().GetImGuiTexture(path);
if (tex is not null && tex.ImGuiHandle != nint.Zero)
{
this.iconTextures[iconId] = tex;
}
else
{
tex?.Dispose();
}
}
catch (Exception ex)
{
PluginLog.LogError($"Failed loading texture for icon {iconId} - {ex.Message}");
}
});
}
public TextureWrap? GetIcon(uint iconId)
{
if (this.iconTextures.TryGetValue(iconId, out var value))
{
return value;
}
this.iconTextures.Add(iconId, null);
this.LoadIconTexture(iconId);
return this.iconTextures[iconId];
}
}

View file

@ -0,0 +1,58 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game;
using Dalamud.Game.Gui;
using FFXIVClientStructs.FFXIV.Client.UI;
namespace Dalamud.Fools.Helper.YesHealMe;
public unsafe class PartyListAddon : IEnumerable<PartyListAddonData>, IDisposable
{
private readonly List<PartyListAddonData> addonData = new();
public PartyListAddon()
{
Service<Framework>.Get().Update += this.OnFrameworkUpdate;
}
private static AddonPartyList* PartyList => (AddonPartyList*)Service<GameGui>.Get()?.GetAddonByName("_PartyList");
private static bool DataAvailable => PartyList != null && PartyList->AtkUnitBase.RootNode != null;
public void Dispose()
{
Service<Framework>.Get().Update -= this.OnFrameworkUpdate;
}
public IEnumerator<PartyListAddonData> GetEnumerator()
{
return this.addonData.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
private void OnFrameworkUpdate(Framework framework)
{
this.addonData.Clear();
if (!DataAvailable || PartyList->MemberCount <= 0)
{
return;
}
foreach (var index in Enumerable.Range(0, PartyList->MemberCount))
{
var playerCharacter = HudHelper.GetPlayerCharacter(index);
var userInterface = PartyList->PartyMember[index];
this.addonData.Add(new PartyListAddonData
{
PlayerCharacter = playerCharacter,
UserInterface = userInterface,
});
}
}
}

View file

@ -0,0 +1,10 @@
using Dalamud.Game.ClientState.Objects.SubKinds;
using FFXIVClientStructs.FFXIV.Client.UI;
namespace Dalamud.Fools.Helper.YesHealMe;
public readonly struct PartyListAddonData
{
public AddonPartyList.PartyListMemberStruct UserInterface { get; init; }
public PlayerCharacter? PlayerCharacter { get; init; }
}

View file

@ -0,0 +1,8 @@
using System;
namespace Dalamud.Fools;
public interface IFoolsPlugin : IDisposable
{
public void DrawUi() { }
}

View file

@ -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<ClientState>.Get();
var sigscanner = Service<SigScanner>.Get();
var openAddr = sigscanner.ScanText("E8 ?? ?? ?? ?? C7 43 ?? ?? ?? ?? ?? 48 8B 0D ?? ?? ?? ?? E8 ?? ?? ?? ??");
BalloonOpen = Marshal.GetDelegateForFunctionPointer<BalloonOpenDelegate>(openAddr);
var updateAddr = sigscanner.ScanText("48 85 D2 0F 84 ?? ?? ?? ?? 48 89 5C 24 ?? 57 48 83 EC 20 8B 41 0C");
BalloonUpdateHook = Hook<BalloonUpdateDelegate>.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<string> strs1 = new() { "mrrp", "nya", "mew", "meow", "mraow", "purr" };
private readonly List<string> strs2 = new() { ":3", ":3c", "=^-^=" };
private readonly List<string> strs3 = new() { "zxcvbnm,./`-=", "qweasdzxc", "fghjkl;mnbvcxz", "plokmijnuhkjgs" };
private string GetRandStr(List<string> list)
{
var x = Rng.Next(list.Count);
return list[x];
}
private string GenerateCatSpeak()
{
var items = new List<string>();
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<BalloonUpdateDelegate> 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);
}
}

View file

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using ImGuiNET;
namespace Dalamud.Fools.Plugins;
public class ComplicatedTweaksPlugin : IFoolsPlugin
{
enum Widget
{
Button,
Checkbox,
DragFloat,
InputFloat,
}
private List<Widget> widgets;
public ComplicatedTweaksPlugin()
{
this.widgets = new List<Widget>();
var random = new Random();
var possibleWidgets = Enum.GetValues(typeof(Widget)).Cast<Widget>().ToList();
for (var i = 0; i < 100; i++)
{
var widget = possibleWidgets[random.Next(possibleWidgets.Count)];
this.widgets.Add(widget);
}
}
public void DrawUi()
{
if (ImGui.Begin("Complicated Tweaks"))
{
foreach (var widget in this.widgets)
{
switch (widget)
{
case Widget.Button:
ImGui.Button("Click me!");
break;
case Widget.Checkbox:
var iHateImgui = false;
ImGui.Checkbox(string.Empty, ref iHateImgui);
break;
case Widget.DragFloat:
var dragFloat = 0f;
ImGui.DragFloat(string.Empty, ref dragFloat);
break;
case Widget.InputFloat:
var inputFloat = 0f;
ImGui.InputFloat(string.Empty, ref inputFloat);
break;
}
}
}
ImGui.End();
}
public void Dispose() { }
}

View file

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Fools.Helper;
using Dalamud.Game;
namespace Dalamud.Fools.Plugins;
public class DailyLifeDutyPlugin : IFoolsPlugin
{
private const string PluginName = "DailyLifeDuty";
private static readonly List<Duty> Duties = new[]
{
new Duty("Dishes", i => $"{i} dish(es) to be cleaned"),
new Duty("Taxes", _ => "Taxes need to be filed"),
new Duty("Pets", i => $"{i} dog(s) waiting to be pet"),
new Duty("Garbage", i => $"{i} garbage bag(s) to be put out"),
new Duty("Bank", i => $"{i} bill(s) waiting payment"),
new Duty("Hydration", i => $"{i} glasses(s) of water remaining to reach Full Hydration"),
// new Duty("FINAL FANTASY XIV", i => $"At least {i} minute(s) left on your sub... maybe. Time is relative."),
}.ToList();
private long lastMessage;
public DailyLifeDutyPlugin()
{
Service<Framework>.Get().Update += this.OnUpdate;
this.EmitDutyReminder();
}
public void Dispose()
{
Service<Framework>.Get().Update -= this.OnUpdate;
}
private void OnUpdate(Framework framework)
{
if (DateTimeOffset.UtcNow.ToUnixTimeSeconds() - this.lastMessage > 60 * 5)
{
this.EmitDutyReminder();
}
}
private void EmitDutyReminder()
{
var duty = Duties[Random.Shared.Next(Duties.Count)];
Chat.Print(PluginName, duty.Tag, duty.Message(Random.Shared.Next(20)));
this.lastMessage = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
}
private class Duty
{
public Duty(string tag, Func<int, string> message)
{
this.Tag = tag;
this.Message = message;
}
internal string Tag { get; init; }
internal Func<int, string> Message { get; init; }
}
}

View file

@ -0,0 +1,46 @@
using System;
using System.Numerics;
using Dalamud.Game;
using Dalamud.Hooking;
namespace Dalamud.Fools.Plugins
{
public class GoodVibesPlugin : IFoolsPlugin
{
// Plugin
public GoodVibesPlugin()
{
var addr = Service<SigScanner>.Get().ScanText("48 83 EC 08 8B 02");
OffsetModelHook = Hook<OffsetModelDelegate>.FromAddress(addr, OffsetModelDetour);
OffsetModelHook.Enable();
}
public void Dispose()
{
OffsetModelHook.Disable();
OffsetModelHook.Dispose();
}
// brrrrrrrr
private readonly Random Rng = new();
private Vector3 GenRandVec()
{
var Pos = new float[3];
for (var i = 0; i < 3; i++)
Pos[i] = Rng.Next(-5, 5) / 1000f;
return new Vector3(Pos);
}
private delegate nint OffsetModelDelegate(nint a1, nint a2);
private Hook<OffsetModelDelegate> OffsetModelHook = null!;
private unsafe nint OffsetModelDetour(nint a1, nint a2)
{
*(Vector3*)a2 += GenRandVec();
return OffsetModelHook.Original(a1, a2);
}
}
}

View file

@ -0,0 +1,280 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Speech.Recognition;
using System.Speech.Synthesis;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Data;
using Dalamud.Fools.Helper;
using Dalamud.Game.ClientState;
using Dalamud.Game.Command;
using Dalamud.Logging.Internal;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.UI;
using Lumina.Excel.GeneratedSheets;
using Newtonsoft.Json;
namespace Dalamud.Fools.Plugins;
// The server for this one is open-source and available at: https://github.com/avafloww/HeyDalamud
public class HeyDalamudPlugin : IFoolsPlugin
{
public enum DetectionState
{
Hotword,
Action
}
private const uint StartSoundId = 23;
private const uint ErrorSoundId = 24;
private const uint StopSoundId = 31;
private const string ApiEndpoint = "https://allagan-voice-terminal.goat.place";
private static readonly string PluginName = "Hey, Dalamud!";
private static readonly ModuleLog Log = new("FOOLS-HD");
private readonly CancellationTokenSource noComprehendTaskCancellationTokenSource = new();
private Grammar actionGrammar;
private readonly ClientState clientState;
private readonly DataManager dataManager;
private readonly CommandManager commandManager;
private readonly CultureInfo definedCulture = CultureInfo.GetCultureInfo("en-US");
private Grammar heyDalamudGrammar;
private SpeechRecognitionEngine recognizer;
private DetectionState state;
private readonly SpeechSynthesizer synthesizer = new();
public HeyDalamudPlugin()
{
this.clientState = Service<ClientState>.Get();
this.dataManager = Service<DataManager>.Get();
this.commandManager = Service<CommandManager>.Get();
try
{
Thread.CurrentThread.CurrentCulture = this.definedCulture;
this.SetupSpeech();
Log.Information("Voice recognition initialized");
Chat.Print(PluginName, "Activated",
"Hi, welcome to \"Hey, Dalamud!\". I use the most advanced Allagan Voice Recognition Technology (AVRT) to process your commands. Say \"Hey, Dalamud!\" to get started!");
}
catch (Exception ex)
{
Chat.Print(PluginName, "Error",
"Could not start voice recognition. Please make sure that you have the American English Windows Language Pack installed.");
Log.Error(ex, "Could not init voice recognition");
}
}
public void Dispose()
{
this.synthesizer.Dispose();
this.recognizer.RecognizeAsyncStop();
this.recognizer.Dispose();
}
[DllImport("winmm.dll")]
public static extern int waveInGetNumDevs();
private void SetupSpeech()
{
this.state = DetectionState.Hotword;
this.recognizer?.RecognizeAsyncStop();
this.recognizer?.Dispose();
this.synthesizer.SetOutputToDefaultAudioDevice();
this.recognizer = new SpeechRecognitionEngine(this.definedCulture);
var numDevs = waveInGetNumDevs();
Log.Information("[REC] NumDevs: {0}", numDevs);
var heyDalamudBuilder = new GrammarBuilder("hey dalamud");
heyDalamudBuilder.Culture = this.definedCulture;
this.heyDalamudGrammar = new Grammar(heyDalamudBuilder);
this.heyDalamudGrammar.Name = "heyDalamudGrammar";
var actionBuilder = new GrammarBuilder();
actionBuilder.Culture = this.definedCulture;
actionBuilder.AppendDictation();
this.actionGrammar = new Grammar(actionBuilder)
{
Name = "actionGrammar"
};
// Create and load a dictation grammar.
this.recognizer.LoadGrammar(this.heyDalamudGrammar);
// Add a handler for the speech recognized event.
this.recognizer.SpeechRecognized += this.recognizer_SpeechRecognized;
// Configure input to the speech recognizer.
this.recognizer.SetInputToDefaultAudioDevice();
// Start asynchronous, continuous speech recognition.
this.recognizer.RecognizeAsync(RecognizeMode.Multiple);
}
private void SwitchToActionMode()
{
Log.Information("SwitchToActionMode");
Chat.Print(PluginName, "Listening", "Allagan Voice Recognition Technology is now active.");
UIModule.PlaySound(StartSoundId, 0, 0, 0);
this.recognizer.RecognizeAsyncStop();
this.state = DetectionState.Action;
this.recognizer.UnloadAllGrammars();
this.recognizer.LoadGrammar(this.actionGrammar);
this.recognizer.SetInputToDefaultAudioDevice();
this.recognizer.RecognizeAsync(RecognizeMode.Single);
Task.Run(
() =>
{
Thread.Sleep(9000);
if (this.state != DetectionState.Action) return;
UIModule.PlaySound(ErrorSoundId, 0, 0, 0);
this.synthesizer.SpeakAsync("Sorry, I didn't quite get that, please try again.");
this.recognizer.RecognizeAsyncStop();
this.recognizer.UnloadAllGrammars();
this.recognizer.LoadGrammar(this.heyDalamudGrammar);
this.state = DetectionState.Hotword;
this.recognizer.SetInputToDefaultAudioDevice();
this.recognizer.RecognizeAsync(RecognizeMode.Multiple);
}, this.noComprehendTaskCancellationTokenSource.Token
);
}
private async void recognizer_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
{
Log.Information("[REC] In mode: {0} Recognized text: {1}", this.state, e.Result.Text);
try
{
switch (this.state)
{
case DetectionState.Hotword:
this.SwitchToActionMode();
break;
case DetectionState.Action:
this.state = DetectionState.Hotword;
try
{
Chat.Print(PluginName, "Recognized", e.Result.Text);
this.noComprehendTaskCancellationTokenSource.Cancel();
this.recognizer.RecognizeAsyncStop();
QueryRequestPayload payload;
if (this.clientState.LocalPlayer != null)
{
var territoryType = this.dataManager.GetExcelSheet<TerritoryType>()!
.GetRow(this.clientState.TerritoryType);
var activeDuty = territoryType?.ContentFinderCondition.Value?.Name.RawString;
var activeArea = territoryType?.PlaceName.Value?.Name.RawString;
payload = new QueryRequestPayload
{
IsInGame = true,
Query = e.Result.Text,
CharacterFirstName = this.clientState.LocalPlayer?.Name.TextValue.Split(' ')[0],
ActiveAreaName = string.IsNullOrEmpty(activeArea) ? null : activeArea,
ActiveDutyName = string.IsNullOrEmpty(activeDuty) ? null : activeDuty,
};
}
else
{
payload = new QueryRequestPayload
{
IsInGame = false,
Query = e.Result.Text,
};
}
UIModule.PlaySound(StopSoundId, 0, 0, 0);
// make the request
var json = JsonConvert.SerializeObject(payload);
Log.Debug("[REC] Sending request: {0}", json);
var response = await Util.HttpClient.PostAsync(
$"{ApiEndpoint}/Query",
new StringContent(json, Encoding.UTF8, "application/json")
);
var responseData = await response.Content.ReadAsStringAsync();
Log.Debug("[REC] Got response: {0}", responseData);
var responseObject = JsonConvert.DeserializeObject<QueryResponsePayload>(responseData);
if (!string.IsNullOrEmpty(responseObject.Response))
{
Chat.Print(PluginName, "Response", responseObject.Response);
this.synthesizer.SpeakAsync(responseObject.Response);
}
if (!string.IsNullOrEmpty(responseObject.Command))
{
Log.Information("[REC] Executing command: {0}", responseObject.Command);
//this.commandManager.ProcessCommand(responseObject.Command);
}
} finally
{
this.recognizer.UnloadGrammar(this.actionGrammar);
this.recognizer.LoadGrammar(this.heyDalamudGrammar);
this.recognizer.SetInputToDefaultAudioDevice();
this.recognizer.RecognizeAsync(RecognizeMode.Multiple);
}
break;
}
}
catch (Exception ex)
{
Log.Error(ex, "Error in voice handling");
Chat.Print(PluginName, "Error", "Could not handle your voice input. Please try again.");
}
}
private class QueryRequestPayload
{
public string Query { get; set; }
public bool IsInGame { get; set; }
public string? CharacterFirstName { get; set; }
public string? ActiveDutyName { get; set; }
public string? ActiveAreaName { get; set; }
}
private class QueryResponsePayload
{
public bool Success { get; set; }
public string Response { get; set; }
public string? Command { get; set; }
}
}

View file

@ -0,0 +1,106 @@
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;
namespace Dalamud.Fools.Plugins;
public class OopsMaybeLalafells : IFoolsPlugin
{
// Oops, Maybe Lalafells?
// This plugin is deliberately nerfed to prevent a fully-formed revival of the original.
public OopsMaybeLalafells()
{
var scanner = Service<SigScanner>.Get();
var addr = scanner.Module.BaseAddress + 0x0484F60; // Deliberate choice in line with the above comment - this is intended to break after the next patch.
SetupCharacterHook = Hook<SetupCharacterDelegate>.FromAddress(addr, SetupCharacterDetour);
SetupCharacterHook.Enable();
RedrawAll();
}
public void Dispose()
{
SetupCharacterHook.Disable();
SetupCharacterHook.Dispose();
RedrawAll();
}
private unsafe void RedrawAll()
{
Service<Framework>.Get().RunOnFrameworkThread(() => {
var objects = Service<ObjectTable>.Get();
foreach (var obj in objects)
{
if (obj.ObjectIndex > 241 && obj.ObjectIndex < 301) continue;
var csObject = (GameObject*)obj.Address;
if (csObject == null) continue;
csObject->DisableDraw();
csObject->EnableDraw();
}
});
}
// The Lalafellinator
private readonly Random Rng = new();
private readonly List<ushort> ReplaceIDs = new() { 84, 85, 86, 87, 88, 89, 90, 91, 257, 258, 581, 597, 744 };
private delegate char SetupCharacterDelegate(nint a1, nint a2);
private Hook<SetupCharacterDelegate> SetupCharacterHook = null!;
private char SetupCharacterDetour(nint a1, nint a2)
{
try
{
var custom = Marshal.PtrToStructure<CustomizeData>(a2);
// Roll the dice
if (custom.Race != 3 && Rng.Next(0, 4) == 0)
{
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;
}
}

View file

@ -0,0 +1,53 @@
using System;
using System.Numerics;
using Dalamud.Game.ClientState;
using Dalamud.Game.Gui;
using Dalamud.Interface;
using ImGuiNET;
using static ImGuiNET.ImGuiWindowFlags;
namespace Dalamud.Fools.Plugins;
public class PixelImperfectPlugin : IFoolsPlugin
{
private ClientState clientState;
private GameGui gameGui;
public PixelImperfectPlugin()
{
this.clientState = Service<ClientState>.Get();
this.gameGui = Service<GameGui>.Get();
}
public void DrawUi()
{
if (this.clientState.LocalPlayer == null) return;
// Copied directly from PixelPerfect
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0, 0));
ImGuiHelpers.ForceNextWindowMainViewport();
ImGuiHelpers.SetNextWindowPosRelativeMainViewport(new Vector2(0, 0));
ImGui.Begin("Canvas", NoInputs | NoNav | NoTitleBar | NoScrollbar | NoBackground);
ImGui.SetWindowSize(ImGui.GetIO().DisplaySize);
var xOffset = Math.Sin(Environment.TickCount / 500.0);
var yOffset = Math.Sin(Environment.TickCount / 1500.0);
var actorPos = this.clientState.LocalPlayer.Position;
this.gameGui.WorldToScreen(
new Vector3(actorPos.X + (float)xOffset, actorPos.Y, actorPos.Z + (float)yOffset),
out var pos);
ImGui.GetWindowDrawList().AddCircle(
new Vector2(pos.X, pos.Y),
2,
ImGui.GetColorU32(new Vector4(255, 255, 255, 255)),
100,
10);
ImGui.End();
ImGui.PopStyleVar();
}
public void Dispose() { }
}

View file

@ -0,0 +1,71 @@
using System;
using System.IO;
using System.Numerics;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Interface;
using Dalamud.Interface.Internal;
using ImGuiNET;
using ImGuiScene;
using static ImGuiNET.ImGuiWindowFlags;
namespace Dalamud.Fools.Plugins;
public class ScreensaverPlugin : IFoolsPlugin
{
private readonly TextureWrap logoTexture;
private readonly Condition condition;
private int x;
private int y;
private bool xDir = true;
private bool yDir = true;
private double lastTime;
public ScreensaverPlugin()
{
var interfaceManager = Service<InterfaceManager>.Get();
var dalamud = Service<Dalamud>.Get();
this.condition = Service<Condition>.Get();
this.logoTexture =
interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "logo.png"))!;
}
public void DrawUi()
{
var time = Environment.TickCount64 / 1000.0;
var diff = time - this.lastTime;
diff = diff > 1 ? 1 : diff;
this.lastTime = time;
if (!this.condition[ConditionFlag.BetweenAreas])
{
return;
}
var textureSize = new Vector2(100);
var maxSize = ImGui.GetMainViewport().Size - textureSize;
this.xDir = this.xDir ? this.x < maxSize.X : this.x > 0;
this.yDir = this.yDir ? this.y < maxSize.Y : this.y > 0;
this.x += (int)(diff * (this.xDir ? 1 : -1) * 100);
this.y += (int)(diff * (this.yDir ? 1 : -1) * 100);
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0, 0));
ImGuiHelpers.ForceNextWindowMainViewport();
ImGuiHelpers.SetNextWindowPosRelativeMainViewport(new Vector2(this.x, this.y));
ImGui.Begin("Screensaver", NoInputs | NoNav | NoTitleBar | NoScrollbar | NoBackground);
ImGui.SetWindowSize(textureSize);
ImGui.Image(this.logoTexture.ImGuiHandle, textureSize);
ImGui.End();
ImGui.PopStyleVar();
}
public void Dispose()
{
this.logoTexture.Dispose();
}
}

View file

@ -0,0 +1,31 @@
using Dalamud.Fools.Helper.YesHealMe;
using Dalamud.Interface;
namespace Dalamud.Fools.Plugins;
public class YesHealMePlugin : IFoolsPlugin
{
private readonly FontManager fontManager;
private readonly PartyListAddon partyListAddon = new();
private int iconId = 1;
public YesHealMePlugin()
{
const string nameSpace = "fools+YesHealMe";
var uiBuilder = new UiBuilder(nameSpace);
this.fontManager = new FontManager(uiBuilder);
}
/// <inheritdoc/>
public void Dispose()
{
this.fontManager.Dispose();
this.partyListAddon.Dispose();
IconCache.Cleanup();
}
public void DrawUi()
{
YesHealMePluginWindow.Draw(this.partyListAddon, this.fontManager, ref this.iconId);
}
}

View file

@ -0,0 +1,92 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Fools.Helper.YesHealMe;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Interface.Internal;
using ImGuiNET;
namespace Dalamud.Fools.Plugins;
public static class YesHealMePluginWindow
{
private const string WindowName = "##foolsYesHealMeBanner";
private const string WarningText = "HEAL ME";
private const int Length = 300;
private const int SectionHeight = 100;
private const float Scale = 1f;
private static readonly Vector2 Position = new(200, 200);
private static readonly Vector2 Size = new(Length, SectionHeight);
private static IEnumerable<PlayerCharacter> Characters(PartyListAddon partyListAddon)
{
return partyListAddon.Any() ? partyListAddon.Select(pla => pla.PlayerCharacter) : new[] { Service<ClientState>.Get().LocalPlayer };
}
private static List<PlayerCharacter> HurtingCharacters(IEnumerable<PlayerCharacter> characters)
{
return characters
.Where(pc => pc.CurrentHp < pc.MaxHp ||
Service<DalamudInterface>.Get()
.IsDevMenuOpen)
.ToList();
}
public static void Draw(PartyListAddon partyListAddon, FontManager fontManager, ref int iconId)
{
ImGui.SetNextWindowPos(Position, ImGuiCond.FirstUseEver);
ImGui.SetNextWindowSize(Size);
ImGui.SetNextWindowSizeConstraints(Size, Size);
ImGui.Begin(WindowName, ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoBackground);
var playersDrawn = 0;
var hurtingCharacters = HurtingCharacters(Characters(partyListAddon));
if (hurtingCharacters.Count > 0)
{
var windowPos = ImGui.GetCursorScreenPos();
foreach (var hurtingCharacter in hurtingCharacters)
{
var position = windowPos + new Vector2(0, playersDrawn * SectionHeight);
var healMeTextSize = DrawUtilities.CalculateTextSize(fontManager, WarningText, Scale);
var healMePosition = position with
{
X = position.X + healMeTextSize.X,
};
DrawHealMeText(fontManager, position);
DrawPlayerName(fontManager, hurtingCharacter, healMePosition);
DrawIcon(fontManager, healMePosition);
playersDrawn += 1;
}
}
ImGui.End();
}
private static void DrawIcon(FontManager fontManager, Vector2 healMePosition)
{
DrawUtilities.DrawIconWithName(fontManager, healMePosition, 62582, "pls? owo", 1, 1);
}
private static void DrawHealMeText(FontManager fontManager, Vector2 position)
{
// HEAL ME text
DrawUtilities.TextOutlined(fontManager, position, WarningText, 1, Colors.White);
}
private static void DrawPlayerName(
FontManager fontManager, PlayerCharacter hurtingCharacter, Vector2 healMePosition)
{
var textSize = DrawUtilities.CalculateTextSize(fontManager, hurtingCharacter.Name.TextValue, Scale);
var namePosition = new Vector2
{
X = healMePosition.X - (textSize.X / 2.0f),
Y = healMePosition.Y + textSize.Y,
};
DrawUtilities.TextOutlined(
fontManager,
namePosition,
hurtingCharacter.Name.TextValue,
Scale / 2f,
Colors.White);
}
}

View file

@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using Dalamud.Data;
using Dalamud.Game;
using Dalamud.Game.Gui;
using Dalamud.Game.Text;
using Dalamud.Utility;
using Lumina.Excel.GeneratedSheets;
namespace Dalamud.Fools.Plugins;
public class YesSolicitingPlugin : IFoolsPlugin
{
private readonly Framework framework;
private readonly ChatGui chatGui;
private readonly List<string> firstNames;
private readonly List<string> lastNames;
private long nextUpdate;
public YesSolicitingPlugin()
{
this.framework = Service<Framework>.Get();
this.framework.Update += this.OnUpdate;
this.chatGui = Service<ChatGui>.Get();
var dataManager = Service<DataManager>.Get();
var charaMakeName = dataManager.GetExcelSheet<CharaMakeName>()!;
this.firstNames = new List<string>();
this.lastNames = new List<string>();
for (uint i = 0; i < charaMakeName.RowCount; i++)
{
var row = charaMakeName.GetRow(i);
if (row == null)
{
break;
}
// moon cats best cats, fight me
var firstName = row.MiqoteMoonFemale.ToDalamudString().TextValue;
var lastName = row.MiqoteMoonLastname.ToDalamudString().TextValue;
if (firstName.Trim() == string.Empty || lastName.Trim() == string.Empty)
{
break;
}
this.firstNames.Add(firstName);
this.lastNames.Add(lastName);
}
}
public void Dispose()
{
this.framework.Update -= this.OnUpdate;
}
private void Emit()
{
var firstName = this.firstNames[Random.Shared.Next(0, this.firstNames.Count)];
var lastName = this.lastNames[Random.Shared.Next(0, this.lastNames.Count)];
var messages = new List<string>
{
// legally required to put "april fools" in each of these so someone doesn't go outing themselves
"//**goat2023;PLUGIN SELLING FAST AND CHEAP;20 MINUTE WAIT TIME;APRIL FOOLS JOKE;https://goatcorp.github.io/;**//",
"(GOATCORP.GITHUB.IO) Buy April Fools Joke, Cheap FFXIV Plugins, Fast shipping ~-!<L>@#",
"Need plugins?|[GOATCORP.GITHUB.IO]|10mins Delivery time|CheapFast100%|[CODE:APRILFOOLS,2023%OFF]",
"GOATCORP.GITHUB.IO - 10min Delivery time - Cheap - Fast - 100% - CODE:APRILFOOLS,2023%OFF",
"Like to ERP? Join our Extraordinary Raid Party today!",
"Bored? Hungry? Visit the Alternate Reality Plugins section today!",
"Selling iTomestone 14 Pro - has world-first 0.5x mechanic zoom camera, /tell if interested",
"buying gf 10k gil",
"ULTIMATE TWENTY NINE HOUR RAID SESSION BEGINS NOW. WATCH LIVE AT twitch.tomestone/xXx_HARDCORE_GAMING_xXx",
// Copilot wrote this joke and it was so funny I had to keep it
"looking for group to clear ultimates with. i am 2000+ ilvl tank tell if interested",
"Are you looking for a night out of fun? Want to meet new people? See things you've never seen before? Meet me at the back alley of Ul'dah at 10pm tonight! Bring a robe.",
};
var message = messages[Random.Shared.Next(0, messages.Count)];
this.chatGui.PrintChat(new XivChatEntry
{
Name = $"[YesSoliciting] {firstName} {lastName}",
Message = message,
Type = XivChatType.Shout,
});
}
private void OnUpdate(Framework fr)
{
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (now >= this.nextUpdate)
{
this.Emit();
this.nextUpdate = now + (60 * 10);
}
}
}

View file

@ -9,6 +9,7 @@ using System.Runtime.InteropServices;
using CheapLoc;
using Dalamud.Configuration.Internal;
using Dalamud.Fools;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.Gui;
using Dalamud.Game.Internal;
@ -63,6 +64,7 @@ internal class DalamudInterface : IDisposable, IServiceType
private readonly ProfilerWindow profilerWindow;
private readonly BranchSwitcherWindow branchSwitcherWindow;
private readonly HitchSettingsWindow hitchSettingsWindow;
private readonly FoolsWindow foolsWindow;
private readonly TextureWrap logoTexture;
private readonly TextureWrap tsmLogoTexture;
@ -111,6 +113,7 @@ internal class DalamudInterface : IDisposable, IServiceType
this.profilerWindow = new ProfilerWindow() { IsOpen = false };
this.branchSwitcherWindow = new BranchSwitcherWindow() { IsOpen = false };
this.hitchSettingsWindow = new HitchSettingsWindow() { IsOpen = false };
this.foolsWindow = new FoolsWindow() { IsOpen = false };
this.WindowSystem.AddWindow(this.changelogWindow);
this.WindowSystem.AddWindow(this.colorDemoWindow);
@ -128,6 +131,7 @@ internal class DalamudInterface : IDisposable, IServiceType
this.WindowSystem.AddWindow(this.profilerWindow);
this.WindowSystem.AddWindow(this.branchSwitcherWindow);
this.WindowSystem.AddWindow(this.hitchSettingsWindow);
this.WindowSystem.AddWindow(this.foolsWindow);
ImGuiManagedAsserts.AssertsEnabled = configuration.AssertsEnabledAtStartup;
this.isImGuiDrawDevMenu = this.isImGuiDrawDevMenu || configuration.DevBarOpenAtStartup;
@ -339,6 +343,18 @@ internal class DalamudInterface : IDisposable, IServiceType
this.branchSwitcherWindow.BringToFront();
}
public void OpenFoolsWindow()
{
this.foolsWindow.IsOpen = true;
this.foolsWindow.BringToFront();
}
public void OpenPluginInstallerFools()
{
this.pluginWindow.OpenFools();
this.pluginWindow.BringToFront();
}
#endregion
#region Close

View file

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using CheapLoc;
using Dalamud.Plugin.Internal;
using Dalamud.Plugin.Internal.Types;
@ -23,11 +22,13 @@ internal class PluginCategoryManager
{
new(0, "special.all", () => Locs.Category_All),
new(1, "special.isTesting", () => Locs.Category_IsTesting, CategoryInfo.AppearCondition.DoPluginTest),
new(2, "special.availableForTesting", () => Locs.Category_AvailableForTesting, CategoryInfo.AppearCondition.DoPluginTest),
new(2, "special.availableForTesting", () => Locs.Category_AvailableForTesting,
CategoryInfo.AppearCondition.DoPluginTest),
new(10, "special.devInstalled", () => Locs.Category_DevInstalled),
new(11, "special.devIconTester", () => Locs.Category_IconTester),
new(12, "special.dalamud", () => Locs.Category_Dalamud),
new(13, "special.plugins", () => Locs.Category_Plugins),
new(14, "special.alternateReality", () => "Alternate Reality", CategoryInfo.AppearCondition.Fools23),
new(FirstTagBasedCategoryId + 0, "other", () => Locs.Category_Other),
new(FirstTagBasedCategoryId + 1, "jobs", () => Locs.Category_Jobs),
new(FirstTagBasedCategoryId + 2, "ui", () => Locs.Category_UI),
@ -36,7 +37,6 @@ internal class PluginCategoryManager
new(FirstTagBasedCategoryId + 5, "sound", () => Locs.Category_Sound),
new(FirstTagBasedCategoryId + 6, "social", () => Locs.Category_Social),
new(FirstTagBasedCategoryId + 7, "utility", () => Locs.Category_Utility),
// order doesn't matter, all tag driven categories should have Id >= FirstTagBasedCategoryId
};
@ -46,6 +46,7 @@ internal class PluginCategoryManager
new(GroupKind.Installed, () => Locs.Group_Installed, 0, 1),
new(GroupKind.Available, () => Locs.Group_Available, 0),
new(GroupKind.Changelog, () => Locs.Group_Changelog, 0, 12, 13),
new(GroupKind.AlternateReality, () => "Alternate Reality", 14),
// order important, used for drawing, keep in sync with defaults for currentGroupIdx
};
@ -81,6 +82,11 @@ internal class PluginCategoryManager
/// UI group: changelog of plugins.
/// </summary>
Changelog,
/// <summary>
/// April fools!
/// </summary>
AlternateReality
}
/// <summary>
@ -167,7 +173,8 @@ internal class PluginCategoryManager
foreach (var tag in pluginCategoryTags)
{
// only tags from whitelist can be accepted
var matchIdx = Array.FindIndex(this.CategoryList, x => x.Tag.Equals(tag, StringComparison.InvariantCultureIgnoreCase));
var matchIdx = Array.FindIndex(this.CategoryList,
x => x.Tag.Equals(tag, StringComparison.InvariantCultureIgnoreCase));
if (matchIdx >= 0)
{
var categoryId = this.CategoryList[matchIdx].CategoryId;
@ -235,7 +242,9 @@ internal class PluginCategoryManager
}
else
{
var selectedCategoryInfo = Array.Find(this.categoryList, x => x.CategoryId == groupInfo.Categories[this.currentCategoryIdx]);
var selectedCategoryInfo = Array.Find(this.categoryList,
x => x.CategoryId ==
groupInfo.Categories[this.currentCategoryIdx]);
foreach (var plugin in plugins)
{
@ -330,7 +339,8 @@ internal class PluginCategoryManager
/// <param name="tag">Tag to match.</param>
/// <param name="nameFunc">Function returning localized name of category.</param>
/// <param name="condition">Condition to be checked when deciding whether this category should be shown.</param>
public CategoryInfo(int categoryId, string tag, Func<string> nameFunc, AppearCondition condition = AppearCondition.None)
public CategoryInfo(
int categoryId, string tag, Func<string> nameFunc, AppearCondition condition = AppearCondition.None)
{
this.CategoryId = categoryId;
this.Tag = tag;
@ -352,6 +362,8 @@ internal class PluginCategoryManager
/// Check if plugin testing is enabled.
/// </summary>
DoPluginTest,
Fools23,
}
/// <summary>
@ -403,7 +415,8 @@ internal class PluginCategoryManager
public string Name => this.nameFunc();
}
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "locs")]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented",
Justification = "locs")]
internal static class Locs
{
#region UI groups
@ -424,9 +437,11 @@ internal class PluginCategoryManager
public static string Category_IsTesting => Loc.Localize("InstallerCategoryIsTesting", "Currently Testing");
public static string Category_AvailableForTesting => Loc.Localize("InstallerCategoryAvailableForTesting", "Testing Available");
public static string Category_AvailableForTesting =>
Loc.Localize("InstallerCategoryAvailableForTesting", "Testing Available");
public static string Category_DevInstalled => Loc.Localize("InstallerInstalledDevPlugins", "Installed Dev Plugins");
public static string Category_DevInstalled =>
Loc.Localize("InstallerInstalledDevPlugins", "Installed Dev Plugins");
public static string Category_IconTester => "Image/Icon Tester";