using System.Drawing; using System.Numerics; using Dalamud.Bindings.ImGui; using Dalamud.Game.ClientState.Keys; using Dalamud.Interface; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using FFXIVClientStructs.FFXIV.Client.Game.UI; using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Agent; using KamiLib.Classes; using KamiLib.CommandManager; using KamiLib.Window; using Lumina.Excel.Sheets; using Mappy.Classes; using Mappy.Classes.MapWindowComponents; using Mappy.Controllers; using Mappy.Data; using Map = Lumina.Excel.Sheets.Map; namespace Mappy.Windows; public class MapWindow : Window { public Vector2 MapDrawOffset { get; private set; } public HoverFlags HoveredFlags { get; private set; } public bool ProcessingCommand { get; set; } private bool isDragStarted; private Vector2 lastWindowSize; private uint lastMapId; private uint lastAreaPlaceNameId; private uint lastSubAreaPlaceNameId; private readonly MapToolbar mapToolbar = new(); private readonly MapCoordinateBar mapCoordinateBar = new(); private readonly MapContextMenu mapContextMenu = new(); public MapWindow() : base("###HSMappyMapWindow", new Vector2(400.0f, 250.0f)) { UpdateTitle(); DisableWindowSounds = true; RegisterCommands(); } public override bool DrawConditions() => IntegrationsController.ShouldShowMap(); public override unsafe void PreOpenCheck() { // If you managed to open the window while the agent says it should be closed if (System.MapWindow.IsOpen && AgentMap.Instance()->AddonId is 0) { Service.Log.Debug("[OnShow] MapWindow can not be open now."); IsOpen = false; } if (System.SystemConfig.KeepOpen) { IsOpen = true; } if (Service.ClientState is { IsLoggedIn: false } or { IsPvP: true }) IsOpen = false; } public override void OnOpen() { if (ProcessingCommand) { ProcessingCommand = false; System.SystemConfig.FollowPlayer = false; return; } if (System.SystemConfig.FollowOnOpen) { System.IntegrationsController.OpenOccupiedMap(); System.SystemConfig.FollowPlayer = true; } switch (System.SystemConfig.CenterOnOpen) { case CenterTarget.Player when Service.ObjectTable.LocalPlayer is { } localPlayer: System.MapRenderer.CenterOnGameObject(localPlayer); break; case CenterTarget.Map: System.SystemConfig.FollowPlayer = false; System.MapRenderer.DrawOffset = Vector2.Zero; break; case CenterTarget.Disabled: default: break; } } protected override void DrawContents() { UpdateTitle(); UpdateStyle(); UpdateSizePosition(); HoveredFlags = HoverFlags.Nothing; if (WindowBounds.IsBoundedBy(ImGui.GetMousePos(), ImGui.GetCursorScreenPos(), ImGui.GetCursorScreenPos() + ImGui.GetContentRegionMax())) { HoveredFlags |= HoverFlags.Window; } MapDrawOffset = ImGui.GetCursorScreenPos(); using var fade = ImRaii.PushStyle(ImGuiStyleVar.Alpha, System.SystemConfig.FadePercent, ShouldFade()); using (var renderChild = ImRaii.Child("render_child", ImGui.GetContentRegionAvail(), false, ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoScrollbar)) { if (!renderChild) return; if (!System.SystemConfig.AcceptedSpoilerWarning) { DrawSpoilerWarning(); return; } DrawMapElements(); // Reset Draw Position for Overlay Extras ImGui.SetCursorPos(Vector2.Zero); DrawToolbar(); DrawCoordinateBar(); } if (ImGui.IsItemHovered()) { HoveredFlags |= HoverFlags.WindowInnerFrame; } // Process Inputs ProcessInputs(); } private void DrawMapElements() { System.MapRenderer.DrawBaseTexture(); if (ImGui.IsItemHovered()) { HoveredFlags |= HoverFlags.MapTexture; } System.MapRenderer.DrawDynamicElements(); } private void DrawToolbar() { if (!ShouldShowToolbar()) return; using (ImRaii.Group()) { mapToolbar.Draw(); } if (ImGui.IsItemHovered()) { HoveredFlags |= HoverFlags.Toolbar; } } private void DrawCoordinateBar() { if (!System.SystemConfig.ShowCoordinateBar) return; using (ImRaii.Group()) { mapCoordinateBar.Draw(HoveredFlags.HasFlag(HoverFlags.MapTexture), MapDrawOffset); } if (ImGui.IsItemHovered()) { HoveredFlags |= HoverFlags.CoordinateBar; } } private bool ShouldShowToolbar() { if (isDragStarted) return false; if (System.SystemConfig.ShowToolbarOnHover && HoveredFlags.Any()) return true; if (System.SystemConfig.AlwaysShowToolbar) return true; return false; } private unsafe void UpdateTitle() { var mapChanged = lastMapId != AgentMap.Instance()->SelectedMapId; var areaChanged = lastAreaPlaceNameId != TerritoryInfo.Instance()->AreaPlaceNameId; var subAreaChanged = lastSubAreaPlaceNameId != TerritoryInfo.Instance()->SubAreaPlaceNameId; var locationChanged = mapChanged || areaChanged || subAreaChanged; if (!locationChanged) return; var subLocationString = string.Empty; var mapData = Service.DataManager.GetExcelSheet().GetRow(AgentMap.Instance()->SelectedMapId); if (System.SystemConfig.ShowRegionLabel) { var mapRegionName = mapData.PlaceNameRegion.Value.Name.ExtractText(); subLocationString += $" - {mapRegionName}"; } if (System.SystemConfig.ShowMapLabel) { var mapName = mapData.PlaceName.Value.Name.ExtractText(); subLocationString += $" - {mapName}"; } // Don't show specific locations if we aren't there. if (AgentMap.Instance()->SelectedMapId == AgentMap.Instance()->CurrentMapId) { if (TerritoryInfo.Instance()->AreaPlaceNameId is not 0 && System.SystemConfig.ShowAreaLabel) { var areaLabel = Service.DataManager.GetExcelSheet().GetRow(TerritoryInfo.Instance()->AreaPlaceNameId); subLocationString += $" - {areaLabel.Name}"; } if (TerritoryInfo.Instance()->SubAreaPlaceNameId is not 0 && System.SystemConfig.ShowSubAreaLabel) { var subAreaLabel = Service.DataManager.GetExcelSheet().GetRow(TerritoryInfo.Instance()->SubAreaPlaceNameId); subLocationString += $" - {subAreaLabel.Name}"; } } WindowName = $"{subLocationString}###HSMappyMapWindow".TrimStart(['-',' ']); lastMapId = AgentMap.Instance()->SelectedMapId; lastAreaPlaceNameId = TerritoryInfo.Instance()->AreaPlaceNameId; lastSubAreaPlaceNameId = TerritoryInfo.Instance()->SubAreaPlaceNameId; } public void RefreshTitle() { lastMapId = 0; lastAreaPlaceNameId = 0; lastSubAreaPlaceNameId = 0; } private void ProcessInputs() { if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) { ImGui.OpenPopup("Mappy_Context_Menu"); } else { if (HoveredFlags.Any()) { if (System.SystemConfig.EnableShiftDragMove && ImGui.GetIO().KeyShift) { Flags &= ~ImGuiWindowFlags.NoMove; } else { ProcessMouseScroll(); ProcessMapDragStart(); Flags |= ImGuiWindowFlags.NoMove; } } ProcessMapDragDragging(); ProcessMapDragEnd(); } // Draw Context Menu mapContextMenu.Draw(MapDrawOffset); } private unsafe void UpdateStyle() { if (System.SystemConfig.HideWindowFrame) { Flags |= ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoBackground; } else { Flags &= ~(ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoBackground); } if (System.SystemConfig.LockWindow) { Flags |= ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove; } else { Flags &= ~(ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove); } if (System.SystemConfig.NoFocusOnAppear) { Flags |= ImGuiWindowFlags.NoFocusOnAppearing; } else { Flags &= ~ImGuiWindowFlags.NoFocusOnAppearing; } if (System.SystemConfig.HideWindowBackground) { Flags |= ImGuiWindowFlags.NoBackground; } else { Flags &= ~ImGuiWindowFlags.NoBackground; } if (Service.KeyState[VirtualKey.ESCAPE] && IsFocused && !IsMapLocked()) { AgentMap.Instance()->Hide(); } if (System.SystemConfig.FollowPlayer && Service.ObjectTable is { LocalPlayer: { } localPlayer }) { System.MapRenderer.CenterOnGameObject(localPlayer); } if (System.SystemConfig.LockCenterOnMap) { System.SystemConfig.FollowPlayer = false; System.MapRenderer.DrawOffset = Vector2.Zero; } } private void UpdateSizePosition() { var systemConfig = System.SystemConfig; var windowPosition = ImGui.GetWindowPos(); var windowSize = ImGui.GetWindowSize(); if (!IsFocused) { if (windowPosition != systemConfig.WindowPosition) { ImGui.SetWindowPos(systemConfig.WindowPosition); } if (windowSize != systemConfig.WindowSize) { ImGui.SetWindowSize(systemConfig.WindowSize); } } else { // If focused if (systemConfig.WindowPosition != windowPosition) { systemConfig.WindowPosition = windowPosition; SystemConfig.Save(); } if (systemConfig.WindowSize != windowSize) { systemConfig.WindowSize = windowSize; SystemConfig.Save(); } } } private static void DrawSpoilerWarning() { using (ImRaii.PushColor(ImGuiCol.Text, KnownColor.Orange.Vector())) { const string warningLine1 = "Warning, HSMappy does not protect you from spoilers and will show everything."; const string warningLine2 = "Do not use HSMappy if you are not comfortable with this."; ImGui.SetCursorPos(ImGui.GetContentRegionAvail() / 2.0f - (ImGui.CalcTextSize(warningLine1) * 2.0f) with { X = 0.0f }); ImGuiHelpers.CenteredText(warningLine1); ImGuiHelpers.CenteredText(warningLine2); } ImGuiHelpers.ScaledDummy(30.0f); ImGui.SetCursorPosX(ImGui.GetContentRegionAvail().X / 3.0f); using (ImRaii.Disabled(!(ImGui.GetIO().KeyShift && ImGui.GetIO().KeyCtrl))) { if (ImGui.Button("I understand", new Vector2(ImGui.GetContentRegionAvail().X / 2.0f, 23.0f * ImGuiHelpers.GlobalScale))) { System.SystemConfig.AcceptedSpoilerWarning = true; SystemConfig.Save(); } using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, 1.0f)) { if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) { ImGui.SetTooltip("Hold Shift + Control while clicking activate button"); } } } } public override unsafe void OnClose() { AgentMap.Instance()->Hide(); SystemConfig.Save(); } private void ProcessMouseScroll() { if (System.SystemConfig.ZoomLocked) return; if (ImGui.GetIO().MouseWheel is 0) return; if (!HoveredFlags.HasFlag(HoverFlags.WindowInnerFrame)) return; if (System.SystemConfig.UseLinearZoom) { MapRenderer.MapRenderer.Scale += System.SystemConfig.ZoomSpeed * ImGui.GetIO().MouseWheel; } else { MapRenderer.MapRenderer.Scale *= 1.0f + System.SystemConfig.ZoomSpeed * ImGui.GetIO().MouseWheel; } } private void ProcessMapDragDragging() { if (ImGui.IsMouseDragging(ImGuiMouseButton.Left) && isDragStarted) { System.MapRenderer.DrawOffset += ImGui.GetMouseDragDelta() / MapRenderer.MapRenderer.Scale; ImGui.ResetMouseDragDelta(); } } private void ProcessMapDragEnd() { if (ImGui.IsMouseReleased(ImGuiMouseButton.Left)) { isDragStarted = false; } } private void ProcessMapDragStart() { // Don't allow a drag to start if the window size is changing if (ImGui.GetWindowSize() == lastWindowSize) { if (ImGui.IsItemClicked(ImGuiMouseButton.Left) && !isDragStarted) { isDragStarted = true; System.SystemConfig.FollowPlayer = false; } } else { lastWindowSize = ImGui.GetWindowSize(); isDragStarted = false; } } private unsafe bool ShouldFade() => System.SystemConfig.FadeMode.HasFlag(FadeMode.Always) || System.SystemConfig.FadeMode.HasFlag(FadeMode.WhenFocused) && IsFocused || System.SystemConfig.FadeMode.HasFlag(FadeMode.WhenMoving) && AgentMap.Instance()->IsPlayerMoving || System.SystemConfig.FadeMode.HasFlag(FadeMode.WhenUnFocused) && !IsFocused; private void RegisterCommands() { System.CommandManager.RegisterCommand(new ToggleCommandHandler { UseShowHideText = true, BaseActivationPath = "/map", EnableDelegate = _ => System.MapWindow.UnCollapseOrShow(), DisableDelegate = _ => System.MapWindow.Close(), ToggleDelegate = _ => System.MapWindow.UnCollapseOrToggle(), }); System.CommandManager.RegisterCommand(new CommandHandler { ActivationPath = "/map/follow", Delegate = _ => { System.SystemConfig.FollowPlayer = true; SystemConfig.Save(); }, }); System.CommandManager.RegisterCommand(new CommandHandler { ActivationPath = "/map/unfollow", Delegate = _ => { System.SystemConfig.FollowPlayer = false; SystemConfig.Save(); }, }); System.CommandManager.RegisterCommand(new ToggleCommandHandler { BaseActivationPath = "/autofollow", EnableDelegate = _ => { System.SystemConfig.FollowOnOpen = true; SystemConfig.Save(); }, DisableDelegate = _ => { System.SystemConfig.FollowOnOpen = false; SystemConfig.Save(); }, ToggleDelegate = _ => { System.SystemConfig.FollowOnOpen = !System.SystemConfig.FollowOnOpen; SystemConfig.Save(); }, }); System.CommandManager.RegisterCommand(new ToggleCommandHandler { BaseActivationPath = "/keepopen", EnableDelegate = _ => { System.SystemConfig.KeepOpen = true; SystemConfig.Save(); }, DisableDelegate = _ => { System.SystemConfig.KeepOpen = false; SystemConfig.Save(); }, ToggleDelegate = _ => { System.SystemConfig.KeepOpen = !System.SystemConfig.KeepOpen; SystemConfig.Save(); }, }); System.CommandManager.RegisterCommand(new CommandHandler { ActivationPath = "/center/player", Delegate = _ => { if (Service.ObjectTable.LocalPlayer is { } localPlayer) { System.MapRenderer.CenterOnGameObject(localPlayer); } }, }); System.CommandManager.RegisterCommand(new CommandHandler { ActivationPath = "/center/map", Delegate = _ => { System.SystemConfig.FollowPlayer = false; System.MapRenderer.DrawOffset = Vector2.Zero; }, }); } private static unsafe bool IsMapLocked() { var addon = Service.GameGui.GetAddonByName("AreaMap"); if (addon is null || addon->RootNode is null) return false; return (addon->Param & 0x8_0000) > 0; } }