├── images └── icon.png ├── .gitattributes ├── PlayerTrack.Plugin ├── Models │ ├── Enums │ │ ├── NoCategoryPlacement.cs │ │ ├── PanelType.cs │ │ ├── SearchType.cs │ │ ├── FreeCompanyState.cs │ │ ├── LodestoneLookupType.cs │ │ ├── PlayerHistorySource.cs │ │ ├── VisibilityType.cs │ │ ├── PlayerConfigType.cs │ │ ├── BackupType.cs │ │ ├── NameplateTitleType.cs │ │ ├── LodestoneLocale.cs │ │ ├── DataActionType.cs │ │ ├── InheritOverride.cs │ │ ├── LodestoneServiceStatus.cs │ │ ├── ArchiveType.cs │ │ ├── PlayerListFilter.cs │ │ ├── ConfigMenuOption.cs │ │ ├── LodestoneStatus.cs │ │ ├── CustomizeIndex.cs │ │ └── SocialListType.cs │ ├── Structs │ │ ├── ExtractedProperty.cs │ │ ├── ConfigValue.cs │ │ └── CharaCustomizeData.cs │ ├── Models │ │ ├── Config │ │ │ ├── TrackingLocationConfig.cs │ │ │ ├── EncounterDataActionOptions.cs │ │ │ ├── PlayerDataActionOptions.cs │ │ │ ├── PlayerSettingsDataActionOptions.cs │ │ │ └── IPluginConfig.cs │ │ ├── PlayerFilter.cs │ │ ├── Player │ │ │ ├── PlayerTag.cs │ │ │ ├── PlayerCategory.cs │ │ │ ├── PlayerConfigSet.cs │ │ │ ├── PlayerCustomizeHistory.cs │ │ │ ├── PlayerEncounter.cs │ │ │ ├── PlayerNameWorldHistory.cs │ │ │ ├── PlayerNameplate.cs │ │ │ └── PlayerConfig.cs │ │ ├── Tag.cs │ │ ├── Integration │ │ │ └── VisibilityEntry.cs │ │ ├── ArchiveRecord.cs │ │ ├── Lodestone │ │ │ ├── LodestoneRefreshRequest.cs │ │ │ ├── LodestoneResponse.cs │ │ │ ├── LodestoneBatchRequest.cs │ │ │ └── LodestoneLookup.cs │ │ ├── Encounter.cs │ │ ├── Category.cs │ │ ├── SocialListMember.cs │ │ ├── LocalPlayer.cs │ │ ├── Backup.cs │ │ └── SocialList.cs │ └── Comparers │ │ └── PlayerComparer.cs ├── Infrastructure │ ├── DTOs │ │ ├── PlayerTagDTO.cs │ │ ├── EncounterDTO.cs │ │ ├── TagDTO.cs │ │ ├── PlayerCategoryDTO.cs │ │ ├── ConfigEntryDTO.cs │ │ ├── PlayerCustomizeHistoryDTO.cs │ │ ├── CategoryDTO.cs │ │ ├── ArchiveRecordDTO.cs │ │ ├── PlayerEncounterDTO.cs │ │ ├── LocalPlayerDTO.cs │ │ ├── BackupDTO.cs │ │ ├── PlayerNameWorldHistoryDTO.cs │ │ ├── SocialListMemberDTO.cs │ │ ├── SocialListDTO.cs │ │ ├── PlayerDTO.cs │ │ └── PlayerConfigDTO.cs │ ├── Migrations │ │ ├── M005_Cleanup.cs │ │ ├── M006_FirstSeen.cs │ │ ├── M004_PlayerIDs.cs │ │ └── M003_LodestoneAPI.cs │ ├── Mappings │ │ ├── TagMappingProfile.cs │ │ ├── PlayerTagMappingProfile.cs │ │ ├── EncounterMappingProfile.cs │ │ ├── ArchiveRecordMappingProfile.cs │ │ ├── PlayerCategoryMappingProfile.cs │ │ ├── CategoryMappingProfile.cs │ │ ├── PlayerCustomizeHistoryMappingProfile.cs │ │ ├── LocalPlayerMappingProfile.cs │ │ ├── PlayerEncounterMappingProfile.cs │ │ ├── PlayerNameWorldHistoryMappingProfile.cs │ │ ├── BackupMappingProfile.cs │ │ ├── SocialListMemberMappingProfile.cs │ │ ├── ConfigMapper.cs │ │ └── SocialListMappingProfile.cs │ └── Repositories │ │ ├── ConfigRepository.cs │ │ ├── LocalPlayerRepository.cs │ │ ├── SocialListMemberRepository.cs │ │ ├── ArchiveRecordRepository.cs │ │ └── TagRepository.cs ├── Enums │ └── ActionRequest.cs ├── Domain │ ├── Common │ │ ├── PlayerKeyBuilder.cs │ │ ├── CacheService.cs │ │ └── PlayerFCHelper.cs │ ├── Services │ │ ├── PlayerServices │ │ │ ├── Caches │ │ │ │ └── Interfaces │ │ │ │ │ ├── IPlayerCache.cs │ │ │ │ │ ├── IBasicPlayerCache.cs │ │ │ │ │ └── IGroupedPlayerCache.cs │ │ │ ├── PlayerTagService.cs │ │ │ └── PlayerNameplateService.cs │ │ ├── ConfigService.cs │ │ └── LocalPlayerService.cs │ └── ServiceContext.cs ├── Windows │ ├── Main │ │ ├── Views │ │ │ ├── IViewWithPanel.cs │ │ │ ├── PanelView.cs │ │ │ └── PlayerList.cs │ │ ├── Presenters │ │ │ └── IMainPresenter.cs │ │ └── Components │ │ │ ├── PanelComponent.cs │ │ │ ├── PlayerActionComponent.cs │ │ │ ├── PlayerHistoryComponent.cs │ │ │ ├── PlayerEncounterComponent.cs │ │ │ ├── AddPlayerComponent.cs │ │ │ └── PlayerComponent.cs │ ├── ViewModels │ │ ├── PlayerNameWorldHistoryView.cs │ │ ├── PlayerCustomizeHistoryView.cs │ │ ├── PlayerEncounterView.cs │ │ └── PlayerView.cs │ ├── Components │ │ └── ViewComponent.cs │ ├── Views │ │ └── PlayerTrackView.cs │ ├── Config │ │ └── Components │ │ │ ├── PlayerDefaultsComponent.cs │ │ │ ├── ConfigViewComponent.cs │ │ │ ├── ContextMenuComponent.cs │ │ │ ├── IntegrationComponent.cs │ │ │ ├── ContributeComponent.cs │ │ │ ├── AboutComponent.cs │ │ │ ├── HelpComponent.cs │ │ │ ├── LocationComponent.cs │ │ │ └── TagComponent.cs │ ├── WindowManager.cs │ └── Helpers │ │ └── FormatHelper.cs ├── Data │ ├── DCData.cs │ ├── UiColorData.cs │ ├── WorldData.cs │ ├── ClassJobData.cs │ ├── RaceData.cs │ ├── TribeData.cs │ ├── LocalPlayerData.cs │ ├── SocialListMemberData.cs │ ├── LocationData.cs │ └── PlayerData.cs ├── PlayerTrack.yaml ├── Resource │ ├── Language.it.resx │ ├── Language.nb.resx │ ├── Language.pt.resx │ ├── Language.ja.resx │ └── Language.ru.resx ├── Extensions │ ├── UintExtension.cs │ ├── ClientStateExtension.cs │ ├── StringExtension.cs │ ├── ChatGuiExtension.cs │ ├── ObjectTableExtension.cs │ ├── PlayerCharacterExtension.cs │ ├── MenuItemClickedArgsExtension.cs │ ├── MenuOpenedArgsExtension.cs │ ├── PluginInterfaceExtension.cs │ └── TargetManagerExtension.cs ├── Handler │ ├── CommandHandler.cs │ ├── PlayerLocationManager.cs │ └── ContextMenuHandler.cs ├── API │ └── IPlayerTrackAPI.cs ├── PlayerTrack.Plugin.csproj.DotSettings └── PlayerTrack.Plugin.csproj ├── crowdin.yml ├── README.md ├── LICENSE └── PlayerTrack.sln /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Infiziert90/PlayerTrack/HEAD/images/icon.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Generated files 2 | PlayerTrack.Plugin/Resource/Language.*.resx linguist-generated=true 3 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Enums/NoCategoryPlacement.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public enum NoCategoryPlacement 4 | { 5 | Bottom, 6 | Top, 7 | } -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Enums/PanelType.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public enum PanelType 4 | { 5 | None, 6 | Player, 7 | AddPlayer, 8 | } 9 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Enums/SearchType.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public enum SearchType 4 | { 5 | Contains, 6 | StartsWith, 7 | Exact, 8 | } 9 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Enums/FreeCompanyState.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public enum FreeCompanyState 4 | { 5 | Unknown, 6 | InFC, 7 | NotInFC, 8 | } 9 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Enums/LodestoneLookupType.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public enum LodestoneLookupType 4 | { 5 | Batch = 0, 6 | Refresh = 1 7 | } 8 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Enums/PlayerHistorySource.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public enum PlayerHistorySource 4 | { 5 | ContentId, 6 | Lodestone, 7 | } 8 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Enums/VisibilityType.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public enum VisibilityType 4 | { 5 | None, 6 | Voidlist, 7 | Whitelist, 8 | } 9 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Enums/PlayerConfigType.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public enum PlayerConfigType 4 | { 5 | Default, 6 | Category, 7 | Player, 8 | } 9 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Enums/BackupType.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public enum BackupType 4 | { 5 | Unknown, 6 | Automatic, 7 | Upgrade, 8 | Manual, 9 | } 10 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Enums/NameplateTitleType.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public enum NameplateTitleType 4 | { 5 | NoChange, 6 | CustomTitle, 7 | CategoryName, 8 | } 9 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Enums/LodestoneLocale.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public enum LodestoneLocale 4 | { 5 | NA, 6 | EU, 7 | FR, 8 | DE, 9 | JP, 10 | } 11 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Enums/DataActionType.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public enum DataActionType 4 | { 5 | DeletePlayers, 6 | DeletePlayerSettings, 7 | DeleteEncounters, 8 | } 9 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Enums/InheritOverride.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public enum InheritOverride 4 | { 5 | None, // used for the top level config 6 | Inherit, 7 | Override, 8 | } 9 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Enums/LodestoneServiceStatus.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public enum LodestoneServiceStatus 4 | { 5 | ServiceAvailable, 6 | ServiceUnavailable, 7 | ServiceDisabled, 8 | } -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Enums/ArchiveType.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public enum ArchiveType 4 | { 5 | MigrationToV3Config, 6 | MigrationToV3Category, 7 | MigrationToV3Player, 8 | MigrationToV3Encounter, 9 | } 10 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Enums/PlayerListFilter.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public enum PlayerListFilter 4 | { 5 | CurrentPlayers, 6 | RecentPlayers, 7 | AllPlayers, 8 | PlayersByCategory, 9 | PlayersByTag, 10 | } 11 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Structs/ExtractedProperty.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models.Structs; 2 | 3 | public struct ExtractedProperty 4 | { 5 | public PlayerConfigType PlayerConfigType; 6 | public T PropertyValue; 7 | public int CategoryId; 8 | } 9 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/Config/TrackingLocationConfig.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public class TrackingLocationConfig 4 | { 5 | public bool AddPlayers = true; 6 | public bool AddEncounters = true; 7 | public int DefaultCategoryId; 8 | } 9 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/DTOs/PlayerTagDTO.cs: -------------------------------------------------------------------------------- 1 | using FluentDapperLite.Repository; 2 | 3 | namespace PlayerTrack.Infrastructure; 4 | 5 | public class PlayerTagDTO : DTO 6 | { 7 | public int player_id { get; set; } 8 | 9 | public int tag_id { get; set; } 10 | } 11 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Enums/ActionRequest.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Enums; 2 | 3 | /// 4 | /// Action Request state used for confirming delete and other actions. 5 | /// 6 | public enum ActionRequest 7 | { 8 | None, 9 | Pending, 10 | Confirmed, 11 | } 12 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Domain/Common/PlayerKeyBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Domain.Common; 2 | 3 | public static class PlayerKeyBuilder 4 | { 5 | public static string Build(string name, uint worldId) => 6 | string.Concat(name.Replace(' ', '_').ToUpperInvariant(), "_", worldId); 7 | } 8 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/DTOs/EncounterDTO.cs: -------------------------------------------------------------------------------- 1 | using FluentDapperLite.Repository; 2 | 3 | namespace PlayerTrack.Infrastructure; 4 | 5 | public class EncounterDTO : DTO 6 | { 7 | public ushort territory_type_id { get; set; } 8 | 9 | public long ended { get; set; } 10 | } 11 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/DTOs/TagDTO.cs: -------------------------------------------------------------------------------- 1 | using FluentDapperLite.Repository; 2 | 3 | namespace PlayerTrack.Infrastructure; 4 | 5 | public class TagDTO : DTO 6 | { 7 | public string name { get; set; } = string.Empty; 8 | 9 | public uint color { get; set; } = 5; 10 | } 11 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/DTOs/PlayerCategoryDTO.cs: -------------------------------------------------------------------------------- 1 | using FluentDapperLite.Repository; 2 | 3 | namespace PlayerTrack.Infrastructure; 4 | 5 | public class PlayerCategoryDTO : DTO 6 | { 7 | public int player_id { get; set; } 8 | 9 | public int category_id { get; set; } 10 | } 11 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/Main/Views/IViewWithPanel.cs: -------------------------------------------------------------------------------- 1 | using PlayerTrack.Models; 2 | 3 | namespace PlayerTrack.Windows.Main.Views; 4 | 5 | public interface IViewWithPanel 6 | { 7 | void TogglePanel(PanelType panelType); 8 | 9 | void HidePanel(); 10 | 11 | void ShowPanel(PanelType panelType); 12 | } 13 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/ViewModels/PlayerNameWorldHistoryView.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Windows.ViewModels; 2 | 3 | public class PlayerNameWorldHistoryView 4 | { 5 | public int Id { get; set; } 6 | 7 | public string Time { get; init; } = null!; 8 | 9 | public string NameWorld { get; set; } = null!; 10 | } 11 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/DTOs/ConfigEntryDTO.cs: -------------------------------------------------------------------------------- 1 | using Dapper.Contrib.Extensions; 2 | 3 | namespace PlayerTrack.Infrastructure; 4 | 5 | public class ConfigEntryDTO 6 | { 7 | [ExplicitKey] 8 | public string key { get; set; } = string.Empty; 9 | 10 | public string value { get; set; } = string.Empty; 11 | } 12 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/ViewModels/PlayerCustomizeHistoryView.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Windows.ViewModels; 2 | 3 | public class PlayerCustomizeHistoryView 4 | { 5 | public int Id { get; set; } 6 | 7 | public string Time { get; init; } = null!; 8 | 9 | public string Appearance { get; init; } = null!; 10 | } 11 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/PlayerFilter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace PlayerTrack.Models; 4 | 5 | public class PlayerFilter 6 | { 7 | public List FilterIds { get; set; } = null!; 8 | 9 | public List FilterNames { get; set; } = null!; 10 | 11 | public int TotalFilters { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/Player/PlayerTag.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public class PlayerTag 4 | { 5 | public int Id { get; set; } 6 | 7 | public long Created { get; set; } 8 | 9 | public long Updated { get; set; } 10 | 11 | public int PlayerId { get; set; } 12 | 13 | public int TagId { get; set; } 14 | } 15 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/Tag.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public class Tag 4 | { 5 | public int Id { get; set; } 6 | 7 | public string Name { get; set; } = string.Empty; 8 | 9 | public long Created { get; set; } 10 | 11 | public long Updated { get; set; } 12 | 13 | public uint Color { get; set; } = 5; 14 | } 15 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/Integration/VisibilityEntry.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models.Integration; 2 | 3 | public class VisibilityEntry 4 | { 5 | public string Key { get; set; } = null!; 6 | public string Name { get; init; } = string.Empty; 7 | public uint HomeWorldId { get; init; } 8 | public string Reason { get; init; } = string.Empty; 9 | } 10 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/Player/PlayerCategory.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public class PlayerCategory 4 | { 5 | public int Id { get; set; } 6 | 7 | public long Created { get; set; } 8 | 9 | public long Updated { get; set; } 10 | 11 | public int PlayerId { get; set; } 12 | 13 | public int CategoryId { get; set; } 14 | } 15 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/DTOs/PlayerCustomizeHistoryDTO.cs: -------------------------------------------------------------------------------- 1 | using FluentDapperLite.Repository; 2 | 3 | namespace PlayerTrack.Infrastructure; 4 | 5 | public class PlayerCustomizeHistoryDTO : DTO 6 | { 7 | public bool is_migrated { get; set; } 8 | 9 | public int player_id { get; set; } 10 | 11 | public byte[] customize { get; init; } = []; 12 | } 13 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/DTOs/CategoryDTO.cs: -------------------------------------------------------------------------------- 1 | using FluentDapperLite.Repository; 2 | using PlayerTrack.Models; 3 | 4 | namespace PlayerTrack.Infrastructure; 5 | 6 | public class CategoryDTO : DTO 7 | { 8 | public string name { get; set; } = string.Empty; 9 | 10 | public int rank { get; set; } 11 | 12 | public int social_list_id { get; set; } 13 | } 14 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/ArchiveRecord.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public class ArchiveRecord 4 | { 5 | public int Id { get; set; } 6 | 7 | public long Created { get; set; } 8 | 9 | public long Updated { get; set; } 10 | 11 | public ArchiveType ArchiveType { get; set; } 12 | 13 | public string Data { get; set; } = string.Empty; 14 | } 15 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Structs/ConfigValue.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models.Structs; 2 | 3 | public struct ConfigValue 4 | { 5 | public InheritOverride InheritOverride; 6 | public T Value; 7 | 8 | public ConfigValue(InheritOverride inheritOverride, T value) 9 | { 10 | InheritOverride = inheritOverride; 11 | Value = value; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/DTOs/ArchiveRecordDTO.cs: -------------------------------------------------------------------------------- 1 | using FluentDapperLite.Repository; 2 | using PlayerTrack.Models; 3 | 4 | namespace PlayerTrack.Infrastructure; 5 | 6 | public class ArchiveRecordDTO : DTO 7 | { 8 | public int Id { get; set; } 9 | 10 | public ArchiveType archive_type { get; set; } 11 | 12 | public string data { get; set; } = string.Empty; 13 | } 14 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Enums/ConfigMenuOption.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public enum ConfigMenuOption 4 | { 5 | Window, 6 | ContextMenu, 7 | Icons, 8 | Tags, 9 | PlayerDefaults, 10 | Categories, 11 | Locations, 12 | SocialLists, 13 | Integrations, 14 | Backups, 15 | Data, 16 | Contribute, 17 | Help, 18 | About, 19 | } 20 | -------------------------------------------------------------------------------- /crowdin.yml: -------------------------------------------------------------------------------- 1 | "project_id" : "435668" 2 | "base_path" : "." 3 | "base_url" : "https://api.crowdin.com" 4 | "preserve_hierarchy": true 5 | 6 | files: [ 7 | { 8 | "source" : "/PlayerTrack.Plugin/Resource/Language.resx", 9 | "translation" : "/PlayerTrack.Plugin/Resource/Language.%two_letters_code%.resx", 10 | "dest" : "/Language.resx", 11 | "skip_untranslated_strings": true, 12 | } 13 | ] 14 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/Player/PlayerConfigSet.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | using System.Collections.Generic; 4 | 5 | public class PlayerConfigSet 6 | { 7 | public PlayerConfigType PlayerConfigType { get; set; } 8 | 9 | public PlayerConfig CurrentPlayerConfig { get; set; } = new(); 10 | 11 | public List CategoryPlayerConfigs { get; set; } = []; 12 | } 13 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/Lodestone/LodestoneRefreshRequest.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public class LodestoneRefreshRequest 4 | { 5 | public LodestoneRefreshRequest(int playerId, uint lodestoneId) 6 | { 7 | PlayerId = playerId; 8 | LodestoneId = lodestoneId; 9 | } 10 | 11 | public int PlayerId { get; } 12 | public uint LodestoneId { get; } 13 | } 14 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Data/DCData.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Data; 2 | 3 | /// 4 | /// DataCenter data. 5 | /// 6 | public class DCData 7 | { 8 | /// 9 | /// Gets or sets data center id. 10 | /// 11 | public uint Id { get; set; } 12 | 13 | /// 14 | /// Gets data center name. 15 | /// 16 | public string Name { get; init; } = string.Empty; 17 | } 18 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/Components/ViewComponent.cs: -------------------------------------------------------------------------------- 1 | using PlayerTrack.Domain; 2 | using PlayerTrack.Models; 3 | 4 | namespace PlayerTrack.Windows.Components; 5 | 6 | public abstract class ViewComponent 7 | { 8 | protected readonly PluginConfig Config; 9 | 10 | protected ViewComponent() 11 | { 12 | Config = ServiceContext.ConfigService.GetConfig(); 13 | } 14 | 15 | public abstract void Draw(); 16 | } 17 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/DTOs/PlayerEncounterDTO.cs: -------------------------------------------------------------------------------- 1 | using FluentDapperLite.Repository; 2 | 3 | namespace PlayerTrack.Infrastructure; 4 | 5 | public class PlayerEncounterDTO : DTO 6 | { 7 | public int player_id { get; set; } 8 | 9 | public int encounter_id { get; set; } 10 | 11 | public uint job_id { get; set; } 12 | 13 | public byte job_lvl { get; set; } 14 | 15 | public long ended { get; set; } 16 | } 17 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/Config/EncounterDataActionOptions.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public class EncounterDataActionOptions 4 | { 5 | public bool KeepEncountersInOverworld { get; set; } = true; 6 | public bool KeepEncountersInNormalContent { get; set; } = true; 7 | public bool KeepEncountersInHighEndContent { get; set; } = true; 8 | public bool KeepEncountersFromLast90Days { get; set; } = true; 9 | } 10 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/Player/PlayerCustomizeHistory.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public class PlayerCustomizeHistory 4 | { 5 | public int Id { get; set; } 6 | 7 | public long Created { get; set; } 8 | 9 | public long Updated { get; set; } 10 | 11 | public bool IsMigrated { get; set; } 12 | 13 | public int PlayerId { get; set; } 14 | 15 | public byte[] Customize { get; init; } = []; 16 | } 17 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Domain/Services/PlayerServices/Caches/Interfaces/IPlayerCache.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PlayerTrack.Models; 3 | 4 | namespace PlayerTrack.Domain.Caches.Interfaces; 5 | 6 | public interface IPlayerCache 7 | { 8 | void Initialize(IComparer comparer); 9 | void Add(Player playerToAdd); 10 | void Remove(Player playerToRemove); 11 | void Resort(IComparer comparer); 12 | Player? Get(int playerId); 13 | } -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/ViewModels/PlayerEncounterView.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Windows.ViewModels; 2 | 3 | public class PlayerEncounterView 4 | { 5 | public int Id { get; set; } 6 | 7 | public string Time { get; init; } = null!; 8 | 9 | public string Duration { get; init; } = null!; 10 | 11 | public string Job { get; init; } = null!; 12 | 13 | public string Level { get; init; } = null!; 14 | 15 | public string Location { get; init; } = null!; 16 | } 17 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/DTOs/LocalPlayerDTO.cs: -------------------------------------------------------------------------------- 1 | using FluentDapperLite.Repository; 2 | 3 | namespace PlayerTrack.Infrastructure; 4 | 5 | public class LocalPlayerDTO : DTO 6 | { 7 | 8 | public string name { get; set; } = string.Empty; 9 | 10 | public uint world_id { get; set; } 11 | 12 | public string key { get; set; } = string.Empty; 13 | 14 | public byte[]? customize { get; set; } 15 | 16 | public ulong content_id { get; set; } 17 | 18 | } -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/DTOs/BackupDTO.cs: -------------------------------------------------------------------------------- 1 | using FluentDapperLite.Repository; 2 | 3 | namespace PlayerTrack.Infrastructure; 4 | 5 | public class BackupDTO : DTO 6 | { 7 | public int backup_type { get; set; } 8 | 9 | public string name { get; set; } = string.Empty; 10 | 11 | public long size { get; set; } 12 | 13 | public bool is_restorable { get; set; } 14 | 15 | public bool is_protected { get; set; } 16 | 17 | public string notes { get; set; } = string.Empty; 18 | } 19 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/Player/PlayerEncounter.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public class PlayerEncounter 4 | { 5 | public int Id { get; set; } 6 | 7 | public long Created { get; set; } 8 | 9 | public long Updated { get; set; } 10 | 11 | public int PlayerId { get; set; } 12 | 13 | public int EncounterId { get; init; } 14 | 15 | public uint JobId { get; init; } 16 | 17 | public byte JobLvl { get; init; } 18 | 19 | public long Ended { get; set; } 20 | } 21 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/Views/PlayerTrackView.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Bindings.ImGui; 2 | using PlayerTrack.Models; 3 | using PlayerTrack.Windows.Main; 4 | 5 | namespace PlayerTrack.Windows.Views; 6 | 7 | public abstract class PlayerTrackView : WindowEx 8 | { 9 | protected new readonly PluginConfig Config; 10 | 11 | protected PlayerTrackView(string name, PluginConfig config, ImGuiWindowFlags flags = ImGuiWindowFlags.None) : base(name, config, flags) 12 | { 13 | Config = config; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/DTOs/PlayerNameWorldHistoryDTO.cs: -------------------------------------------------------------------------------- 1 | using FluentDapperLite.Repository; 2 | using PlayerTrack.Models; 3 | 4 | namespace PlayerTrack.Infrastructure; 5 | 6 | public class PlayerNameWorldHistoryDTO : DTO 7 | { 8 | public bool is_migrated { get; set; } 9 | 10 | public PlayerHistorySource source { get; set; } 11 | 12 | public string player_name { get; set; } = string.Empty; 13 | 14 | public uint world_id { get; set; } 15 | 16 | public int player_id { get; set; } 17 | } 18 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/DTOs/SocialListMemberDTO.cs: -------------------------------------------------------------------------------- 1 | using FluentDapperLite.Repository; 2 | using PlayerTrack.Models; 3 | 4 | namespace PlayerTrack.Infrastructure; 5 | 6 | public class SocialListMemberDTO : DTO 7 | { 8 | public ulong content_id { get; set; } 9 | public string key { get; set; } = string.Empty; 10 | public string name { get; set; } = string.Empty; 11 | public uint world_id { get; set; } 12 | public ushort page_number { get; set; } 13 | public int social_list_id { get; set; } 14 | } -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Data/UiColorData.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Data; 2 | 3 | /// 4 | /// ToadUIColor model. 5 | /// 6 | public class UiColorData 7 | { 8 | /// 9 | /// Gets or sets ui color id. 10 | /// 11 | public uint Id { get; set; } 12 | 13 | /// 14 | /// Gets or sets ui foreground. 15 | /// 16 | public uint Foreground { get; set; } 17 | 18 | /// 19 | /// Gets or sets ui glow. 20 | /// 21 | public uint Glow { get; set; } 22 | } 23 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Data/WorldData.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Data; 2 | 3 | /// 4 | /// World data. 5 | /// 6 | public class WorldData 7 | { 8 | /// 9 | /// Gets or sets world id. 10 | /// 11 | public uint Id { get; set; } 12 | 13 | /// 14 | /// Gets world name. 15 | /// 16 | public string Name { get; init; } = string.Empty; 17 | 18 | /// 19 | /// Gets world's data center. 20 | /// 21 | public uint DataCenterId { get; init; } 22 | } 23 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/Config/PlayerDataActionOptions.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public class PlayerDataActionOptions 4 | { 5 | public bool KeepPlayersWithNotes { get; set; } = true; 6 | public bool KeepPlayersWithCategories { get; set; } = true; 7 | public bool KeepPlayersWithAnySettings { get; set; } = true; 8 | public bool KeepPlayersWithEncounters { get; set; } = true; 9 | public bool KeepPlayersSeenInLast90Days { get; set; } = true; 10 | public bool KeepPlayersVerifiedOnLodestone { get; set; } = true; 11 | } 12 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/PlayerTrack.yaml: -------------------------------------------------------------------------------- 1 | name: PlayerTrack 2 | author: Infi, kalilistic 3 | punchline: Keep track of players you meet. 4 | description: |- 5 | PlayerTrack helps you keep a record of who you meet and the content you played together. 6 | Organize players into categories, keep notes, and track them across name/world changes. 7 | Customization options include colors, icons, nameplates, and alerts. 8 | tags: 9 | - player 10 | - track 11 | - orwell 12 | repo_url: https://github.com/Infiziert90/PlayerTrack 13 | accepts_feedback: true 14 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/Player/PlayerNameWorldHistory.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public class PlayerNameWorldHistory 4 | { 5 | public int Id { get; set; } 6 | 7 | public long Created { get; set; } 8 | 9 | public long Updated { get; set; } 10 | 11 | public bool IsMigrated { get; set; } 12 | 13 | public PlayerHistorySource Source { get; set; } 14 | 15 | public int PlayerId { get; set; } 16 | 17 | public string PlayerName { get; init; } = string.Empty; 18 | 19 | public uint WorldId { get; set; } 20 | } 21 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Data/ClassJobData.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Data; 2 | 3 | /// 4 | /// ClassJob data. 5 | /// 6 | public class ClassJobData 7 | { 8 | /// 9 | /// Gets or sets class job id. 10 | /// 11 | public uint Id { get; set; } 12 | 13 | /// 14 | /// Gets class job name. 15 | /// 16 | public string Name { get; init; } = string.Empty; 17 | 18 | /// 19 | /// Gets class job abbreviation. 20 | /// 21 | public string Code { get; init; } = string.Empty; 22 | } 23 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Data/RaceData.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Data; 2 | 3 | /// 4 | /// Race data. 5 | /// 6 | public class RaceData 7 | { 8 | /// 9 | /// Gets or sets race Id. 10 | /// 11 | public uint Id { get; set; } 12 | 13 | /// 14 | /// Gets or sets masculine name. 15 | /// 16 | public string MasculineName { get; set; } = string.Empty; 17 | 18 | /// 19 | /// Gets or sets feminine name. 20 | /// 21 | public string FeminineName { get; set; } = string.Empty; 22 | } 23 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/Encounter.cs: -------------------------------------------------------------------------------- 1 | using Dapper.Contrib.Extensions; 2 | 3 | namespace PlayerTrack.Models; 4 | 5 | public class Encounter 6 | { 7 | public int Id { get; set; } 8 | 9 | public ushort TerritoryTypeId { get; set; } 10 | 11 | public long Ended { get; set; } 12 | 13 | public long Created { get; set; } 14 | 15 | public long Updated { get; set; } 16 | 17 | [Write(false)] public bool SaveEncounter { get; set; } 18 | 19 | [Write(false)] public bool SavePlayers { get; set; } 20 | 21 | [Write(false)] public int CategoryId { get; set; } 22 | } 23 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Data/TribeData.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Data; 2 | 3 | /// 4 | /// Tribe data. 5 | /// 6 | public class TribeData 7 | { 8 | /// 9 | /// Gets or sets tribe Id. 10 | /// 11 | public uint Id { get; set; } 12 | 13 | /// 14 | /// Gets or sets masculine name. 15 | /// 16 | public string MasculineName { get; set; } = string.Empty; 17 | 18 | /// 19 | /// Gets or sets feminine name. 20 | /// 21 | public string FeminineName { get; set; } = string.Empty; 22 | } 23 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/Category.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public class Category 4 | { 5 | public int Id { get; set; } 6 | 7 | public string Name { get; set; } = string.Empty; 8 | 9 | public long Created { get; set; } 10 | 11 | public long Updated { get; set; } 12 | 13 | public int Rank { get; set; } 14 | 15 | public PlayerConfig PlayerConfig { get; set; } = new(PlayerConfigType.Category); 16 | 17 | public int SocialListId { get; set; } 18 | 19 | public bool IsDynamicCategory() 20 | { 21 | return SocialListId != 0; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/SocialListMember.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public class SocialListMember 4 | { 5 | public int Id { get; set; } 6 | 7 | public long Created { get; set; } 8 | 9 | public long Updated { get; set; } 10 | 11 | public ulong ContentId { get; set; } 12 | 13 | public string Key { get; set; } = string.Empty; 14 | 15 | public string Name { get; set; } = string.Empty; 16 | 17 | public uint WorldId { get; set; } 18 | 19 | public ushort PageNumber { get; set; } 20 | 21 | public int SocialListId { get; set; } 22 | } 23 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Domain/Services/PlayerServices/Caches/Interfaces/IBasicPlayerCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using PlayerTrack.Models; 4 | 5 | namespace PlayerTrack.Domain.Caches.Interfaces; 6 | 7 | public interface IBasicPlayerCache: IPlayerCache 8 | { 9 | Player? FindFirst(Func filter); 10 | List Get(int start, int count); 11 | List Get(Func filter, int start, int count); 12 | List Get(Func filter); 13 | List GetAll(); 14 | int Count(); 15 | int Count(Func filter); 16 | } -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/Lodestone/LodestoneResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace PlayerTrack.Models; 4 | 5 | public class LodestoneResponse 6 | { 7 | [JsonProperty("playerName")] 8 | public string PlayerName { get; set; } = string.Empty; 9 | 10 | [JsonProperty("worldId")] 11 | public uint WorldId { get; set; } 12 | 13 | [JsonProperty("lodestoneId")] 14 | public uint LodestoneId { get; set; } 15 | 16 | [JsonProperty("code")] 17 | public int StatusCode { get; set; } 18 | 19 | [JsonProperty("message")] 20 | public string Message { get; set; } = string.Empty; 21 | } 22 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/Config/PlayerSettingsDataActionOptions.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public class PlayerSettingsDataActionOptions 4 | { 5 | public bool KeepSettingsForPlayersWithNotes { get; set; } = true; 6 | public bool KeepSettingsForPlayersWithCategories { get; set; } = true; 7 | public bool KeepSettingsForPlayersWithAnySettings { get; set; } = true; 8 | public bool KeepSettingsForPlayersWithEncounters { get; set; } = true; 9 | public bool KeepSettingsForPlayersSeenInLast90Days { get; set; } = true; 10 | public bool KeepSettingsForPlayersVerifiedOnLodestone { get; set; } = true; 11 | } 12 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Resource/Language.it.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | text/microsoft-resx 4 | 5 | 6 | 1.3 7 | 8 | 9 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 10 | 11 | 12 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 13 | 14 | 15 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Resource/Language.nb.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | text/microsoft-resx 4 | 5 | 6 | 1.3 7 | 8 | 9 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 10 | 11 | 12 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 13 | 14 | 15 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Resource/Language.pt.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | text/microsoft-resx 4 | 5 | 6 | 1.3 7 | 8 | 9 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 10 | 11 | 12 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 13 | 14 | 15 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/DTOs/SocialListDTO.cs: -------------------------------------------------------------------------------- 1 | using FluentDapperLite.Repository; 2 | using PlayerTrack.Models; 3 | 4 | namespace PlayerTrack.Infrastructure; 5 | 6 | public class SocialListDTO : DTO 7 | { 8 | public ulong content_id { get; set; } 9 | public SocialListType list_type { get; set; } 10 | public ushort list_number { get; set; } 11 | public uint data_center_id { get; set; } 12 | public ushort page_count { get; set; } 13 | public bool add_players { get; set; } 14 | public bool sync_with_category { get; set; } 15 | public int default_category_id { get; set; } 16 | public string page_last_updated { get; set; } = string.Empty; 17 | } -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/Migrations/M005_Cleanup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Data; 5 | using System.Linq; 6 | using FluentDapperLite.Extension; 7 | using FluentMigrator; 8 | using Newtonsoft.Json; 9 | using PlayerTrack.Infrastructure; 10 | using PlayerTrack.Models; 11 | 12 | namespace PlayerTrack.Repositories.Migrations; 13 | 14 | [Migration(20240721120000)] 15 | public class M005_Cleanup: FluentMigrator.Migration 16 | { 17 | 18 | public override void Up() 19 | { 20 | Delete.Table("lodestone_lookups"); 21 | } 22 | 23 | public override void Down() 24 | { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/Lodestone/LodestoneBatchRequest.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace PlayerTrack.Models; 4 | 5 | public class LodestoneBatchRequest 6 | { 7 | public LodestoneBatchRequest(int playerId, string playerName, uint worldId) 8 | { 9 | PlayerId = playerId; 10 | PlayerName = playerName; 11 | WorldId = worldId; 12 | } 13 | 14 | [JsonIgnore] 15 | public int PlayerId { get; } 16 | 17 | [JsonProperty("playerName", Required = Required.Always)] 18 | public string PlayerName { get; set; } 19 | 20 | [JsonProperty("worldId", Required = Required.Always)] 21 | public uint WorldId { get; set; } 22 | } 23 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/LocalPlayer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace PlayerTrack.Models; 4 | 5 | public class LocalPlayer 6 | { 7 | public int Id { get; set; } 8 | 9 | public long Created { get; set; } 10 | 11 | public long Updated { get; set; } 12 | 13 | public ulong ContentId { get; set; } 14 | 15 | public byte[]? Customize { get; set; } 16 | 17 | public string Key { get; set; } = string.Empty; 18 | 19 | public string Name { get; set; } = string.Empty; 20 | 21 | public uint WorldId { get; set; } 22 | 23 | public KeyValuePair FreeCompany { get; set; } = new(FreeCompanyState.Unknown, string.Empty); 24 | } 25 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Enums/LodestoneStatus.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public enum LodestoneStatus 4 | { 5 | Unverified = 0, // created but not yet looked up 6 | Verified = 1, // successfully looked up 7 | Failed = 2, // failed to look up but can be retried 8 | Banned = 3, // failed to look up and cannot be retried 9 | NotApplicable = 4, // can't be looked up (e.g. test character) 10 | Blocked = 5, // blocked from looking up (e.g. dependency on another lookup) 11 | Cancelled = 6, // superseded by another lookup or system found bad data 12 | Unavailable = 7, // not able to lookup (e.g. deleted character) 13 | Invalid = 8, // invalid character name/world (shouldn't happen...) 14 | } 15 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/Player/PlayerNameplate.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Game.Text.SeStringHandling; 2 | 3 | namespace PlayerTrack.Models; 4 | 5 | public class PlayerNameplate 6 | { 7 | public bool CustomizeNameplate { get; set; } 8 | 9 | public bool NameplateUseColorIfDead { get; set; } 10 | 11 | public bool HasCustomTitle { get; set; } 12 | 13 | public SeString? CustomTitle { get; set; } 14 | 15 | public SeString? TitleLeftQuote { get; set; } 16 | 17 | public SeString? TitleRightQuote { get; set; } 18 | 19 | public (SeString, SeString)? NameTextWrap { get; set; } 20 | 21 | public SeString? FreeCompanyLeftQuote { get; set; } 22 | 23 | public SeString? FreeCompanyRightQuote { get; set; } 24 | } 25 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Extensions/UintExtension.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | 3 | namespace PlayerTrack.Extensions; 4 | 5 | /// 6 | /// Uint Extensions. 7 | /// 8 | public static class UintExtensions 9 | { 10 | /// 11 | /// Convert a UIColor (uint) to a Vector4. 12 | /// 13 | /// color. 14 | /// color as vector4. 15 | public static Vector4 ToVector4(this uint col) 16 | { 17 | const float inv255 = 1 / 255f; 18 | return new Vector4( 19 | (col >> 24 & 255) * inv255, 20 | (col >> 16 & 255) * inv255, 21 | (col >> 8 & 255) * inv255, 22 | (col & 255) * inv255); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Domain/Services/PlayerServices/Caches/Interfaces/IGroupedPlayerCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using PlayerTrack.Models; 4 | 5 | namespace PlayerTrack.Domain.Caches.Interfaces; 6 | 7 | public interface IGroupedPlayerCache: IPlayerCache 8 | { 9 | Player? FindFirst(int groupId, Func filter); 10 | List Get(int groupId, int start, int count); 11 | List Get(int groupId, Func filter, int start, int count); 12 | List GetAll(int groupId); 13 | Dictionary? GetGroup(int groupId); 14 | void AddGroup(int groupId); 15 | void RemoveGroup(int groupId); 16 | int Count(int groupId); 17 | int Count(int groupId, Func filter); 18 | } -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/Main/Presenters/IMainPresenter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PlayerTrack.Models; 3 | using PlayerTrack.Windows.ViewModels; 4 | 5 | namespace PlayerTrack.Windows.Main.Presenters; 6 | 7 | public interface IMainPresenter 8 | { 9 | PlayerView? GetSelectedPlayer(); 10 | 11 | void ClosePlayer(); 12 | 13 | bool IsPlayerLoading(); 14 | 15 | int GetPlayersCount(); 16 | 17 | List GetPlayers(int displayStart, int displayEnd); 18 | 19 | void TogglePanel(PanelType panelType); 20 | 21 | void HidePanel(); 22 | 23 | void SelectPlayer(Player player); 24 | 25 | void ShowPanel(PanelType panelType); 26 | 27 | void ClearCache(); 28 | 29 | void OpenConfig(ConfigMenuOption configMenuOption); 30 | } 31 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Enums/CustomizeIndex.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public enum CustomizeIndex 4 | { 5 | Race = 0x00, 6 | Gender = 0x01, 7 | Tribe = 0x04, 8 | Height = 0x03, 9 | ModelType = 0x02, 10 | FaceType = 0x05, 11 | HairStyle = 0x06, 12 | HasHighlights = 0x07, 13 | SkinColor = 0x08, 14 | EyeColor = 0x09, 15 | HairColor = 0x0A, 16 | HairColor2 = 0x0B, 17 | FaceFeatures = 0x0C, 18 | FaceFeaturesColor = 0x0D, 19 | Eyebrows = 0x0E, 20 | EyeColor2 = 0x0F, 21 | EyeShape = 0x10, 22 | NoseShape = 0x11, 23 | JawShape = 0x12, 24 | LipStyle = 0x13, 25 | LipColor = 0x14, 26 | RaceFeatureSize = 0x15, 27 | RaceFeatureType = 0x16, 28 | BustSize = 0x17, 29 | Facepaint = 0x18, 30 | FacepaintColor = 0x19, 31 | } 32 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/Migrations/M006_FirstSeen.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Data; 5 | using System.Linq; 6 | using FluentDapperLite.Extension; 7 | using FluentMigrator; 8 | using Newtonsoft.Json; 9 | using PlayerTrack.Infrastructure; 10 | using PlayerTrack.Models; 11 | 12 | namespace PlayerTrack.Repositories.Migrations; 13 | 14 | [Migration(20240723120000)] 15 | public class M006_FirstSeen: FluentMigrator.Migration 16 | { 17 | 18 | public override void Up() 19 | { 20 | Alter.Table("players") 21 | .AddColumn("first_seen").AsInt64().NotNullable().WithDefaultValue(0); 22 | Execute.Sql(@" 23 | UPDATE players 24 | SET first_seen = created 25 | WHERE seen_count > 0; 26 | "); 27 | } 28 | 29 | public override void Down() 30 | { 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PlayerTrack 2 | Keep track of players you meet. 3 | 4 | ## Description 5 | PlayerTrack helps you keep a record of who you meet and the content you played together. 6 | Organize players into categories, keep notes, and track them across name/world changes. 7 | Customization options include colors, icons, nameplates, and alerts. 8 | 9 | ## How to Use 10 | - Install through XIVLauncher/Dalamud. 11 | - Use `/ptrack` to open the main window. 12 | - Use `/ptrackconfig` to open settings. 13 | 14 | ## Libraries Used 15 | - [AutoMapper](https://github.com/AutoMapper/AutoMapper) 16 | - [Dapper](https://github.com/DapperLib/Dapper) 17 | - [FlexConfig](https://github.com/kalilistic/FlexConfig) 18 | - [FluentDapperLite](https://github.com/kalilistic/FluentDapperLite) 19 | - [FluentMigrator](https://github.com/fluentmigrator/fluentmigrator) 20 | - [SQLite](https://github.com/sqlite/sqlite) 21 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Domain/Common/CacheService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Threading; 4 | 5 | namespace PlayerTrack.Domain.Common; 6 | 7 | public abstract class CacheService : IDisposable 8 | { 9 | private readonly ReaderWriterLockSlim ResetLock = new (); 10 | private volatile bool IsResettingCache; 11 | protected ConcurrentDictionary Cache = null!; 12 | 13 | public void Dispose() 14 | { 15 | ResetLock.Dispose(); 16 | GC.SuppressFinalize(this); 17 | } 18 | 19 | protected void ExecuteReloadCache(Action customAction) 20 | { 21 | if (IsResettingCache) 22 | { 23 | Plugin.PluginLog.Verbose("A cache reset is already in progress. Ignoring this request."); 24 | return; 25 | } 26 | 27 | customAction.Invoke(); 28 | IsResettingCache = false; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/Config/Components/PlayerDefaultsComponent.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Interface.Utility.Raii; 2 | using Dalamud.Bindings.ImGui; 3 | using PlayerTrack.Domain; 4 | using PlayerTrack.Windows.Components; 5 | 6 | namespace PlayerTrack.Windows.Config.Components; 7 | 8 | public class PlayerDefaultsComponent : ConfigViewComponent 9 | { 10 | public override void Draw() 11 | { 12 | using var tabBar = ImRaii.TabBar("###Player_TabBar", ImGuiTabBarFlags.None); 13 | if (!tabBar.Success) 14 | return; 15 | 16 | var playerConfig = PlayerConfigComponent.DrawDefaultConfigTabs(); 17 | if (playerConfig.IsChanged) 18 | { 19 | playerConfig.IsChanged = false; 20 | ServiceContext.ConfigService.SaveConfig(Config); 21 | ServiceContext.PlayerDataService.RefreshAllPlayers(); 22 | NotifyConfigChanged(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Data/LocalPlayerData.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Utility; 2 | 3 | namespace PlayerTrack.Data; 4 | 5 | /// 6 | /// Subset of key properties from local player for eventing. 7 | /// 8 | public class LocalPlayerData 9 | { 10 | /// 11 | /// The content ID of the local character. 12 | /// 13 | public ulong ContentId; 14 | 15 | /// 16 | /// Player Name. 17 | /// 18 | public string Name = string.Empty; 19 | 20 | /// 21 | /// Player HomeWorld ID. 22 | /// 23 | public uint HomeWorld; 24 | 25 | /// 26 | /// Player Customize Array. 27 | /// 28 | public byte[]? Customize; 29 | 30 | /// 31 | /// Validate if local player is valid. 32 | /// 33 | /// Indicator if local player is valid. 34 | public bool IsValid() => ContentId != 0 && Name.IsValidCharacterName() && HomeWorld != 0; 35 | } 36 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/Backup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using PlayerTrack.Extensions; 3 | 4 | namespace PlayerTrack.Models; 5 | 6 | public class Backup 7 | { 8 | public int Id { get; set; } 9 | 10 | public long Created { get; set; } 11 | 12 | public long Updated { get; set; } 13 | 14 | public BackupType BackupType { get; init; } = BackupType.Automatic; 15 | 16 | public string Name { get; init; } = string.Empty; 17 | 18 | public long Size { get; set; } 19 | 20 | public bool IsRestorable { get; init; } = true; 21 | 22 | public bool IsProtected { get; set; } 23 | 24 | public string Notes { get; set; } = string.Empty; 25 | 26 | public string DisplayName 27 | { 28 | get 29 | { 30 | if (string.IsNullOrEmpty(Name)) 31 | return Name; 32 | 33 | var nameWithoutExtension = Name.EndsWith(".zip", StringComparison.Ordinal) ? Name[..^4] : Name; 34 | return nameWithoutExtension.TruncateWithEllipsis(25); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/Main/Components/PanelComponent.cs: -------------------------------------------------------------------------------- 1 | using PlayerTrack.Models; 2 | using PlayerTrack.Windows.Components; 3 | 4 | namespace PlayerTrack.Windows.Main.Components; 5 | 6 | public class PanelComponent : ViewComponent 7 | { 8 | private readonly PlayerComponent PlayerComponent; 9 | private readonly AddPlayerComponent AddPlayerComponent; 10 | 11 | public PanelComponent(PlayerComponent playerComponent, AddPlayerComponent addPlayerComponent) 12 | { 13 | PlayerComponent = playerComponent; 14 | AddPlayerComponent = addPlayerComponent; 15 | } 16 | 17 | public override void Draw() 18 | { 19 | switch (Config.PanelType) 20 | { 21 | case PanelType.Player: 22 | PlayerComponent.Draw(); 23 | break; 24 | case PanelType.AddPlayer: 25 | AddPlayerComponent.Draw(); 26 | break; 27 | case PanelType.None: 28 | default: 29 | break; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/Config/Components/ConfigViewComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using PlayerTrack.Windows.Components; 4 | 5 | namespace PlayerTrack.Windows.Config.Components; 6 | 7 | public abstract class ConfigViewComponent : ViewComponent, IDisposable 8 | { 9 | private readonly TimeSpan DebounceDelay = TimeSpan.FromMilliseconds(300); 10 | private Timer? DebounceTimer; 11 | 12 | public event Action OnPlayerConfigChanged = null!; 13 | 14 | public void Dispose() 15 | { 16 | DisposeDebounceTimer(); 17 | GC.SuppressFinalize(this); 18 | } 19 | 20 | protected void NotifyConfigChanged() 21 | { 22 | ResetDebounceTimer(); 23 | DebounceTimer = new Timer(DebounceCallback, null, DebounceDelay, Timeout.InfiniteTimeSpan); 24 | } 25 | 26 | private void DisposeDebounceTimer() => DebounceTimer?.Dispose(); 27 | 28 | private void ResetDebounceTimer() => DisposeDebounceTimer(); 29 | 30 | private void DebounceCallback(object? state) => OnPlayerConfigChanged(); 31 | } 32 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Extensions/ClientStateExtension.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Plugin.Services; 2 | using PlayerTrack.Data; 3 | 4 | namespace PlayerTrack.Extensions; 5 | 6 | /// 7 | /// Dalamud ClientStateHandler extensions. 8 | /// 9 | public static class ClientStateExtension 10 | { 11 | /// 12 | /// Validate if actor is valid player character. 13 | /// 14 | /// actor. 15 | /// Indicator if player character is valid. 16 | public static LocalPlayerData? GetLocalPlayer(this IClientState value) 17 | { 18 | if (value.LocalPlayer == null) 19 | return null; 20 | 21 | var localPlayer = new LocalPlayerData 22 | { 23 | Name = value.LocalPlayer.Name.TextValue, 24 | HomeWorld = value.LocalPlayer.HomeWorld.RowId, 25 | ContentId = value.LocalContentId, 26 | Customize = value.LocalPlayer.Customize, 27 | }; 28 | 29 | return localPlayer.IsValid() ? localPlayer : null; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/Config/Components/ContextMenuComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using PlayerTrack.Domain; 3 | using PlayerTrack.Resource; 4 | 5 | namespace PlayerTrack.Windows.Config.Components; 6 | 7 | public class ContextMenuComponent : ConfigViewComponent 8 | { 9 | public Action? UpdateContextMenu; 10 | 11 | public override void Draw() => DrawControls(); 12 | 13 | private void DrawControls() 14 | { 15 | var showOpenInPlayerTrack = Config.ShowOpenInPlayerTrack; 16 | if (Helper.Checkbox(Language.ShowOpenPlayerTracker, ref showOpenInPlayerTrack)) 17 | { 18 | Config.ShowOpenInPlayerTrack = showOpenInPlayerTrack; 19 | ServiceContext.ConfigService.SaveConfig(Config); 20 | } 21 | 22 | var showOpenInLodestone = Config.ShowOpenLodestone; 23 | if (Helper.Checkbox(Language.ShowOpenLodestone, ref showOpenInLodestone)) 24 | { 25 | Config.ShowOpenLodestone = showOpenInLodestone; 26 | ServiceContext.ConfigService.SaveConfig(Config); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Infi, kalilistic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Domain/Common/PlayerFCHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PlayerTrack.Models; 3 | 4 | namespace PlayerTrack.Domain.Common; 5 | 6 | public static class PlayerFCHelper 7 | { 8 | public static KeyValuePair CheckFreeCompany(string tag, bool inContent) => 9 | DetermineFCState(tag, inContent); 10 | 11 | public static KeyValuePair CheckFreeCompany(string tag, KeyValuePair previousState, bool inContent) => 12 | inContent ? previousState : DetermineFCState(tag, inContent); 13 | 14 | private static KeyValuePair DetermineFCState(string tag, bool inContent) 15 | { 16 | Plugin.PluginLog.Verbose($"Entering PlayerFCHelper.DetermineFCState(): {tag}, {inContent}"); 17 | if (inContent) 18 | return new KeyValuePair(FreeCompanyState.Unknown, tag); 19 | 20 | var state = string.IsNullOrEmpty(tag) ? FreeCompanyState.NotInFC : FreeCompanyState.InFC; 21 | return new KeyValuePair(state, tag); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/SocialList.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Dapper.Contrib.Extensions; 4 | 5 | namespace PlayerTrack.Models; 6 | 7 | public class SocialList 8 | { 9 | public int Id { get; set; } 10 | 11 | public long Created { get; set; } 12 | 13 | public long Updated { get; set; } 14 | 15 | public ulong ContentId { get; set; } 16 | 17 | public SocialListType ListType { get; set; } 18 | 19 | public ushort ListNumber { get; set; } 20 | 21 | public uint DataCenterId { get; set; } 22 | 23 | public ushort PageCount { get; set; } 24 | 25 | public bool AddPlayers { get; set; } 26 | 27 | public bool SyncWithCategory { get; set; } 28 | 29 | public int DefaultCategoryId { get; set; } 30 | 31 | public readonly Dictionary PageLastUpdated = new(); 32 | 33 | public long GetLastUpdated() 34 | { 35 | // only free company uses multiple pages 36 | if (ListType == SocialListType.FreeCompany) 37 | return PageLastUpdated.Count == 0 ? 0 : PageLastUpdated.Values.Min(); 38 | 39 | return Updated; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/Mappings/TagMappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using PlayerTrack.Models; 3 | 4 | namespace PlayerTrack.Infrastructure; 5 | 6 | public class TagMappingProfile : Profile 7 | { 8 | public TagMappingProfile() 9 | { 10 | CreateMap() 11 | .ForMember(dest => dest.id, opt => opt.MapFrom(src => src.Id)) 12 | .ForMember(dest => dest.created, opt => opt.MapFrom(src => src.Created)) 13 | .ForMember(dest => dest.updated, opt => opt.MapFrom(src => src.Updated)) 14 | .ForMember(dest => dest.name, opt => opt.MapFrom(src => src.Name)) 15 | .ForMember(dest => dest.color, opt => opt.MapFrom(src => src.Color)); 16 | 17 | CreateMap() 18 | .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.id)) 19 | .ForMember(dest => dest.Created, opt => opt.MapFrom(src => src.created)) 20 | .ForMember(dest => dest.Updated, opt => opt.MapFrom(src => src.updated)) 21 | .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.name)) 22 | .ForMember(dest => dest.Color, opt => opt.MapFrom(src => src.color)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/DTOs/PlayerDTO.cs: -------------------------------------------------------------------------------- 1 | using FluentDapperLite.Repository; 2 | using PlayerTrack.Models; 3 | 4 | // ReSharper disable InconsistentNaming 5 | namespace PlayerTrack.Infrastructure; 6 | 7 | public class PlayerDTO : DTO 8 | { 9 | public long last_alert_sent { get; set; } 10 | 11 | public long last_seen { get; set; } 12 | 13 | public long first_seen { get; set; } 14 | 15 | public byte[]? customize { get; set; } 16 | 17 | public int seen_count { get; set; } 18 | 19 | public int lodestone_status { get; set; } 20 | 21 | public long lodestone_verified_on { get; set; } 22 | 23 | public FreeCompanyState free_company_state { get; set; } = FreeCompanyState.Unknown; 24 | 25 | public string free_company_tag { get; set; } = string.Empty; 26 | 27 | public string key { get; set; } = string.Empty; 28 | 29 | public string name { get; set; } = string.Empty; 30 | 31 | public string notes { get; set; } = string.Empty; 32 | 33 | public uint lodestone_id { get; set; } 34 | 35 | public uint entity_id { get; set; } 36 | 37 | public uint world_id { get; set; } 38 | 39 | public ushort last_territory_type { get; set; } 40 | 41 | public ulong content_id { get; set; } 42 | } 43 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/Mappings/PlayerTagMappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using PlayerTrack.Models; 3 | 4 | namespace PlayerTrack.Infrastructure; 5 | 6 | public class PlayerTagMappingProfile : Profile 7 | { 8 | public PlayerTagMappingProfile() 9 | { 10 | CreateMap() 11 | .ForMember(dest => dest.id, opt => opt.MapFrom(src => src.Id)) 12 | .ForMember(dest => dest.created, opt => opt.MapFrom(src => src.Created)) 13 | .ForMember(dest => dest.updated, opt => opt.MapFrom(src => src.Updated)) 14 | .ForMember(dest => dest.player_id, opt => opt.MapFrom(src => src.PlayerId)) 15 | .ForMember(dest => dest.tag_id, opt => opt.MapFrom(src => src.TagId)); 16 | 17 | CreateMap() 18 | .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.id)) 19 | .ForMember(dest => dest.Created, opt => opt.MapFrom(src => src.created)) 20 | .ForMember(dest => dest.Updated, opt => opt.MapFrom(src => src.updated)) 21 | .ForMember(dest => dest.PlayerId, opt => opt.MapFrom(src => src.player_id)) 22 | .ForMember(dest => dest.TagId, opt => opt.MapFrom(src => src.tag_id)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/Mappings/EncounterMappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using PlayerTrack.Models; 3 | 4 | namespace PlayerTrack.Infrastructure; 5 | 6 | public class EncounterMappingProfile : Profile 7 | { 8 | public EncounterMappingProfile() 9 | { 10 | CreateMap() 11 | .ForMember(dest => dest.id, opt => opt.MapFrom(src => src.Id)) 12 | .ForMember(dest => dest.created, opt => opt.MapFrom(src => src.Created)) 13 | .ForMember(dest => dest.updated, opt => opt.MapFrom(src => src.Updated)) 14 | .ForMember(dest => dest.territory_type_id, opt => opt.MapFrom(src => src.TerritoryTypeId)) 15 | .ForMember(dest => dest.ended, opt => opt.MapFrom(src => src.Ended)); 16 | 17 | CreateMap() 18 | .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.id)) 19 | .ForMember(dest => dest.Created, opt => opt.MapFrom(src => src.created)) 20 | .ForMember(dest => dest.Updated, opt => opt.MapFrom(src => src.updated)) 21 | .ForMember(dest => dest.TerritoryTypeId, opt => opt.MapFrom(src => src.territory_type_id)) 22 | .ForMember(dest => dest.Ended, opt => opt.MapFrom(src => src.ended)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/Mappings/ArchiveRecordMappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using PlayerTrack.Models; 3 | 4 | namespace PlayerTrack.Infrastructure; 5 | 6 | public class ArchiveRecordMappingProfile : Profile 7 | { 8 | public ArchiveRecordMappingProfile() 9 | { 10 | CreateMap() 11 | .ForMember(dest => dest.id, opt => opt.MapFrom(src => src.Id)) 12 | .ForMember(dest => dest.created, opt => opt.MapFrom(src => src.Created)) 13 | .ForMember(dest => dest.updated, opt => opt.MapFrom(src => src.Updated)) 14 | .ForMember(dest => dest.archive_type, opt => opt.MapFrom(src => src.ArchiveType)) 15 | .ForMember(dest => dest.data, opt => opt.MapFrom(src => src.Data)); 16 | 17 | CreateMap() 18 | .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.id)) 19 | .ForMember(dest => dest.Created, opt => opt.MapFrom(src => src.created)) 20 | .ForMember(dest => dest.Updated, opt => opt.MapFrom(src => src.updated)) 21 | .ForMember(dest => dest.ArchiveType, opt => opt.MapFrom(src => src.archive_type)) 22 | .ForMember(dest => dest.Data, opt => opt.MapFrom(src => src.data)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Handler/CommandHandler.cs: -------------------------------------------------------------------------------- 1 | using PlayerTrack.Resource; 2 | 3 | namespace PlayerTrack.Handler; 4 | 5 | using Dalamud.Game.Command; 6 | 7 | public static class CommandHandler 8 | { 9 | public delegate void PlayerWindowToggledDelegate(); 10 | public delegate void ConfigWindowToggledDelegate(); 11 | 12 | public static event PlayerWindowToggledDelegate? OnPlayerWindowToggled; 13 | public static event ConfigWindowToggledDelegate? OnConfigWindowToggled; 14 | 15 | public static void Start() 16 | { 17 | Plugin.PluginLog.Verbose("Entering CommandHandler.Start()"); 18 | Plugin.CommandManager.AddHandler("/ptrack", new CommandInfo((_, _) => { OnPlayerWindowToggled?.Invoke(); }) 19 | { 20 | HelpMessage = Language.ShowHidePlayerTrack, 21 | ShowInHelp = true, 22 | }); 23 | Plugin.CommandManager.AddHandler("/ptrackconfig", new CommandInfo((_, _) => { OnConfigWindowToggled?.Invoke(); }) 24 | { 25 | HelpMessage = Language.ShowHidePlayerTrackConfig, 26 | ShowInHelp = true, 27 | }); 28 | } 29 | 30 | public static void Dispose() 31 | { 32 | Plugin.CommandManager.RemoveHandler("/ptrack"); 33 | Plugin.CommandManager.RemoveHandler("/ptrackconfig"); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/Mappings/PlayerCategoryMappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using PlayerTrack.Models; 3 | 4 | namespace PlayerTrack.Infrastructure; 5 | 6 | public class PlayerCategoryMappingProfile : Profile 7 | { 8 | public PlayerCategoryMappingProfile() 9 | { 10 | CreateMap() 11 | .ForMember(dest => dest.id, opt => opt.MapFrom(src => src.Id)) 12 | .ForMember(dest => dest.created, opt => opt.MapFrom(src => src.Created)) 13 | .ForMember(dest => dest.updated, opt => opt.MapFrom(src => src.Updated)) 14 | .ForMember(dest => dest.player_id, opt => opt.MapFrom(src => src.PlayerId)) 15 | .ForMember(dest => dest.category_id, opt => opt.MapFrom(src => src.CategoryId)); 16 | 17 | CreateMap() 18 | .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.id)) 19 | .ForMember(dest => dest.Created, opt => opt.MapFrom(src => src.created)) 20 | .ForMember(dest => dest.Updated, opt => opt.MapFrom(src => src.updated)) 21 | .ForMember(dest => dest.PlayerId, opt => opt.MapFrom(src => src.player_id)) 22 | .ForMember(dest => dest.CategoryId, opt => opt.MapFrom(src => src.category_id)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Extensions/StringExtension.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace PlayerTrack.Extensions; 4 | 5 | public static class StringExtension 6 | { 7 | /// 8 | /// Truncates the string to a specified length and appends an ellipsis if truncated. 9 | /// 10 | /// string to truncate. 11 | /// maximum length of the string. 12 | /// truncated string. 13 | public static string TruncateWithEllipsis(this string value, int lengthLimit) 14 | { 15 | if (string.IsNullOrEmpty(value)) 16 | return string.Empty; 17 | 18 | return value.Length > lengthLimit ? new StringBuilder(value, 0, lengthLimit, lengthLimit + 3).Append("...").ToString() : value; 19 | } 20 | 21 | /// 22 | /// Converts the first character of the string to lowercase. 23 | /// 24 | /// The input string. 25 | /// The string with its first character converted to lowercase. 26 | public static string FirstCharToLower(this string input) 27 | { 28 | if (string.IsNullOrEmpty(input)) 29 | return input; 30 | 31 | return char.ToLowerInvariant(input[0]) + input[1..]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Extensions/ChatGuiExtension.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Dalamud.Game.Text; 4 | using Dalamud.Game.Text.SeStringHandling; 5 | using Dalamud.Game.Text.SeStringHandling.Payloads; 6 | using Dalamud.Plugin.Services; 7 | 8 | namespace PlayerTrack.Extensions; 9 | 10 | /// 11 | /// Dalamud IChatGui extensions. 12 | /// 13 | public static class ChatGuiExtensions 14 | { 15 | /// 16 | /// Print message with plugin name to notice channel. 17 | /// 18 | /// chat gui service. 19 | /// list of payloads. 20 | public static void PluginPrintNotice(this IChatGui value, IEnumerable payloads) 21 | { 22 | value.Print(new XivChatEntry 23 | { 24 | Message = BuildSeString(Plugin.PluginInterface.InternalName, payloads), 25 | Type = XivChatType.Notice, 26 | }); 27 | } 28 | 29 | private static SeString BuildSeString(string? pluginName, IEnumerable payloads) 30 | { 31 | var builder = new SeStringBuilder(); 32 | builder.AddUiForeground(548); 33 | builder.AddText($"[{pluginName}] "); 34 | builder.Append(payloads); 35 | builder.AddUiForegroundOff(); 36 | 37 | return builder.BuiltString; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/API/IPlayerTrackAPI.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable InconsistentNaming 2 | namespace PlayerTrack.API; 3 | 4 | /// 5 | /// Interface to communicate with PlayerTrack. 6 | /// 7 | public interface IPlayerTrackAPI 8 | { 9 | /// 10 | /// Gets api version. 11 | /// 12 | public int APIVersion { get; } 13 | 14 | /// 15 | /// Gets the player's current name and world. 16 | /// 17 | /// full player name at point in time. 18 | /// player home world id at point in time. 19 | /// string in the form of (name worldId). 20 | public string GetPlayerCurrentNameWorld(string name, uint worldId); 21 | 22 | /// 23 | /// Get notes for player. 24 | /// 25 | /// player's full name. 26 | /// player home world id. 27 | /// notes. 28 | public string GetPlayerNotes(string name, uint worldId); 29 | 30 | /// 31 | /// Retrieves all player names/world history records. 32 | /// 33 | /// tuple array of current (player name, world id) and an array of all previous (player name, world id) combos. 34 | public ((string, uint), (string, uint)[])[] GetAllPlayerNameWorldHistories(); 35 | } 36 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/Mappings/CategoryMappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using PlayerTrack.Models; 3 | 4 | namespace PlayerTrack.Infrastructure; 5 | 6 | public class CategoryMappingProfile : Profile 7 | { 8 | public CategoryMappingProfile() 9 | { 10 | CreateMap() 11 | .ForMember(dest => dest.id, opt => opt.MapFrom(src => src.Id)) 12 | .ForMember(dest => dest.created, opt => opt.MapFrom(src => src.Created)) 13 | .ForMember(dest => dest.updated, opt => opt.MapFrom(src => src.Updated)) 14 | .ForMember(dest => dest.name, opt => opt.MapFrom(src => src.Name)) 15 | .ForMember(dest => dest.rank, opt => opt.MapFrom(src => src.Rank)) 16 | .ForMember(dest => dest.social_list_id, opt => opt.MapFrom(src => src.SocialListId)); 17 | 18 | CreateMap() 19 | .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.id)) 20 | .ForMember(dest => dest.Created, opt => opt.MapFrom(src => src.created)) 21 | .ForMember(dest => dest.Updated, opt => opt.MapFrom(src => src.updated)) 22 | .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.name)) 23 | .ForMember(dest => dest.Rank, opt => opt.MapFrom(src => src.rank)) 24 | .ForMember(dest => dest.SocialListId, opt => opt.MapFrom(src => src.social_list_id)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/PlayerTrack.Plugin.csproj.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | False 3 | True 4 | True 5 | True 6 | True 7 | True 8 | True 9 | True -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/Config/IPluginConfig.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | /// 4 | /// Base plugin configuration class to be extended. 5 | /// 6 | public interface IPluginConfig 7 | { 8 | /// 9 | /// Gets or sets the unique identifier for this configuration. 10 | /// 11 | int Id { get; set; } 12 | 13 | /// 14 | /// Gets or sets the timestamp for when this configuration was created. Typically in Unix epoch format. 15 | /// 16 | long Created { get; set; } 17 | 18 | /// 19 | /// Gets or sets the timestamp for the last time this configuration was updated. Typically in Unix epoch format. 20 | /// 21 | long Updated { get; set; } 22 | 23 | /// 24 | /// Gets or sets a value indicating whether plugin is enabled. 25 | /// 26 | bool IsPluginEnabled { get; set; } 27 | 28 | /// 29 | /// Gets or sets a value indicating whether to lock window size. 30 | /// 31 | bool IsWindowSizeLocked { get; set; } 32 | 33 | /// 34 | /// Gets or sets a value indicating whether to lock window size. 35 | /// 36 | bool IsWindowPositionLocked { get; set; } 37 | 38 | /// 39 | /// Gets or sets a value indicating whether to show window when not logged in. 40 | /// 41 | bool OnlyShowWindowWhenLoggedIn { get; set; } 42 | } 43 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/Mappings/PlayerCustomizeHistoryMappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using PlayerTrack.Models; 3 | 4 | namespace PlayerTrack.Infrastructure; 5 | 6 | public class PlayerCustomizeHistoryMappingProfile : Profile 7 | { 8 | public PlayerCustomizeHistoryMappingProfile() 9 | { 10 | CreateMap() 11 | .ForMember(dest => dest.id, opt => opt.MapFrom(src => src.Id)) 12 | .ForMember(dest => dest.created, opt => opt.MapFrom(src => src.Created)) 13 | .ForMember(dest => dest.updated, opt => opt.MapFrom(src => src.Updated)) 14 | .ForMember(dest => dest.is_migrated, opt => opt.MapFrom(src => src.IsMigrated)) 15 | .ForMember(dest => dest.player_id, opt => opt.MapFrom(src => src.PlayerId)) 16 | .ForMember(dest => dest.customize, opt => opt.MapFrom(src => src.Customize)); 17 | 18 | CreateMap() 19 | .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.id)) 20 | .ForMember(dest => dest.Created, opt => opt.MapFrom(src => src.created)) 21 | .ForMember(dest => dest.Updated, opt => opt.MapFrom(src => src.updated)) 22 | .ForMember(dest => dest.IsMigrated, opt => opt.MapFrom(src => src.is_migrated)) 23 | .ForMember(dest => dest.PlayerId, opt => opt.MapFrom(src => src.player_id)) 24 | .ForMember(dest => dest.Customize, opt => opt.MapFrom(src => src.customize)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Extensions/ObjectTableExtension.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Dalamud.Game.ClientState.Objects.Enums; 4 | using Dalamud.Game.ClientState.Objects.SubKinds; 5 | using Dalamud.Plugin.Services; 6 | using PlayerTrack.Data; 7 | 8 | namespace PlayerTrack.Extensions; 9 | 10 | /// 11 | /// ObjectTable extensions. 12 | /// 13 | public static class ObjectTableExtension 14 | { 15 | /// 16 | /// Retrieve all players. 17 | /// 18 | /// Dalamud ObjectTable. 19 | /// all players. 20 | public static IEnumerable GetPlayers(this IObjectTable objectTable) => 21 | objectTable.Skip(1) 22 | .Where(x => x.ObjectKind == ObjectKind.Player && x is IPlayerCharacter) 23 | .OfType() 24 | .Select(pc => pc.ToPlayerData()) 25 | .Where(tp => tp.IsValid()) 26 | .ToList(); 27 | 28 | /// 29 | /// Retrieve player by content id. 30 | /// 31 | /// Dalamud ObjectTable. 32 | /// content id. 33 | /// player if exists. 34 | public static PlayerData? GetPlayerByContentId(this IObjectTable objectTable, ulong contentId) => 35 | objectTable.OfType().FirstOrDefault(playerCharacter => playerCharacter.GetContentId() == contentId)?.ToPlayerData(); 36 | } 37 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Extensions/PlayerCharacterExtension.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Game.ClientState.Objects.SubKinds; 2 | using FFXIVClientStructs.FFXIV.Client.Game.Character; 3 | using PlayerTrack.Data; 4 | 5 | namespace PlayerTrack.Extensions; 6 | 7 | /// 8 | /// PlayerCharacter extensions. 9 | /// 10 | public static class PlayerCharacterExtension 11 | { 12 | /// 13 | /// Convert IPlayerCharacter to PlayerData. 14 | /// 15 | /// IPlayerCharacter. 16 | /// PlayerData. 17 | public static PlayerData ToPlayerData(this IPlayerCharacter character) => 18 | new () 19 | { 20 | GameObjectId = character.GameObjectId, 21 | EntityId = character.EntityId, 22 | ContentId = character.GetContentId(), 23 | Name = character.Name.TextValue, 24 | HomeWorld = character.HomeWorld.RowId, 25 | ClassJob = character.ClassJob.RowId, 26 | Level = character.Level, 27 | Customize = character.Customize, 28 | CompanyTag = character.CompanyTag.TextValue, 29 | IsLocalPlayer = false, 30 | IsDead = character.IsDead, 31 | }; 32 | 33 | /// 34 | /// Get content id. 35 | /// 36 | /// IPlayerCharacter. 37 | /// content id. 38 | public static unsafe ulong GetContentId(this IPlayerCharacter character) => 39 | ((Character*)character.Address)->ContentId; 40 | } 41 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Data/SocialListMemberData.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Utility; 2 | 3 | namespace PlayerTrack.Data; 4 | 5 | public class SocialListMemberData 6 | { 7 | /// 8 | /// The content ID of the local character. 9 | /// 10 | public ulong ContentId; 11 | 12 | /// 13 | /// Player Name. 14 | /// 15 | public string Name = string.Empty; 16 | 17 | /// 18 | /// Player HomeWorld ID. 19 | /// 20 | public ushort HomeWorld; 21 | 22 | /// 23 | /// Player is unable to retrieve (no name). 24 | /// 25 | public bool IsUnableToRetrieve; 26 | 27 | /// 28 | /// Player is expected to have content id (set to false where not available). 29 | /// 30 | public bool ShouldHaveContentId = true; 31 | 32 | /// 33 | /// Validate if player is valid (expected to have content id). 34 | /// 35 | /// Indicator if player is valid. 36 | public bool IsValid() 37 | { 38 | if (!ShouldHaveContentId) 39 | return Sheets.IsValidWorld(HomeWorld) && Name.IsValidCharacterName(); 40 | 41 | if (ContentId == 0) 42 | return false; 43 | 44 | // If the player's name is null or empty, the player is valid only if they are marked as unable to retrieve. 45 | if (string.IsNullOrEmpty(Name)) 46 | return IsUnableToRetrieve; 47 | 48 | return Sheets.IsValidWorld(HomeWorld) && Name.IsValidCharacterName(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/ViewModels/PlayerView.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PlayerTrack.Models; 3 | 4 | namespace PlayerTrack.Windows.ViewModels; 5 | 6 | public class PlayerView 7 | { 8 | public int Id { get; set; } 9 | 10 | public string Name { get; set; } = null!; 11 | 12 | public string HomeWorld { get; set; } = null!; 13 | 14 | public string FreeCompany { get; set; } = null!; 15 | 16 | public uint LodestoneId { get; set; } 17 | 18 | public string FirstSeen { get; set; } = null!; 19 | 20 | public string LastSeen { get; set; } = null!; 21 | 22 | public string LastLocation { get; set; } = null!; 23 | 24 | public string SeenCount { get; set; } = null!; 25 | 26 | public string Appearance { get; set; } = null!; 27 | 28 | public string PreviousNames { get; set; } = null!; 29 | 30 | public string PreviousWorlds { get; set; } = null!; 31 | 32 | public List AssignedTags { get; set; } = []; 33 | 34 | public List UnassignedTags { get; set; } = []; 35 | 36 | public int PrimaryCategoryId { get; set; } 37 | 38 | public List AssignedCategories { get; set; } = []; 39 | 40 | public List UnassignedCategories { get; set; } = []; 41 | 42 | public string Notes { get; set; } = null!; 43 | 44 | public PlayerConfig PlayerConfig { get; set; } = null!; 45 | 46 | public List Encounters { get; set; } = []; 47 | 48 | public List PlayerNameWorldHistories { get; set; } = []; 49 | 50 | public List PlayerCustomizeHistories { get; set; } = []; 51 | } 52 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/DTOs/PlayerConfigDTO.cs: -------------------------------------------------------------------------------- 1 | using FluentDapperLite.Repository; 2 | 3 | namespace PlayerTrack.Infrastructure; 4 | 5 | using Models; 6 | 7 | public class PlayerConfigDTO : DTO 8 | { 9 | public PlayerConfigType player_config_type { get; set; } 10 | 11 | public string player_list_name_color { get; set; } = string.Empty; 12 | 13 | public string player_list_icon { get; set; } = string.Empty; 14 | 15 | public string nameplate_custom_title { get; set; } = string.Empty; 16 | 17 | public string nameplate_show_in_overworld { get; set; } = string.Empty; 18 | 19 | public string nameplate_show_in_content { get; set; } = string.Empty; 20 | 21 | public string nameplate_show_in_high_end_content { get; set; } = string.Empty; 22 | 23 | public string nameplate_color { get; set; } = string.Empty; 24 | 25 | public string nameplate_use_color { get; set; } = string.Empty; 26 | 27 | public string nameplate_use_color_if_dead { get; set; } = string.Empty; 28 | 29 | public string nameplate_title_type { get; set; } = string.Empty; 30 | 31 | public string alert_name_change { get; set; } = string.Empty; 32 | 33 | public string alert_world_transfer { get; set; } = string.Empty; 34 | 35 | public string alert_proximity { get; set; } = string.Empty; 36 | 37 | public string alert_format_include_category { get; set; } = string.Empty; 38 | 39 | public string alert_format_include_custom_title { get; set; } = string.Empty; 40 | 41 | public string visibility_type { get; set; } = string.Empty; 42 | 43 | public int? player_id { get; set; } 44 | 45 | public int? category_id { get; set; } 46 | } 47 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/Config/Components/IntegrationComponent.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Interface.Utility; 2 | using Dalamud.Interface.Utility.Raii; 3 | using Dalamud.Bindings.ImGui; 4 | using PlayerTrack.Domain; 5 | using PlayerTrack.Resource; 6 | 7 | namespace PlayerTrack.Windows.Config.Components; 8 | 9 | public class IntegrationComponent : ConfigViewComponent 10 | { 11 | public override void Draw() 12 | { 13 | using var tabBar = ImRaii.TabBar("###Integration_TabBar", ImGuiTabBarFlags.None); 14 | if (!tabBar.Success) 15 | return; 16 | 17 | using (var tabItem = ImRaii.TabItem(Language.Lodestone)) 18 | { 19 | if (tabItem.Success) 20 | DrawLodestoneTab(); 21 | } 22 | 23 | using (var tabItem = ImRaii.TabItem(Language.Visibility)) 24 | { 25 | if (tabItem.Success) 26 | DrawVisibilityTab(); 27 | } 28 | } 29 | 30 | private void DrawLodestoneTab() 31 | { 32 | var lodestoneLocale = Config.LodestoneLocale; 33 | if (Helper.Combo(Language.LodestoneLocale, ref lodestoneLocale, 60)) 34 | { 35 | Config.LodestoneLocale = lodestoneLocale; 36 | ServiceContext.ConfigService.SaveConfig(Config); 37 | } 38 | } 39 | 40 | private void DrawVisibilityTab() 41 | { 42 | ImGuiHelpers.ScaledDummy(1f); 43 | var syncWithVisibility = Config.SyncWithVisibility; 44 | if (Helper.Checkbox(Language.SyncWithVisibility, ref syncWithVisibility)) 45 | { 46 | Config.SyncWithVisibility = syncWithVisibility; 47 | ServiceContext.ConfigService.SaveConfig(Config); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/Mappings/LocalPlayerMappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using PlayerTrack.Models; 3 | 4 | namespace PlayerTrack.Infrastructure; 5 | 6 | public class LocalPlayerMappingProfile : Profile 7 | { 8 | public LocalPlayerMappingProfile() 9 | { 10 | CreateMap() 11 | .ForMember(dest => dest.id, opt => opt.MapFrom(src => src.Id)) 12 | .ForMember(dest => dest.created, opt => opt.MapFrom(src => src.Created)) 13 | .ForMember(dest => dest.updated, opt => opt.MapFrom(src => src.Updated)) 14 | .ForMember(dest => dest.content_id, opt => opt.MapFrom(src => src.ContentId)) 15 | .ForMember(dest => dest.name, opt => opt.MapFrom(src => src.Name)) 16 | .ForMember(dest => dest.world_id, opt => opt.MapFrom(src => src.WorldId)) 17 | .ForMember(dest => dest.key, opt => opt.MapFrom(src => src.Key)) 18 | .ForMember(dest => dest.customize, opt => opt.MapFrom(src => src.Customize)); 19 | 20 | CreateMap() 21 | .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.id)) 22 | .ForMember(dest => dest.Created, opt => opt.MapFrom(src => src.created)) 23 | .ForMember(dest => dest.Updated, opt => opt.MapFrom(src => src.updated)) 24 | .ForMember(dest => dest.ContentId, opt => opt.MapFrom(src => src.content_id)) 25 | .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.name)) 26 | .ForMember(dest => dest.WorldId, opt => opt.MapFrom(src => src.world_id)) 27 | .ForMember(dest => dest.Key, opt => opt.MapFrom(src => src.key)) 28 | .ForMember(dest => dest.Customize, opt => opt.MapFrom(src => src.customize)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Enums/SocialListType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using PlayerTrack.Resource; 3 | 4 | namespace PlayerTrack.Models; 5 | 6 | public enum SocialListType 7 | { 8 | None, 9 | FriendList, 10 | BlackList, 11 | FreeCompany, 12 | LinkShell, 13 | CrossWorldLinkShell, 14 | } 15 | 16 | public static class SocialListTypeExtension 17 | { 18 | public static string ToLocalizedString(this SocialListType socialListType) 19 | { 20 | return socialListType switch 21 | { 22 | SocialListType.None => Language.None, 23 | SocialListType.FriendList => Language.FriendList, 24 | SocialListType.BlackList => Language.BlackList, 25 | SocialListType.FreeCompany => Language.FreeCompany, 26 | SocialListType.LinkShell => Language.LinkShell, 27 | SocialListType.CrossWorldLinkShell => Language.CrossWorldLinkShell, 28 | _ => throw new ArgumentOutOfRangeException(nameof(socialListType), socialListType, null) 29 | }; 30 | } 31 | 32 | public static string ToAbrLocalizedString(this SocialListType socialListType) 33 | { 34 | return socialListType switch 35 | { 36 | SocialListType.None => Language.None, 37 | SocialListType.FriendList => Language.FriendList_Abbreviation, 38 | SocialListType.BlackList => Language.BlackList_Abbreviation, 39 | SocialListType.FreeCompany => Language.FreeCompany_Abbreviation, 40 | SocialListType.LinkShell => Language.LinkShell_Abbreviation, 41 | SocialListType.CrossWorldLinkShell => Language.CrossWorldLinkShell_Abbreviation, 42 | _ => throw new ArgumentOutOfRangeException(nameof(socialListType), socialListType, null) 43 | }; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Extensions/MenuItemClickedArgsExtension.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Game.Gui.ContextMenu; 2 | using Dalamud.Utility; 3 | using PlayerTrack.Data; 4 | 5 | namespace PlayerTrack.Extensions; 6 | 7 | /// 8 | /// Provides extension methods for . 9 | /// 10 | public static class MenuItemClickedArgsExtension 11 | { 12 | /// 13 | /// Gets the player from the menu item clicked arguments. 14 | /// 15 | /// The menu item clicked arguments. 16 | /// The player or if the player is invalid. 17 | public static PlayerData? GetPlayer(this IMenuItemClickedArgs menuOpenedArgs) 18 | { 19 | if (menuOpenedArgs.Target is not MenuTargetDefault menuTargetDefault) 20 | { 21 | Plugin.PluginLog.Warning("ContextMenu: Invalid target"); 22 | return null; 23 | } 24 | 25 | var playerName = menuTargetDefault.TargetName; 26 | var worldId = menuTargetDefault.TargetHomeWorld.RowId; 27 | var contentId = menuTargetDefault.TargetContentId; 28 | var objectId = menuTargetDefault.TargetObjectId; 29 | if (playerName.IsValidCharacterName() && Sheets.IsValidWorld(worldId) && contentId != 0 && objectId != 0) 30 | { 31 | return new PlayerData 32 | { 33 | Name = playerName, 34 | HomeWorld = worldId, 35 | ContentId = contentId, 36 | GameObjectId = objectId, 37 | CompanyTag = string.Empty, 38 | }; 39 | } 40 | 41 | Plugin.PluginLog.Warning($"ContextMenu: Invalid player {playerName} {worldId} {contentId} {objectId}"); 42 | return null; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/Mappings/PlayerEncounterMappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using PlayerTrack.Models; 3 | 4 | namespace PlayerTrack.Infrastructure; 5 | 6 | public class PlayerEncounterMappingProfile : Profile 7 | { 8 | public PlayerEncounterMappingProfile() 9 | { 10 | CreateMap() 11 | .ForMember(dest => dest.id, opt => opt.MapFrom(src => src.Id)) 12 | .ForMember(dest => dest.created, opt => opt.MapFrom(src => src.Created)) 13 | .ForMember(dest => dest.updated, opt => opt.MapFrom(src => src.Updated)) 14 | .ForMember(dest => dest.ended, opt => opt.MapFrom(src => src.Ended)) 15 | .ForMember(dest => dest.job_id, opt => opt.MapFrom(src => src.JobId)) 16 | .ForMember(dest => dest.job_lvl, opt => opt.MapFrom(src => src.JobLvl)) 17 | .ForMember(dest => dest.player_id, opt => opt.MapFrom(src => src.PlayerId)) 18 | .ForMember(dest => dest.encounter_id, opt => opt.MapFrom(src => src.EncounterId)); 19 | 20 | CreateMap() 21 | .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.id)) 22 | .ForMember(dest => dest.Created, opt => opt.MapFrom(src => src.created)) 23 | .ForMember(dest => dest.Updated, opt => opt.MapFrom(src => src.updated)) 24 | .ForMember(dest => dest.Ended, opt => opt.MapFrom(src => src.ended)) 25 | .ForMember(dest => dest.JobId, opt => opt.MapFrom(src => src.job_id)) 26 | .ForMember(dest => dest.JobLvl, opt => opt.MapFrom(src => src.job_lvl)) 27 | .ForMember(dest => dest.PlayerId, opt => opt.MapFrom(src => src.player_id)) 28 | .ForMember(dest => dest.EncounterId, opt => opt.MapFrom(src => src.encounter_id)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Comparers/PlayerComparer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace PlayerTrack.Models.Comparers; 5 | 6 | public class PlayerComparer : IComparer 7 | { 8 | public readonly Dictionary CategoryRanks; 9 | public readonly int DefaultRank; 10 | 11 | public PlayerComparer(Dictionary categoryRanks, int defaultRank) 12 | { 13 | CategoryRanks = categoryRanks; 14 | CategoryRanks.Add(0, defaultRank); 15 | DefaultRank = defaultRank; 16 | } 17 | 18 | public int Compare(Player? x, Player? y) 19 | { 20 | try 21 | { 22 | if (ReferenceEquals(x, y)) 23 | return 0; 24 | 25 | if (ReferenceEquals(null, y)) 26 | return 1; 27 | 28 | if (ReferenceEquals(null, x)) 29 | return -1; 30 | 31 | var xRank = CategoryRanks.GetValueOrDefault(x.PrimaryCategoryId, DefaultRank); 32 | var yRank = CategoryRanks.GetValueOrDefault(y.PrimaryCategoryId, DefaultRank); 33 | 34 | var categoryComparison = xRank.CompareTo(yRank); 35 | if (categoryComparison != 0) 36 | return categoryComparison; 37 | 38 | var nameComparison = string.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase); 39 | if (nameComparison != 0) 40 | return nameComparison; 41 | 42 | var worldComparison = x.WorldId.CompareTo(y.WorldId); 43 | 44 | // ReSharper disable once ConvertIfStatementToReturnStatement 45 | if (worldComparison != 0) 46 | return worldComparison; 47 | 48 | return x.Created.CompareTo(y.Created); 49 | } 50 | catch (Exception) 51 | { 52 | return 0; 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/Mappings/PlayerNameWorldHistoryMappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | 3 | namespace PlayerTrack.Infrastructure; 4 | 5 | public class PlayerNameWorldHistoryMappingProfile : Profile 6 | { 7 | public PlayerNameWorldHistoryMappingProfile() 8 | { 9 | CreateMap() 10 | .ForMember(dest => dest.id, opt => opt.MapFrom(src => src.Id)) 11 | .ForMember(dest => dest.created, opt => opt.MapFrom(src => src.Created)) 12 | .ForMember(dest => dest.updated, opt => opt.MapFrom(src => src.Updated)) 13 | .ForMember(dest => dest.is_migrated, opt => opt.MapFrom(src => src.IsMigrated)) 14 | .ForMember(dest => dest.source, opt => opt.MapFrom(src => src.Source)) 15 | .ForMember(dest => dest.player_name, opt => opt.MapFrom(src => src.PlayerName)) 16 | .ForMember(dest => dest.world_id, opt => opt.MapFrom(src => src.WorldId)) 17 | .ForMember(dest => dest.player_id, opt => opt.MapFrom(src => src.PlayerId)); 18 | 19 | CreateMap() 20 | .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.id)) 21 | .ForMember(dest => dest.Created, opt => opt.MapFrom(src => src.created)) 22 | .ForMember(dest => dest.Updated, opt => opt.MapFrom(src => src.updated)) 23 | .ForMember(dest => dest.IsMigrated, opt => opt.MapFrom(src => src.is_migrated)) 24 | .ForMember(dest => dest.Source, opt => opt.MapFrom(src => src.source)) 25 | .ForMember(dest => dest.PlayerName, opt => opt.MapFrom(src => src.player_name)) 26 | .ForMember(dest => dest.WorldId, opt => opt.MapFrom(src => src.world_id)) 27 | .ForMember(dest => dest.PlayerId, opt => opt.MapFrom(src => src.player_id)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Data/LocationData.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Data; 2 | 3 | public enum LocationType 4 | { 5 | /// 6 | /// None such as in the character select screen. 7 | /// 8 | None, 9 | 10 | /// 11 | /// Overworld such as Limsa Lominsa. 12 | /// 13 | Overworld, 14 | 15 | /// 16 | /// Content such as Trials or Raids. 17 | /// 18 | Content, 19 | 20 | /// 21 | /// High end duty such as Extreme Trials or Savage Raids. 22 | /// 23 | HighEndContent, 24 | } 25 | 26 | public class LocationData 27 | { 28 | /// 29 | /// Gets or sets territoryTypeId. 30 | /// 31 | public ushort TerritoryId { get; set; } 32 | 33 | /// 34 | /// Gets or sets territory type place name. 35 | /// 36 | public string TerritoryName { get; set; } = string.Empty; 37 | 38 | /// 39 | /// Gets or sets CFC id. 40 | /// 41 | public uint ContentId { get; set; } 42 | 43 | /// 44 | /// Gets or sets CFC name. 45 | /// 46 | public string ContentName { get; set; } = string.Empty; 47 | 48 | /// 49 | /// Gets or sets Location type (e.g. overworld, duty, high-end duty). 50 | /// 51 | public LocationType LocationType { get; set; } 52 | 53 | /// 54 | /// Gets or sets a value indicating whether the location is in content. 55 | /// 56 | /// indicator whether in content. 57 | public bool InContent() => ContentId != 0; 58 | 59 | /// 60 | /// Get content name if available otherwise place name. 61 | /// 62 | /// effective name. 63 | public string GetName() 64 | { 65 | return !InContent() ? TerritoryName : ContentName; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/Mappings/BackupMappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using PlayerTrack.Models; 3 | 4 | namespace PlayerTrack.Infrastructure; 5 | 6 | public class BackupMappingProfile : Profile 7 | { 8 | public BackupMappingProfile() 9 | { 10 | CreateMap() 11 | .ForMember(dest => dest.id, opt => opt.MapFrom(src => src.Id)) 12 | .ForMember(dest => dest.created, opt => opt.MapFrom(src => src.Created)) 13 | .ForMember(dest => dest.updated, opt => opt.MapFrom(src => src.Updated)) 14 | .ForMember(dest => dest.backup_type, opt => opt.MapFrom(src => (int)src.BackupType)) 15 | .ForMember(dest => dest.name, opt => opt.MapFrom(src => src.Name)) 16 | .ForMember(dest => dest.size, opt => opt.MapFrom(src => src.Size)) 17 | .ForMember(dest => dest.is_restorable, opt => opt.MapFrom(src => src.IsRestorable)) 18 | .ForMember(dest => dest.is_protected, opt => opt.MapFrom(src => src.IsProtected)) 19 | .ForMember(dest => dest.notes, opt => opt.MapFrom(src => src.Notes)); 20 | 21 | CreateMap() 22 | .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.id)) 23 | .ForMember(dest => dest.Created, opt => opt.MapFrom(src => src.created)) 24 | .ForMember(dest => dest.Updated, opt => opt.MapFrom(src => src.updated)) 25 | .ForMember(dest => dest.BackupType, opt => opt.MapFrom(src => (BackupType)src.backup_type)) 26 | .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.name)) 27 | .ForMember(dest => dest.Size, opt => opt.MapFrom(src => src.size)) 28 | .ForMember(dest => dest.IsRestorable, opt => opt.MapFrom(src => src.is_restorable)) 29 | .ForMember(dest => dest.IsProtected, opt => opt.MapFrom(src => src.is_protected)) 30 | .ForMember(dest => dest.Notes, opt => opt.MapFrom(src => src.notes)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/Mappings/SocialListMemberMappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using PlayerTrack.Models; 3 | 4 | namespace PlayerTrack.Infrastructure; 5 | 6 | public class SocialListMemberMappingProfile : Profile 7 | { 8 | public SocialListMemberMappingProfile() 9 | { 10 | CreateMap() 11 | .ForMember(dest => dest.id, opt => opt.MapFrom(src => src.Id)) 12 | .ForMember(dest => dest.created, opt => opt.MapFrom(src => src.Created)) 13 | .ForMember(dest => dest.updated, opt => opt.MapFrom(src => src.Updated)) 14 | .ForMember(dest => dest.content_id, opt => opt.MapFrom(src => src.ContentId)) 15 | .ForMember(dest => dest.key, opt => opt.MapFrom(src => src.Key)) 16 | .ForMember(dest => dest.name, opt => opt.MapFrom(src => src.Name)) 17 | .ForMember(dest => dest.world_id, opt => opt.MapFrom(src => src.WorldId)) 18 | .ForMember(dest => dest.page_number, opt => opt.MapFrom(src => src.PageNumber)) 19 | .ForMember(dest => dest.social_list_id, opt => opt.MapFrom(src => src.SocialListId)); 20 | 21 | CreateMap() 22 | .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.id)) 23 | .ForMember(dest => dest.Created, opt => opt.MapFrom(src => src.created)) 24 | .ForMember(dest => dest.Updated, opt => opt.MapFrom(src => src.updated)) 25 | .ForMember(dest => dest.ContentId, opt => opt.MapFrom(src => src.content_id)) 26 | .ForMember(dest => dest.Key, opt => opt.MapFrom(src => src.key)) 27 | .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.name)) 28 | .ForMember(dest => dest.WorldId, opt => opt.MapFrom(src => src.world_id)) 29 | .ForMember(dest => dest.PageNumber, opt => opt.MapFrom(src => src.page_number)) 30 | .ForMember(dest => dest.SocialListId, opt => opt.MapFrom(src => src.social_list_id)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Resource/Language.ja.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | text/microsoft-resx 4 | 5 | 6 | 1.3 7 | 8 | 9 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 10 | 11 | 12 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 13 | 14 | 15 | アクション 16 | 17 | 18 | エンカウンターを追加 19 | 20 | 21 | プレイヤーを追加する: 22 | 23 | 24 | 手動でリストにプレーヤーを追加する。 25 | 26 | 27 | プレイヤーを追加する: 28 | 29 | 30 | 31 | 32 | 33 | アラート 34 | 35 | 36 | すべてのプレイヤー 37 | 38 | 39 | 表示 40 | 41 | 42 | カテゴリの設定 43 | 44 | 45 | 自動 46 | 47 | 48 | 問題が発生しました。コンピュータを再起動して、もう一度お試しください。 49 | 50 | 51 | バックアップ 52 | 53 | 54 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/Repositories/ConfigRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Linq; 4 | using AutoMapper; 5 | 6 | using Dapper; 7 | using FluentDapperLite.Repository; 8 | using PlayerTrack.Models; 9 | 10 | namespace PlayerTrack.Infrastructure; 11 | 12 | public class ConfigRepository : BaseRepository 13 | { 14 | public ConfigRepository(IDbConnection connection, IMapper mapper) : base(connection, mapper) { } 15 | 16 | public PluginConfig? GetPluginConfig() 17 | { 18 | Plugin.PluginLog.Verbose("Entering ConfigRepository.GetPluginConfig()"); 19 | try 20 | { 21 | const string sql = "SELECT * FROM configs"; 22 | var configEntryDTOs = Connection.Query(sql).ToArray(); 23 | return configEntryDTOs.Length == 0 ? null : ConfigMapper.ToModel(configEntryDTOs); 24 | } 25 | catch (Exception ex) 26 | { 27 | Plugin.PluginLog.Error(ex, "Failed to get plugin configuration from the database."); 28 | return null; 29 | } 30 | } 31 | 32 | public bool UpdatePluginConfig(PluginConfig pluginConfig) 33 | { 34 | Plugin.PluginLog.Verbose("Entering ConfigRepository.UpdatePluginConfig()"); 35 | var configEntryDTOs = ConfigMapper.ToDTOs(pluginConfig); 36 | 37 | using var transaction = Connection.BeginTransaction(); 38 | try 39 | { 40 | foreach (var configEntryDTO in configEntryDTOs) 41 | { 42 | const string sql = "INSERT OR REPLACE INTO configs (key, value) VALUES (@key, @value)"; 43 | Connection.Execute(sql, new { configEntryDTO.key, configEntryDTO.value }, transaction: transaction); 44 | } 45 | 46 | transaction.Commit(); 47 | return true; 48 | } 49 | catch (Exception ex) 50 | { 51 | Plugin.PluginLog.Error(ex, "Failed to update config.", pluginConfig); 52 | transaction.Rollback(); 53 | return false; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/Config/Components/ContributeComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Dalamud.Interface.Colors; 3 | using Dalamud.Interface.Utility; 4 | using Dalamud.Interface.Utility.Raii; 5 | using Dalamud.Bindings.ImGui; 6 | using PlayerTrack.Resource; 7 | 8 | namespace PlayerTrack.Windows.Config.Components; 9 | 10 | public class ContributeComponent : ConfigViewComponent 11 | { 12 | public override void Draw() => DrawContribute(); 13 | 14 | private static void DrawContribute() 15 | { 16 | ImGuiHelpers.ScaledDummy(1f); 17 | ImGui.TextUnformatted(Language.PitchInIntro); 18 | ImGuiHelpers.ScaledDummy(1f); 19 | 20 | Helper.TextColored(ImGuiColors.DalamudViolet, Language.ForEveryoneTitle); 21 | Helper.BulletText(Language.SignUpTest); 22 | Helper.BulletText(Language.ReportFindings); 23 | Helper.BulletText(Language.StableExperience); 24 | ImGuiHelpers.ScaledDummy(1f); 25 | 26 | Helper.TextColored(ImGuiColors.DalamudViolet, Language.ForTranslatorsTitle); 27 | Helper.BulletText(Language.HelpWithTranslation); 28 | Helper.BulletText(Language.CrowdinProject); 29 | Helper.BulletText(Language.DiscordRoleInfo); 30 | ImGuiHelpers.ScaledDummy(1f); 31 | 32 | Helper.TextColored(ImGuiColors.DalamudViolet, Language.ForDevelopersTitle); 33 | Helper.BulletText(Language.OpenForDevHelp); 34 | Helper.BulletText(Language.ReviewGitHub); 35 | Helper.BulletText(Language.LargeFeatureDiscussion); 36 | ImGuiHelpers.ScaledDummy(1f); 37 | 38 | Helper.TextColored(ImGuiColors.DalamudViolet, Language.SupportFurtherTitle); 39 | Helper.BulletText(Language.DonationsAppreciated); 40 | Helper.BulletText(Language.NoSpecialPerks); 41 | Helper.BulletText(Language.ContributionUsage); 42 | ImGuiHelpers.ScaledDummy(1f); 43 | using (ImRaii.PushColor(ImGuiCol.Button, new Vector4(0.12549f, 0.74902f, 0.33333f, 0.6f))) 44 | { 45 | if (ImGui.Button(Language.AboutKoFi)) 46 | Dalamud.Utility.Util.OpenLink("https://ko-fi.com/infiii"); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /PlayerTrack.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlayerTrack.Plugin", "PlayerTrack.Plugin\PlayerTrack.Plugin.csproj", "{92380CDF-883F-4E69-BDFD-C33208F4876A}" 4 | EndProject 5 | Global 6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 | Release|Any CPU = Release|Any CPU 8 | Debug|Any CPU = Debug|Any CPU 9 | EndGlobalSection 10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 11 | {92380CDF-883F-4E69-BDFD-C33208F4876A}.Release|Any CPU.ActiveCfg = Release|x64 12 | {92380CDF-883F-4E69-BDFD-C33208F4876A}.Release|Any CPU.Build.0 = Release|x64 13 | {92380CDF-883F-4E69-BDFD-C33208F4876A}.Debug|Any CPU.ActiveCfg = Debug|x64 14 | {92380CDF-883F-4E69-BDFD-C33208F4876A}.Debug|Any CPU.Build.0 = Debug|x64 15 | {69D0736E-391C-4A90-B9B6-5B5F6CC8858F}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {69D0736E-391C-4A90-B9B6-5B5F6CC8858F}.Release|Any CPU.Build.0 = Release|Any CPU 17 | {69D0736E-391C-4A90-B9B6-5B5F6CC8858F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {69D0736E-391C-4A90-B9B6-5B5F6CC8858F}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {62794DA0-3C4D-45A4-A31E-4A8569CA2B65}.Release|Any CPU.ActiveCfg = Release|x64 20 | {62794DA0-3C4D-45A4-A31E-4A8569CA2B65}.Release|Any CPU.Build.0 = Release|x64 21 | {62794DA0-3C4D-45A4-A31E-4A8569CA2B65}.Debug|Any CPU.ActiveCfg = Debug|x64 22 | {62794DA0-3C4D-45A4-A31E-4A8569CA2B65}.Debug|Any CPU.Build.0 = Debug|x64 23 | {B266AE31-41EA-417B-8091-901E7096A0D9}.Release|Any CPU.ActiveCfg = Release|x64 24 | {B266AE31-41EA-417B-8091-901E7096A0D9}.Release|Any CPU.Build.0 = Release|x64 25 | {B266AE31-41EA-417B-8091-901E7096A0D9}.Debug|Any CPU.ActiveCfg = Debug|x64 26 | {B266AE31-41EA-417B-8091-901E7096A0D9}.Debug|Any CPU.Build.0 = Debug|x64 27 | {529C5026-4E2A-4684-B220-39EE1E3E5C5E}.Release|Any CPU.ActiveCfg = Release|x64 28 | {529C5026-4E2A-4684-B220-39EE1E3E5C5E}.Release|Any CPU.Build.0 = Release|x64 29 | {529C5026-4E2A-4684-B220-39EE1E3E5C5E}.Debug|Any CPU.ActiveCfg = Debug|x64 30 | {529C5026-4E2A-4684-B220-39EE1E3E5C5E}.Debug|Any CPU.Build.0 = Debug|x64 31 | EndGlobalSection 32 | EndGlobal 33 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Data/PlayerData.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Data; 2 | 3 | /// 4 | /// Subset of key properties from IPlayerCharacter for eventing. 5 | /// 6 | // ReSharper disable ConditionIsAlwaysTrueOrFalse 7 | public class PlayerData 8 | { 9 | /// 10 | /// Player Job ID. 11 | /// 12 | public uint ClassJob; 13 | 14 | /// 15 | /// Player Free Company. 16 | /// 17 | public string CompanyTag = null!; 18 | 19 | /// 20 | /// Player Customize Array. 21 | /// 22 | public byte[]? Customize; 23 | 24 | /// 25 | /// Player HomeWorld ID. 26 | /// 27 | public uint HomeWorld; 28 | 29 | /// 30 | /// Player Entity ID assigned to networked GameObjects. 31 | /// 32 | public uint EntityId; 33 | 34 | /// 35 | /// Player GameObject ID. 36 | /// 37 | public ulong GameObjectId; 38 | 39 | /// 40 | /// Is Player Dead. 41 | /// 42 | public bool IsDead; 43 | 44 | /// 45 | /// Is Local Player. 46 | /// 47 | public bool IsLocalPlayer; 48 | 49 | /// 50 | /// Player Job Level. 51 | /// 52 | public byte Level; 53 | 54 | /// 55 | /// Player Name. 56 | /// 57 | public string Name = null!; 58 | 59 | /// 60 | /// Player Content ID. 61 | /// 62 | public ulong ContentId; 63 | 64 | /// 65 | /// Is Player Valid. 66 | /// 67 | /// 68 | /// Use Dalamud's IsValidCharacterName() for more robust checks. 69 | /// 70 | /// Indicator if player is valid. 71 | public bool IsValid() => ContentId > 0 && 72 | !string.IsNullOrEmpty(Name) && 73 | HomeWorld != ushort.MaxValue && 74 | HomeWorld != 0 && 75 | ClassJob != 0 && 76 | EntityId >= 0 && 77 | EntityId != uint.MaxValue; 78 | } 79 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/Main/Views/PanelView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Numerics; 3 | using Dalamud.Interface.Utility; 4 | using Dalamud.Bindings.ImGui; 5 | using PlayerTrack.Models; 6 | using PlayerTrack.Windows.Main.Components; 7 | using PlayerTrack.Windows.Main.Presenters; 8 | using PlayerTrack.Windows.Views; 9 | 10 | namespace PlayerTrack.Windows.Main.Views; 11 | 12 | public class PanelView : PlayerTrackView 13 | { 14 | private readonly IMainPresenter Presenter; 15 | private readonly PanelComponent PanelComponent; 16 | 17 | public PanelView(string name, PluginConfig config, PlayerComponent playerComponent, AddPlayerComponent addPlayerComponent, IMainPresenter presenter, ImGuiWindowFlags flags = ImGuiWindowFlags.None) : base(name, config, flags) 18 | { 19 | Size = new Vector2(730f, 380f); 20 | Presenter = presenter; 21 | PanelComponent = new PanelComponent(playerComponent, addPlayerComponent); 22 | } 23 | 24 | public override bool DrawConditions() 25 | { 26 | if (!base.DrawConditions()) 27 | return false; 28 | 29 | if (Config.PanelType == PanelType.None) 30 | return false; 31 | 32 | return true; 33 | } 34 | 35 | public override void Draw() 36 | { 37 | Size = ImGui.GetWindowSize() / ImGuiHelpers.GlobalScale; 38 | PanelComponent.Draw(); 39 | } 40 | 41 | public override void OnClose() 42 | { 43 | Presenter.ClosePlayer(); 44 | Config.PanelType = PanelType.None; 45 | } 46 | 47 | public override void Initialize() 48 | { 49 | SetWindowFlags(); 50 | ValidateWindowConfig(); 51 | } 52 | 53 | public void ResetWindow() 54 | { 55 | Config.PanelType = PanelType.None; 56 | SetWindowFlags(); 57 | } 58 | 59 | private void ValidateWindowConfig() 60 | { 61 | if (!Enum.IsDefined(Config.PanelType)) 62 | Config.PanelType = PanelType.None; 63 | 64 | if (Size.HasValue) 65 | { 66 | if (Size.Value.X < 0f || Size.Value.Y < 0f) 67 | Size = new Vector2(730f, 380f); 68 | } 69 | else 70 | { 71 | Size = new Vector2(730f, 380f); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Handler/PlayerLocationManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PlayerTrack.Data; 3 | 4 | namespace PlayerTrack.Handler; 5 | 6 | /// 7 | /// Manages player locations based on territory changes and game data. 8 | /// Provides events for location start and end points. 9 | /// 10 | public class PlayerLocationManager 11 | { 12 | private ushort CurrentTerritoryType; 13 | 14 | public delegate void LocationDelegate(LocationData toadLocation); 15 | 16 | public event LocationDelegate? OnLocationStarted; 17 | public event LocationDelegate? OnLocationEnded; 18 | 19 | /// 20 | /// Starts the location manager and begins processing territory changes. 21 | /// 22 | public void Start() 23 | { 24 | Plugin.ClientStateHandler.TerritoryChanged += ProcessTerritoryChange; 25 | Plugin.ClientStateHandler.Logout += OnLogout; 26 | if (Plugin.ClientStateHandler.IsLoggedIn) 27 | ProcessTerritoryChange(Plugin.ClientStateHandler.TerritoryType); 28 | } 29 | 30 | /// 31 | /// Retrieves the current location based on the territory type. 32 | /// 33 | /// The current location as a . 34 | public LocationData? GetCurrentLocation() => 35 | Sheets.Locations.GetValueOrDefault(CurrentTerritoryType); 36 | 37 | /// 38 | /// Disposes the location manager and stops processing territory changes. 39 | /// 40 | public void Dispose() 41 | { 42 | Plugin.ClientStateHandler.TerritoryChanged -= ProcessTerritoryChange; 43 | Plugin.ClientStateHandler.Logout -= OnLogout; 44 | OnLocationStarted = null; 45 | OnLocationEnded = null; 46 | } 47 | 48 | private void ProcessTerritoryChange(ushort newTerritoryType) 49 | { 50 | if (CurrentTerritoryType != 0) 51 | OnLocationEnded?.Invoke(Sheets.Locations[CurrentTerritoryType]); 52 | 53 | if (newTerritoryType != 0) 54 | OnLocationStarted?.Invoke(Sheets.Locations[newTerritoryType]); 55 | 56 | CurrentTerritoryType = newTerritoryType; 57 | } 58 | 59 | private void OnLogout(int type, int code) 60 | { 61 | OnLocationEnded?.Invoke(Sheets.Locations[CurrentTerritoryType]); 62 | ProcessTerritoryChange(0); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/WindowManager.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Interface.Windowing; 2 | using PlayerTrack.Windows.Main; 3 | 4 | namespace PlayerTrack.Windows; 5 | 6 | /// 7 | /// Wrapper for WindowSystem using implementations to simplify ImGui windowing. 8 | /// 9 | public class WindowManager 10 | { 11 | private readonly WindowSystem WindowSystem; 12 | 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | public WindowManager() 17 | { 18 | WindowSystem = new WindowSystem(Plugin.PluginInterface.InternalName); 19 | Plugin.PluginInterface.UiBuilder.Draw += Draw; 20 | } 21 | 22 | private bool IsEnabled { get; set; } 23 | 24 | /// 25 | /// Enable windows. 26 | /// 27 | public void Enable() 28 | { 29 | foreach (var window in WindowSystem.Windows) 30 | { 31 | var windowEx = (WindowEx)window; 32 | windowEx.Initialize(); 33 | } 34 | 35 | IsEnabled = true; 36 | } 37 | 38 | /// 39 | /// Disable windows. 40 | /// 41 | public void Disable() => IsEnabled = false; 42 | 43 | /// 44 | /// Add a window to this . 45 | /// 46 | /// The window(s) to add. 47 | public void AddWindows(params Window[] newWindows) 48 | { 49 | foreach (var window in newWindows) 50 | WindowSystem.AddWindow(window); 51 | } 52 | 53 | /// 54 | /// Remove window to this . 55 | /// 56 | /// The window(s) to remove. 57 | public void RemoveWindows(params Window[] windows) 58 | { 59 | foreach (var window in windows) 60 | WindowSystem.RemoveWindow(window); 61 | } 62 | 63 | /// 64 | /// Clean up windows and events. 65 | /// 66 | public void Dispose() 67 | { 68 | Disable(); 69 | Plugin.PluginInterface.UiBuilder.Draw -= Draw; 70 | WindowSystem.RemoveAllWindows(); 71 | } 72 | 73 | private void Draw() 74 | { 75 | if (!IsEnabled) 76 | return; 77 | 78 | WindowSystem.Draw(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/Main/Components/PlayerActionComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Dalamud.Interface; 3 | using Dalamud.Interface.Colors; 4 | using Dalamud.Interface.Utility; 5 | using Dalamud.Interface.Utility.Raii; 6 | using Dalamud.Bindings.ImGui; 7 | using PlayerTrack.Domain; 8 | using PlayerTrack.Resource; 9 | using PlayerTrack.Windows.Components; 10 | using PlayerTrack.Windows.Main.Presenters; 11 | 12 | namespace PlayerTrack.Windows.Main.Components; 13 | 14 | public class PlayerActionComponent : ViewComponent 15 | { 16 | private readonly IMainPresenter Presenter; 17 | 18 | public PlayerActionComponent(IMainPresenter presenter) 19 | { 20 | Presenter = presenter; 21 | } 22 | 23 | public override void Draw() 24 | { 25 | var player = Presenter.GetSelectedPlayer(); 26 | if (player == null) 27 | return; 28 | 29 | using var child = ImRaii.Child("###PlayerAction", new Vector2(-1, 0), false); 30 | if (!child.Success) 31 | return; 32 | 33 | var buttonSize = ImGuiHelpers.ScaledVector2(120f, 25f); 34 | ImGuiHelpers.ScaledDummy(1f); 35 | using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow)) 36 | { 37 | using (ImRaii.PushFont(UiBuilder.IconFont)) 38 | ImGui.TextUnformatted(FontAwesomeIcon.ExclamationTriangle.ToIconString()); 39 | 40 | ImGui.SameLine(); 41 | ImGui.TextUnformatted(Language.WarningZone); 42 | } 43 | 44 | ImGuiHelpers.ScaledDummy(1f); 45 | 46 | if (ImGui.Button(Language.Reset, buttonSize)) 47 | { 48 | PlayerConfigService.ResetPlayerConfig(player.Id); 49 | PlayerCategoryService.UnassignCategoriesFromPlayer(player.Id); 50 | PlayerTagService.UnassignTagsFromPlayer(player.Id); 51 | Presenter.ClosePlayer(); 52 | Presenter.HidePanel(); 53 | } 54 | 55 | if (ImGui.Button(Language.Delete, buttonSize)) 56 | { 57 | ServiceContext.PlayerDataService.DeletePlayer(player.Id); 58 | Presenter.ClosePlayer(); 59 | Presenter.HidePanel(); 60 | } 61 | 62 | if (ImGui.Button(Language.DeleteHistory, buttonSize)) 63 | { 64 | ServiceContext.PlayerDataService.DeleteHistory(player.Id); 65 | Presenter.ClosePlayer(); 66 | Presenter.HidePanel(); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/Main/Components/PlayerHistoryComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Dalamud.Interface.Colors; 3 | using Dalamud.Interface.Utility; 4 | using Dalamud.Interface.Utility.Raii; 5 | using Dalamud.Bindings.ImGui; 6 | using PlayerTrack.Resource; 7 | using PlayerTrack.Windows.Components; 8 | using PlayerTrack.Windows.Main.Presenters; 9 | 10 | namespace PlayerTrack.Windows.Main.Components; 11 | 12 | public class PlayerHistoryComponent(IMainPresenter presenter) : ViewComponent 13 | { 14 | private const float SameLineOffset1 = 120f; 15 | 16 | public override void Draw() 17 | { 18 | var player = presenter.GetSelectedPlayer(); 19 | if (player == null) 20 | return; 21 | 22 | using var child = ImRaii.Child("###PlayerSummary_History", new Vector2(-1, 0), false); 23 | if (!child.Success) 24 | return; 25 | 26 | // Player Name History 27 | Helper.TextColored(ImGuiColors.DalamudViolet, Language.Time); 28 | ImGuiHelpers.ScaledRelativeSameLine(SameLineOffset1); 29 | Helper.TextColored(ImGuiColors.DalamudViolet, Language.NameWorld); 30 | if (player.PlayerNameWorldHistories.Count == 0) 31 | { 32 | ImGui.TextUnformatted(Language.NoHistoryMessage); 33 | } 34 | else 35 | { 36 | foreach (var ph in player.PlayerNameWorldHistories) 37 | { 38 | ImGui.TextUnformatted(ph.Time); 39 | ImGuiHelpers.ScaledRelativeSameLine(SameLineOffset1); 40 | ImGui.TextUnformatted(ph.NameWorld); 41 | } 42 | } 43 | 44 | // Player Appearance History 45 | ImGuiHelpers.ScaledDummy(new Vector2(0, 10)); 46 | Helper.TextColored(ImGuiColors.DalamudViolet, Language.Time); 47 | ImGuiHelpers.ScaledRelativeSameLine(SameLineOffset1); 48 | Helper.TextColored(ImGuiColors.DalamudViolet, Language.Appearance); 49 | if (player.PlayerCustomizeHistories.Count == 0) 50 | { 51 | ImGui.TextUnformatted(Language.NoHistoryMessage); 52 | } 53 | else 54 | { 55 | foreach (var ph in player.PlayerCustomizeHistories) 56 | { 57 | ImGui.TextUnformatted(ph.Time); 58 | ImGuiHelpers.ScaledRelativeSameLine(SameLineOffset1); 59 | ImGui.TextUnformatted(ph.Appearance); 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/Lodestone/LodestoneLookup.cs: -------------------------------------------------------------------------------- 1 | namespace PlayerTrack.Models; 2 | 3 | public class LodestoneLookup 4 | { 5 | public int Id { get; set; } 6 | 7 | public long Created { get; set; } 8 | 9 | public long Updated { get; set; } 10 | 11 | public int PlayerId { get; set; } 12 | 13 | public string PlayerName { get; init; } = string.Empty; 14 | 15 | public uint WorldId { get; init; } 16 | 17 | public string UpdatedPlayerName { get; set; } = string.Empty; 18 | 19 | public uint UpdatedWorldId { get; set; } 20 | 21 | public uint LodestoneId { get; private set; } 22 | 23 | public int FailureCount { get; set; } 24 | 25 | public bool IsDone { get; private set; } 26 | 27 | public int? PrerequisiteLookupId { get; set; } 28 | 29 | public LodestoneStatus LodestoneStatus { get; private set; } = LodestoneStatus.Unverified; 30 | 31 | public LodestoneLookupType LodestoneLookupType { get; init; } = LodestoneLookupType.Batch; 32 | 33 | public void SetLodestoneStatus(LodestoneStatus lodestoneStatus) 34 | { 35 | LodestoneStatus = lodestoneStatus; 36 | IsDone = LodestoneStatus is 37 | LodestoneStatus.Verified or 38 | LodestoneStatus.Banned or 39 | LodestoneStatus.NotApplicable or 40 | LodestoneStatus.Cancelled or 41 | LodestoneStatus.Unavailable; 42 | } 43 | 44 | public void SetLodestoneId(uint lodestoneId) 45 | { 46 | if (lodestoneId == 0 || LodestoneId != 0) return; 47 | LodestoneId = lodestoneId; 48 | } 49 | 50 | public void UpdateLookupAsFailed(bool allowRetry) 51 | { 52 | FailureCount++; 53 | 54 | if (FailureCount < 4 && allowRetry) 55 | SetLodestoneStatus(LodestoneStatus.Failed); 56 | else 57 | SetLodestoneStatus(LodestoneStatus.Banned); 58 | } 59 | 60 | public void UpdateLookupAsUnavailable() 61 | { 62 | SetLodestoneStatus(LodestoneStatus.Unavailable); 63 | } 64 | 65 | public void UpdateLookupAsInvalid() 66 | { 67 | SetLodestoneStatus(LodestoneStatus.Invalid); 68 | } 69 | 70 | public void UpdateLookupAsSuccess(LodestoneResponse response, LodestoneStatus status) 71 | { 72 | UpdatedPlayerName = response.PlayerName; 73 | UpdatedWorldId = response.WorldId; 74 | SetLodestoneId(response.LodestoneId); 75 | SetLodestoneStatus(status); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/Mappings/ConfigMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using Newtonsoft.Json; 6 | using PlayerTrack.Models; 7 | 8 | namespace PlayerTrack.Infrastructure; 9 | 10 | public static class ConfigMapper 11 | { 12 | public static PluginConfig ToModel(ConfigEntryDTO[] configEntryDTOs) 13 | { 14 | Plugin.PluginLog.Verbose("Entering ConfigMapper.ToModel()"); 15 | var config = new PluginConfig(); 16 | foreach (var property in typeof(PluginConfig).GetProperties(BindingFlags.Public | BindingFlags.Instance)) 17 | { 18 | var configEntryDTO = configEntryDTOs.FirstOrDefault(dto => dto.key == property.Name); 19 | if (configEntryDTO == null) continue; 20 | object typedValue; 21 | if (property.PropertyType.IsPrimitive || property.PropertyType == typeof(string)) 22 | typedValue = Convert.ChangeType(configEntryDTO.value, property.PropertyType); 23 | else 24 | typedValue = JsonConvert.DeserializeObject(configEntryDTO.value, property.PropertyType) ?? string.Empty; 25 | 26 | property.SetValue(config, typedValue); 27 | } 28 | 29 | return config; 30 | } 31 | 32 | public static IEnumerable ToDTOs(PluginConfig pluginConfig) 33 | { 34 | Plugin.PluginLog.Verbose("Entering ConfigMapper.ToDTOs()"); 35 | 36 | var configEntryDTOs = new List(); 37 | foreach (var property in typeof(PluginConfig).GetProperties(BindingFlags.Public | BindingFlags.Instance)) 38 | { 39 | var ignoreAttribute = property.GetCustomAttribute(); 40 | if (ignoreAttribute != null) 41 | continue; 42 | 43 | var key = property.Name; 44 | var propertyValue = property.GetValue(pluginConfig); 45 | 46 | string value; 47 | if (property.PropertyType.IsPrimitive || property.PropertyType == typeof(string)) 48 | value = Convert.ToString(propertyValue) ?? string.Empty; 49 | else 50 | value = JsonConvert.SerializeObject(propertyValue); 51 | 52 | if (!string.IsNullOrEmpty(value)) 53 | configEntryDTOs.Add(new ConfigEntryDTO { key = key, value = value, }); 54 | } 55 | 56 | return configEntryDTOs.ToArray(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/Repositories/LocalPlayerRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Data; 3 | using System.Linq; 4 | using AutoMapper; 5 | using Dapper; 6 | using FluentDapperLite.Repository; 7 | using PlayerTrack.Models; 8 | 9 | namespace PlayerTrack.Infrastructure; 10 | 11 | public class LocalPlayerRepository : BaseRepository 12 | { 13 | public LocalPlayerRepository(IDbConnection connection, IMapper mapper) : base(connection, mapper) { } 14 | 15 | public List GetAllLocalPlayers() 16 | { 17 | const string sql = "SELECT * FROM local_players"; 18 | var localPlayerDTOs = Connection.Query(sql).ToList(); 19 | return localPlayerDTOs.Select(dto => Mapper.Map(dto)).ToList(); 20 | } 21 | 22 | public LocalPlayer? GetLocalPlayer(ulong contentId) 23 | { 24 | const string sql = "SELECT * FROM local_players WHERE content_id = @contentId"; 25 | var localPlayerDTO = Connection.QuerySingleOrDefault(sql, new { contentId }); 26 | return localPlayerDTO == null ? null : Mapper.Map(localPlayerDTO); 27 | } 28 | 29 | public int CreateLocalPlayer(LocalPlayer localPlayer) 30 | { 31 | var localPlayerDTO = Mapper.Map(localPlayer); 32 | SetCreateTimestamp(localPlayerDTO); 33 | const string sql = @" 34 | INSERT INTO local_players (content_id, name, world_id, customize, key, created, updated) 35 | VALUES (@content_id, @name, @world_id, @customize, @key, @created, @updated) 36 | RETURNING id;"; 37 | return Connection.ExecuteScalar(sql, localPlayerDTO); 38 | } 39 | 40 | public void UpdateLocalPlayer(LocalPlayer localPlayer) 41 | { 42 | var localPlayerDTO = Mapper.Map(localPlayer); 43 | SetUpdateTimestamp(localPlayerDTO); 44 | const string sql = @" 45 | UPDATE local_players 46 | SET name = @name, 47 | world_id = @world_id, 48 | customize = @customize, 49 | key = @key, 50 | updated = @updated 51 | WHERE id = @id;"; 52 | Connection.Execute(sql, localPlayerDTO); 53 | } 54 | 55 | public void DeleteLocalPlayer(int id) 56 | { 57 | const string sql = "DELETE FROM local_players WHERE id = @id"; 58 | Connection.Execute(sql, new { id }); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/Migrations/M004_PlayerIDs.cs: -------------------------------------------------------------------------------- 1 | using FluentDapperLite.Extension; 2 | using FluentMigrator; 3 | 4 | namespace PlayerTrack.Repositories.Migrations; 5 | 6 | [Migration(20240709120000)] 7 | public class M004_PlayerIDs: FluentMigrator.Migration 8 | { 9 | 10 | public override void Up() 11 | { 12 | AddNewFields(); 13 | DeleteObjectId(); 14 | FixContentId(); 15 | FixPlayerEncounterEnded(); 16 | SetNameHistorySource(); 17 | RemovePlayerKeyUniqueConstraint(); 18 | } 19 | 20 | private void AddNewFields() 21 | { 22 | Alter.Table("players") 23 | .AddColumn("entity_id").AsUInt32("entity_id").NotNullable().WithDefaultValue(0); 24 | Alter.Table("player_name_world_histories") 25 | .AddColumn("source").AsInt32().NotNullable().WithDefaultValue(0); 26 | } 27 | 28 | private void DeleteObjectId() 29 | { 30 | Execute.Sql(@" 31 | UPDATE players 32 | SET entity_id = object_id 33 | WHERE object_id != 0"); 34 | Execute.Sql("ALTER TABLE players DROP COLUMN object_id"); 35 | } 36 | 37 | private void FixContentId() 38 | { 39 | Execute.Sql("DROP INDEX IF EXISTS idx_players_content_id"); 40 | Execute.Sql(@" 41 | UPDATE players 42 | SET content_id = 0 43 | WHERE content_id IS NULL OR content_id != 0 44 | "); 45 | Execute.Sql("CREATE INDEX idx_players_content_id ON players (content_id ASC)"); 46 | } 47 | 48 | private void FixPlayerEncounterEnded() 49 | { 50 | Execute.Sql(@" 51 | UPDATE player_encounters 52 | SET ended = ( 53 | SELECT encounters.ended 54 | FROM encounters 55 | WHERE player_encounters.encounter_id = encounters.id 56 | ) 57 | WHERE player_encounters.ended = 0; 58 | "); 59 | } 60 | 61 | private void SetNameHistorySource() 62 | { 63 | Execute.Sql(@" 64 | UPDATE player_name_world_histories 65 | SET source = 1 66 | WHERE source = 0 67 | "); 68 | } 69 | 70 | private void RemovePlayerKeyUniqueConstraint() 71 | { 72 | Execute.Sql("DROP INDEX IF EXISTS idx_players_key"); 73 | Execute.Sql("CREATE INDEX idx_players_key ON players (key ASC)"); 74 | } 75 | 76 | public override void Down() 77 | { 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Extensions/MenuOpenedArgsExtension.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Game.Gui.ContextMenu; 2 | 3 | namespace PlayerTrack.Extensions; 4 | 5 | /// 6 | /// Provides extension methods for . 7 | /// 8 | public static class MenuOpenedArgsExtensions 9 | { 10 | /// 11 | /// Determines whether the context menu is a valid player menu. 12 | /// 13 | /// The menu opened arguments. 14 | /// if set to , include the local player in the check. 15 | /// if the context menu is a valid player menu; otherwise, . 16 | public static bool IsValidPlayerMenu(this IMenuOpenedArgs menuOpenedArgs, bool includeSelf = false) 17 | { 18 | if (!Plugin.PluginInterface.UiBuilder.ShouldModifyUi || menuOpenedArgs.Target is not MenuTargetDefault menuTargetDefault) 19 | return false; 20 | 21 | switch (menuOpenedArgs.AddonName) 22 | { 23 | case null: 24 | case "LookingForGroup": 25 | case "PartyMemberList": 26 | case "FriendList": 27 | case "FreeCompany": 28 | case "SocialList": 29 | case "ContactList": 30 | case "ChatLog": 31 | case "_PartyList": 32 | case "LinkShell": 33 | case "CrossWorldLinkshell": 34 | case "ContentMemberList": 35 | case "BeginnerChatList": 36 | if (menuTargetDefault.TargetName != string.Empty && Sheets.IsValidWorld(menuTargetDefault.TargetHomeWorld.RowId)) 37 | { 38 | if (!includeSelf) 39 | { 40 | var name = Plugin.ClientStateHandler.LocalPlayer?.Name.TextValue; 41 | var worldId = Plugin.ClientStateHandler.LocalPlayer?.HomeWorld.RowId; 42 | if (menuTargetDefault.TargetName == name && menuTargetDefault.TargetHomeWorld.RowId == worldId) 43 | { 44 | Plugin.PluginLog.Verbose("ContextMenu: Self context menu."); 45 | return false; 46 | } 47 | } 48 | 49 | return true; 50 | } 51 | 52 | return false; 53 | } 54 | 55 | return false; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/Migrations/M003_LodestoneAPI.cs: -------------------------------------------------------------------------------- 1 | using FluentDapperLite.Extension; 2 | using FluentMigrator; 3 | 4 | namespace PlayerTrack.Repositories.Migrations; 5 | 6 | [Migration(20240604015600)] 7 | public class M003_LodestoneAPI : FluentMigrator.Migration 8 | { 9 | public override void Up() 10 | { 11 | Alter.Table("lodestone_lookups") 12 | .AddColumn("world_id").AsUInt32("world_id").NotNullable().WithDefaultValue(0) 13 | .AddColumn("updated_player_name").AsString().NotNullable().WithDefaultValue(string.Empty) 14 | .AddColumn("updated_world_id").AsUInt32("updated_world_id").NotNullable().WithDefaultValue(0) 15 | .AddColumn("lookup_type").AsInt32().NotNullable().WithDefaultValue(0) 16 | .AddColumn("is_done").AsBoolean().NotNullable().WithDefaultValue(false) 17 | .AddColumn("prerequisite_lookup_id").AsInt32().Nullable(); 18 | Create.Index("idx_lodestone_lookups_lookup_type").OnTable("lodestone_lookups").OnColumn("lookup_type").Ascending(); 19 | Create.ForeignKey("fk_lodestone_lookups_prerequisite_lookup_id") 20 | .FromTable("lodestone_lookups").ForeignColumn("prerequisite_lookup_id") 21 | .ToTable("lodestone_lookups").PrimaryColumn("id"); 22 | Execute.Sql(@" 23 | UPDATE lodestone_lookups 24 | SET is_done = 'true' 25 | WHERE lookup_status IN (1, 3) 26 | "); 27 | foreach (var world in Sheets.Worlds.Values) 28 | { 29 | Execute.Sql($@" 30 | UPDATE lodestone_lookups 31 | SET world_id = {world.Id} 32 | WHERE world_name = '{world.Name.Replace("'", "''")}' 33 | "); 34 | } 35 | Execute.Sql(@" 36 | UPDATE lodestone_lookups 37 | SET lookup_status = 6, is_done = 'true' 38 | WHERE world_id = 0 39 | "); 40 | } 41 | 42 | public override void Down() 43 | { 44 | Delete.ForeignKey("fk_lodestone_lookups_prerequisite_lookup_id").OnTable("lodestone_lookups"); 45 | Delete.Index("idx_lodestone_lookups_lookup_type").OnTable("lodestone_lookups"); 46 | Delete.Column("prerequisite_lookup_id").FromTable("lodestone_lookups"); 47 | Delete.Column("is_done").FromTable("lodestone_lookups"); 48 | Delete.Column("world_id").FromTable("lodestone_lookups"); 49 | Delete.Column("lookup_type").FromTable("lodestone_lookups"); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/Repositories/SocialListMemberRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Data; 3 | using System.Linq; 4 | using AutoMapper; 5 | using Dapper; 6 | using FluentDapperLite.Repository; 7 | using PlayerTrack.Models; 8 | 9 | namespace PlayerTrack.Infrastructure; 10 | 11 | public class SocialListMemberRepository : BaseRepository 12 | { 13 | public SocialListMemberRepository(IDbConnection connection, IMapper mapper) : base(connection, mapper) { } 14 | 15 | public List GetSocialListMembers(int socialListId) 16 | { 17 | const string sql = "SELECT * FROM social_list_members WHERE social_list_id = @social_list_id"; 18 | var socialListMemberDTOs = Connection.Query(sql, new { social_list_id = socialListId }).ToList(); 19 | return socialListMemberDTOs.Select(dto => Mapper.Map(dto)).ToList(); 20 | } 21 | 22 | public List GetSocialListMembers(int socialListId, int pageNumber) 23 | { 24 | const string sql = "SELECT * FROM social_list_members WHERE social_list_id = @social_list_id AND page_number = @page_number"; 25 | var socialListMemberDTOs = Connection.Query(sql, new { social_list_id = socialListId, page_number = pageNumber }).ToList(); 26 | return socialListMemberDTOs.Select(dto => Mapper.Map(dto)).ToList(); 27 | } 28 | 29 | public int CreateSocialListMember(SocialListMember member) 30 | { 31 | var socialListMemberDTO = Mapper.Map(member); 32 | SetCreateTimestamp(socialListMemberDTO); 33 | const string sql = @" 34 | INSERT INTO social_list_members (content_id, key, name, world_id, page_number, social_list_id, created, updated) 35 | VALUES (@content_id, @key, @name, @world_id, @page_number, @social_list_id, @created, @updated) 36 | RETURNING id;"; 37 | return Connection.ExecuteScalar(sql, socialListMemberDTO); 38 | } 39 | 40 | public void DeleteSocialListMember(int id) 41 | { 42 | const string sql = "DELETE FROM social_list_members WHERE id = @id"; 43 | Connection.Execute(sql, new { id }); 44 | } 45 | 46 | public void DeleteSocialListMembers(int socialListId) 47 | { 48 | const string sql = "DELETE FROM social_list_members WHERE social_list_id = @id"; 49 | Connection.Execute(sql, new { id = socialListId }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/Helpers/FormatHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using PlayerTrack.Resource; 4 | 5 | namespace PlayerTrack.Windows.Helpers; 6 | 7 | public static class FormatHelper 8 | { 9 | public static string FormatFileSize(this long value) 10 | { 11 | string[] sizes = 12 | [ 13 | Language.Byte, Language.Kilobyte, 14 | Language.Megabyte, Language.Gigabyte, 15 | Language.Terabyte 16 | ]; 17 | 18 | var order = 0; 19 | while (value >= 1024 && order < sizes.Length - 1) 20 | { 21 | order++; 22 | value /= 1024; 23 | } 24 | 25 | return $"{value:0.##} {sizes[order]}"; 26 | } 27 | 28 | public static string ToDuration(this long value) 29 | { 30 | var parts = new List(); 31 | void Add(int val, string unit) 32 | { 33 | if (val > 0) 34 | parts.Add($"{val}{unit}"); 35 | } 36 | 37 | var t = TimeSpan.FromMilliseconds(value); 38 | Add(t.Days, Language.Day); 39 | Add(t.Hours, Language.Hour); 40 | Add(t.Minutes, Language.Minute); 41 | var timeSpan = string.Join(" ", parts); 42 | return string.IsNullOrEmpty(timeSpan) ? $"< 1 {Language.Minute}" : timeSpan; 43 | } 44 | 45 | public static string ToTimeSpan(this long value) 46 | { 47 | var currentTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); 48 | string timeSpan; 49 | if (currentTime > value) 50 | { 51 | timeSpan = ConvertToShortTimeSpan(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - value); 52 | return string.IsNullOrEmpty(timeSpan) ? Language.Now : $"{timeSpan} {Language.Ago}"; 53 | } 54 | 55 | timeSpan = ConvertToShortTimeSpan(value - DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()); 56 | return string.IsNullOrEmpty(timeSpan) ? Language.Now : $"{timeSpan} {Language.FromNow}"; 57 | } 58 | 59 | private static string ConvertToShortTimeSpan(long value) 60 | { 61 | var timeSpan = TimeSpan.FromMilliseconds(value); 62 | if (timeSpan.Days > 0) 63 | return $"{timeSpan.Days}{Language.Day}"; 64 | 65 | if (timeSpan.Hours > 0) 66 | return $"{timeSpan.Hours}{Language.Hour}"; 67 | 68 | if (timeSpan.Minutes > 0) 69 | return $"{timeSpan.Minutes}{Language.Minute}"; 70 | 71 | return string.Empty; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Handler/ContextMenuHandler.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Game.Gui.ContextMenu; 2 | using PlayerTrack.Data; 3 | using PlayerTrack.Domain; 4 | using PlayerTrack.Extensions; 5 | using PlayerTrack.Resource; 6 | 7 | namespace PlayerTrack.Handler; 8 | 9 | public static class ContextMenuHandler 10 | { 11 | private const char PrefixChar = 'P'; 12 | public delegate void SelectPlayerDelegate(PlayerData player, bool isCurrent); 13 | public static event SelectPlayerDelegate? OnSelectPlayer; 14 | 15 | public static void Start() 16 | { 17 | Plugin.ContextMenu.OnMenuOpened += OnMenuOpen; 18 | } 19 | 20 | public static void Restart() 21 | { 22 | Dispose(); 23 | Start(); 24 | } 25 | 26 | public static void Dispose() 27 | { 28 | Plugin.ContextMenu.OnMenuOpened -= OnMenuOpen; 29 | } 30 | 31 | private static void OnMenuOpen(IMenuOpenedArgs menuOpenedArgs) 32 | { 33 | if (!menuOpenedArgs.IsValidPlayerMenu()) 34 | return; 35 | 36 | if (ServiceContext.ConfigService.GetConfig().ShowOpenInPlayerTrack) 37 | { 38 | menuOpenedArgs.AddMenuItem(new MenuItem 39 | { 40 | PrefixChar = PrefixChar, 41 | Name = Language.OpenPlayerTrack, 42 | OnClicked = OpenPlayerTrack 43 | }); 44 | } 45 | if (ServiceContext.ConfigService.GetConfig().ShowOpenLodestone) 46 | { 47 | menuOpenedArgs.AddMenuItem(new MenuItem 48 | { 49 | PrefixChar = PrefixChar, 50 | Name = Language.OpenLodestone, 51 | OnClicked = OpenLodestone 52 | }); 53 | } 54 | } 55 | 56 | private static void OpenPlayerTrack(IMenuItemClickedArgs menuItemClickedArgs) 57 | { 58 | var selectedPlayer = menuItemClickedArgs.GetPlayer(); 59 | if (selectedPlayer == null) 60 | return; 61 | 62 | Plugin.GameFramework.RunOnFrameworkThread(() => 63 | { 64 | var currentPlayer = Plugin.ObjectCollection.GetPlayerByContentId(selectedPlayer.ContentId); 65 | if (currentPlayer != null) 66 | OnSelectPlayer?.Invoke(currentPlayer, true); 67 | else 68 | OnSelectPlayer?.Invoke(selectedPlayer, false); 69 | }); 70 | } 71 | 72 | private static void OpenLodestone(IMenuItemClickedArgs menuItemClickedArgs) 73 | { 74 | var selectedPlayer = menuItemClickedArgs.GetPlayer(); 75 | if (selectedPlayer == null) 76 | return; 77 | 78 | ServiceContext.LodestoneService.OpenLodestoneProfile(selectedPlayer.Name, selectedPlayer.HomeWorld); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/Main/Components/PlayerEncounterComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Dalamud.Interface.Colors; 3 | using Dalamud.Interface.Utility; 4 | using Dalamud.Interface.Utility.Raii; 5 | using Dalamud.Bindings.ImGui; 6 | using PlayerTrack.Resource; 7 | using PlayerTrack.Windows.Components; 8 | using PlayerTrack.Windows.Main.Presenters; 9 | 10 | namespace PlayerTrack.Windows.Main.Components; 11 | 12 | public class PlayerEncounterComponent : ViewComponent 13 | { 14 | private const float SameLineOffset1 = 70f; 15 | private const float SameLineOffset2 = 160f; 16 | private const float SameLineOffset3 = 200f; 17 | private const float SameLineOffset4 = 230f; 18 | private readonly IMainPresenter Presenter; 19 | 20 | public PlayerEncounterComponent(IMainPresenter presenter) 21 | { 22 | Presenter = presenter; 23 | } 24 | 25 | public override void Draw() 26 | { 27 | var player = Presenter.GetSelectedPlayer(); 28 | if (player == null) 29 | return; 30 | 31 | using var child = ImRaii.Child("###PlayerSummary_Encounter", new Vector2(-1, 0), false); 32 | if (!child.Success) 33 | return; 34 | 35 | if (player.Encounters.Count == 0) 36 | { 37 | ImGui.TextUnformatted(Language.NoEncountersMessage); 38 | } 39 | else 40 | { 41 | Helper.TextColored(ImGuiColors.DalamudViolet, Language.Time); 42 | ImGuiHelpers.ScaledRelativeSameLine(SameLineOffset1); 43 | Helper.TextColored(ImGuiColors.DalamudViolet, Language.Duration); 44 | ImGuiHelpers.ScaledRelativeSameLine(SameLineOffset2); 45 | Helper.TextColored(ImGuiColors.DalamudViolet, Language.Job); 46 | ImGuiHelpers.ScaledRelativeSameLine(SameLineOffset3); 47 | Helper.TextColored(ImGuiColors.DalamudViolet, Language.Level); 48 | ImGuiHelpers.ScaledRelativeSameLine(SameLineOffset4); 49 | Helper.TextColored(ImGuiColors.DalamudViolet, Language.Location); 50 | foreach (var enc in player.Encounters) 51 | { 52 | ImGui.TextUnformatted(enc.Time); 53 | ImGuiHelpers.ScaledRelativeSameLine(SameLineOffset1); 54 | ImGui.TextUnformatted(enc.Duration); 55 | ImGuiHelpers.ScaledRelativeSameLine(SameLineOffset2); 56 | ImGui.TextUnformatted(enc.Job); 57 | ImGuiHelpers.ScaledRelativeSameLine(SameLineOffset3); 58 | ImGui.TextUnformatted(enc.Level); 59 | ImGuiHelpers.ScaledRelativeSameLine(SameLineOffset4); 60 | ImGui.TextUnformatted(enc.Location); 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Extensions/PluginInterfaceExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using Dalamud.Plugin; 5 | 6 | namespace PlayerTrack.Extensions; 7 | 8 | /// 9 | /// Dalamud PluginInterface extensions. 10 | /// 11 | public static class PluginInterfaceExtension 12 | { 13 | /// 14 | /// Get the plugin backup directory for windows (don't use for Wine). 15 | /// 16 | /// dalamud plugin interface. 17 | /// Plugin backup directory. 18 | public static string WindowsPluginBackupDirectory(this IDalamudPluginInterface value) 19 | { 20 | var appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); 21 | var backupsDir = Path.Combine(appDataDir, "XIVLauncher", $"{value.InternalName.FirstCharToLower()}Backups"); 22 | Directory.CreateDirectory(backupsDir); 23 | return backupsDir; 24 | } 25 | 26 | /// 27 | /// Get the plugin backup directory. 28 | /// 29 | /// dalamud plugin interface. 30 | /// Plugin backup directory. 31 | public static string PluginBackupDirectory(this IDalamudPluginInterface value) 32 | { 33 | var configDir = value.ConfigDirectory.Parent; 34 | var appDir = configDir?.Parent; 35 | if (appDir == null) 36 | return WindowsPluginBackupDirectory(value); // use as a fallback 37 | 38 | var backupsDir = Path.Combine(appDir.FullName, $"{value.InternalName.FirstCharToLower()}Backups"); 39 | Directory.CreateDirectory(backupsDir); 40 | return backupsDir; 41 | } 42 | 43 | /// 44 | /// Check if different version of plugin is loaded. 45 | /// 46 | /// dalamud plugin interface. 47 | /// version to check. 48 | /// Indicator if another version of the plugin is loaded. 49 | public static bool IsDifferentVersionLoaded(this IDalamudPluginInterface value, string version = "Canary") 50 | { 51 | var internalName = value.InternalName; 52 | if (!internalName.EndsWith(version, StringComparison.CurrentCulture)) 53 | return IsPluginLoaded(value, $"{internalName}{version}"); 54 | 55 | var stableName = internalName.Replace(version, string.Empty); 56 | return IsPluginLoaded(value, stableName); 57 | } 58 | 59 | private static bool IsPluginLoaded(IDalamudPluginInterface pluginInterface, string pluginName) 60 | { 61 | var plugin = pluginInterface.InstalledPlugins.FirstOrDefault(p => p.Name == pluginName); 62 | return plugin is { IsLoaded: true }; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/Mappings/SocialListMappingProfile.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using AutoMapper; 3 | using Newtonsoft.Json; 4 | using PlayerTrack.Models; 5 | 6 | namespace PlayerTrack.Infrastructure; 7 | 8 | public class SocialListMappingProfile : Profile 9 | { 10 | public SocialListMappingProfile() 11 | { 12 | CreateMap() 13 | .ForMember(dest => dest.id, opt => opt.MapFrom(src => src.Id)) 14 | .ForMember(dest => dest.created, opt => opt.MapFrom(src => src.Created)) 15 | .ForMember(dest => dest.updated, opt => opt.MapFrom(src => src.Updated)) 16 | .ForMember(dest => dest.content_id, opt => opt.MapFrom(src => src.ContentId)) 17 | .ForMember(dest => dest.list_type, opt => opt.MapFrom(src => src.ListType)) 18 | .ForMember(dest => dest.list_number, opt => opt.MapFrom(src => src.ListNumber)) 19 | .ForMember(dest => dest.data_center_id, opt => opt.MapFrom(src => src.DataCenterId)) 20 | .ForMember(dest => dest.page_count, opt => opt.MapFrom(src => src.PageCount)) 21 | .ForMember(dest => dest.add_players, opt => opt.MapFrom(src => src.AddPlayers)) 22 | .ForMember(dest => dest.sync_with_category, opt => opt.MapFrom(src => src.SyncWithCategory)) 23 | .ForMember(dest => dest.default_category_id, opt => opt.MapFrom(src => src.DefaultCategoryId)) 24 | .ForMember(dest => dest.page_last_updated, opt => opt.MapFrom(src => JsonConvert.SerializeObject(src.PageLastUpdated))); 25 | 26 | CreateMap() 27 | .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.id)) 28 | .ForMember(dest => dest.Created, opt => opt.MapFrom(src => src.created)) 29 | .ForMember(dest => dest.Updated, opt => opt.MapFrom(src => src.updated)) 30 | .ForMember(dest => dest.ContentId, opt => opt.MapFrom(src => src.content_id)) 31 | .ForMember(dest => dest.ListType, opt => opt.MapFrom(src => src.list_type)) 32 | .ForMember(dest => dest.ListNumber, opt => opt.MapFrom(src => src.list_number)) 33 | .ForMember(dest => dest.DataCenterId, opt => opt.MapFrom(src => src.data_center_id)) 34 | .ForMember(dest => dest.PageCount, opt => opt.MapFrom(src => src.page_count)) 35 | .ForMember(dest => dest.AddPlayers, opt => opt.MapFrom(src => src.add_players)) 36 | .ForMember(dest => dest.SyncWithCategory, opt => opt.MapFrom(src => src.sync_with_category)) 37 | .ForMember(dest => dest.DefaultCategoryId, opt => opt.MapFrom(src => src.default_category_id)) 38 | .ForMember(dest => dest.PageLastUpdated, opt => opt.MapFrom(src => JsonConvert.DeserializeObject>(src.page_last_updated))); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Domain/Services/ConfigService.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Interface; 2 | using PlayerTrack.Infrastructure; 3 | using PlayerTrack.Models; 4 | 5 | namespace PlayerTrack.Domain; 6 | 7 | public class ConfigService 8 | { 9 | private PluginConfig PluginConfig = null!; 10 | 11 | public ConfigService() 12 | { 13 | ReloadCache(); 14 | } 15 | 16 | public void SaveConfig(IPluginConfig config) 17 | { 18 | Plugin.PluginLog.Verbose("Entering ConfigService.SaveConfig()"); 19 | var updatedPluginConfig = (PluginConfig)config; 20 | 21 | Plugin.PluginLog.Verbose($"Saving config with playerConfig of type: {updatedPluginConfig.PlayerConfig.PlayerConfigType}"); 22 | RepositoryContext.PlayerConfigRepository.UpdatePlayerConfig(updatedPluginConfig.PlayerConfig); 23 | if (RepositoryContext.ConfigRepository.UpdatePluginConfig(updatedPluginConfig)) 24 | PluginConfig = updatedPluginConfig; 25 | } 26 | 27 | public void SyncIcons() 28 | { 29 | var icons = RepositoryContext.PlayerConfigRepository.GetDistinctIcons(); 30 | if (icons.Count == 0) 31 | return; 32 | 33 | var existingIcons = PluginConfig.Icons; 34 | foreach (var icon in icons) 35 | if (!existingIcons.Contains((FontAwesomeIcon)icon.Value)) 36 | existingIcons.Add((FontAwesomeIcon)icon.Value); 37 | 38 | PluginConfig.Icons = existingIcons; 39 | SaveConfig(PluginConfig); 40 | } 41 | 42 | public PluginConfig GetConfig() => 43 | PluginConfig; 44 | 45 | private void ReloadCache() 46 | { 47 | Plugin.PluginLog.Verbose("Entering ConfigService.ReloadCache()"); 48 | var config = RepositoryContext.ConfigRepository.GetPluginConfig(); 49 | if (config == null) 50 | { 51 | Plugin.PluginLog.Verbose("Creating default config."); 52 | PluginConfig = new PluginConfig(); 53 | 54 | SaveConfig(PluginConfig); 55 | PluginConfig.PlayerConfig.Id = RepositoryContext.PlayerConfigRepository.CreatePlayerConfig(PluginConfig.PlayerConfig); 56 | } 57 | else 58 | { 59 | var playerConfig = RepositoryContext.PlayerConfigRepository.GetDefaultPlayerConfig(); 60 | if (playerConfig == null) 61 | { 62 | Plugin.PluginLog.Verbose("Player config not found, creating default."); 63 | config.PlayerConfig.Id = RepositoryContext.PlayerConfigRepository.CreatePlayerConfig(config.PlayerConfig); 64 | } 65 | else 66 | { 67 | Plugin.PluginLog.Verbose($"Player config found with id {playerConfig.Id}."); 68 | config.PlayerConfig = playerConfig; 69 | } 70 | 71 | PluginConfig = config; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/Repositories/ArchiveRecordRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Linq; 5 | using AutoMapper; 6 | 7 | using Dapper; 8 | using FluentDapperLite.Repository; 9 | using PlayerTrack.Models; 10 | 11 | namespace PlayerTrack.Infrastructure; 12 | 13 | public class ArchiveRecordRepository : BaseRepository 14 | { 15 | public ArchiveRecordRepository(IDbConnection connection, IMapper mapper) : base(connection, mapper) { } 16 | 17 | public bool CreateArchiveRecord(ArchiveRecord archiveRecord) 18 | { 19 | Plugin.PluginLog.Verbose("Entering ArchiveRecordRepository.CreateArchiveRecord()"); 20 | using var transaction = Connection.BeginTransaction(); 21 | try 22 | { 23 | var migrationArchiveDTO = Mapper.Map(archiveRecord); 24 | SetCreateTimestamp(migrationArchiveDTO); 25 | 26 | const string sql = 27 | "INSERT INTO archive_records (archive_type, data, created, updated) " + 28 | "VALUES (@archive_type, @data, @created, @updated)"; 29 | Connection.Execute(sql, migrationArchiveDTO, transaction); 30 | 31 | transaction.Commit(); 32 | return true; 33 | } 34 | catch (Exception ex) 35 | { 36 | transaction.Rollback(); 37 | Plugin.PluginLog.Error(ex, "Failed to create new migration archive."); 38 | return false; 39 | } 40 | } 41 | 42 | public bool CreateArchiveRecords(IEnumerable migrationArchiveRecords) 43 | { 44 | Plugin.PluginLog.Verbose("Entering ArchiveRecordRepository.CreateArchiveRecords()"); 45 | using var transaction = Connection.BeginTransaction(); 46 | try 47 | { 48 | const string sql = @" 49 | INSERT INTO archive_records ( 50 | archive_type, 51 | data, 52 | created, 53 | updated) 54 | VALUES ( 55 | @archive_type, 56 | @data, 57 | @created, 58 | @updated)"; 59 | 60 | var migrationArchiveDTOs = migrationArchiveRecords 61 | .Select(Mapper.Map) 62 | .ToList(); 63 | 64 | foreach (var dto in migrationArchiveDTOs) 65 | SetCreateTimestamp(dto); 66 | 67 | Connection.Execute(sql, migrationArchiveDTOs, transaction); 68 | 69 | transaction.Commit(); 70 | return true; 71 | } 72 | catch (Exception ex) 73 | { 74 | transaction.Rollback(); 75 | Plugin.PluginLog.Error(ex, "Failed to create new migration archives."); 76 | return false; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/Config/Components/AboutComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Dalamud.Interface.Colors; 3 | using Dalamud.Interface.Utility; 4 | using Dalamud.Interface.Utility.Raii; 5 | using Dalamud.Bindings.ImGui; 6 | using PlayerTrack.Resource; 7 | 8 | namespace PlayerTrack.Windows.Config.Components; 9 | 10 | public class AboutComponent : ConfigViewComponent 11 | { 12 | private const float SeparatorPadding = 1.0f; 13 | private static float GetSeparatorPaddingHeight => SeparatorPadding * ImGuiHelpers.GlobalScale; 14 | 15 | public override void Draw() => DrawAbout(); 16 | 17 | private static void DrawAbout() 18 | { 19 | var buttonHeight = ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().WindowPadding.Y + GetSeparatorPaddingHeight; 20 | using (var contentChild = ImRaii.Child("AboutContent", new Vector2(0, -buttonHeight))) 21 | { 22 | if (contentChild) 23 | { 24 | ImGuiHelpers.ScaledDummy(5.0f); 25 | 26 | ImGui.TextUnformatted(Language.AboutAuthor); 27 | ImGui.SameLine(); 28 | ImGui.TextColored(ImGuiColors.ParsedGold, Plugin.PluginInterface.Manifest.Author); 29 | 30 | ImGui.TextUnformatted(Language.AboutDiscord); 31 | ImGui.SameLine(); 32 | ImGui.TextColored(ImGuiColors.ParsedGold, "@infi"); 33 | 34 | ImGui.TextUnformatted(Language.AboutVersion); 35 | ImGui.SameLine(); 36 | ImGui.TextColored(ImGuiColors.ParsedOrange, Plugin.PluginInterface.Manifest.AssemblyVersion.ToString()); 37 | } 38 | } 39 | 40 | ImGui.Separator(); 41 | ImGuiHelpers.ScaledDummy(1.0f); 42 | 43 | using var bottomChild = ImRaii.Child("AboutBottomBar", new Vector2(0, 0), false, 0); 44 | if (bottomChild) 45 | { 46 | using (ImRaii.PushColor(ImGuiCol.Button, ImGuiColors.ParsedBlue)) 47 | { 48 | if (ImGui.Button(Language.AboutDiscordThread)) 49 | Dalamud.Utility.Util.OpenLink("https://discord.com/channels/581875019861328007/1019649653454688376"); 50 | } 51 | 52 | ImGui.SameLine(); 53 | 54 | using (ImRaii.PushColor(ImGuiCol.Button, ImGuiColors.DPSRed)) 55 | { 56 | if (ImGui.Button(Language.AboutIssues)) 57 | Dalamud.Utility.Util.OpenLink("https://github.com/Infiziert90/PlayerTrack/issues"); 58 | } 59 | 60 | ImGui.SameLine(); 61 | 62 | using (ImRaii.PushColor(ImGuiCol.Button, new Vector4(0.12549f, 0.74902f, 0.33333f, 0.6f))) 63 | { 64 | if (ImGui.Button(Language.AboutKoFi)) 65 | Dalamud.Utility.Util.OpenLink("https://ko-fi.com/infiii"); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/Config/Components/HelpComponent.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Interface.Colors; 2 | using Dalamud.Interface.Utility; 3 | using Dalamud.Interface.Utility.Raii; 4 | using Dalamud.Bindings.ImGui; 5 | using PlayerTrack.Resource; 6 | 7 | namespace PlayerTrack.Windows.Config.Components; 8 | 9 | public class HelpComponent : ConfigViewComponent 10 | { 11 | public override void Draw() 12 | { 13 | using var tabBar = ImRaii.TabBar("Help_TabBar", ImGuiTabBarFlags.None); 14 | if (!tabBar.Success) 15 | return; 16 | 17 | using var tabItem = ImRaii.TabItem(Language.Search); 18 | if (!tabItem.Success) 19 | return; 20 | 21 | ImGuiHelpers.ScaledDummy(1f); 22 | 23 | // General Syntax Section 24 | DrawSection(Language.HowToSearchTitle, Language.HowToSearchExplanation); 25 | 26 | // Search Keys Section 27 | DrawSection(Language.SearchOptionsTitle, Language.SearchOptionsExplanation, 28 | [ 29 | Language.SearchOptionsExplanationNotes, 30 | Language.SearchOptionsExplanationFC, 31 | Language.SearchOptionsExplanationTags, 32 | Language.SearchOptionsExplanationRace, 33 | Language.SearchOptionsExplanationGender, 34 | Language.SearchOptionsExplanationWorld, 35 | Language.SearchOptionsExplanationDC, 36 | Language.SearchOptionsExplanationDefault 37 | ]); 38 | 39 | // Wildcard Patterns Section 40 | DrawSection(Language.AdvancedMatchingTitle, Language.AdvancedMatchingExplanation, 41 | [ 42 | Language.AdvancedMatchingExplanationExact, 43 | Language.AdvancedMatchingExplanationStart, 44 | Language.AdvancedMatchingExplanationEnd, 45 | Language.AdvancedMatchingExplanationContains, 46 | Language.AdvancedMatchingExplanationExclude 47 | ]); 48 | 49 | // Examples Section 50 | DrawSection(Language.ExamplesTitle, Language.ExamplesExplanation, 51 | [ 52 | Language.ExamplesExplanation1, 53 | Language.ExamplesExplanation2, 54 | Language.ExamplesExplanation3 55 | ]); 56 | } 57 | 58 | private static void DrawSection(string title, string description, string[]? bullets = null) 59 | { 60 | Helper.TextColored(ImGuiColors.DalamudViolet, title); 61 | Helper.TextWrapped(description); 62 | ImGuiHelpers.ScaledDummy(1f); 63 | 64 | if (bullets != null) 65 | { 66 | using (ImRaii.PushIndent()) 67 | { 68 | foreach (var bullet in bullets) 69 | Helper.BulletText(bullet); 70 | } 71 | } 72 | 73 | ImGuiHelpers.ScaledDummy(2f); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Models/Player/PlayerConfig.cs: -------------------------------------------------------------------------------- 1 | using PlayerTrack.Models.Structs; 2 | 3 | namespace PlayerTrack.Models; 4 | 5 | public class PlayerConfig 6 | { 7 | public bool IsChanged; 8 | public int? PlayerId; 9 | public int? CategoryId; 10 | public PlayerConfigType PlayerConfigType; 11 | public ConfigValue PlayerListNameColor; 12 | public ConfigValue PlayerListIcon; 13 | public ConfigValue NameplateCustomTitle; 14 | public ConfigValue NameplateShowInOverworld; 15 | public ConfigValue NameplateShowInContent; 16 | public ConfigValue NameplateShowInHighEndContent; 17 | public ConfigValue NameplateColor; 18 | public ConfigValue NameplateUseColor; 19 | public ConfigValue NameplateUseColorIfDead; 20 | public ConfigValue NameplateTitleType; 21 | public ConfigValue AlertNameChange; 22 | public ConfigValue AlertWorldTransfer; 23 | public ConfigValue AlertProximity; 24 | public ConfigValue AlertFormatIncludeCategory; 25 | public ConfigValue AlertFormatIncludeCustomTitle; 26 | public ConfigValue VisibilityType; 27 | 28 | public PlayerConfig() { } 29 | 30 | public PlayerConfig(PlayerConfigType playerConfigType) 31 | { 32 | PlayerConfigType = playerConfigType; 33 | var inheritOverride = PlayerConfigType == PlayerConfigType.Default ? InheritOverride.None : InheritOverride.Inherit; 34 | PlayerListNameColor = new ConfigValue(inheritOverride, 1); 35 | PlayerListIcon = new ConfigValue(inheritOverride, (char)61447); 36 | NameplateCustomTitle = new ConfigValue(inheritOverride, string.Empty); 37 | NameplateShowInOverworld = new ConfigValue(inheritOverride, true); 38 | NameplateShowInContent = new ConfigValue(inheritOverride, true); 39 | NameplateShowInHighEndContent = new ConfigValue(inheritOverride, true); 40 | NameplateColor = new ConfigValue(inheritOverride, 1); 41 | NameplateUseColor = new ConfigValue(inheritOverride, false); 42 | NameplateUseColorIfDead = new ConfigValue(inheritOverride, false); 43 | NameplateTitleType = new ConfigValue(inheritOverride, Models.NameplateTitleType.NoChange); 44 | AlertNameChange = new ConfigValue(inheritOverride, true); 45 | AlertWorldTransfer = new ConfigValue(inheritOverride, true); 46 | AlertProximity = new ConfigValue(inheritOverride, false); 47 | AlertFormatIncludeCategory = new ConfigValue(inheritOverride, true); 48 | AlertFormatIncludeCustomTitle = new ConfigValue(inheritOverride, true); 49 | VisibilityType = new ConfigValue(inheritOverride, Models.VisibilityType.None); 50 | } 51 | 52 | public int Id { get; set; } 53 | 54 | public long Created { get; set; } 55 | 56 | public long Updated { get; set; } 57 | } 58 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Resource/Language.ru.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | text/microsoft-resx 4 | 5 | 6 | 1.3 7 | 8 | 9 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 10 | 11 | 12 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 13 | 14 | 15 | Добавить игрока 16 | 17 | 18 | Что-то пошло не так. Перезапустите компьютер и попробуйте снова. 19 | 20 | 21 | Заблокирован 22 | 23 | 24 | Отмена 25 | 26 | 27 | Категории 28 | 29 | 30 | Для разработчиков 31 | 32 | 33 | Для всех 34 | 35 | 36 | Для переводчиков 37 | 38 | 39 | ФК 40 | 41 | 42 | Общее 43 | 44 | 45 | ГБ 46 | 47 | 48 | Вы можете помочь с переводами на PlayerTrack. 49 | 50 | 51 | ч 52 | 53 | 54 | Иконка 55 | 56 | 57 | Иконки 58 | 59 | 60 | Наследовать 61 | 62 | 63 | Интеграции 64 | 65 | 66 | Пожалуйста, правильно введите имя. 67 | 68 | 69 | за последние 90 дней 70 | 71 | 72 | Сохранить настройки для игроков 73 | 74 | 75 | КБ 76 | 77 | 78 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Domain/Services/PlayerServices/PlayerTagService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PlayerTrack.Infrastructure; 3 | using PlayerTrack.Models; 4 | 5 | namespace PlayerTrack.Domain; 6 | 7 | public class PlayerTagService 8 | { 9 | public static void UpdateTags(int playerId, List tags) 10 | { 11 | Plugin.PluginLog.Verbose($"Entering PlayerTagService.UpdateTags(), playerId: {playerId}, tags: {tags.Count}"); 12 | var player = ServiceContext.PlayerDataService.GetPlayer(playerId); 13 | if (player == null) 14 | { 15 | Plugin.PluginLog.Warning("Player not found, cannot update tags."); 16 | return; 17 | } 18 | 19 | player.AssignedTags = tags; 20 | ServiceContext.PlayerDataService.UpdatePlayer(player); 21 | } 22 | 23 | public static void UnassignTagsFromPlayer(int playerId) 24 | { 25 | Plugin.PluginLog.Verbose($"Entering PlayerTagService.UnassignTagsFromPlayer(), playerId: {playerId}"); 26 | var tags = ServiceContext.PlayerDataService.GetPlayer(playerId)?.AssignedTags; 27 | if (tags == null || tags.Count == 0) 28 | { 29 | Plugin.PluginLog.Warning("Player not found, cannot remove tag."); 30 | return; 31 | } 32 | 33 | tags = []; 34 | UpdateTags(playerId, tags); 35 | RepositoryContext.PlayerTagRepository.DeletePlayerTagByPlayerId(playerId); 36 | } 37 | 38 | public static void UnassignTagFromPlayer(int playerId, int tagId) 39 | { 40 | Plugin.PluginLog.Verbose($"Entering PlayerTagService.UnassignTagFromPlayer(), playerId: {playerId}, tagId: {tagId}"); 41 | var tags = ServiceContext.PlayerDataService.GetPlayer(playerId)?.AssignedTags; 42 | if (tags == null || tags.Count == 0) 43 | { 44 | Plugin.PluginLog.Warning("Player not found, cannot remove tag."); 45 | return; 46 | } 47 | 48 | tags.RemoveAll(t => t.Id == tagId); 49 | UpdateTags(playerId, tags); 50 | RepositoryContext.PlayerTagRepository.DeletePlayerTag(playerId, tagId); 51 | } 52 | 53 | public static void AssignTag(int playerId, int tagId) 54 | { 55 | Plugin.PluginLog.Verbose($"Entering PlayerTagService.AssignTag(), playerId: {playerId}, tagId: {tagId}"); 56 | var tag = ServiceContext.TagService.GetTagById(tagId); 57 | if (tag == null) 58 | { 59 | Plugin.PluginLog.Warning("Tag not found, cannot assign tag."); 60 | return; 61 | } 62 | 63 | var tags = ServiceContext.PlayerDataService.GetPlayer(playerId)?.AssignedTags ?? []; 64 | tags.Add(tag); 65 | UpdateTags(playerId, tags); 66 | RepositoryContext.PlayerTagRepository.CreatePlayerTag(playerId, tagId); 67 | } 68 | 69 | public static void DeletePlayerTagsByTagId(int tagId) => 70 | RepositoryContext.PlayerTagRepository.DeletePlayerTag(tagId); 71 | 72 | public static void DeletePlayerTagsByPlayerId(int playerId) => 73 | RepositoryContext.PlayerTagRepository.DeletePlayerTagByPlayerId(playerId); 74 | } 75 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/Config/Components/LocationComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Dalamud.Interface.Utility; 4 | using Dalamud.Interface.Utility.Raii; 5 | using Dalamud.Bindings.ImGui; 6 | using PlayerTrack.Domain; 7 | using PlayerTrack.Models; 8 | using PlayerTrack.Resource; 9 | 10 | namespace PlayerTrack.Windows.Config.Components; 11 | 12 | public class LocationComponent : ConfigViewComponent 13 | { 14 | public override void Draw() 15 | { 16 | var categoryNames = ServiceContext.CategoryService.GetCategoryNames(); 17 | 18 | using var tabBar = ImRaii.TabBar("Tracking_TabBar", ImGuiTabBarFlags.None); 19 | if (!tabBar.Success) 20 | return; 21 | 22 | DrawLocationTab(Language.Overworld, Config.Overworld, categoryNames); 23 | DrawLocationTab(Language.Content, Config.Content, categoryNames); 24 | DrawLocationTab(Language.HighEndContent, Config.HighEndContent, categoryNames); 25 | } 26 | 27 | private void DrawLocationTab(string header, TrackingLocationConfig trackingLocationConfig, IReadOnlyCollection categoryNames) 28 | { 29 | using var tabItem = ImRaii.TabItem(header); 30 | if (!tabItem.Success) 31 | return; 32 | 33 | ImGuiHelpers.ScaledDummy(1f); 34 | var addPlayers = trackingLocationConfig.AddPlayers; 35 | if (Helper.Checkbox(Language.AddPlayers, ref addPlayers)) 36 | { 37 | trackingLocationConfig.AddPlayers = addPlayers; 38 | ServiceContext.ConfigService.SaveConfig(Config); 39 | } 40 | 41 | var addEncounters = trackingLocationConfig.AddEncounters; 42 | if (Helper.Checkbox(Language.AddEncounters, ref addEncounters)) 43 | { 44 | trackingLocationConfig.AddEncounters = addEncounters; 45 | ServiceContext.ConfigService.SaveConfig(Config); 46 | } 47 | 48 | var disableCategoryBox = categoryNames.Count == 1; 49 | using (ImRaii.Disabled(disableCategoryBox)) 50 | { 51 | var selectedCategoryIndex = 0; 52 | var categoryName = ServiceContext.CategoryService.GetCategory(trackingLocationConfig.DefaultCategoryId)?.Name; 53 | if (!string.IsNullOrEmpty(categoryName)) 54 | selectedCategoryIndex = categoryNames.ToList().IndexOf(categoryName); 55 | 56 | if (Helper.Combo(Language.DefaultCategory, ref selectedCategoryIndex, categoryNames)) 57 | { 58 | var category = ServiceContext.CategoryService.GetCategoryByName(categoryNames.ElementAt(selectedCategoryIndex)); 59 | if (category?.Id != null) 60 | { 61 | trackingLocationConfig.DefaultCategoryId = category.Id; 62 | ServiceContext.ConfigService.SaveConfig(Config); 63 | } 64 | else if (selectedCategoryIndex == 0) 65 | { 66 | trackingLocationConfig.DefaultCategoryId = 0; 67 | ServiceContext.ConfigService.SaveConfig(Config); 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Domain/Services/PlayerServices/PlayerNameplateService.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Game.Text.SeStringHandling; 2 | using Dalamud.Game.Text.SeStringHandling.Payloads; 3 | using PlayerTrack.Data; 4 | using PlayerTrack.Models; 5 | 6 | namespace PlayerTrack.Domain; 7 | 8 | public class PlayerNameplateService 9 | { 10 | public static PlayerNameplate GetPlayerNameplate(Player player, LocationType locationType) 11 | { 12 | Plugin.PluginLog.Verbose($"Entering PlayerNameplateService.GetPlayerNameplate(): {player.Id}, {locationType}"); 13 | var nameplate = new PlayerNameplate 14 | { 15 | CustomizeNameplate = locationType switch 16 | { 17 | LocationType.Overworld => PlayerConfigService.GetNameplateShowInOverworld(player), 18 | LocationType.Content => PlayerConfigService.GetNameplateShowInContent(player), 19 | LocationType.HighEndContent => PlayerConfigService.GetNameplateShowInHighEndContent(player), 20 | _ => false, 21 | }, 22 | }; 23 | 24 | if (!nameplate.CustomizeNameplate) 25 | { 26 | Plugin.PluginLog.Debug($"CustomizeNameplate is false for {player.Name}"); 27 | return nameplate; 28 | } 29 | 30 | var isColorEnabled = PlayerConfigService.GetNameplateUseColor(player); 31 | ushort color = 0; 32 | if (isColorEnabled) 33 | { 34 | color = (ushort)PlayerConfigService.GetNameplateColor(player); 35 | nameplate.TitleLeftQuote = new SeString().Append(new UIForegroundPayload(color)).Append("《"); 36 | nameplate.TitleRightQuote = new SeString().Append("》").Append(UIForegroundPayload.UIForegroundOff); 37 | nameplate.NameTextWrap = (new SeString(new UIForegroundPayload(color)), new SeString(UIForegroundPayload.UIForegroundOff)); 38 | nameplate.FreeCompanyLeftQuote = new SeString().Append(new UIForegroundPayload(color)).Append(" «"); 39 | nameplate.FreeCompanyRightQuote = new SeString().Append("»").Append(UIForegroundPayload.UIForegroundOff); 40 | } 41 | 42 | nameplate.NameplateUseColorIfDead = PlayerConfigService.GetNameplateUseColorIfDead(player); 43 | 44 | var nameplateTitleType = PlayerConfigService.GetNameplateTitleType(player); 45 | 46 | var title = nameplateTitleType switch 47 | { 48 | NameplateTitleType.CustomTitle => PlayerConfigService.GetNameplateCustomTitle(player), 49 | NameplateTitleType.CategoryName when player.PrimaryCategoryId != 0 => ServiceContext.CategoryService 50 | .GetCategory(player.PrimaryCategoryId) 51 | ?.Name ?? string.Empty, 52 | _ => string.Empty 53 | }; 54 | 55 | if (nameplateTitleType != NameplateTitleType.NoChange && !string.IsNullOrEmpty(title)) 56 | { 57 | nameplate.CustomTitle = title; 58 | nameplate.HasCustomTitle = true; 59 | } 60 | 61 | if ((!isColorEnabled || color == 0) && !nameplate.HasCustomTitle) 62 | nameplate.CustomizeNameplate = false; 63 | 64 | return nameplate; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/Config/Components/TagComponent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Dalamud.Interface; 3 | using Dalamud.Interface.Utility; 4 | using Dalamud.Interface.Utility.Raii; 5 | using Dalamud.Bindings.ImGui; 6 | using PlayerTrack.Domain; 7 | using PlayerTrack.Enums; 8 | using PlayerTrack.Models; 9 | using PlayerTrack.Resource; 10 | 11 | namespace PlayerTrack.Windows.Config.Components; 12 | 13 | public class TagComponent : ConfigViewComponent 14 | { 15 | private string TagInput = string.Empty; 16 | private Tuple? TagToDelete; 17 | 18 | public override void Draw() 19 | { 20 | DrawTags(); 21 | DrawAddTagInput(); 22 | } 23 | 24 | private void DrawTags() 25 | { 26 | var tags = ServiceContext.TagService.GetAllTags(); 27 | 28 | foreach (var tag in tags) 29 | DrawTag(tag); 30 | } 31 | 32 | private void DrawTag(Tag tag) 33 | { 34 | var tagText = tag.Name; 35 | ImGui.SetNextItemWidth(240f * ImGuiHelpers.GlobalScale); 36 | if (ImGui.InputText($"###EditTagInput{tag.Id}", ref tagText, 20)) 37 | UpdateTagText(tag, tagText); 38 | 39 | ImGui.SameLine(); 40 | 41 | var color = Sheets.GetUiColorAsVector4(tag.Color); 42 | if (Helper.SimpleUiColorPicker($"###TagColorPicker{tag.Id}", tag.Color, ref color, false)) 43 | UpdateTagColor(tag, color); 44 | 45 | ImGui.SameLine(); 46 | 47 | DrawTagDeleteConfirmation(tag); 48 | } 49 | 50 | private void UpdateTagText(Tag tag, string text) 51 | { 52 | tag.Name = text; 53 | ServiceContext.TagService.UpdateTag(tag); 54 | NotifyConfigChanged(); 55 | } 56 | 57 | private void UpdateTagColor(Tag tag, System.Numerics.Vector4 color) 58 | { 59 | tag.Color = Sheets.FindClosestUiColor(color).Id; 60 | ServiceContext.TagService.UpdateTag(tag); 61 | NotifyConfigChanged(); 62 | } 63 | 64 | private void DrawTagDeleteConfirmation(Tag tag) 65 | { 66 | Helper.Confirm(tag, FontAwesomeIcon.Trash, Language.ConfirmDelete, ref TagToDelete); 67 | 68 | if (TagToDelete?.Item1 == ActionRequest.Confirmed) 69 | { 70 | ServiceContext.TagService.DeleteTag(TagToDelete.Item2); 71 | TagToDelete = null; 72 | NotifyConfigChanged(); 73 | } 74 | else if (TagToDelete?.Item1 == ActionRequest.None) 75 | { 76 | TagToDelete = null; 77 | } 78 | } 79 | 80 | private void DrawAddTagInput() 81 | { 82 | ImGui.SetNextItemWidth(240f * ImGuiHelpers.GlobalScale); 83 | ImGui.InputTextWithHint("###AddTagInput", Language.NewTagHint, ref TagInput, 20); 84 | ImGui.SameLine(); 85 | using (ImRaii.PushFont(UiBuilder.IconFont)) 86 | { 87 | ImGui.TextUnformatted(FontAwesomeIcon.Plus.ToIconString()); 88 | if (ImGui.IsItemClicked() && !string.IsNullOrEmpty(TagInput)) 89 | AddNewTag(); 90 | } 91 | } 92 | 93 | private void AddNewTag() 94 | { 95 | ServiceContext.TagService.CreateTag(TagInput); 96 | TagInput = string.Empty; 97 | NotifyConfigChanged(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/PlayerTrack.Plugin.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | PlayerTrack.Plugin 4 | Infi, kalilistic 5 | 3.5.2.0 6 | net9.0-windows 7 | x64 8 | latest 9 | true 10 | enable 11 | true 12 | false 13 | true 14 | false 15 | true 16 | true 17 | false 18 | true 19 | Debug;Release 20 | PlayerTrack 21 | PlayerTrack 22 | $(Version) 23 | $(Version) 24 | CS1591;MSB3277 25 | 26 | 27 | 28 | true 29 | full 30 | false 31 | bin\Debug\ 32 | DEBUG;TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | pdbonly 39 | true 40 | true 41 | bin\Release\ 42 | TRACE 43 | prompt 44 | 4 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | ResXFileCodeGenerator 64 | Language.Designer.cs 65 | 66 | 67 | 68 | 69 | 70 | True 71 | True 72 | Language.resx 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Extensions/TargetManagerExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Dalamud.Game.ClientState.Objects; 4 | using FFXIVClientStructs.FFXIV.Client.Game.Object; 5 | using FFXIVClientStructs.FFXIV.Client.UI.Agent; 6 | 7 | namespace PlayerTrack.Extensions; 8 | 9 | /// 10 | /// Target manager extension to provide additional functionality. 11 | /// 12 | public static class TargetManagerExtension 13 | { 14 | /// 15 | /// Sets the target to the specified object ID. If the object ID is already targeted, it will clear the target. 16 | /// 17 | /// this 18 | /// The object ID to target 19 | public static void SetTarget(this ITargetManager manager, uint objectId) 20 | { 21 | var obj = Plugin.ObjectCollection.SearchById(objectId); 22 | if (obj == null) 23 | return; 24 | 25 | if (manager.Target?.EntityId == obj.EntityId) 26 | { 27 | manager.Target = null; 28 | return; 29 | } 30 | 31 | manager.Target = obj; 32 | } 33 | 34 | /// 35 | /// Sets the focus target to the specified object ID. If the object ID is already focused, it will clear the focus 36 | /// target. 37 | /// 38 | /// this 39 | /// The object ID to set as focus target. 40 | public static void SetFocusTarget(this ITargetManager manager, uint objectId) 41 | { 42 | var obj = Plugin.ObjectCollection.SearchById(objectId); 43 | if (obj == null) 44 | return; 45 | 46 | if (manager.FocusTarget?.EntityId == obj.EntityId) 47 | { 48 | manager.FocusTarget = null; 49 | return; 50 | } 51 | 52 | manager.FocusTarget = obj; 53 | } 54 | 55 | /// 56 | /// Opens the plate window for the specified object ID. 57 | /// 58 | /// this 59 | /// The object ID for which to open the plate window. 60 | public static unsafe void OpenPlateWindow(this ITargetManager manager, uint objectId) 61 | { 62 | var obj = Plugin.ObjectCollection.FirstOrDefault(i => i.EntityId == objectId); 63 | if (obj == null) 64 | return; 65 | 66 | try 67 | { 68 | AgentCharaCard.Instance()->OpenCharaCard((GameObject*)obj.Address); 69 | } 70 | catch (Exception ex) 71 | { 72 | Plugin.PluginLog.Error(ex, "Failed to open plate window."); 73 | } 74 | } 75 | 76 | /// 77 | /// Examines the specified object ID. 78 | /// 79 | /// this 80 | /// The object ID to examine. 81 | public static unsafe void ExamineTarget(this ITargetManager manager, uint objectId) 82 | { 83 | var obj = Plugin.ObjectCollection.FirstOrDefault(i => i.EntityId == objectId); 84 | if (obj == null) 85 | return; 86 | 87 | try 88 | { 89 | AgentInspect.Instance()->ExamineCharacter(objectId); 90 | } 91 | catch (Exception ex) 92 | { 93 | Plugin.PluginLog.Error(ex, "Failed to examine target."); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Domain/ServiceContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PlayerTrack.Domain; 4 | 5 | public static class ServiceContext 6 | { 7 | public static BackupService BackupService { get; set; } = null!; 8 | public static CategoryService CategoryService { get; set; } = null!; 9 | public static ConfigService ConfigService { get; set; } = null!; 10 | public static EncounterService EncounterService { get; set; } = null!; 11 | public static LodestoneService LodestoneService { get; set; } = null!; 12 | public static PlayerDataService PlayerDataService { get; set; } = null!; 13 | public static PlayerConfigService PlayerConfigService { get; set; } = null!; 14 | public static PlayerNameplateService PlayerNameplateService { get; set; } = null!; 15 | public static PlayerCategoryService PlayerCategoryService { get; set; } = null!; 16 | public static PlayerTagService PlayerTagService { get; set; } = null!; 17 | public static PlayerChangeService PlayerChangeService { get; set; } = null!; 18 | public static PlayerEncounterService PlayerEncounterService { get; set; } = null!; 19 | public static PlayerAlertService PlayerAlertService { get; set; } = null!; 20 | public static PlayerProcessService PlayerProcessService { get; set; } = null!; 21 | public static PlayerCacheService PlayerCacheService { get; set; } = null!; 22 | public static TagService TagService { get; set; } = null!; 23 | public static VisibilityService VisibilityService { get; set; } = null!; 24 | public static LocalPlayerService LocalPlayerService { get; set; } = null!; 25 | public static SocialListService SocialListService { get; set; } = null!; 26 | 27 | public static void Initialize() 28 | { 29 | Plugin.PluginLog.Verbose("Entering ServiceContext.Initialize()"); 30 | ConfigService = new ConfigService(); 31 | BackupService = new BackupService(); 32 | CategoryService = new CategoryService(); 33 | TagService = new TagService(); 34 | EncounterService = new EncounterService(); 35 | PlayerNameplateService = new PlayerNameplateService(); 36 | PlayerCategoryService = new PlayerCategoryService(); 37 | PlayerTagService = new PlayerTagService(); 38 | PlayerChangeService = new PlayerChangeService(); 39 | PlayerCacheService = new PlayerCacheService(); 40 | PlayerDataService = new PlayerDataService(); 41 | PlayerEncounterService = new PlayerEncounterService(); 42 | PlayerAlertService = new PlayerAlertService(); 43 | PlayerProcessService = new PlayerProcessService(); 44 | PlayerConfigService = new PlayerConfigService(); 45 | LodestoneService = new LodestoneService(); 46 | VisibilityService = new VisibilityService(); 47 | LocalPlayerService = new LocalPlayerService(); 48 | SocialListService = new SocialListService(); 49 | } 50 | 51 | public static void Dispose() 52 | { 53 | Plugin.PluginLog.Verbose("Entering ServiceContext.Dispose()"); 54 | try 55 | { 56 | PlayerCacheService.Dispose(); 57 | PlayerProcessService.Dispose(); 58 | EncounterService.Dispose(); 59 | VisibilityService.Dispose(); 60 | PlayerAlertService.Dispose(); 61 | } 62 | catch (Exception) 63 | { 64 | Plugin.PluginLog.Warning("Failed to dispose services."); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Models/Structs/CharaCustomizeData.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace PlayerTrack.Models.Structs; 4 | 5 | [StructLayout(LayoutKind.Explicit)] 6 | public readonly struct CharaCustomizeData 7 | { 8 | [FieldOffset((int)CustomizeIndex.BustSize)] 9 | public readonly byte BustSize; 10 | 11 | [FieldOffset((int)CustomizeIndex.Eyebrows)] 12 | public readonly byte Eyebrows; 13 | 14 | [FieldOffset((int)CustomizeIndex.EyeColor)] 15 | public readonly byte EyeColor; 16 | 17 | [FieldOffset((int)CustomizeIndex.EyeColor2)] 18 | public readonly byte EyeColor2; 19 | 20 | [FieldOffset((int)CustomizeIndex.EyeShape)] 21 | public readonly byte EyeShape; 22 | 23 | [FieldOffset((int)CustomizeIndex.FaceFeatures)] 24 | public readonly byte FaceFeatures; 25 | 26 | [FieldOffset((int)CustomizeIndex.FaceFeaturesColor)] 27 | public readonly byte FaceFeaturesColor; 28 | 29 | [FieldOffset((int)CustomizeIndex.Facepaint)] 30 | public readonly byte Facepaint; 31 | 32 | [FieldOffset((int)CustomizeIndex.FacepaintColor)] 33 | public readonly byte FacepaintColor; 34 | 35 | [FieldOffset((int)CustomizeIndex.FaceType)] 36 | public readonly byte FaceType; 37 | 38 | [FieldOffset((int)CustomizeIndex.Gender)] 39 | public readonly byte Gender; 40 | 41 | [FieldOffset((int)CustomizeIndex.HairColor)] 42 | public readonly byte HairColor; 43 | 44 | [FieldOffset((int)CustomizeIndex.HairColor2)] 45 | public readonly byte HairColor2; 46 | 47 | [FieldOffset((int)CustomizeIndex.HairStyle)] 48 | public readonly byte HairStyle; 49 | 50 | [FieldOffset((int)CustomizeIndex.HasHighlights)] 51 | public readonly byte HasHighlights; 52 | 53 | [FieldOffset((int)CustomizeIndex.Height)] 54 | public readonly byte Height; 55 | 56 | [FieldOffset((int)CustomizeIndex.JawShape)] 57 | public readonly byte JawShape; 58 | 59 | [FieldOffset((int)CustomizeIndex.LipColor)] 60 | public readonly byte LipColor; 61 | 62 | [FieldOffset((int)CustomizeIndex.LipStyle)] 63 | public readonly byte LipStyle; 64 | 65 | [FieldOffset((int)CustomizeIndex.ModelType)] 66 | public readonly byte ModelType; 67 | 68 | [FieldOffset((int)CustomizeIndex.NoseShape)] 69 | public readonly byte NoseShape; 70 | 71 | [FieldOffset((int)CustomizeIndex.Race)] 72 | public readonly byte Race; 73 | 74 | [FieldOffset((int)CustomizeIndex.RaceFeatureSize)] 75 | public readonly byte RaceFeatureSize; 76 | 77 | [FieldOffset((int)CustomizeIndex.RaceFeatureType)] 78 | public readonly byte RaceFeatureType; 79 | 80 | [FieldOffset((int)CustomizeIndex.SkinColor)] 81 | public readonly byte SkinColor; 82 | 83 | [FieldOffset((int)CustomizeIndex.Tribe)] 84 | public readonly byte Tribe; 85 | 86 | public static CharaCustomizeData MapCustomizeData(byte[] customizeIndex) 87 | { 88 | var handle = GCHandle.Alloc(customizeIndex, GCHandleType.Pinned); 89 | CharaCustomizeData charaCustomizeData; 90 | try 91 | { 92 | charaCustomizeData = (CharaCustomizeData)(Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(CharaCustomizeData)) ?? 0); 93 | } 94 | finally 95 | { 96 | handle.Free(); 97 | } 98 | 99 | return charaCustomizeData; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/Main/Components/AddPlayerComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Numerics; 3 | using Dalamud.Interface.Colors; 4 | using Dalamud.Interface.Utility; 5 | using Dalamud.Interface.Utility.Raii; 6 | using Dalamud.Utility; 7 | using Dalamud.Bindings.ImGui; 8 | using PlayerTrack.Domain; 9 | using PlayerTrack.Resource; 10 | using PlayerTrack.Windows.Components; 11 | 12 | namespace PlayerTrack.Windows.Main.Components; 13 | 14 | public class AddPlayerComponent : ViewComponent 15 | { 16 | private readonly uint[] WorldIds; 17 | private readonly string[] WorldNames; 18 | private int SelectedWorld; 19 | private string AddPlayerInput = string.Empty; 20 | private bool ShowInvalidNameError; 21 | private bool ShowInvalidWorldError; 22 | private bool ShowDuplicatePlayerError; 23 | private bool ShowSuccessMessage; 24 | 25 | public AddPlayerComponent() 26 | { 27 | WorldIds = Sheets.Worlds.Select(pair => pair.Value.Id).Prepend(0u).ToArray(); 28 | WorldNames = Sheets.Worlds.Select(pair => pair.Value.Name).Prepend(string.Empty).ToArray(); 29 | } 30 | 31 | public override void Draw() 32 | { 33 | using var child = ImRaii.Child("###AddPlayerManually", new Vector2(-1, 0), false); 34 | if (!child.Success) 35 | return; 36 | 37 | Helper.TextColored(ImGuiColors.DalamudViolet, Language.AddPlayerInstructions); 38 | ImGuiHelpers.ScaledDummy(3f); 39 | ImGui.SetNextItemWidth(150f * ImGuiHelpers.GlobalScale); 40 | if (ImGui.InputText(Language.PlayerName, ref AddPlayerInput, 30)) 41 | ShowSuccessMessage = false; 42 | 43 | if (Helper.Combo(Language.PlayerWorld, ref SelectedWorld, WorldNames, 150)) 44 | ShowSuccessMessage = false; 45 | 46 | ImGuiHelpers.ScaledDummy(10f); 47 | if (ImGui.Button(Language.AddPlayer)) 48 | { 49 | ShowInvalidNameError = false; 50 | ShowInvalidWorldError = false; 51 | ShowDuplicatePlayerError = false; 52 | 53 | if (SelectedWorld == 0) 54 | { 55 | ShowInvalidWorldError = true; 56 | } 57 | else if (AddPlayerInput.IsValidCharacterName()) 58 | { 59 | var worldId = WorldIds[SelectedWorld]; 60 | var existingPlayer = ServiceContext.PlayerDataService.GetPlayer(AddPlayerInput, worldId); 61 | if (existingPlayer != null) 62 | { 63 | ShowDuplicatePlayerError = true; 64 | } 65 | else 66 | { 67 | PlayerProcessService.CreateNewPlayer(AddPlayerInput, worldId, 0, false); 68 | AddPlayerInput = string.Empty; 69 | ShowSuccessMessage = true; 70 | } 71 | } 72 | else 73 | { 74 | ShowInvalidNameError = true; 75 | } 76 | } 77 | 78 | ImGui.Spacing(); 79 | if (ShowInvalidWorldError) 80 | Helper.TextColored(ImGuiColors.DPSRed, Language.NoWorldError); 81 | else if (ShowInvalidNameError) 82 | Helper.TextColored(ImGuiColors.DPSRed, Language.InvalidNameError); 83 | else if (ShowDuplicatePlayerError) 84 | Helper.TextColored(ImGuiColors.DPSRed, Language.DuplicatePlayerError); 85 | else if (ShowSuccessMessage) 86 | Helper.TextColored(ImGuiColors.HealerGreen, Language.PlayerAddedSuccessfully); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Domain/Services/LocalPlayerService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PlayerTrack.Data; 3 | using PlayerTrack.Domain.Common; 4 | using PlayerTrack.Extensions; 5 | using PlayerTrack.Infrastructure; 6 | using PlayerTrack.Models; 7 | 8 | namespace PlayerTrack.Domain; 9 | 10 | public class LocalPlayerService 11 | { 12 | public static void AddOrUpdateLocalPlayer(LocalPlayerData? localPlayer) 13 | { 14 | Plugin.PluginLog.Verbose($"AddOrUpdateLocalPlayer: {localPlayer?.ContentId}"); 15 | if (localPlayer == null) 16 | { 17 | Plugin.PluginLog.Warning("AddOrUpdateLocalPlayer: localPlayer is null"); 18 | return; 19 | } 20 | 21 | var existingPlayer = RepositoryContext.LocalPlayerRepository.GetLocalPlayer(localPlayer.ContentId); 22 | if (existingPlayer != null) 23 | { 24 | Plugin.PluginLog.Verbose($"UpdateLocalPlayer: {localPlayer.ContentId}"); 25 | existingPlayer.Name = localPlayer.Name; 26 | existingPlayer.WorldId = localPlayer.HomeWorld; 27 | existingPlayer.Key = PlayerKeyBuilder.Build(localPlayer.Name, localPlayer.HomeWorld); 28 | existingPlayer.Customize = localPlayer.Customize; 29 | RepositoryContext.LocalPlayerRepository.UpdateLocalPlayer(existingPlayer); 30 | } 31 | else 32 | { 33 | Plugin.PluginLog.Verbose($"CreateLocalPlayer: {localPlayer.ContentId}"); 34 | RepositoryContext.LocalPlayerRepository.CreateLocalPlayer(new LocalPlayer 35 | { 36 | ContentId = localPlayer.ContentId, 37 | Name = localPlayer.Name, 38 | WorldId = localPlayer.HomeWorld, 39 | Key = PlayerKeyBuilder.Build(localPlayer.Name, localPlayer.HomeWorld), 40 | Customize = localPlayer.Customize 41 | }); 42 | } 43 | } 44 | 45 | public static LocalPlayer? GetLocalPlayer(ulong contentId) 46 | { 47 | return RepositoryContext.LocalPlayerRepository.GetLocalPlayer(contentId); 48 | } 49 | 50 | public static string GetLocalPlayerFullName(ulong contentId) 51 | { 52 | var localPlayer = RepositoryContext.LocalPlayerRepository.GetLocalPlayer(contentId); 53 | var worldName = Sheets.GetWorldNameById(localPlayer?.WorldId ?? 0); 54 | return $"{localPlayer?.Name}@{worldName}"; 55 | } 56 | 57 | public static List GetLocalPlayers() 58 | { 59 | return RepositoryContext.LocalPlayerRepository.GetAllLocalPlayers(); 60 | } 61 | 62 | public static List GetLocalPlayerNames() 63 | { 64 | var names = new List(); 65 | foreach (var localPlayer in GetLocalPlayers()) 66 | { 67 | var worldName = Sheets.GetWorldNameById(localPlayer.WorldId); 68 | names.Add($"{localPlayer.Name}@{worldName}"); 69 | } 70 | 71 | return names; 72 | } 73 | 74 | public static uint GetLocalPlayerDataCenter() 75 | { 76 | var localPlayer = Plugin.ClientStateHandler.GetLocalPlayer(); 77 | return localPlayer == null ? 0 : Sheets.Worlds[localPlayer.HomeWorld].DataCenterId; 78 | } 79 | 80 | public static void DeleteLocalPlayer(LocalPlayer localPlayer) 81 | { 82 | Plugin.PluginLog.Verbose($"DeleteLocalPlayer: {localPlayer.Id}"); 83 | RepositoryContext.LocalPlayerRepository.DeleteLocalPlayer(localPlayer.Id); 84 | SocialListService.DeleteSocialLists(localPlayer.ContentId); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/Main/Components/PlayerComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Dalamud.Interface.Utility; 3 | using Dalamud.Interface.Utility.Raii; 4 | using Dalamud.Bindings.ImGui; 5 | using PlayerTrack.Domain; 6 | using PlayerTrack.Models; 7 | using PlayerTrack.Resource; 8 | using PlayerTrack.Windows.Components; 9 | using PlayerTrack.Windows.Main.Presenters; 10 | 11 | namespace PlayerTrack.Windows.Main.Components; 12 | 13 | public class PlayerComponent 14 | { 15 | private readonly IMainPresenter Presenter; 16 | 17 | private readonly PlayerSummaryComponent PlayerSummaryComponent; 18 | private readonly PlayerEncounterComponent PlayerEncounterComponent; 19 | private readonly PlayerHistoryComponent PlayerHistoryComponent; 20 | private readonly PlayerActionComponent PlayerActionComponent; 21 | 22 | public PlayerComponent(IMainPresenter presenter) 23 | { 24 | Presenter = presenter; 25 | 26 | PlayerSummaryComponent = new PlayerSummaryComponent(presenter); 27 | PlayerEncounterComponent = new PlayerEncounterComponent(presenter); 28 | PlayerHistoryComponent = new PlayerHistoryComponent(presenter); 29 | PlayerActionComponent = new PlayerActionComponent(presenter); 30 | } 31 | 32 | public void Draw() 33 | { 34 | var player = Presenter.GetSelectedPlayer(); 35 | var isLoadingPlayer = Presenter.IsPlayerLoading(); 36 | if (player == null) 37 | return; 38 | 39 | using var child = ImRaii.Child("###ConfigMenuOption_Player", new Vector2(-1, 0), false); 40 | if (!child.Success) 41 | return; 42 | 43 | using var disabled = ImRaii.Disabled(isLoadingPlayer); 44 | 45 | using var tabBar = ImRaii.TabBar("###PlayerSummary_TabBar", ImGuiTabBarFlags.None); 46 | if (!tabBar.Success) 47 | return; 48 | 49 | using (var tabItem = ImRaii.TabItem(Language.Players)) 50 | { 51 | if (tabItem.Success) 52 | PlayerSummaryComponent.Draw(); 53 | } 54 | using (var tabItem = ImRaii.TabItem(Language.Encounters)) 55 | { 56 | if (tabItem.Success) 57 | PlayerEncounterComponent.Draw(); 58 | } 59 | 60 | using (var tabItem = ImRaii.TabItem(Language.History)) 61 | { 62 | if (tabItem.Success) 63 | PlayerHistoryComponent.Draw(); 64 | } 65 | 66 | using (var tabItem = ImRaii.TabItem(Language.Settings)) 67 | { 68 | if (tabItem.Success) 69 | { 70 | using var indent = ImRaii.PushIndent(6f); 71 | ImGuiHelpers.ScaledDummy(3f); 72 | 73 | using var innerTabBar = ImRaii.TabBar("###PlayerConfigTabBar", ImGuiTabBarFlags.None); 74 | if (innerTabBar.Success) 75 | { 76 | player.PlayerConfig.PlayerConfigType = PlayerConfigType.Player; 77 | player.PlayerConfig = PlayerConfigComponent.DrawPlayerConfigTabs(player); 78 | if (player.PlayerConfig.IsChanged) 79 | { 80 | player.PlayerConfig.IsChanged = false; 81 | PlayerConfigService.UpdateConfig(player.Id, player.PlayerConfig); 82 | } 83 | } 84 | } 85 | } 86 | 87 | using (var tabItem = ImRaii.TabItem(Language.Actions)) 88 | { 89 | if (tabItem.Success) 90 | PlayerActionComponent.Draw(); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Windows/Main/Views/PlayerList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Numerics; 3 | using Dalamud.Interface.Utility; 4 | using Dalamud.Bindings.ImGui; 5 | using PlayerTrack.Domain; 6 | using PlayerTrack.Models; 7 | using PlayerTrack.Windows.Main.Components; 8 | using PlayerTrack.Windows.Main.Presenters; 9 | using PlayerTrack.Windows.Views; 10 | 11 | namespace PlayerTrack.Windows.Main.Views; 12 | 13 | public class PlayerList : PlayerTrackView, IViewWithPanel 14 | { 15 | private readonly IMainPresenter Presenter; 16 | private readonly PlayerListComponent PlayerListComponent; 17 | private bool IsPendingSizeUpdate; 18 | private Vector2 LastSize; 19 | 20 | public PlayerList(string name, PluginConfig config, IMainPresenter presenter, ImGuiWindowFlags flags = ImGuiWindowFlags.None) : base(name, config, flags) 21 | { 22 | Presenter = presenter; 23 | PlayerListComponent = new PlayerListComponent(presenter); 24 | PlayerListComponent.OnPlayerListComponentOpenConfig += () => OnOpenConfig?.Invoke(); 25 | } 26 | 27 | public delegate void OpenConfigDelegate(); 28 | public delegate void OpenPanelViewDelegate(); 29 | 30 | public event OpenConfigDelegate? OnOpenConfig; 31 | public event OpenPanelViewDelegate? OnOpenPanelView; 32 | 33 | public override void Draw() 34 | { 35 | UpdateWindowSizes(); 36 | CheckResize(); 37 | PlayerListComponent.Draw(); 38 | } 39 | 40 | public override void Initialize() 41 | { 42 | SetWindowFlags(); 43 | IsPendingSizeUpdate = true; 44 | UpdateWindowSizes(); 45 | ValidateWindowConfig(); 46 | } 47 | 48 | public void RefreshWindowConfig() 49 | { 50 | Config.PanelType = PanelType.None; 51 | Presenter.ClosePlayer(); 52 | SetWindowFlags(); 53 | } 54 | 55 | public void HidePanel() 56 | { 57 | Config.PanelType = PanelType.None; 58 | IsPendingSizeUpdate = true; 59 | } 60 | 61 | public void ShowPanel(PanelType panelType) 62 | { 63 | Config.PanelType = panelType; 64 | OnOpenPanelView?.Invoke(); 65 | IsOpen = true; 66 | IsPendingSizeUpdate = true; 67 | } 68 | 69 | public void TogglePanel(PanelType panelType) 70 | { 71 | if (Config.PanelType == panelType) 72 | HidePanel(); 73 | else 74 | ShowPanel(panelType); 75 | } 76 | 77 | private void CheckResize() 78 | { 79 | if (ImGui.GetWindowSize() != LastSize) 80 | { 81 | LastSize = ImGui.GetWindowSize(); 82 | Config.MainWindowHeight = LastSize.Y / ImGuiHelpers.GlobalScale; 83 | ServiceContext.ConfigService.SaveConfig(Config); 84 | } 85 | } 86 | 87 | private void UpdateWindowSizes() 88 | { 89 | if (!IsPendingSizeUpdate) 90 | { 91 | SizeCondition = ImGuiCond.FirstUseEver; 92 | return; 93 | } 94 | 95 | IsPendingSizeUpdate = false; 96 | SizeConstraints = new WindowSizeConstraints 97 | { 98 | MinimumSize = new Vector2(221f, 120f), 99 | MaximumSize = new Vector2(221f, 1000f), 100 | }; 101 | Size = new Vector2(221f, Config.MainWindowHeight); 102 | SizeCondition = ImGuiCond.Always; 103 | } 104 | 105 | private void ValidateWindowConfig() 106 | { 107 | if (!Enum.IsDefined(Config.PanelType)) 108 | Config.PanelType = PanelType.None; 109 | 110 | if (Config.MainWindowHeight is < 120f or > 1000f) 111 | Config.MainWindowHeight = 120f; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /PlayerTrack.Plugin/Infrastructure/Repositories/TagRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Linq; 5 | using AutoMapper; 6 | 7 | using Dapper; 8 | using FluentDapperLite.Repository; 9 | using PlayerTrack.Models; 10 | 11 | namespace PlayerTrack.Infrastructure; 12 | 13 | public class TagRepository : BaseRepository 14 | { 15 | public TagRepository(IDbConnection connection, IMapper mapper) : base(connection, mapper) { } 16 | 17 | public IEnumerable? GetAllTags() 18 | { 19 | Plugin.PluginLog.Verbose("Entering TagRepository.GetAllTags()."); 20 | try 21 | { 22 | const string sql = "SELECT * FROM tags"; 23 | var tagDTOs = Connection.Query(sql); 24 | return Mapper.Map>(tagDTOs); 25 | } 26 | catch (Exception ex) 27 | { 28 | Plugin.PluginLog.Error(ex, "Failed to get all tags from the database."); 29 | return null; 30 | } 31 | } 32 | 33 | public int CreateTag(Tag tag) 34 | { 35 | const string sql = 36 | "INSERT INTO tags (name, color, created, updated) VALUES (@name, @color, @created, @updated) RETURNING id"; 37 | 38 | var tagDTO = Mapper.Map(tag); 39 | SetCreateTimestamp(tagDTO); 40 | 41 | var newId = Connection.ExecuteScalar(sql, tagDTO); 42 | return newId; 43 | } 44 | 45 | public bool UpdateTag(Tag tag) 46 | { 47 | Plugin.PluginLog.Verbose($"Entering TagRepository.UpdateTag(): {tag.Name}."); 48 | try 49 | { 50 | var tagDTO = Mapper.Map(tag); 51 | SetUpdateTimestamp(tagDTO); 52 | const string sql = "UPDATE tags SET name = @name, color = @color, updated = @updated WHERE id = @id"; 53 | Connection.Execute(sql, tagDTO); 54 | return true; 55 | } 56 | catch (Exception ex) 57 | { 58 | Plugin.PluginLog.Error(ex, $"Failed to update tag {tag.Name}.", tag); 59 | return false; 60 | } 61 | } 62 | 63 | public bool DeleteTag(int id) 64 | { 65 | Plugin.PluginLog.Verbose($"Entering TagRepository.DeleteTag(): {id}."); 66 | try 67 | { 68 | const string sql = "DELETE FROM tags WHERE id = @id"; 69 | Connection.Execute(sql, new { id }); 70 | return true; 71 | } 72 | catch (Exception ex) 73 | { 74 | Plugin.PluginLog.Error(ex, $"Failed to delete tag by ID {id}."); 75 | return false; 76 | } 77 | } 78 | 79 | public bool CreateTags(List tags) 80 | { 81 | Plugin.PluginLog.Verbose($"Entering TagRepository.CreateTags(): {tags.Count}."); 82 | using var transaction = Connection.BeginTransaction(); 83 | try 84 | { 85 | const string sql = @" 86 | INSERT INTO tags ( 87 | id, 88 | name, 89 | color, 90 | created, 91 | updated) 92 | VALUES ( 93 | @id, 94 | @name, 95 | @color, 96 | @created, 97 | @updated)"; 98 | 99 | var tagDTOs = tags.Select(Mapper.Map).ToList(); 100 | Connection.Execute(sql, tagDTOs, transaction); 101 | 102 | transaction.Commit(); 103 | return true; 104 | } 105 | catch (Exception ex) 106 | { 107 | Plugin.PluginLog.Error(ex, "Failed to create tags."); 108 | transaction.Rollback(); 109 | return false; 110 | } 111 | } 112 | } 113 | --------------------------------------------------------------------------------