2020-07-29 16:01:00 +00:00
using Dalamud.Game.ClientState ;
using Dalamud.Game.ClientState.Actors.Types ;
using Dalamud.Plugin ;
using ImGuiNET ;
using System ;
2020-07-30 01:01:22 +00:00
using System.Collections.Generic ;
2020-07-30 01:54:04 +00:00
using System.Linq ;
2020-07-29 16:01:00 +00:00
using System.Runtime.InteropServices ;
2020-07-30 01:54:04 +00:00
// TODO: Job swaps?
// TODO: Zone swaps?
2020-07-29 16:01:00 +00:00
namespace HudSwap {
public class PluginUI {
private readonly HudSwapPlugin plugin ;
private readonly DalamudPluginInterface pi ;
private readonly Statuses statuses ;
private bool _settingsVisible = false ;
public bool SettingsVisible { get = > this . _settingsVisible ; set = > this . _settingsVisible = value ; }
public PluginUI ( HudSwapPlugin plugin , DalamudPluginInterface pi ) {
this . plugin = plugin ;
this . pi = pi ;
this . statuses = new Statuses ( this . plugin , this . pi ) ;
}
public void ConfigUI ( object sender , EventArgs args ) {
this . SettingsVisible = true ;
}
2020-07-29 16:30:14 +00:00
2020-07-30 01:01:22 +00:00
private string importName = "" ;
private Guid selectedLayout = Guid . Empty ;
2020-07-30 02:42:57 +00:00
private static bool configErrorOpen = true ;
public static void ConfigError ( ) {
if ( ImGui . Begin ( "HudSwap error" , ref configErrorOpen ) ) {
ImGui . Text ( "Could not load HudSwap configuration." ) ;
ImGui . Spacing ( ) ;
ImGui . Text ( "If you are updating from a previous version, please\ndelete your configuration file and restart the game." ) ;
ImGui . End ( ) ;
}
}
2020-07-29 16:01:00 +00:00
public void DrawSettings ( ) {
if ( ! this . SettingsVisible ) {
return ;
}
PlayerCharacter player = this . pi . ClientState . LocalPlayer ;
if ( ImGui . Begin ( "HudSwap" , ref this . _settingsVisible , ImGuiWindowFlags . AlwaysAutoResize ) ) {
2020-07-30 01:01:22 +00:00
if ( ImGui . BeginTabBar ( "##hudswap-tabs" ) ) {
if ( ImGui . BeginTabItem ( "Layouts" ) ) {
ImGui . Text ( "Saved layouts" ) ;
if ( this . plugin . config . Layouts . Keys . Count = = 0 ) {
ImGui . Text ( "None saved!" ) ;
} else {
if ( ImGui . ListBoxHeader ( "##saved-layouts" ) ) {
foreach ( KeyValuePair < Guid , Tuple < string , byte [ ] > > entry in this . plugin . config . Layouts ) {
if ( ImGui . Selectable ( entry . Value . Item1 , this . selectedLayout = = entry . Key ) ) {
this . selectedLayout = entry . Key ;
}
}
ImGui . ListBoxFooter ( ) ;
}
ImGui . Text ( "Copy onto slot..." ) ;
foreach ( HudSlot slot in Enum . GetValues ( typeof ( HudSlot ) ) ) {
string buttonName = $"{(int)slot + 1}##copy" ;
if ( ImGui . Button ( buttonName ) & & this . selectedLayout ! = null ) {
byte [ ] layout = this . plugin . config . Layouts [ this . selectedLayout ] . Item2 ;
this . plugin . hud . WriteLayout ( slot , layout ) ;
}
ImGui . SameLine ( ) ;
}
if ( ImGui . Button ( "Delete" ) & & this . selectedLayout ! = null ) {
this . plugin . config . Layouts . Remove ( this . selectedLayout ) ;
this . selectedLayout = Guid . Empty ;
this . plugin . config . Save ( ) ;
}
}
ImGui . Separator ( ) ;
ImGui . Text ( "Import" ) ;
ImGui . InputText ( "Imported layout name" , ref this . importName , 100 ) ;
foreach ( HudSlot slot in Enum . GetValues ( typeof ( HudSlot ) ) ) {
string buttonName = $"{(int)slot + 1}##import" ;
if ( ImGui . Button ( buttonName ) & & this . importName ! = "" ) {
2020-07-30 17:53:13 +00:00
this . ImportSlot ( slot , this . importName ) ;
2020-07-30 01:01:22 +00:00
this . importName = "" ;
}
if ( slot ! = HudSlot . Four ) {
ImGui . SameLine ( ) ;
}
}
ImGui . EndTabItem ( ) ;
}
if ( ImGui . BeginTabItem ( "Swaps" ) ) {
bool enabled = this . plugin . config . SwapsEnabled ;
2020-07-30 17:20:30 +00:00
if ( ImGui . Checkbox ( "Enable swaps" , ref enabled ) ) {
2020-07-30 01:01:22 +00:00
this . plugin . config . SwapsEnabled = enabled ;
this . plugin . config . Save ( ) ;
}
2020-07-30 17:20:30 +00:00
ImGui . Text ( "Note: Disable swaps when editing your HUD." ) ;
2020-07-30 01:01:22 +00:00
2020-07-30 17:54:13 +00:00
ImGui . Spacing ( ) ;
string staging = ( ( int ) this . plugin . config . StagingSlot + 1 ) . ToString ( ) ;
if ( ImGui . BeginCombo ( "Staging slot" , staging ) ) {
foreach ( HudSlot slot in Enum . GetValues ( typeof ( HudSlot ) ) ) {
if ( ImGui . Selectable ( ( ( int ) slot + 1 ) . ToString ( ) ) ) {
this . plugin . config . StagingSlot = slot ;
this . plugin . config . Save ( ) ;
}
}
ImGui . EndCombo ( ) ;
}
ImGui . SameLine ( ) ;
HelpMarker ( "The staging slot is the HUD layout slot that will be used as your HUD layout. All changes will be written to this slot when swaps are enabled." ) ;
2020-07-30 01:01:22 +00:00
ImGui . Separator ( ) ;
ImGui . Text ( "This is the default layout. If none of the below conditions are\nsatisfied, this layout will be enabled." ) ;
if ( ImGui . BeginCombo ( "##default-layout" , this . LayoutNameOrDefault ( this . plugin . config . defaultLayout ) ) ) {
foreach ( KeyValuePair < Guid , Tuple < string , byte [ ] > > entry in this . plugin . config . Layouts ) {
if ( ImGui . Selectable ( entry . Value . Item1 ) ) {
this . plugin . config . defaultLayout = entry . Key ;
this . plugin . config . Save ( ) ;
}
}
ImGui . EndCombo ( ) ;
}
ImGui . Spacing ( ) ;
ImGui . Text ( "These settings are ordered from highest priority to lowest priority.\nHigher priorities overwrite lower priorities when enabled." ) ;
ImGui . Spacing ( ) ;
ImGui . Columns ( 2 ) ;
this . LayoutBox ( "In combat" , ref this . plugin . config . combatLayout , player ) ;
this . LayoutBox ( "Weapon drawn" , ref this . plugin . config . weaponDrawnLayout , player ) ;
this . LayoutBox ( "In instance" , ref this . plugin . config . instanceLayout , player ) ;
this . LayoutBox ( "Crafting" , ref this . plugin . config . craftingLayout , player ) ;
this . LayoutBox ( "Gathering" , ref this . plugin . config . gatheringLayout , player ) ;
2020-07-30 01:54:04 +00:00
this . LayoutBox ( "Fishing" , ref this . plugin . config . fishingLayout , player ) ;
2020-07-30 01:01:22 +00:00
ImGui . Columns ( 1 ) ;
ImGui . EndTabItem ( ) ;
}
ImGui . EndTabBar ( ) ;
2020-07-29 16:01:00 +00:00
}
ImGui . End ( ) ;
}
}
2020-07-30 17:54:13 +00:00
private void HelpMarker ( string text ) {
ImGui . TextDisabled ( "(?)" ) ;
if ( ImGui . IsItemHovered ( ) ) {
ImGui . BeginTooltip ( ) ;
ImGui . PushTextWrapPos ( ImGui . GetFontSize ( ) * 20f ) ;
ImGui . TextUnformatted ( text ) ;
ImGui . PopTextWrapPos ( ) ;
ImGui . EndTooltip ( ) ;
}
}
2020-07-30 01:01:22 +00:00
private string LayoutNameOrDefault ( Guid key ) {
Tuple < string , byte [ ] > tuple ;
if ( this . plugin . config . Layouts . TryGetValue ( key , out tuple ) ) {
return tuple . Item1 ;
} else {
return "" ;
}
}
2020-07-29 16:01:00 +00:00
public void Draw ( ) {
this . DrawSettings ( ) ;
2020-07-30 01:01:22 +00:00
if ( ! this . plugin . config . SwapsEnabled ) {
return ;
}
2020-07-29 16:01:00 +00:00
PlayerCharacter player = this . pi . ClientState . LocalPlayer ;
if ( player = = null ) {
return ;
}
if ( this . statuses . Update ( player ) ) {
this . statuses . SetHudLayout ( null ) ;
}
}
2020-07-30 01:01:22 +00:00
private void LayoutBox ( string name , ref Guid layout , PlayerCharacter player ) {
ImGui . Text ( name ) ;
ImGui . NextColumn ( ) ;
if ( ImGui . BeginCombo ( $"##{name}-layout" , this . LayoutNameOrDefault ( layout ) ) ) {
if ( ImGui . Selectable ( "Not set" ) ) {
layout = Guid . Empty ;
this . plugin . config . Save ( ) ;
2020-07-30 17:54:45 +00:00
if ( this . plugin . config . SwapsEnabled ) {
this . statuses . SetHudLayout ( player , true ) ;
}
2020-07-30 01:01:22 +00:00
}
ImGui . Separator ( ) ;
foreach ( KeyValuePair < Guid , Tuple < string , byte [ ] > > entry in this . plugin . config . Layouts ) {
if ( ImGui . Selectable ( entry . Value . Item1 ) ) {
layout = entry . Key ;
this . plugin . config . Save ( ) ;
2020-07-30 17:54:45 +00:00
if ( this . plugin . config . SwapsEnabled ) {
this . statuses . SetHudLayout ( player , true ) ;
}
2020-07-30 01:01:22 +00:00
}
}
ImGui . EndCombo ( ) ;
}
ImGui . NextColumn ( ) ;
}
2020-07-30 17:53:13 +00:00
public void ImportSlot ( HudSlot slot , string name , bool save = true ) {
this . plugin . config . Layouts [ Guid . NewGuid ( ) ] = new Tuple < string , byte [ ] > ( name , this . plugin . hud . ReadLayout ( slot ) ) ;
if ( save ) {
this . plugin . config . Save ( ) ;
}
}
2020-07-29 16:01:00 +00:00
}
public class Statuses {
2020-07-30 01:54:04 +00:00
private readonly HudSwapPlugin plugin ;
private readonly DalamudPluginInterface pi ;
2020-07-29 16:01:00 +00:00
2020-07-30 01:54:04 +00:00
private readonly bool [ ] condition = new bool [ ORDER . Length ] ;
// Order: lowest to highest priority
// For conditions that require custom logic, use ConditionFlag.None
private static readonly ConditionFlag [ ] ORDER = {
ConditionFlag . Fishing ,
ConditionFlag . Gathering ,
ConditionFlag . Crafting ,
ConditionFlag . BoundByDuty ,
ConditionFlag . None , // weapon drawn
ConditionFlag . InCombat ,
} ;
private delegate bool CustomCondition ( HudSwapPlugin plugin , DalamudPluginInterface pi , PlayerCharacter player ) ;
// Add handlers in the order that ConditionFlag.None flags appear in ORDER.
private static readonly CustomCondition [ ] CUSTOM = {
// weapon drawn
( plugin , pi , player ) = > ( GetStatus ( pi , player ) & 4 ) > 0 ,
} ;
protected static byte GetStatus ( DalamudPluginInterface pi , Actor actor ) {
IntPtr statusPtr = pi . TargetModuleScanner . ResolveRelativeAddress ( actor . Address , 0x1901 ) ;
return Marshal . ReadByte ( statusPtr ) ;
}
2020-07-29 16:01:00 +00:00
public Statuses ( HudSwapPlugin plugin , DalamudPluginInterface pi ) {
this . plugin = plugin ;
this . pi = pi ;
2020-07-30 01:54:04 +00:00
if ( ORDER . Length ! = this . GetLayouts ( ) . Length ) {
throw new ApplicationException ( "Statuses.ORDER is not the same length as the array returned by Statuses.GetLayouts()" ) ;
}
if ( ORDER . Where ( flag = > flag = = ConditionFlag . None ) . Count ( ) ! = CUSTOM . Length ) {
throw new ApplicationException ( "Statuses.CUSTOM does not have an amount of handlers equalling the amount of ConditionFlag.None in Statuses.ORDER" ) ;
}
2020-07-29 16:01:00 +00:00
}
2020-07-30 01:54:04 +00:00
private Guid [ ] GetLayouts ( ) {
// These layouts must be in the same order as the flags in ORDER are defined
Guid [ ] layouts = {
this . plugin . config . fishingLayout ,
this . plugin . config . gatheringLayout ,
this . plugin . config . craftingLayout ,
this . plugin . config . instanceLayout ,
this . plugin . config . weaponDrawnLayout ,
this . plugin . config . combatLayout ,
} ;
return layouts ;
2020-07-30 01:01:22 +00:00
}
2020-07-29 16:01:00 +00:00
public bool Update ( PlayerCharacter player ) {
if ( player = = null ) {
return false ;
}
2020-07-30 01:54:04 +00:00
int customs = 0 ;
bool [ ] old = ( bool [ ] ) this . condition . Clone ( ) ;
2020-07-30 01:01:22 +00:00
Condition condition = this . pi . ClientState . Condition ;
2020-07-29 16:01:00 +00:00
2020-07-30 01:54:04 +00:00
bool anyChanged = false ;
for ( int i = 0 ; i < ORDER . Length ; i + + ) {
ConditionFlag flag = ORDER [ i ] ;
if ( flag = = ConditionFlag . None ) {
this . condition [ i ] = CUSTOM [ customs ] . Invoke ( this . plugin , this . pi , player ) ;
customs + = 1 ;
} else {
this . condition [ i ] = condition [ flag ] ;
}
anyChanged | = old [ i ] ! = this . condition [ i ] ;
}
2020-07-29 16:01:00 +00:00
return anyChanged ;
}
2020-07-30 01:01:22 +00:00
public Guid CalculateCurrentHud ( ) {
2020-07-29 16:01:00 +00:00
PlayerCharacter player = this . pi . ClientState . LocalPlayer ;
if ( player = = null ) {
2020-07-30 01:01:22 +00:00
return Guid . Empty ;
2020-07-29 16:01:00 +00:00
}
this . Update ( player ) ;
2020-07-30 01:01:22 +00:00
Guid layout = this . plugin . config . defaultLayout ;
2020-07-30 01:54:04 +00:00
Guid [ ] layouts = this . GetLayouts ( ) ;
2020-07-29 16:01:00 +00:00
2020-07-30 01:54:04 +00:00
for ( int i = 0 ; i < ORDER . Length ; i + + ) {
Guid flagLayout = layouts [ i ] ;
if ( this . condition [ i ] & & flagLayout ! = Guid . Empty ) {
layout = flagLayout ;
}
2020-07-29 16:01:00 +00:00
}
return layout ;
}
public void SetHudLayout ( PlayerCharacter player , bool update = false ) {
if ( update & & player ! = null ) {
this . Update ( player ) ;
}
2020-07-30 01:01:22 +00:00
Guid layout = this . CalculateCurrentHud ( ) ;
if ( layout = = Guid . Empty ) {
return ; // FIXME: do something better
}
byte [ ] layoutBytes = this . plugin . config . Layouts [ layout ] ? . Item2 ;
if ( layoutBytes = = null ) {
return ; // FIXME: do something better
}
2020-07-30 17:54:13 +00:00
this . plugin . hud . WriteLayout ( this . plugin . config . StagingSlot , entry . Item2 ) ;
this . plugin . hud . SelectSlot ( this . plugin . config . StagingSlot , true ) ;
2020-07-29 16:01:00 +00:00
}
}
}