2022-10-23 02:38:58 +00:00
using Dalamud.Game ;
using Dalamud.Game.ClientState.Conditions ;
using Dalamud.Game.ClientState.Objects.Types ;
2022-10-25 17:28:11 +00:00
using Dalamud.Game.Command ;
2022-11-30 21:15:26 +00:00
using Dalamud.Game.Gui ;
2022-10-26 21:38:29 +00:00
using Dalamud.Game.Text ;
using Dalamud.Game.Text.SeStringHandling ;
2022-10-23 02:38:58 +00:00
using Dalamud.Interface.Windowing ;
2022-11-30 20:17:47 +00:00
using Dalamud.Logging ;
2022-10-23 02:38:58 +00:00
using Dalamud.Plugin ;
using ECommons ;
2022-10-26 18:43:24 +00:00
using Grpc.Core ;
2022-10-23 02:38:58 +00:00
using ImGuiNET ;
2022-10-26 21:38:29 +00:00
using Lumina.Excel.GeneratedSheets ;
2023-02-08 15:06:43 +00:00
using Pal.Client.Rendering ;
2022-12-22 00:01:09 +00:00
using Pal.Client.Scheduled ;
2022-10-26 18:43:24 +00:00
using Pal.Client.Windows ;
2022-11-30 10:37:34 +00:00
using Pal.Common ;
2022-10-23 02:38:58 +00:00
using System ;
using System.Collections.Concurrent ;
using System.Collections.Generic ;
using System.Linq ;
using System.Numerics ;
using System.Runtime.InteropServices ;
2022-10-26 21:38:29 +00:00
using System.Text.RegularExpressions ;
2022-10-23 02:38:58 +00:00
using System.Threading.Tasks ;
namespace Pal.Client
{
public class Plugin : IDalamudPlugin
{
2023-02-08 15:06:43 +00:00
internal const uint COLOR_INVISIBLE = 0 ;
2022-10-23 02:38:58 +00:00
2022-10-26 21:38:29 +00:00
private LocalizedChatMessages _localizedChatMessages = new ( ) ;
2022-10-23 02:38:58 +00:00
2022-10-29 22:21:17 +00:00
internal ConcurrentDictionary < ushort , LocalState > FloorMarkers { get ; } = new ( ) ;
2022-10-25 21:31:35 +00:00
internal ConcurrentBag < Marker > EphemeralMarkers { get ; set ; } = new ( ) ;
2022-12-22 00:01:09 +00:00
internal ushort LastTerritory { get ; set ; }
2022-10-23 02:38:58 +00:00
public SyncState TerritorySyncState { get ; set ; }
2022-10-26 21:38:29 +00:00
public PomanderState PomanderOfSight { get ; set ; } = PomanderState . Inactive ;
public PomanderState PomanderOfIntuition { get ; set ; } = PomanderState . Inactive ;
2022-10-30 10:02:49 +00:00
public string? DebugMessage { get ; set ; }
2022-12-22 00:01:09 +00:00
internal Queue < IQueueOnFrameworkThread > EarlyEventQueue { get ; } = new ( ) ;
internal Queue < IQueueOnFrameworkThread > LateEventQueue { get ; } = new ( ) ;
2023-02-05 03:21:24 +00:00
internal ConcurrentQueue < nint > NextUpdateObjects { get ; } = new ( ) ;
2023-02-08 15:06:43 +00:00
internal IRenderer Renderer { get ; private set ; } = null ! ;
2022-10-23 02:38:58 +00:00
public string Name = > "Palace Pal" ;
2022-11-30 21:15:26 +00:00
public Plugin ( DalamudPluginInterface pluginInterface , ChatGui chat )
2022-10-23 02:38:58 +00:00
{
2022-11-30 21:15:26 +00:00
PluginLog . Information ( $"Install source: {pluginInterface.SourceRepository}" ) ;
#if RELEASE
// You're welcome to remove this code in your fork, as long as:
// - none of the links accessible within FFXIV open the original repo (e.g. in the plugin installer), and
// - you host your own server instance
2022-12-07 16:29:50 +00:00
if ( ! pluginInterface . IsDev
& & ! pluginInterface . SourceRepository . StartsWith ( "https://raw.githubusercontent.com/carvelli/" )
& & ! pluginInterface . SourceRepository . StartsWith ( "https://github.com/carvelli/" ) )
2022-11-30 21:15:26 +00:00
{
chat . PrintError ( "[Palace Pal] Please install this plugin from the official repository at https://github.com/carvelli/Dalamud-Plugins to continue using it." ) ;
throw new InvalidOperationException ( ) ;
}
#endif
2022-10-23 02:38:58 +00:00
pluginInterface . Create < Service > ( ) ;
Service . Plugin = this ;
2022-10-30 10:02:49 +00:00
Service . Configuration = ( Configuration ? ) pluginInterface . GetPluginConfig ( ) ? ? pluginInterface . Create < Configuration > ( ) ! ;
2022-10-31 15:21:27 +00:00
Service . Configuration . Migrate ( ) ;
2023-02-08 15:06:43 +00:00
ResetRenderer ( ) ;
2023-02-05 03:21:24 +00:00
Service . Hooks = new Hooks ( ) ;
2022-10-23 02:38:58 +00:00
var agreementWindow = pluginInterface . Create < AgreementWindow > ( ) ;
if ( agreementWindow is not null )
{
agreementWindow . IsOpen = Service . Configuration . FirstUse ;
Service . WindowSystem . AddWindow ( agreementWindow ) ;
}
var configWindow = pluginInterface . Create < ConfigWindow > ( ) ;
if ( configWindow is not null )
{
Service . WindowSystem . AddWindow ( configWindow ) ;
}
2022-10-26 18:43:24 +00:00
var statisticsWindow = pluginInterface . Create < StatisticsWindow > ( ) ;
if ( statisticsWindow is not null )
{
Service . WindowSystem . AddWindow ( statisticsWindow ) ;
}
2023-02-08 15:06:43 +00:00
pluginInterface . UiBuilder . Draw + = Draw ;
2022-10-23 02:38:58 +00:00
pluginInterface . UiBuilder . OpenConfigUi + = OnOpenConfigUi ;
Service . Framework . Update + = OnFrameworkUpdate ;
2022-10-26 21:38:29 +00:00
Service . Chat . ChatMessage + = OnChatMessage ;
2022-10-25 17:28:11 +00:00
Service . CommandManager . AddHandler ( "/pal" , new CommandInfo ( OnCommand )
{
HelpMessage = "Open the configuration/debug window"
} ) ;
2022-10-26 21:38:29 +00:00
ReloadLanguageStrings ( ) ;
2022-10-23 02:38:58 +00:00
}
public void OnOpenConfigUi ( )
{
2022-10-30 10:02:49 +00:00
Window ? configWindow ;
2022-10-23 02:38:58 +00:00
if ( Service . Configuration . FirstUse )
configWindow = Service . WindowSystem . GetWindow < AgreementWindow > ( ) ;
else
configWindow = Service . WindowSystem . GetWindow < ConfigWindow > ( ) ;
if ( configWindow ! = null )
configWindow . IsOpen = true ;
}
2022-10-25 17:28:11 +00:00
private void OnCommand ( string command , string arguments )
{
if ( Service . Configuration . FirstUse )
{
Service . Chat . PrintError ( "[Palace Pal] Please finish the first-time setup first." ) ;
return ;
}
2022-10-29 22:21:17 +00:00
try
2022-10-26 18:43:24 +00:00
{
2022-11-30 10:53:26 +00:00
arguments = arguments . Trim ( ) ;
2022-10-29 22:21:17 +00:00
switch ( arguments )
{
case "stats" :
Task . Run ( async ( ) = > await FetchFloorStatistics ( ) ) ;
break ;
2022-11-25 08:43:24 +00:00
case "test-connection" :
case "tc" :
var configWindow = Service . WindowSystem . GetWindow < ConfigWindow > ( ) ;
if ( configWindow = = null )
return ;
configWindow . IsOpen = true ;
configWindow . TestConnection ( ) ;
break ;
2022-10-29 22:21:17 +00:00
#if DEBUG
case "update-saves" :
LocalState . UpdateAll ( ) ;
Service . Chat . Print ( "Updated all locally cached marker files to latest version." ) ;
break ;
#endif
2022-11-30 10:53:26 +00:00
case "" :
2022-11-30 15:39:50 +00:00
case "config" :
2022-10-29 22:21:17 +00:00
Service . WindowSystem . GetWindow < ConfigWindow > ( ) ? . Toggle ( ) ;
break ;
2022-11-30 10:53:26 +00:00
2023-02-02 16:16:03 +00:00
case "near" :
DebugNearest ( m = > true ) ;
break ;
case "tnear" :
DebugNearest ( m = > m . Type = = Marker . EType . Trap ) ;
break ;
case "hnear" :
DebugNearest ( m = > m . Type = = Marker . EType . Hoard ) ;
break ;
2022-11-30 10:53:26 +00:00
default :
Service . Chat . PrintError ( $"[Palace Pal] Unknown sub-command '{arguments}' for '{command}'." ) ;
break ;
2022-10-29 22:21:17 +00:00
}
}
catch ( Exception e )
{
Service . Chat . PrintError ( $"[Palace Pal] {e}" ) ;
2022-10-26 18:43:24 +00:00
}
2022-10-25 17:28:11 +00:00
}
2022-10-23 02:38:58 +00:00
#region IDisposable Support
protected virtual void Dispose ( bool disposing )
{
if ( ! disposing ) return ;
2022-10-25 17:28:11 +00:00
Service . CommandManager . RemoveHandler ( "/pal" ) ;
2023-02-08 15:06:43 +00:00
Service . PluginInterface . UiBuilder . Draw - = Draw ;
2022-10-23 02:38:58 +00:00
Service . PluginInterface . UiBuilder . OpenConfigUi - = OnOpenConfigUi ;
Service . Framework . Update - = OnFrameworkUpdate ;
2022-10-26 21:38:29 +00:00
Service . Chat . ChatMessage - = OnChatMessage ;
2022-10-23 02:38:58 +00:00
Service . WindowSystem . RemoveAllWindows ( ) ;
Service . RemoteApi . Dispose ( ) ;
2023-02-05 03:21:24 +00:00
Service . Hooks . Dispose ( ) ;
2022-10-28 18:31:33 +00:00
2023-02-08 15:06:43 +00:00
if ( Renderer is IDisposable disposable )
disposable . Dispose ( ) ;
2022-10-23 02:38:58 +00:00
}
public void Dispose ( )
{
Dispose ( true ) ;
GC . SuppressFinalize ( this ) ;
}
#endregion
2022-10-26 21:38:29 +00:00
private void OnChatMessage ( XivChatType type , uint senderId , ref SeString sender , ref SeString seMessage , ref bool isHandled )
{
2022-11-01 18:20:43 +00:00
if ( Service . Configuration . FirstUse )
return ;
2022-10-26 21:38:29 +00:00
if ( type ! = ( XivChatType ) 2105 )
return ;
string message = seMessage . ToString ( ) ;
if ( _localizedChatMessages . FloorChanged . IsMatch ( message ) )
{
PomanderOfSight = PomanderState . Inactive ;
if ( PomanderOfIntuition = = PomanderState . FoundOnCurrentFloor )
PomanderOfIntuition = PomanderState . Inactive ;
}
else if ( message . EndsWith ( _localizedChatMessages . MapRevealed ) )
{
PomanderOfSight = PomanderState . Active ;
}
else if ( message . EndsWith ( _localizedChatMessages . AllTrapsRemoved ) )
{
PomanderOfSight = PomanderState . PomanderOfSafetyUsed ;
}
else if ( message . EndsWith ( _localizedChatMessages . HoardNotOnCurrentFloor ) | | message . EndsWith ( _localizedChatMessages . HoardOnCurrentFloor ) )
{
// There is no functional difference between these - if you don't open the marked coffer,
// going to higher floors will keep the pomander active.
PomanderOfIntuition = PomanderState . Active ;
}
else if ( message . EndsWith ( _localizedChatMessages . HoardCofferOpened ) )
{
PomanderOfIntuition = PomanderState . FoundOnCurrentFloor ;
}
else
return ;
}
2022-10-23 02:38:58 +00:00
private void OnFrameworkUpdate ( Framework framework )
{
2022-11-01 18:20:43 +00:00
if ( Service . Configuration . FirstUse )
return ;
2022-10-23 02:38:58 +00:00
try
{
2022-10-25 21:31:35 +00:00
bool recreateLayout = false ;
2022-10-23 02:38:58 +00:00
bool saveMarkers = false ;
2022-12-22 00:01:09 +00:00
while ( EarlyEventQueue . TryDequeue ( out IQueueOnFrameworkThread ? queued ) )
queued ? . Run ( this , ref recreateLayout , ref saveMarkers ) ;
2022-10-23 02:38:58 +00:00
if ( LastTerritory ! = Service . ClientState . TerritoryType )
{
LastTerritory = Service . ClientState . TerritoryType ;
TerritorySyncState = SyncState . NotAttempted ;
2023-02-05 03:21:24 +00:00
NextUpdateObjects . Clear ( ) ;
2022-10-23 02:38:58 +00:00
2022-11-30 10:37:34 +00:00
if ( IsInDeepDungeon ( ) )
2022-12-22 00:01:09 +00:00
GetFloorMarkers ( LastTerritory ) ;
2022-10-25 21:31:35 +00:00
EphemeralMarkers . Clear ( ) ;
2022-10-26 21:38:29 +00:00
PomanderOfSight = PomanderState . Inactive ;
PomanderOfIntuition = PomanderState . Inactive ;
2022-10-23 02:38:58 +00:00
recreateLayout = true ;
2022-10-24 07:29:46 +00:00
DebugMessage = null ;
2022-10-23 02:38:58 +00:00
}
2022-11-30 10:37:34 +00:00
if ( ! IsInDeepDungeon ( ) )
2022-10-23 02:38:58 +00:00
return ;
if ( Service . Configuration . Mode = = Configuration . EMode . Online & & TerritorySyncState = = SyncState . NotAttempted )
{
TerritorySyncState = SyncState . Started ;
Task . Run ( async ( ) = > await DownloadMarkersForTerritory ( LastTerritory ) ) ;
}
2022-12-22 00:01:09 +00:00
while ( LateEventQueue . TryDequeue ( out IQueueOnFrameworkThread ? queued ) )
queued ? . Run ( this , ref recreateLayout , ref saveMarkers ) ;
2022-10-23 02:38:58 +00:00
2022-12-22 00:01:09 +00:00
var currentFloor = GetFloorMarkers ( LastTerritory ) ;
2022-10-23 02:38:58 +00:00
IList < Marker > visibleMarkers = GetRelevantGameObjects ( ) ;
2022-10-29 22:21:17 +00:00
HandlePersistentMarkers ( currentFloor , visibleMarkers . Where ( x = > x . IsPermanent ( ) ) . ToList ( ) , saveMarkers , recreateLayout ) ;
2022-10-25 21:31:35 +00:00
HandleEphemeralMarkers ( visibleMarkers . Where ( x = > ! x . IsPermanent ( ) ) . ToList ( ) , recreateLayout ) ;
}
catch ( Exception e )
{
DebugMessage = $"{DateTime.Now}\n{e}" ;
}
}
2022-10-23 02:38:58 +00:00
2022-12-22 00:01:09 +00:00
internal LocalState GetFloorMarkers ( ushort territoryType )
{
return FloorMarkers . GetOrAdd ( territoryType , tt = > LocalState . Load ( tt ) ? ? new LocalState ( tt ) ) ;
}
2023-02-08 15:06:43 +00:00
#region Rendering markers
2022-10-29 22:21:17 +00:00
private void HandlePersistentMarkers ( LocalState currentFloor , IList < Marker > visibleMarkers , bool saveMarkers , bool recreateLayout )
2022-10-25 21:31:35 +00:00
{
2022-10-26 21:38:29 +00:00
var config = Service . Configuration ;
2022-10-29 22:21:17 +00:00
var currentFloorMarkers = currentFloor . Markers ;
2022-10-23 02:38:58 +00:00
2022-10-31 16:34:47 +00:00
bool updateSeenMarkers = false ;
2023-02-06 21:00:38 +00:00
var partialAccountId = Service . RemoteApi . PartialAccountId ;
2022-10-25 21:31:35 +00:00
foreach ( var visibleMarker in visibleMarkers )
{
2022-10-30 10:02:49 +00:00
Marker ? knownMarker = currentFloorMarkers . SingleOrDefault ( x = > x = = visibleMarker ) ;
2022-10-25 21:31:35 +00:00
if ( knownMarker ! = null )
2022-10-23 02:38:58 +00:00
{
2022-10-25 21:31:35 +00:00
if ( ! knownMarker . Seen )
2022-10-23 02:38:58 +00:00
{
2022-10-25 21:31:35 +00:00
knownMarker . Seen = true ;
saveMarkers = true ;
2022-10-23 02:38:58 +00:00
}
2022-10-31 16:34:47 +00:00
// This requires you to have seen a trap/hoard marker once per floor to synchronize this for older local states,
// markers discovered afterwards are automatically marked seen.
2023-02-06 21:00:38 +00:00
if ( partialAccountId ! = null & & knownMarker . NetworkId ! = null & & ! knownMarker . RemoteSeenRequested & & ! knownMarker . RemoteSeenOn . Contains ( partialAccountId ) )
2022-10-31 16:34:47 +00:00
updateSeenMarkers = true ;
2022-12-01 16:56:26 +00:00
2022-10-25 21:31:35 +00:00
continue ;
2022-10-23 02:38:58 +00:00
}
2022-10-25 21:31:35 +00:00
currentFloorMarkers . Add ( visibleMarker ) ;
recreateLayout = true ;
saveMarkers = true ;
}
2022-10-23 02:38:58 +00:00
2022-10-26 22:25:09 +00:00
if ( ! recreateLayout & & currentFloorMarkers . Count > 0 & & ( config . OnlyVisibleTrapsAfterPomander | | config . OnlyVisibleHoardAfterPomander ) )
2022-10-26 21:38:29 +00:00
{
2022-10-26 22:25:09 +00:00
try
{
foreach ( var marker in currentFloorMarkers )
2022-10-26 21:38:29 +00:00
{
2022-10-26 22:25:09 +00:00
uint desiredColor = DetermineColor ( marker , visibleMarkers ) ;
2023-02-08 15:06:43 +00:00
if ( marker . RenderElement = = null | | ! marker . RenderElement . IsValid )
2022-10-26 21:38:29 +00:00
{
2022-10-26 22:25:09 +00:00
recreateLayout = true ;
break ;
2022-10-26 21:38:29 +00:00
}
2022-10-26 22:25:09 +00:00
2023-02-08 15:06:43 +00:00
if ( marker . RenderElement . Color ! = desiredColor )
marker . RenderElement . Color = desiredColor ;
2022-10-26 21:38:29 +00:00
}
}
2022-10-26 22:25:09 +00:00
catch ( Exception e )
{
DebugMessage = $"{DateTime.Now}\n{e}" ;
recreateLayout = true ;
}
2022-10-26 21:38:29 +00:00
}
2023-02-06 21:00:38 +00:00
if ( updateSeenMarkers & & partialAccountId ! = null )
2022-10-31 16:34:47 +00:00
{
2023-02-06 21:00:38 +00:00
var markersToUpdate = currentFloorMarkers . Where ( x = > x . Seen & & x . NetworkId ! = null & & ! x . RemoteSeenRequested & & ! x . RemoteSeenOn . Contains ( partialAccountId ) ) . ToList ( ) ;
2022-10-31 16:34:47 +00:00
foreach ( var marker in markersToUpdate )
marker . RemoteSeenRequested = true ;
Task . Run ( async ( ) = > await SyncSeenMarkersForTerritory ( LastTerritory , markersToUpdate ) ) ;
}
2022-10-25 21:31:35 +00:00
if ( saveMarkers )
{
2022-10-29 22:21:17 +00:00
currentFloor . Save ( ) ;
2022-10-23 02:38:58 +00:00
2022-10-25 21:31:35 +00:00
if ( TerritorySyncState = = SyncState . Complete )
{
2022-10-31 16:34:47 +00:00
var markersToUpload = currentFloorMarkers . Where ( x = > x . IsPermanent ( ) & & x . NetworkId = = null & & ! x . UploadRequested ) . ToList ( ) ;
2022-10-31 18:27:54 +00:00
if ( markersToUpload . Count > 0 )
{
foreach ( var marker in markersToUpload )
marker . UploadRequested = true ;
Task . Run ( async ( ) = > await UploadMarkersForTerritory ( LastTerritory , markersToUpload ) ) ;
}
2022-10-25 21:31:35 +00:00
}
}
2022-10-23 02:38:58 +00:00
2022-10-25 21:31:35 +00:00
if ( recreateLayout )
{
2023-02-08 15:06:43 +00:00
Renderer . ResetLayer ( ELayer . TrapHoard ) ;
2022-10-23 02:38:58 +00:00
2023-02-08 15:06:43 +00:00
List < IRenderElement > elements = new ( ) ;
2022-10-25 21:31:35 +00:00
foreach ( var marker in currentFloorMarkers )
{
2022-12-22 00:01:09 +00:00
if ( marker . Seen | | config . Mode = = Configuration . EMode . Online | | ( marker . WasImported & & marker . Imports . Count > 0 ) )
2022-10-23 02:38:58 +00:00
{
2022-10-25 21:31:35 +00:00
if ( marker . Type = = Marker . EType . Trap & & config . ShowTraps )
2022-10-23 02:38:58 +00:00
{
2023-02-08 15:06:43 +00:00
CreateRenderElement ( marker , elements , DetermineColor ( marker , visibleMarkers ) ) ;
2022-10-23 02:38:58 +00:00
}
2022-10-25 21:31:35 +00:00
else if ( marker . Type = = Marker . EType . Hoard & & config . ShowHoard )
2022-10-23 02:38:58 +00:00
{
2023-02-08 15:06:43 +00:00
CreateRenderElement ( marker , elements , DetermineColor ( marker , visibleMarkers ) ) ;
2022-10-23 02:38:58 +00:00
}
2022-10-25 21:31:35 +00:00
}
2022-10-23 02:38:58 +00:00
}
2022-10-25 21:31:35 +00:00
if ( elements . Count = = 0 )
return ;
2023-02-08 15:06:43 +00:00
Renderer . SetLayer ( ELayer . TrapHoard , elements ) ;
2022-10-23 02:38:58 +00:00
}
2022-10-25 21:31:35 +00:00
}
private void HandleEphemeralMarkers ( IList < Marker > visibleMarkers , bool recreateLayout )
{
recreateLayout | = EphemeralMarkers . Any ( existingMarker = > ! visibleMarkers . Any ( x = > x = = existingMarker ) ) ;
recreateLayout | = visibleMarkers . Any ( visibleMarker = > ! EphemeralMarkers . Any ( x = > x = = visibleMarker ) ) ;
if ( recreateLayout )
2022-10-23 02:38:58 +00:00
{
2023-02-08 15:06:43 +00:00
Renderer . ResetLayer ( ELayer . RegularCoffers ) ;
2022-10-25 21:31:35 +00:00
EphemeralMarkers . Clear ( ) ;
var config = Service . Configuration ;
2023-02-08 15:06:43 +00:00
List < IRenderElement > elements = new ( ) ;
2022-10-26 18:43:24 +00:00
foreach ( var marker in visibleMarkers )
{
2022-10-25 21:31:35 +00:00
EphemeralMarkers . Add ( marker ) ;
if ( marker . Type = = Marker . EType . SilverCoffer & & config . ShowSilverCoffers )
{
2023-02-08 15:06:43 +00:00
CreateRenderElement ( marker , elements , DetermineColor ( marker , visibleMarkers ) , config . FillSilverCoffers ) ;
2022-10-25 21:31:35 +00:00
}
}
if ( elements . Count = = 0 )
return ;
2023-02-08 15:06:43 +00:00
Renderer . SetLayer ( ELayer . RegularCoffers , elements ) ;
2022-10-23 02:38:58 +00:00
}
}
2023-02-08 15:06:43 +00:00
private uint DetermineColor ( Marker marker , IList < Marker > visibleMarkers )
{
if ( marker . Type = = Marker . EType . Trap )
{
if ( PomanderOfSight = = PomanderState . Inactive | | ! Service . Configuration . OnlyVisibleTrapsAfterPomander | | visibleMarkers . Any ( x = > x = = marker ) )
return ImGui . ColorConvertFloat4ToU32 ( Service . Configuration . TrapColor ) ;
else
return COLOR_INVISIBLE ;
}
else if ( marker . Type = = Marker . EType . Hoard )
{
if ( PomanderOfIntuition = = PomanderState . Inactive | | ! Service . Configuration . OnlyVisibleHoardAfterPomander | | visibleMarkers . Any ( x = > x = = marker ) )
return ImGui . ColorConvertFloat4ToU32 ( Service . Configuration . HoardColor ) ;
else
return COLOR_INVISIBLE ;
}
else if ( marker . Type = = Marker . EType . SilverCoffer )
return ImGui . ColorConvertFloat4ToU32 ( Service . Configuration . SilverCofferColor ) ;
else
return ImGui . ColorConvertFloat4ToU32 ( new Vector4 ( 1 , 0.5f , 1 , 0.4f ) ) ;
}
private void CreateRenderElement ( Marker marker , List < IRenderElement > elements , uint color , bool fill = false )
{
var element = Renderer . CreateElement ( marker . Type , marker . Position , color , fill ) ;
marker . RenderElement = element ;
elements . Add ( element ) ;
}
#endregion
2023-02-02 16:16:03 +00:00
#region Up - / Download
2022-10-23 02:38:58 +00:00
private async Task DownloadMarkersForTerritory ( ushort territoryId )
{
try
{
var ( success , downloadedMarkers ) = await Service . RemoteApi . DownloadRemoteMarkers ( territoryId ) ;
2022-12-22 00:01:09 +00:00
LateEventQueue . Enqueue ( new QueuedSyncResponse
2022-10-31 16:34:47 +00:00
{
Type = SyncType . Download ,
TerritoryType = territoryId ,
Success = success ,
Markers = downloadedMarkers
} ) ;
}
catch ( Exception e )
{
DebugMessage = $"{DateTime.Now}\n{e}" ;
}
}
private async Task UploadMarkersForTerritory ( ushort territoryId , List < Marker > markersToUpload )
{
try
{
var ( success , uploadedMarkers ) = await Service . RemoteApi . UploadMarker ( territoryId , markersToUpload ) ;
2022-12-22 00:01:09 +00:00
LateEventQueue . Enqueue ( new QueuedSyncResponse
2022-10-31 16:34:47 +00:00
{
Type = SyncType . Upload ,
TerritoryType = territoryId ,
Success = success ,
Markers = uploadedMarkers
} ) ;
2022-10-23 02:38:58 +00:00
}
catch ( Exception e )
{
DebugMessage = $"{DateTime.Now}\n{e}" ;
}
}
2022-10-31 16:34:47 +00:00
private async Task SyncSeenMarkersForTerritory ( ushort territoryId , List < Marker > markersToUpdate )
{
try
{
var success = await Service . RemoteApi . MarkAsSeen ( territoryId , markersToUpdate ) ;
2022-12-22 00:01:09 +00:00
LateEventQueue . Enqueue ( new QueuedSyncResponse
2022-10-31 16:34:47 +00:00
{
Type = SyncType . MarkSeen ,
TerritoryType = territoryId ,
Success = success ,
Markers = markersToUpdate ,
} ) ;
}
catch ( Exception e )
{
DebugMessage = $"{DateTime.Now}\n{e}" ;
}
}
2023-02-02 16:16:03 +00:00
#endregion
2022-10-31 16:34:47 +00:00
2023-02-02 16:16:03 +00:00
#region Command Handling
2022-10-26 18:43:24 +00:00
private async Task FetchFloorStatistics ( )
{
2022-12-11 14:22:41 +00:00
if ( ! Service . RemoteApi . HasRoleOnCurrentServer ( "statistics:view" ) )
2022-10-26 18:43:24 +00:00
{
2022-12-11 14:22:41 +00:00
Service . Chat . Print ( "[Palace Pal] You can view statistics for the floor you're currently on by opening the 'Debug' tab in the configuration window." ) ;
2022-10-26 18:43:24 +00:00
return ;
}
try
{
var ( success , floorStatistics ) = await Service . RemoteApi . FetchStatistics ( ) ;
if ( success )
{
2022-10-30 10:02:49 +00:00
var statisticsWindow = Service . WindowSystem . GetWindow < StatisticsWindow > ( ) ! ;
2022-10-26 18:43:24 +00:00
statisticsWindow . SetFloorData ( floorStatistics ) ;
statisticsWindow . IsOpen = true ;
}
else
{
Service . Chat . PrintError ( "[Palace Pal] Unable to fetch statistics." ) ;
}
}
catch ( RpcException e ) when ( e . StatusCode = = StatusCode . PermissionDenied )
{
2022-12-11 14:22:41 +00:00
Service . Chat . Print ( "[Palace Pal] You can view statistics for the floor you're currently on by opening the 'Debug' tab in the configuration window." ) ;
2022-10-26 18:43:24 +00:00
}
catch ( Exception e )
{
Service . Chat . PrintError ( $"[Palace Pal] {e}" ) ;
}
}
2023-02-02 16:16:03 +00:00
private void DebugNearest ( Predicate < Marker > predicate )
{
if ( ! IsInDeepDungeon ( ) )
return ;
var state = GetFloorMarkers ( Service . ClientState . TerritoryType ) ;
var playerPosition = Service . ClientState . LocalPlayer ? . Position ;
if ( playerPosition = = null )
return ;
2023-02-05 03:21:24 +00:00
Service . Chat . Print ( $"[Palace Pal] {playerPosition}" ) ;
2023-02-02 16:16:03 +00:00
var nearbyMarkers = state . Markers
. Where ( m = > predicate ( m ) )
2023-02-08 15:06:43 +00:00
. Where ( m = > m . RenderElement ! = null & & m . RenderElement . Color ! = COLOR_INVISIBLE )
2023-02-02 16:16:03 +00:00
. Select ( m = > new { m = m , distance = ( playerPosition - m . Position ) ? . Length ( ) ? ? float . MaxValue } )
. OrderBy ( m = > m . distance )
. Take ( 5 )
. ToList ( ) ;
foreach ( var nearbyMarker in nearbyMarkers )
Service . Chat . Print ( $"{nearbyMarker.distance:F2} - {nearbyMarker.m.Type} {nearbyMarker.m.NetworkId?.ToString()?.Substring(0, 8)} - {nearbyMarker.m.Position}" ) ;
}
#endregion
2022-10-23 02:38:58 +00:00
private IList < Marker > GetRelevantGameObjects ( )
{
List < Marker > result = new ( ) ;
2023-02-05 00:18:21 +00:00
for ( int i = 246 ; i < Service . ObjectTable . Length ; i + + )
2022-10-23 02:38:58 +00:00
{
2022-10-30 10:02:49 +00:00
GameObject ? obj = Service . ObjectTable [ i ] ;
2022-10-23 02:38:58 +00:00
if ( obj = = null )
continue ;
switch ( ( uint ) Marshal . ReadInt32 ( obj . Address + 128 ) )
{
case 2007182 :
case 2007183 :
case 2007184 :
case 2007185 :
case 2007186 :
case 2009504 :
2022-10-25 21:31:35 +00:00
result . Add ( new Marker ( Marker . EType . Trap , obj . Position ) { Seen = true } ) ;
2023-02-05 00:18:21 +00:00
break ;
2022-12-01 16:56:26 +00:00
2022-10-23 02:38:58 +00:00
case 2007542 :
case 2007543 :
2022-10-25 21:31:35 +00:00
result . Add ( new Marker ( Marker . EType . Hoard , obj . Position ) { Seen = true } ) ;
2023-02-05 00:18:21 +00:00
break ;
2022-10-25 21:31:35 +00:00
case 2007357 :
result . Add ( new Marker ( Marker . EType . SilverCoffer , obj . Position ) { Seen = true } ) ;
2023-02-05 00:18:21 +00:00
break ;
2022-10-23 02:38:58 +00:00
}
}
2023-02-05 03:21:24 +00:00
while ( NextUpdateObjects . TryDequeue ( out nint address ) )
{
var obj = Service . ObjectTable . FirstOrDefault ( x = > x . Address = = address ) ;
if ( obj ! = null & & obj . Position . Length ( ) > 0.1 )
2023-02-08 19:41:45 +00:00
result . Add ( new Marker ( Marker . EType . Trap , obj . Position ) { Seen = true } ) ;
2023-02-05 03:21:24 +00:00
}
2022-10-23 02:38:58 +00:00
return result ;
}
2022-11-30 10:37:34 +00:00
internal bool IsInDeepDungeon ( ) = >
2022-12-01 16:56:26 +00:00
Service . ClientState . IsLoggedIn
2022-11-30 10:37:34 +00:00
& & Service . Condition [ ConditionFlag . InDeepDungeon ]
& & typeof ( ETerritoryType ) . IsEnumDefined ( Service . ClientState . TerritoryType ) ;
2022-10-23 02:38:58 +00:00
2022-10-26 21:38:29 +00:00
private void ReloadLanguageStrings ( )
{
_localizedChatMessages = new LocalizedChatMessages
{
MapRevealed = GetLocalizedString ( 7256 ) ,
AllTrapsRemoved = GetLocalizedString ( 7255 ) ,
HoardOnCurrentFloor = GetLocalizedString ( 7272 ) ,
HoardNotOnCurrentFloor = GetLocalizedString ( 7273 ) ,
HoardCofferOpened = GetLocalizedString ( 7274 ) ,
FloorChanged = new Regex ( "^" + GetLocalizedString ( 7270 ) . Replace ( "\u0002 \u0003\ufffd\u0002\u0003" , @"(\d+)" ) + "$" ) ,
} ;
}
2023-02-08 15:06:43 +00:00
internal void ResetRenderer ( )
{
if ( Renderer is SplatoonRenderer & & Service . Configuration . Renderer = = Configuration . ERenderer . Splatoon )
return ;
else if ( Renderer is SimpleRenderer & & Service . Configuration . Renderer = = Configuration . ERenderer . Simple )
return ;
if ( Renderer is IDisposable disposable )
disposable . Dispose ( ) ;
if ( Service . Configuration . Renderer = = Configuration . ERenderer . Splatoon )
Renderer = new SplatoonRenderer ( Service . PluginInterface , this ) ;
else
Renderer = new SimpleRenderer ( ) ;
}
private void Draw ( )
{
if ( Renderer is SimpleRenderer sr )
sr . DrawLayers ( ) ;
Service . WindowSystem . Draw ( ) ;
}
2022-10-26 21:38:29 +00:00
private string GetLocalizedString ( uint id )
{
2022-10-30 10:02:49 +00:00
return Service . DataManager . GetExcelSheet < LogMessage > ( ) ? . GetRow ( id ) ? . Text ? . ToString ( ) ? ? "Unknown" ;
2022-10-26 21:38:29 +00:00
}
public enum PomanderState
{
Inactive ,
Active ,
FoundOnCurrentFloor ,
PomanderOfSafetyUsed ,
}
private class LocalizedChatMessages
{
public string MapRevealed { get ; set ; } = "???" ; //"The map for this floor has been revealed!";
public string AllTrapsRemoved { get ; set ; } = "???" ; // "All the traps on this floor have disappeared!";
public string HoardOnCurrentFloor { get ; set ; } = "???" ; // "You sense the Accursed Hoard calling you...";
public string HoardNotOnCurrentFloor { get ; set ; } = "???" ; // "You do not sense the call of the Accursed Hoard on this floor...";
public string HoardCofferOpened { get ; set ; } = "???" ; // "You discover a piece of the Accursed Hoard!";
public Regex FloorChanged { get ; set ; } = new Regex ( @"This isn't a game message, but will be replaced" ) ; // new Regex(@"^Floor (\d+)$");
}
2022-10-23 02:38:58 +00:00
}
}