diff --git a/FS25_TaxMod.zip b/FS25_TaxMod.zip
new file mode 100644
index 0000000..0c8dba8
Binary files /dev/null and b/FS25_TaxMod.zip differ
diff --git a/ROADMAP.md b/ROADMAP.md
new file mode 100644
index 0000000..ea3ece8
--- /dev/null
+++ b/ROADMAP.md
@@ -0,0 +1,35 @@
+# Roadmap: FS25_TaxMod
+
+> Ecosystem role: **Markets and Economy** · Part of the Realistic Farming connected suite
+> Status: FILLED from the ecosystem audit/baseline.
+> Forward-looking only. Shipped history lives in CHANGELOG.md and the releases.
+
+## How to use this file
+- Populate the milestones below from the audit baseline once it lands.
+- Each item should be small enough to map to a `TODO.md` entry.
+- Keep it honest: near-term is committed, mid-term is intended, long-term is aspirational.
+
+## Current baseline
+- Version at baseline: v1.1.3.0
+- Audit reference: ecosystem-dev-tracking Point 1-5 (FS25_TaxMod, 2026-06-30)
+- Baseline date: 2026-06-30
+
+## Near-term (next release cycle)
+- [x] MP tax bug (F15): DONE on development (commit 8ff9989d). Annual-tax `addMoney` gated on `getIsServer`; `applyDailyTax` only accumulates. Re-verify the release asset matches dev HEAD (asset modDesc was mislabeled 1.1.4.1).
+- [x] StateLedger `TaxMod_Data` bridge live (delegate-when-present); `modSettings/` save kept as the safety copy. Shipped v1.1.5.0. (Fully retiring the FS22 path is a later cleanup.)
+- [~] SettingsHub: 5 settings registered (selfPersisted), shipped v1.1.5.0; removing TaxSettingsUI.lua + UIHelper.lua (ESC injection) still open.
+
+## Mid-term (this season)
+- [ ] NetworkSync channel `TaxMod_Sync` for settings broadcast on admin change. Not built yet.
+- [x] MasterHUD `TaxMod_HUD` registered (delegate-when-present; TaxHUD keeps its own draw/update/mouse logic). Shipped v1.1.5.0.
+- [ ] Decide the fate of `returnPercentage` (vestigial in the annual model).
+
+## Long-term / aspirational
+- [ ] Richer tax model (brackets, deductions, or period options) without breaking MP correctness.
+
+## Cross-mod / ecosystem dependencies
+- [~] Bedrock migrations: 3/4 done (StateLedger + MasterHUD + SettingsHub, v1.1.5.0); NetworkSync remaining.
+- [ ] FarmTablet TaxApp (reads `g_currentMission.taxManager`).
+
+## Deferred / parked
+- Consuming a peer earnings API instead of reading `farm.money` directly: parked; direct read is correct and simpler for v1.
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..50362da
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,32 @@
+# TODO: FS25_TaxMod
+
+> Ecosystem role: **Markets and Economy** · Part of the Realistic Farming connected suite
+> Status: FILLED from the ecosystem audit/baseline, kept current.
+> Convention: `[ ]` open · `[~]` in progress · `[x]` done · `[!]` blocked. Newest at the top of each section.
+
+## From the ecosystem audit (Arissani)
+- [x] StateLedger `TaxMod_Data` bridge live (delegate-when-present); own `modSettings/` save kept as the safety copy per the ecosystem pattern. Shipped v1.1.5.0. (Fully retiring the FS22 createXMLFile path is a later cleanup.)
+- [~] SettingsHub bridged (selfPersisted, v1.1.5.0); removing TaxSettingsUI.lua + UIHelper.lua (ESC injection) is still open (ESC retained as the standalone fallback meanwhile).
+- [ ] Decide: keep or remove `returnPercentage`.
+
+## Bugs
+- [x] MP tax bug (F15): RESOLVED on development (commit 8ff9989d, 2026-07-01). The annual-tax `addMoney` is gated on `getIsServer` (main.lua:226); `applyDailyTax` only accumulates and moves no money. Note: the mislabeled pre-release asset (modDesc 1.1.4.1) predates this, so re-verify against dev HEAD before release.
+
+## Features / enhancements
+- [~] Bedrock 3/4 built (delegate-when-present): StateLedger + MasterHUD + SettingsHub (v1.1.5.0). NetworkSync (`TaxMod_Sync`) still open.
+
+## Cross-mod integration
+- [x] SCS-011 irrigation-expense mirror: DONE + IN-GAME VERIFIED 2026-07-17 (`src/integrations/CropStressIrrigationExpense.lua`, commit 9b19058). Reads the SCS B3.2b facade (`getIrrigationSystems` / `getIrrigationCostsEnabled`) once per in-game day and records ONE aggregated "Irrigation Operations" debit via `recordExpense` (bookkeeping only, no money moved; SCS already charges it, so no double-charge). Attributes to `g_currentMission:getFarmId()` (the same local player farm SCS's FinanceIntegration debits; corrects the brief's pivot-ownership OI-1). Depreciation wired but neutral at 0 (OI-2: needs a capital value + useful life from the balance pass). Verified in-game: -€40 Irrigation Operations debit fired on the day change, no errors. Deployed, not released (batched). Soft follow-ups: confirm the amount equals SCS's actual daily deduction; check the `M0` month stamp in the ledger display.
+- [ ] TaxMod has NO self-test harness (verify gap). SCS-011 was verified via Lua 5.1 parse + the SCS-side getter test + in-game; stand up a `tools/test/` harness (mirror SoilFertilizer/SCS) before the next real logic build.
+- [x] StateLedger: `TaxMod_Data` bridge live (delegate-when-present). Shipped v1.1.5.0.
+- [ ] NetworkSync: `TaxMod_Sync` (settings broadcast). Not built yet.
+- [x] MasterHUD: `TaxMod_HUD` registered (delegate-when-present). Shipped v1.1.5.0.
+- [x] SettingsHub: 5 settings registered (selfPersisted). Shipped v1.1.5.0.
+
+## Docs / localization
+- [ ] Keep all 26 languages in step for any new setting.
+- [ ] Update README/version on each release.
+
+## Blocked / waiting on
+- [~] Bedrock migrations: 3/4 adopted (StateLedger/MasterHUD/SettingsHub, v1.1.5.0); NetworkSync remaining.
+- [!] MasterHUD GUI-visibility suppression policy (waits on: audit answer on whether MasterHUD or each panel guards GUI visibility).
diff --git a/VISION.md b/VISION.md
new file mode 100644
index 0000000..98063f7
--- /dev/null
+++ b/VISION.md
@@ -0,0 +1,41 @@
+# Vision: FS25_TaxMod
+
+> Ecosystem role: **Markets and Economy** · Part of the Realistic Farming connected suite
+> Status: FILLED from the ecosystem audit (Point 1-5, ecosystem-map, notes).
+> Last updated: 2026-07-08
+
+## 1. One-line purpose
+Recurring farm taxation: an annual (and optional daily) tax charged against the farm's finances, so profit carries an ongoing liability instead of just accumulating.
+
+## 2. Problem it solves
+FS25 has no taxation. Money accrues with zero ongoing cost, so once a farm is profitable there is nothing pulling against the balance. TaxMod adds a realistic recurring tax so wealth management and reinvestment actually matter.
+
+## 3. Design pillars
+- **Multiplayer-correct.** Tax deductions are server-authoritative. A period's tax is charged once for a farm, never once per connected client.
+- **Finance-based.** The tax scales off the farm's own money/finances, read from base-game farm data.
+- **Configurable rates.** Tax rate and cadence are settings; defaults stay reasonable.
+- **Standard persistence.** State lives in the standard save surface, not a non-standard subfolder.
+
+## 4. Role in the ecosystem
+- Public handle on `g_currentMission.taxManager` (getfenv alias `g_TaxManager`), set in `Mission00.load`.
+- Reads from (consumes): base-game farm finances via `g_farmManager:getFarmById(...).money` directly. It does NOT consume a peer economy API (it is not a confirmed consumer of getEarningsForFarm/getFertilizerSpendForFarm; that was architectural intent, not v1 behaviour).
+- Read by (consumers): FarmTablet TaxApp, via the mission handle read contract.
+- Core-API registration status (specced in Point 1-5, not yet wired):
+ - StateLedger (save/load): planned, module `TaxMod_Data`. Replaces the non-standard `modSettings/FS25_TaxMod.xml` and the FS22 createXMLFile/loadXMLFile usage.
+ - NetworkSync (MP state): planned, channel `TaxMod_Sync` (settings broadcast on admin change).
+ - MasterHUD (overlays): planned, `TaxMod_HUD` (TaxHUD already owns its draw/update/mouse logic).
+ - SettingsHub (admin settings): planned, 5 settings. Removes TaxSettingsUI.lua + UIHelper.lua (the ESC-menu injection).
+
+## 5. Explicit non-goals
+- Does not consume peer economy mods for its base. It reads farm money directly rather than another mod's earnings API.
+- Not a subsidy or income system (that is IncomeMod). TaxMod only takes money.
+- The HUD layout stays client-local.
+
+## 6. Success criteria
+- A period's tax is deducted exactly once per farm, correct in multiplayer.
+- Persistence moves to the standard save surface; no reliance on a `modSettings/` subfolder that may not exist.
+- Settings are admin-gated with no ESC-menu injection remaining after the SettingsHub move.
+
+## 7. Open questions for the audit
+- `returnPercentage` is still in the settings table and save surface but no longer drives the annual tax model. Keep for backward compat or remove?
+- MasterHUD: TaxHUD already guards on `g_gui` visibility itself. Should MasterHUD suppress draws when the GUI is visible, or does each panel keep handling that?
diff --git a/main.lua b/main.lua
index 1679be4..16571c5 100644
--- a/main.lua
+++ b/main.lua
@@ -13,11 +13,15 @@ local modName = g_currentModName
source(modDirectory .. "src/settings/UIHelper.lua")
source(modDirectory .. "src/settings/SettingsUI.lua")
source(modDirectory .. "src/ui/TaxHUD.lua")
+source(modDirectory .. "src/settings/SettingsHubBridge.lua")
+source(modDirectory .. "src/integrations/TaxStateLedgerBridge.lua") -- bedrock: optional StateLedger state bridge
+source(modDirectory .. "src/integrations/TaxMasterHUDBridge.lua") -- bedrock: optional MasterHUD draw bridge
+source(modDirectory .. "src/integrations/CropStressIrrigationExpense.lua") -- SCS-011: mirror SCS irrigation operating cost as a deductible expense
FS25TaxMod = {}
FS25TaxMod.modDir = modDirectory
FS25TaxMod.modName = modName
-FS25TaxMod.version = "1.1.3.0"
+FS25TaxMod.version = "1.1.5.0"
FS25TaxMod.Debug = false
local settings = {
@@ -45,6 +49,13 @@ local stats = {
}
FS25TaxMod.stats = stats
+-- Companion ledger: tracked money adjustments recorded by other mods via
+-- g_TaxManager.recordExpense(). Pure bookkeeping, surfaced in stats/HUD.
+-- It never moves money and never enters any tax computation.
+local LEDGER_MAX_ENTRIES = 10
+local ledger = { farms = {} }
+FS25TaxMod.ledger = ledger
+
local lastDay = -1
local lastMonth = -1
local lastMinuteCheck = -1
@@ -100,6 +111,14 @@ local function saveSettings()
setXMLInt(xmlFile, "settings.stats.lastTaxYear", stats.lastTaxYear or 0) -- New
setXMLInt(xmlFile, "settings.lastDay", tonumber(lastDay) or 1)
setXMLInt(xmlFile, "settings.lastMonth", tonumber(lastMonth) or 1)
+ local farmIndex = 0
+ for farmId, farmLedger in pairs(ledger.farms) do
+ local farmKey = string.format("settings.ledger.farm(%d)", farmIndex)
+ setXMLInt(xmlFile, farmKey .. "#farmId", farmId)
+ setXMLFloat(xmlFile, farmKey .. "#creditTotal", farmLedger.creditTotal or 0)
+ setXMLFloat(xmlFile, farmKey .. "#debitTotal", farmLedger.debitTotal or 0)
+ farmIndex = farmIndex + 1
+ end
saveXMLFile(xmlFile)
delete(xmlFile)
end
@@ -132,10 +151,115 @@ local function loadSettings()
stats.lastTaxYear = Utils.getNoNil(getXMLInt(xmlFile, "settings.stats.lastTaxYear"), 0) -- New
lastDay = Utils.getNoNil(getXMLInt(xmlFile, "settings.lastDay"), lastDay)
lastMonth = Utils.getNoNil(getXMLInt(xmlFile, "settings.lastMonth"), lastMonth)
+ ledger.farms = {}
+ local farmIndex = 0
+ while true do
+ local farmKey = string.format("settings.ledger.farm(%d)", farmIndex)
+ local farmId = getXMLInt(xmlFile, farmKey .. "#farmId")
+ if farmId == nil then break end
+ ledger.farms[farmId] = {
+ creditTotal = Utils.getNoNil(getXMLFloat(xmlFile, farmKey .. "#creditTotal"), 0),
+ debitTotal = Utils.getNoNil(getXMLFloat(xmlFile, farmKey .. "#debitTotal"), 0),
+ entries = {},
+ }
+ farmIndex = farmIndex + 1
+ end
delete(xmlFile)
log("Settings loaded", 2)
end
+-- =====================
+-- STATE SERIALIZATION (StateLedger bridge)
+-- =====================
+-- serializeState/applyState are the in-memory twins of the state portion of
+-- saveSettings/loadSettings (stats + the recordExpense ledger + the day/month
+-- cursors). They carry STATE only, never settings (SettingsHub owns those).
+-- The own XML stays the standalone safety copy; these feed the optional
+-- StateLedger module TaxMod_Data when bedrock is present. Plain-table in/out so
+-- StateLedger's generic serializer can round-trip them (number keys included).
+
+function FS25TaxMod.serializeState()
+ local farms = {}
+ for farmId, fl in pairs(ledger.farms) do
+ local entries = {}
+ if type(fl.entries) == "table" then
+ for i, e in ipairs(fl.entries) do
+ entries[i] = { amount = e.amount, label = e.label, day = e.day, month = e.month }
+ end
+ end
+ farms[farmId] = {
+ creditTotal = fl.creditTotal or 0,
+ debitTotal = fl.debitTotal or 0,
+ entries = entries,
+ }
+ end
+ return {
+ version = 1,
+ stats = {
+ totalTaxesPaid = stats.totalTaxesPaid or 0,
+ totalTaxesReturned = stats.totalTaxesReturned or 0,
+ taxesThisMonth = stats.taxesThisMonth or 0,
+ daysTaxed = stats.daysTaxed or 0,
+ monthsReturned = stats.monthsReturned or 0,
+ taxesAccumulatedAnnual = stats.taxesAccumulatedAnnual or 0,
+ lastTaxYear = stats.lastTaxYear or 0,
+ },
+ lastDay = lastDay,
+ lastMonth = lastMonth,
+ ledger = { farms = farms },
+ }
+end
+
+function FS25TaxMod.applyState(data)
+ if type(data) ~= "table" then return false end
+
+ local st = data.stats
+ if type(st) == "table" then
+ stats.totalTaxesPaid = tonumber(st.totalTaxesPaid) or stats.totalTaxesPaid or 0
+ stats.totalTaxesReturned = tonumber(st.totalTaxesReturned) or stats.totalTaxesReturned or 0
+ stats.taxesThisMonth = tonumber(st.taxesThisMonth) or stats.taxesThisMonth or 0
+ stats.daysTaxed = tonumber(st.daysTaxed) or stats.daysTaxed or 0
+ stats.monthsReturned = tonumber(st.monthsReturned) or stats.monthsReturned or 0
+ stats.taxesAccumulatedAnnual = tonumber(st.taxesAccumulatedAnnual) or stats.taxesAccumulatedAnnual or 0
+ stats.lastTaxYear = tonumber(st.lastTaxYear) or stats.lastTaxYear or 0
+ end
+
+ if data.lastDay ~= nil then lastDay = tonumber(data.lastDay) or lastDay end
+ if data.lastMonth ~= nil then lastMonth = tonumber(data.lastMonth) or lastMonth end
+
+ -- Rebuild ledger.farms IN PLACE so the table identity FS25TaxMod.ledger (and
+ -- the HUD) already holds stays valid.
+ for k in pairs(ledger.farms) do ledger.farms[k] = nil end
+ local df = data.ledger and data.ledger.farms
+ if type(df) == "table" then
+ for farmId, fl in pairs(df) do
+ local fid = tonumber(farmId)
+ if fid ~= nil and type(fl) == "table" then
+ local entries = {}
+ if type(fl.entries) == "table" then
+ for _, e in ipairs(fl.entries) do
+ if type(e) == "table" then
+ entries[#entries + 1] = {
+ amount = tonumber(e.amount) or 0,
+ label = tostring(e.label or ""),
+ day = tonumber(e.day) or 0,
+ month = tonumber(e.month) or 0,
+ }
+ end
+ end
+ end
+ ledger.farms[fid] = {
+ creditTotal = tonumber(fl.creditTotal) or 0,
+ debitTotal = tonumber(fl.debitTotal) or 0,
+ entries = entries,
+ }
+ end
+ end
+ end
+
+ return true
+end
+
local function applyDailyTax()
if not settings.enabled or not g_currentMission then return end
local farmId = g_currentMission:getFarmId()
@@ -191,7 +315,12 @@ local function applyAnnualTax()
return
end
- g_currentMission:addMoney(-taxAmount, farmId, MoneyType.OTHER, true)
+ -- Only the server may move money; the engine syncs the new balance to clients.
+ -- Guard only the addMoney (not the whole function): TaxMod has no MP stat sync, so
+ -- every machine must still reset its own accumulation below, or client HUDs drift.
+ if g_currentMission:getIsServer() then
+ g_currentMission:addMoney(-taxAmount, farmId, MoneyType.OTHER, true)
+ end
stats.totalTaxesPaid = stats.totalTaxesPaid + taxAmount -- Update total paid with annual tax
stats.taxesAccumulatedAnnual = 0 -- Reset annual accumulation
stats.lastTaxYear = currentYear -- Mark tax as paid for this year
@@ -252,6 +381,12 @@ local function createUpdateable()
if env.currentDay ~= lastDay then
lastDay = env.currentDay
applyDailyTax()
+ -- SCS-011: mirror one day of SCS irrigation operating cost
+ -- into the ledger (read-only, server-only, neutral when SCS
+ -- is absent or costs are off).
+ if CropStressIrrigationExpense then
+ CropStressIrrigationExpense.accrueDaily(FS25TaxMod)
+ end
end
-- Monthly checks for annual tax events
@@ -275,6 +410,69 @@ local function createUpdateable()
}
end
+-- =====================
+-- PUBLIC WRITE API (companion mods)
+-- =====================
+-- g_TaxManager.recordExpense(farmId, amount, label) -> boolean success
+-- Records a tracked money adjustment in the running per-farm credit/debit
+-- ledger. Positive amount = credit (money the farm received, e.g. a rebate),
+-- negative amount = debit (money the farm paid out, e.g. a wage).
+-- Bookkeeping only: it never moves money and never changes any tax
+-- computation (TaxMod taxes balance, not income). Callers are expected to be
+-- server-side money code; on a non-server peer the call is rejected.
+function FS25TaxMod.recordExpense(a, b, c, d)
+ -- Tolerate colon-style calls (g_TaxManager:recordExpense(...))
+ local farmId, amount, label = a, b, c
+ if a == FS25TaxMod then
+ farmId, amount, label = b, c, d
+ end
+
+ if g_currentMission == nil or not g_currentMission:getIsServer() then
+ Logging.warning("[%s] recordExpense: server-only API called on a non-server peer, ignoring", modName)
+ return false
+ end
+ if type(farmId) ~= "number" or farmId <= 0 then
+ Logging.warning("[%s] recordExpense: invalid farmId '%s' (positive number expected)", modName, tostring(farmId))
+ return false
+ end
+ if type(amount) ~= "number" or amount ~= amount or amount == math.huge or amount == -math.huge or amount == 0 then
+ Logging.warning("[%s] recordExpense: invalid amount '%s' (finite non-zero number expected)", modName, tostring(amount))
+ return false
+ end
+ if label ~= nil and type(label) ~= "string" then
+ Logging.warning("[%s] recordExpense: invalid label '%s' (string expected)", modName, tostring(label))
+ return false
+ end
+ label = label or "Companion expense"
+
+ local farmLedger = ledger.farms[farmId]
+ if farmLedger == nil then
+ farmLedger = { creditTotal = 0, debitTotal = 0, entries = {} }
+ ledger.farms[farmId] = farmLedger
+ end
+
+ if amount > 0 then
+ farmLedger.creditTotal = farmLedger.creditTotal + amount
+ else
+ farmLedger.debitTotal = farmLedger.debitTotal - amount
+ end
+
+ local env = g_currentMission.environment
+ table.insert(farmLedger.entries, 1, {
+ amount = amount,
+ label = label,
+ day = env and env.currentDay or 0,
+ month = env and env.currentMonth or 0,
+ })
+ while #farmLedger.entries > LEDGER_MAX_ENTRIES do
+ table.remove(farmLedger.entries)
+ end
+
+ log(string.format("recordExpense: farm %d %s%s (%s)", farmId,
+ amount > 0 and "+" or "", tostring(amount), label), 2)
+ return true
+end
+
-- =====================
-- CONSOLE COMMANDS
-- =====================
@@ -356,6 +554,21 @@ function FS25TaxMod:consoleTaxStatistics()
if stats.daysTaxed > 0 then
print("Avg daily tax: " .. formatMoney(math.floor(stats.totalTaxesPaid / stats.daysTaxed)))
end
+ print("=== Companion Ledger (recordExpense) ===")
+ local farmCount = 0
+ for farmId, farmLedger in pairs(ledger.farms) do
+ farmCount = farmCount + 1
+ print(string.format("Farm %d: credits +%s | debits -%s",
+ farmId, formatMoney(farmLedger.creditTotal), formatMoney(farmLedger.debitTotal)))
+ for i = 1, math.min(#farmLedger.entries, 3) do
+ local entry = farmLedger.entries[i]
+ print(string.format(" M%d D%d %s%s (%s)", entry.month, entry.day,
+ entry.amount > 0 and "+" or "-", formatMoney(math.abs(entry.amount)), entry.label))
+ end
+ end
+ if farmCount == 0 then
+ print("No tracked entries yet")
+ end
end
function FS25TaxMod:consoleTaxSimulate()
@@ -433,16 +646,39 @@ local function onLoad(mission)
if ok and id then
FS25TaxMod.toggleHUDEventId = id
g_inputBinding:setActionEventTextPriority(id, GS_PRIO_NORMAL)
-
log("HUD toggle registered", 2)
else
log("HUD toggle registration failed", 1)
end
+
+ -- HUD drag (enter/exit edit mode) — PLAYER context
+ local dragOk, dragId = g_inputBinding:registerActionEvent(
+ InputAction.TM_HUD_DRAG,
+ FS25TaxMod,
+ FS25TaxMod.onHUDDragInput,
+ false, true, false, true
+ )
+ if dragOk and dragId then
+ FS25TaxMod.hudDragEventId = dragId
+ g_inputBinding:setActionEventText(dragId, g_i18n:getText("input_TM_HUD_DRAG") or "Tax HUD Edit Mode")
+ log("HUD drag registered", 2)
+ end
+
g_inputBinding:endActionEventsModification()
end
end
end
+function FS25TaxMod:onHUDDragInput()
+ if not taxHUD then return end
+ if not taxHUD.visible then return end
+ if taxHUD.editMode then
+ taxHUD:exitEditMode()
+ else
+ taxHUD:enterEditMode()
+ end
+end
+
function FS25TaxMod:onToggleHUDInput()
if taxHUD then
taxHUD:toggleVisibility()
@@ -458,6 +694,24 @@ local function onMissionLoaded(mission, node)
loadSettings()
+ -- Register with SettingsHub (if installed) so FarmTablet's System Settings
+ -- app can list Tax Mod's settings. No-ops safely if SettingsHub is absent.
+ TaxSettingsHubBridge.register(FS25TaxMod)
+
+ -- Register with StateLedger (if installed) as the state load source of truth.
+ -- No-ops safely if StateLedger is absent (own XML stays primary). Runs after
+ -- loadSettings so, when the shared ledger is present, it overrides the state
+ -- portion imported from the own XML.
+ TaxStateLedgerBridge.register(FS25TaxMod)
+ if TaxStateLedgerBridge.hasState() then
+ TaxStateLedgerBridge.applyState(FS25TaxMod)
+ end
+
+ -- Register the tax HUD with MasterHUD (if installed) so it owns the single
+ -- suspend-aware draw loop. No-ops safely if MasterHUD is absent; the own
+ -- FSBaseMission.draw hook below stands down when this is active.
+ TaxMasterHUDBridge.register(FS25TaxMod)
+
local env = g_currentMission.environment
if env then
lastDay = tonumber(env.currentDay) or 1
@@ -506,6 +760,10 @@ local function onUnload()
g_inputBinding:removeActionEvent(FS25TaxMod.toggleHUDEventId)
FS25TaxMod.toggleHUDEventId = nil
end
+ if FS25TaxMod.hudDragEventId and g_inputBinding then
+ g_inputBinding:removeActionEvent(FS25TaxMod.hudDragEventId)
+ FS25TaxMod.hudDragEventId = nil
+ end
if FS25TaxMod._inputHookOriginal and PlayerInputComponent then
PlayerInputComponent.registerActionEvents = FS25TaxMod._inputHookOriginal
FS25TaxMod._inputHookOriginal = nil
@@ -525,8 +783,17 @@ FSBaseMission.update = Utils.appendedFunction(FSBaseMission.update, function(mis
if taxHUD then taxHUD:update(dt) end
end)
+-- When MasterHUD is present it drives TaxMasterHUDBridge.drawStack in its own
+-- suspend-aware loop, so this fallback hook stands down to avoid a double draw.
+-- The draw body (the taxHUD + settings.showHUD guard) lives in drawStack so the
+-- two paths can never diverge.
FSBaseMission.draw = Utils.appendedFunction(FSBaseMission.draw, function(mission)
- if taxHUD and settings.showHUD then taxHUD:draw() end
+ if TaxMasterHUDBridge and TaxMasterHUDBridge.active then return end
+ if TaxMasterHUDBridge then
+ TaxMasterHUDBridge.drawStack()
+ elseif taxHUD and settings.showHUD then
+ taxHUD:draw()
+ end
end)
Mission00.saveToXMLFile = Utils.appendedFunction(Mission00.saveToXMLFile, function(mission, xmlFilename)
diff --git a/modDesc.xml b/modDesc.xml
index bb03055..c1aa079 100644
--- a/modDesc.xml
+++ b/modDesc.xml
@@ -4,7 +4,7 @@
xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/NMC-TBone/xml-schema/main/modDesc.xsd">