.github/workflows/build.yml --- YAML 1 name: Build 2 3 on: 4 workflow_dispatch: 5 push: 6 branches: 7 - main 8 9 env: 10 INTERNAL_NAME: AllaganMarket 11 CONFIGURATION: Release 12 DOTNET_CLI_TELEMETRY_OPTOUT: true 13 14 jobs: 15 build: 16 runs-on: windows-2022 17 steps: 18 - name: Checkout 19 uses: actions/checkout@v4 20 with: 21 submodules: recursive 22 - name: Setup MSBuild 23 uses: microsoft/[email protected] 24 - name: Download Dalamud 25 run: | 26 Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/latest.zip -OutFile latest.zip 27 Expand-Archive -Force latest.zip "$env:AppData\XIVLauncher\addon\Hooks\dev\" 28 - name: Restore 29 run: dotnet restore ${{ env.INTERNAL_NAME }} 30 - name: Build 31 run: dotnet build -c ${{ env.CONFIGURATION }} --no-restore ${{ env.INTERNAL_NAME }} 32 - name: Push artifacts 33 uses: actions/upload-artifact@v4 34 with: 35 name: dist 36 path: | 37 ${{ env.INTERNAL_NAME }}\bin\Release\${{ env.INTERNAL_NAME }} 38   .github/workflows/pr.yml --- YAML 1 name: Create D17 PR 2 3 on: 4 push: 5 tags: 6 - '*' 7 8 env: 9 INTERNAL_NAME: AllaganMarket 10 PLUGIN_NAME: Allagan Market 11 UPDATE_NAME: am 12 13 jobs: 14 update-repo: 15 runs-on: ubuntu-latest 16 permissions: 17 contents: write 18 steps: 19 - name: Checkout this repo 20 uses: actions/checkout@v4 21 22 - name: Extract version and changelog 23 id: changelog 24 run: | 25 VERSION=$(grep -m1 -oP '(?<=## \[)[0-9]+\.[0-9]+\.[0-9]+' ${{ env.INTERNAL_NAME }}/CHANGELOG.md) 26 echo "version=1.$VERSION" >> $GITHUB_OUTPUT 27 CHANGELOG=$(awk -v ver=$VERSION '/^#+ \[/ { if (p) { exit }; if ($2 == "["ver"]") { p=1; next} } p && NF' ${{ env.INTERNAL_NAME }}/CHANGELOG.md) 28 echo "log<<EOF" >> $GITHUB_OUTPUT 29 echo "$CHANGELOG" >> $GITHUB_OUTPUT 30 echo "EOF" >> $GITHUB_OUTPUT 31 32 - name: Extract release notes 33 id: extract-release-notes 34 uses: ffurrer2/extract-release-notes@v2 35 with: 36 release_notes_file: notes.md 37 changelog_file: ${{ env.INTERNAL_NAME }}/CHANGELOG.md 38 39 - name: Create release 40 env: 41 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 run: gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --notes-file notes.md 43 44 - name: Checkout fork of external repo 45 uses: actions/checkout@v4 46 with: 47 repository: Critical-Impact/DalamudPluginsD17 48 fetch-depth: '1' 49 path: external 50 token: ${{ secrets.PR_PAT }} 51 52 - name: Configure git and create branch 53 run: | 54 cd external 55 git remote add goatcorp https://github.com/goatcorp/DalamudPluginsD17.git 56 git fetch goatcorp 57 git checkout -b ${{ env.UPDATE_NAME }}-${{ steps.changelog.outputs.version }} goatcorp/main 58 59 - name: Configure git author 60 run: | 61 cd external 62 git config user.name "${GITHUB_ACTOR}" 63 git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" 64 65 - name: Update manifest.toml 66 run: | 67 cd external 68 pip install toml 69 python - <<EOF 70 import toml 71 from pathlib import Path 72 73 manifest_path = Path("stable/${{ env.INTERNAL_NAME }}/manifest.toml") 74 data = toml.load(manifest_path) 75 76 # Update commit and changelog 77 data["plugin"]["commit"] = "${GITHUB_SHA}" 78 data["plugin"]["version"] = "${{ steps.changelog.outputs.version }}" 79 data["plugin"]["changelog"] = """${{ steps.changelog.outputs.log }}""" 80 81 with manifest_path.open("w") as f: 82 toml.dump(data, f) 83 EOF 84 git add stable/${{ env.INTERNAL_NAME }}/manifest.toml 85 git commit -m "${{ env.PLUGIN_NAME }} ${{ steps.changelog.outputs.version }}" 86 87 - name: Push branch to fork 88 run: | 89 cd external 90 git push origin -f HEAD:${{ env.UPDATE_NAME }}-${{ steps.changelog.outputs.version }} 91   AllaganMarket/AllaganMarketPlugin.cs --- 1/2 --- C# 68 68 ITitleScreenMenu titleScreenMenu, 69 69 IDtrBar dtrBar, 70 70 IGameGui gameGui, 71 71 ICondition condition, .. 72 IObjectTable objectTable, .. 73 IPlayerState playerState) 72 74 : base( 73 75 pluginInterface, 74 76 pluginLog,   AllaganMarket/AllaganMarketPlugin.cs --- 2/2 --- C# 85 87 titleScreenMenu, 86 88 dtrBar, 87 89 gameGui, 88 90 condition, .. 91 objectTable, .. 92 playerState) 89 93 { 90 94 this.gameGui = gameGui; 91 95 this.CreateHost();   AllaganMarket/CHANGELOG.md --- 1/2 --- Text 2 2 3 All notable changes to this project will be documented in this file. 3 All notable changes to this project will be documented in this file. 4 4 5 The log versioning the plugin versioning will not match as 1.0.0.0 technically does not match semantic versioning bu 5 The log versioning the plugin versioning will not match as 0.0.0 technically does not match semantic versioning but . t the headache of trying to change this would be too much. . the headache of trying to change this would be too much. 6 Instead the changelog reader and automation surrounding plugin PRs will add the 1. back in 6 Instead the changelog reader and automation surrounding plugin PRs will add the back in 7 7 8 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 8 The format is based on [Keep a Changelog](https://keepachangelog.com/en/0/), 9 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.html). 9 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.html). 10 10 11 ## [1.3.0.0] - 2025-22-12 11 ## [3.0.1] - 2026-01-06 12 12 13 ### Fixed 13 ### Fixed .. 14 - Fixed an issue when a HQ listing attempts to use the NQ pricing for the recommended unit price .. 15 .. 16 ## [3.0.0] - 2025-22-12 .. 17 .. 18 ### Fixed 14 - Support for 7.4 19 - Support for 7.4 15 20 16 ## [1.2.0.5] - 2025-10-17 21 ## [2.0.5] - 2025-10-17 17 22 18 ### Fixed 23 ### Fixed 19 - Fixed a broken signature, I'll see if I can make this less liable to break later 24 - Fixed a broken signature, I'll see if I can make this less liable to break later 20 25 21 ## [1.2.0.4] - 2025-10-08 26 ## [2.0.4] - 2025-10-08 22 27 23 ### Fixed 28 ### Fixed 24 - Fixed broke signature 29 - Fixed broke signature 25 - Changed the way the retainer order is retrieved, this should fix some edge cases where peoples retainers were in t 30 - Changed the way the retainer order is retrieved, this should fix some edge cases where peoples retainers were in t .. he wrong order. .. he wrong order. 26 31 27 ## [1.2.0.3] - 2025-09-05 32 ## [2.0.3] - 2025-09-05 28 33 29 ### Fixed 34 ### Fixed 30 - Fixed signature mismatch 35 - Fixed signature mismatch 31 36 32 ## [1.2.0.2] - 2025-08-31 37 ## [2.0.2] - 2025-08-31 33 38 34 ### Fixed 39 ### Fixed 35 - Fixed a bug causing features/settings to not be loaded within the wizard causing it to never close 40 - Fixed a bug causing features/settings to not be loaded within the wizard causing it to never close 36 41 37 ## [1.2.0.1] - 2025-08-22 42 ## [2.0.1] - 2025-08-22 38 43 39 ### Added 44 ### Added 40 - Added debug windows available for end-users when debugging specific issues 45 - Added debug windows available for end-users when debugging specific issues   AllaganMarket/CHANGELOG.md --- 2/2 --- Text 47 - Fixed how the arrow buttons were rendered 52 - Fixed how the arrow buttons were rendered 48 - Changing the date in the sales summary should update the list instantly instead of needing a reorder 53 - Changing the date in the sales summary should update the list instantly instead of needing a reorder 49 54 50 ## [1.2.0.0] - 2025-08-09 55 ## [2.0.0] - 2025-08-09 51 56 52 ### Fixed 57 ### Fixed 53 - API13 support 58 - API13 support   AllaganMarket/Filtering/SaleFilter.cs --- 1/9 --- C# 21 21 IDataManager dataManager, 22 22 SaleTrackerService saleTrackerService, 23 23 ICharacterMonitorService characterMonitorService, 24 .. ItemUpdatePeriodSetting itemUpdatePeriodSetting, 25 .. Configuration configuration, 26 24 MediatorService mediatorService) 27 25 { 28 .. private readonly IDataManager dataManager = dataManager; 29 .. private readonly MediatorService mediatorService = mediatorService; 30 26 private readonly ExcelSheet<Item> itemSheet = dataManager.GetExcelSheet<Item>()!; 31 27 private long aggSalesTotalGil; 32 28 private long aggSoldTotalGil;   AllaganMarket/Filtering/SaleFilter.cs --- 2/9 --- C# 36 32 private List<SearchResult>? cachedSoldResults; 37 33 private ulong? characterId; 38 34 private bool? showEmpty; 39 .. private bool? needUpdating; 40 35 private bool needsRefresh; 41 36 private uint? worldId; 42 37   AllaganMarket/Filtering/SaleFilter.cs --- 3/9 --- C# 79 74 this.characterId = null; 80 75 this.worldId = null; 81 76 this.showEmpty = null; 82 .. this.needUpdating = null; 83 77 this.needsRefresh = true; 84 78 } 85 79   AllaganMarket/Filtering/SaleFilter.cs --- 4/9 --- C# 93 return this.itemSheet.GetRow(rowId); 87 return this.itemSheet.GetRow(rowId); 94 } 88 } 95 89 96 private List<SearchResult> recalculateSaleResults() 90 private List<SearchResult> RecalculateSaleResults() 97 { 91 { 98 var saleItems = this.GetSaleItems(); 92 var saleItems = this.GetSaleItems(); 99 var searchResults = new List<SearchResult>(); 93 var searchResults = new List<SearchResult>(); 100 searchResults.AddRange(saleItems.Select(c => new SearchResult() { SaleItem = c })); 94 searchResults.AddRange(saleItems.Select(c => new SearchResult() { SaleItem = c })); 101 return searchResults; 95 return searchResults; 102 } 96 } 103 97 104 private List<SearchResult> recalculateSoldResults() 98 private List<SearchResult> RecalculateSoldResults() 105 { 99 { 106 var soldItems = this.GetSoldItems(); 100 var soldItems = this.GetSoldItems(); 107 var searchResults = new List<SearchResult>(); 101 var searchResults = new List<SearchResult>();   AllaganMarket/Filtering/SaleFilter.cs --- 5/9 --- C# 111 105 112 106 public void NotifyRefresh() 113 107 { 114 108 this.mediatorService.Publish(new SaleFilterRefreshedMessage()); 115 109 } 116 110 117 111 public List<SaleItem> GetSaleItems()   AllaganMarket/Filtering/SaleFilter.cs --- 6/9 --- C# 120 { 114 { 121 this.cachedSales = this.RecalculateSaleItems(); 115 this.cachedSales = this.RecalculateSaleItems(); 122 this.cachedSoldItems = this.RecalculateSoldItems(); 116 this.cachedSoldItems = this.RecalculateSoldItems(); 123 this.cachedSaleResults = this.recalculateSaleResults(); 117 this.cachedSaleResults = this.RecalculateSaleResults(); 124 this.cachedSoldResults = this.recalculateSoldResults(); 118 this.cachedSoldResults = this.RecalculateSoldResults(); 125 this.NotifyRefresh(); 119 this.NotifyRefresh(); 126 } 120 } 127 121   AllaganMarket/Filtering/SaleFilter.cs --- 7/9 --- C# 134 { 128 { 135 this.cachedSales = this.RecalculateSaleItems(); 129 this.cachedSales = this.RecalculateSaleItems(); 136 this.cachedSoldItems = this.RecalculateSoldItems(); 130 this.cachedSoldItems = this.RecalculateSoldItems(); 137 this.cachedSaleResults = this.recalculateSaleResults(); 131 this.cachedSaleResults = this.RecalculateSaleResults(); 138 this.cachedSoldResults = this.recalculateSoldResults(); 132 this.cachedSoldResults = this.RecalculateSoldResults(); 139 this.NotifyRefresh(); 133 this.NotifyRefresh(); 140 } 134 } 141 135   AllaganMarket/Filtering/SaleFilter.cs --- 8/9 --- C# 148 { 142 { 149 this.cachedSales = this.RecalculateSaleItems(); 143 this.cachedSales = this.RecalculateSaleItems(); 150 this.cachedSoldItems = this.RecalculateSoldItems(); 144 this.cachedSoldItems = this.RecalculateSoldItems(); 151 this.cachedSaleResults = this.recalculateSaleResults(); 145 this.cachedSaleResults = this.RecalculateSaleResults(); 152 this.cachedSoldResults = this.recalculateSoldResults(); 146 this.cachedSoldResults = this.RecalculateSoldResults(); 153 this.NotifyRefresh(); 147 this.NotifyRefresh(); 154 } 148 } 155 149   AllaganMarket/Filtering/SaleFilter.cs --- 9/9 --- C# 162 { 156 { 163 this.cachedSales = this.RecalculateSaleItems(); 157 this.cachedSales = this.RecalculateSaleItems(); 164 this.cachedSoldItems = this.RecalculateSoldItems(); 158 this.cachedSoldItems = this.RecalculateSoldItems(); 165 this.cachedSaleResults = this.recalculateSaleResults(); 159 this.cachedSaleResults = this.RecalculateSaleResults(); 166 this.cachedSoldResults = this.recalculateSoldResults(); 160 this.cachedSoldResults = this.RecalculateSoldResults(); 167 this.NotifyRefresh(); 161 this.NotifyRefresh(); 168 } 162 } 169 163   AllaganMarket/Models/MarketPriceCache.cs --- C# 45 45 { 46 46 case MarketPriceCacheType.Game: 47 47 return "In-Game"; 48 .. break; 49 48 case MarketPriceCacheType.UniversalisWS: 50 .. return "Universalis"; 51 .. break; 52 49 case MarketPriceCacheType.UniversalisReq: 53 50 return "Universalis"; 54 .. break; 55 51 case MarketPriceCacheType.Override: 56 52 return "Forced Update"; 57 .. break; 58 53 } 59 54 60 55 return "Unknown";   AllaganMarket/Services/CharacterMonitorService.cs --- 1/4 --- C# 23 23 IClientState clientState, 24 24 IRetainerService retainerService, 25 25 IAddonLifecycle addonLifecycle, .. 26 IObjectTable objectTable, .. 27 IPlayerState playerState, 26 28 IPluginLog pluginLog) : ICharacterMonitorService 27 29 { 28 30 private ulong cachedRetainerId;   AllaganMarket/Services/CharacterMonitorService.cs --- 2/4 --- C# 35 37 36 public ulong ActiveRetainerId => retainerService.RetainerId; 38 public ulong ActiveRetainerId => retainerService.RetainerId; 37 39 38 public ulong ActiveCharacterId => clientState.LocalContentId; 40 public ulong ActiveCharacterId => playerState.ContentId; 39 41 40 public bool IsLoggedIn => clientState.IsLoggedIn; 42 public bool IsLoggedIn => clientState.IsLoggedIn; 41 43   AllaganMarket/Services/CharacterMonitorService.cs --- 3/4 --- C# 136 138 137 private void UpdatePlayerCharacter() 139 private void UpdatePlayerCharacter() 138 { 140 { 139 if (clientState.LocalPlayer != null && clientState.LocalContentId != 0) 141 if (objectTable.LocalPlayer != null && playerState.ContentId != 0) 140 { 142 { 141 var newCharacter = new Character( 143 var newCharacter = new Character( 142 CharacterType.Character, 144 CharacterType.Character, 143 clientState.LocalContentId, 145 playerState.ContentId, 144 clientState.LocalPlayer.Name.ToString(), 146 objectTable.LocalPlayer.Name.ToString(), 145 clientState.LocalPlayer.HomeWorld.RowId, 147 objectTable.LocalPlayer.HomeWorld.RowId, 146 clientState.LocalPlayer.ClassJob.RowId, 148 objectTable.LocalPlayer.ClassJob.RowId, 147 clientState.LocalPlayer.Level, 149 objectTable.LocalPlayer.Level, 148 0); 150 0); 149 this.Characters[clientState.LocalContentId] = newCharacter; 151 this.Characters[playerState.ContentId] = newCharacter; 150 } 152 } 151 } 153 } 152 154 153 private unsafe void UpdateRetainer() 155 private unsafe void UpdateRetainer() 154 { 156 { 155 var retainerId = this.cachedRetainerId; 157 var retainerId = this.cachedRetainerId; 156 if (retainerId != 0 && clientState.LocalPlayer != null) 158 if (retainerId != 0 && objectTable.LocalPlayer != null) 157 { 159 { 158 pluginLog.Verbose($"Updating retainers: {retainerId}"); 160 pluginLog.Verbose($"Updating retainers: {retainerId}"); 159 for (byte index = 0; index < 10; index++) 161 for (byte index = 0; index < 10; index++)   AllaganMarket/Services/CharacterMonitorService.cs --- 4/4 --- C# 169 CharacterType.Retainer, 171 CharacterType.Retainer, 170 retainerId, 172 retainerId, 171 retainerName, 173 retainerName, 172 clientState.LocalPlayer.HomeWorld.RowId, 174 objectTable.LocalPlayer.HomeWorld.RowId, 173 retainer->ClassJob, 175 retainer->ClassJob, 174 retainer->Level, 176 retainer->Level, 175 index); 177 index); 176 newRetainer.RetainerTown = retainer->Town; 178 newRetainer.RetainerTown = retainer->Town; 177 newRetainer.OwnerId = clientState.LocalContentId; 179 newRetainer.OwnerId = playerState.ContentId; 178 this.Characters[retainerId] = newRetainer; 180 this.Characters[retainerId] = newRetainer; 179 } 181 } 180 } 182 }   AllaganMarket/Services/HighlightingService.cs --- 1/4 --- C# 45 45 private readonly HighlightingRetainerListSetting retainerListSetting; 46 46 private readonly HighlightingRetainerSellListSetting retainerSellListSetting; 47 47 private readonly IGameGui gameGui; .. 48 private readonly IPlayerState playerState; 48 49 private bool retainerListModified; 49 50 private bool retainerSellListModified; 50 51 private ByteColor? originalRetainerListColor;   AllaganMarket/Services/HighlightingService.cs --- 2/4 --- C# 62 63 ExcelSheet<Item> itemSheet, 63 64 HighlightingRetainerListSetting retainerListSetting, 64 65 HighlightingRetainerSellListSetting retainerSellListSetting, 65 66 IGameGui gameGui, .. 67 IPlayerState playerState) 66 68 { 67 69 this.addonLifecycle = addonLifecycle; 68 70 this.retainerService = retainerService;   AllaganMarket/Services/HighlightingService.cs --- 3/4 --- C# 76 78 this.retainerListSetting = retainerListSetting; 77 79 this.retainerSellListSetting = retainerSellListSetting; 78 80 this.gameGui = gameGui; .. 81 this.playerState = playerState; 79 82 } 80 83 81 84 public static ByteColor ColorFromVector4(Vector4 hexString)   AllaganMarket/Services/HighlightingService.cs --- 4/4 --- C# 121 var componentList = addon->GetComponentListById(27); 124 var componentList = addon->GetComponentListById(27); 122 if (componentList != null) 125 if (componentList != null) 123 { 126 { 124 var retainers = this.characterMonitorService.GetRetainers(this.clientState.LocalContentId) 127 var retainers = this.characterMonitorService.GetRetainers(this.playerState.ContentId) 125 .OrderBy(c => c.DisplayOrder).ToArray(); 128 .OrderBy(c => c.DisplayOrder).ToArray(); 126 129 127 foreach (var index in Enumerable.Range(0, componentList->ListLength)) 130 foreach (var index in Enumerable.Range(0, componentList->ListLength))   AllaganMarket/Services/NotificationService.cs --- 1/5 --- C# 40 40 private readonly ChatNotifyUndercutLoginChatTypeSetting notifyUndercutLoginChatTypeSetting; 41 41 private readonly ICharacterMonitorService characterMonitorService; 42 42 private readonly IRetainerService retainerService; .. 43 private readonly IPlayerState playerState; 43 44 private readonly Subject<(ulong RetainerId, uint ItemId)> undercutQueue = new(); 44 45 45 46 public NotificationService(   AllaganMarket/Services/NotificationService.cs --- 2/5 --- C# 58 59 ChatNotifyUndercutLoginSetting notifyUndercutLoginSetting, 59 60 ChatNotifyUndercutLoginChatTypeSetting notifyUndercutLoginChatTypeSetting, 60 61 ICharacterMonitorService characterMonitorService, 61 62 IRetainerService retainerService, .. 63 IPlayerState playerState) 62 64 { 63 65 this.undercutService = undercutService; 64 66 this.saleTrackerService = saleTrackerService;   AllaganMarket/Services/NotificationService.cs --- 3/5 --- C# 76 78 this.notifyUndercutLoginChatTypeSetting = notifyUndercutLoginChatTypeSetting; 77 79 this.characterMonitorService = characterMonitorService; 78 80 this.retainerService = retainerService; .. 81 this.playerState = playerState; 79 82 this.undercutQueue 80 83 .Buffer(() => this.undercutQueue.Throttle(TimeSpan.FromSeconds(2))) 81 84 .Subscribe(   AllaganMarket/Services/NotificationService.cs --- 4/5 --- C# 118 ulong? characterId = null; 121 ulong? characterId = null; 119 if (characterSetting == ChatNotifyCharacterEnum.OnlyActiveCharacter) 122 if (characterSetting == ChatNotifyCharacterEnum.OnlyActiveCharacter) 120 { 123 { 121 characterId = this.clientState.LocalContentId; 124 characterId = this.playerState.ContentId; 122 } 125 } 123 126 124 var undercutHashSet = undercuts.Distinct().ToHashSet(); 127 var undercutHashSet = undercuts.Distinct().ToHashSet();   AllaganMarket/Services/NotificationService.cs --- 5/5 --- C# 131 { 134 { 132 if (this.notifyUndercutLoginSetting.CurrentValue(this.configuration)) 135 if (this.notifyUndercutLoginSetting.CurrentValue(this.configuration)) 133 { 136 { 134 var currentCharacterId = this.clientState.LocalContentId; 137 var currentCharacterId = this.playerState.ContentId; 135 var currentSales = this.saleTrackerService.GetSales(currentCharacterId, null); 138 var currentSales = this.saleTrackerService.GetSales(currentCharacterId, null); 136 List<SaleItem> undercutItems = []; 139 List<SaleItem> undercutItems = []; 137 foreach (var currentSale in currentSales) 140 foreach (var currentSale in currentSales)   AllaganMarket/Services/RetainerService.cs --- C# 10 /// <summary> 10 /// <summary> 11 /// Wrapper for the item order module's retainer ID. 11 /// Wrapper for the item order module's retainer ID. 12 /// </summary> 12 /// </summary> 13 public class RetainerService(IClientState clientState) : IRetainerService 13 public class RetainerService(IObjectTable objectTable) : IRetainerService 14 { 14 { 15 public uint RetainerWorldId => clientState.LocalPlayer?.HomeWorld.RowId ?? 0; 15 public uint RetainerWorldId => objectTable.LocalPlayer?.HomeWorld.RowId ?? 0; 16 16 17 public ulong RetainerId 17 public ulong RetainerId 18 { 18 {   AllaganMarket/Services/SaleTrackerService.cs --- C# 38 38 ILogger<SaleTrackerService> logger, 39 39 IDataManager dataManager, 40 40 ICharacterMonitorService characterMonitorService, 41 .. IChatGui chatGui, 42 .. NumberFormatInfo gilNumberFormat, 43 41 MediatorService mediatorService) : DisposableMediatorSubscriberBase(logger, mediatorService), IHostedService 44 42 { 45 43 private readonly ExcelSheet<Item> itemSheet = dataManager.GetExcelSheet<Item>()!;   AllaganMarket/Services/UndercutService.cs --- 1/7 --- C# 57 57 private readonly RoundUpDownSetting roundUpDownSetting; 58 58 private readonly RoundToSetting roundToSetting; 59 59 private readonly IFramework framework; .. 60 private readonly IObjectTable objectTable; 60 61 private uint activeHomeWorld; 61 62 62 63 public delegate void ItemUndercutDelegate(ulong retainerId, uint itemId);   AllaganMarket/Services/UndercutService.cs --- 2/7 --- C# 84 85 UndercutAllowFallbackSetting undercutAllowFallbackSetting, 85 86 RoundUpDownSetting roundUpDownSetting, 86 87 RoundToSetting roundToSetting, 87 88 IFramework framework, .. 89 IObjectTable objectTable) 88 90 { 89 91 this.websocketService = websocketService; 90 92 this.mediatorService = mediatorService;   AllaganMarket/Services/UndercutService.cs --- 3/7 --- C# 105 107 this.roundUpDownSetting = roundUpDownSetting; 106 108 this.roundToSetting = roundToSetting; 107 109 this.framework = framework; ... 110 this.objectTable = objectTable; 108 111 this.mediatorService.Subscribe<PluginLoadedMessage>(this, this.PluginLoaded); 109 112 } 110 113 public MediatorService MediatorService => this.mediatorService;   AllaganMarket/Services/UndercutService.cs --- 4/7 --- C# 571 var listings = this.accumulatedListings; 574 var listings = this.accumulatedListings; 572 this.accumulatedListings = new(); 575 this.accumulatedListings = new(); 573 576 574 var currentPlayer = this.clientState.LocalPlayer; 577 var currentPlayer = this.objectTable.LocalPlayer; 575 if (currentPlayer == null) 578 if (currentPlayer == null) 576 { 579 { 577 return; 580 return;   AllaganMarket/Services/UndercutService.cs --- 5/7 --- C# 676 this.websocketService.UnsubscribeFromChannel( 679 this.websocketService.UnsubscribeFromChannel( 677 UniversalisWebsocketService.EventType.ListingsAdd, 680 UniversalisWebsocketService.EventType.ListingsAdd, 678 this.activeHomeWorld); 681 this.activeHomeWorld); ... 682 this.websocketService.UnsubscribeFromChannel( ... 683 UniversalisWebsocketService.EventType.ListingsRemove, ... 684 this.activeHomeWorld); 679 this.activeHomeWorld = 0; 685 this.activeHomeWorld = 0; 680 } 686 } 681 } 687 } 682 688 683 private void OnLogin() 689 private void OnLogin() 684 { 690 { 685 if (this.clientState.LocalPlayer != null) 691 if (this.objectTable.LocalPlayer != null) 686 { 692 { 687 this.pluginLog.Verbose($"Subscribing to universalis websocket for world {this.clientState.LocalPlayer.H 693 this.pluginLog.Verbose($"Subscribing to universalis websocket for world {this.objectTable.LocalPlayer.H ... omeWorld.RowId}."); ... omeWorld.RowId}."); 688 this.websocketService.SubscribeToChannel( 694 this.websocketService.SubscribeToChannel( 689 UniversalisWebsocketService.EventType.ListingsAdd, 695 UniversalisWebsocketService.EventType.ListingsAdd, 690 this.clientState.LocalPlayer.HomeWorld.RowId); 696 this.objectTable.LocalPlayer.HomeWorld.RowId); ... 697 this.websocketService.SubscribeToChannel( ... 698 UniversalisWebsocketService.EventType.ListingsRemove, ... 699 this.objectTable.LocalPlayer.HomeWorld.RowId); 691 this.activeHomeWorld = this.clientState.LocalPlayer.HomeWorld.RowId; 700 this.activeHomeWorld = this.objectTable.LocalPlayer.HomeWorld.RowId; 692 } 701 } 693 } 702 } 694 703   AllaganMarket/Services/UndercutService.cs --- 6/7 --- C# 827 836 (uint)cheapestNqListing.PricePerUnit, 828 837 ownsListing); 829 838 } 830 ... else 831 ... { 832 ... this.pluginLog.Verbose($"No NQ listing received from universalis WS for {itemId}, removing cached prices"); 833 ... this.RemoveMarketPriceCache(itemId, false, message.World, DateTime.Now); 834 ... } 835 839 836 840 var cheapestHqListing = message.Listings.Where(c => c.HQ).DefaultIfEmpty(null).MinBy(c => c?.PricePerUnit ?? 0); 837 841 var oldestReviewTimeHq = message.Listings.Where(c => c.HQ).DefaultIfEmpty(null).Max(c => c?.LastReviewTime);   AllaganMarket/Services/UndercutService.cs --- 7/7 --- C# 850 (uint)cheapestHqListing.PricePerUnit, 854 (uint)cheapestHqListing.PricePerUnit, 851 ownsListing); 855 ownsListing); 852 } 856 } 853 else ... 854 { ... 855 this.pluginLog.Verbose($"No HQ listing received from universalis WS for {itemId}, removing cach ... ... ed prices"); ... 856 this.RemoveMarketPriceCache(itemId, true, message.World, DateTime.Now); ... 857 } ... 858 } 857 } 859 } 858 } ... 859 ... 860 if (message.EventType == UniversalisWebsocketService.EventType.ListingsRemove) ... 861 { ... 862 var itemId = message.Item; ... 863 if (this.saleTrackerService.SaleItemsByItemId.TryGetValue(itemId, out var value)) ... 864 { ... 865 this.pluginLog.Verbose($"Requesting new prices from universalis for {itemId}"); ... 866 //We have no idea what the lowest price is anymore, request the data again(ideally we'd cache all o ... ... f the data but we currently don't and it'd require some restructuring) ... 867 this.universalisApiService.QueuePriceCheck(itemId, message.World); ... 868 } ... 869 } 860 } 870 } 861 } 871 }   AllaganMarket/Windows/MainWindow.cs --- C# No syntactic changes.   AllaganMarket/Windows/RetainerSellOverlayWindow.cs --- C# No syntactic changes.   README.md --- Text 37 37 ## Contributing 38 38 39 39 Contributions are welcome! Feel free to open issues or submit pull requests on the GitHub repository. 40 41 ## Check out my other plugins 42 43 - [Allagan Tools](https://github.com/Critical-Impact/InventoryTools) 44 - [Allagan Item Search](https://github.com/Critical-Impact/AllaganItemSearch) 45 - [Allagan Market](https://github.com/Critical-Impact/AllaganMarket) 46 - [Allagan Tetris](https://github.com/Critical-Impact/AllaganTetris) 47 - [Tippy](https://github.com/Critical-Impact/Tippy) 48 - [Automount BGM](https://github.com/Critical-Impact/AutoMountBgm) 49 - [Fantasy Player](https://github.com/Critical-Impact/FantasyPlayer) 50