From 5c974931d315eddc3d9ad6634fb429a7a3e5e90d Mon Sep 17 00:00:00 2001 From: Ryan <7389646+ryanbarlow97@users.noreply.github.com> Date: Sun, 27 Sep 2026 18:55:58 +0000 Subject: [PATCH] Apply faction-specific tariffs and list received tariffs in the ledger Income lookups asked for the base TARIFFS rate with the guild's faction id, but the handler ignored the id for tariffs, so per-faction tariff overrides were never charged. Tariffs were also gated on the base rate being above zero, so an override on a 0% base did nothing. - Resolve faction-specific tariffs in TaxHandler.getTaxRate - Charge the per-guild rate without the base-rate gate - Clamp specific tariffs when a tariff bracket applies - Scope the tariff proposal preview to the targeted faction, and skip factions with their own rate when previewing a base change - Ledger Tariffs item now lists factions paying us, not what we pay Co-Authored-By: Claude Opus 5.5 (1M context) --- .../government/proposal/Proposal.java | 2 +- .../managers/ProvinceManager.java | 16 ++++-- .../managers/inventory/GuildCreator.java | 19 +++++-- .../objects/handler/TaxHandler.java | 12 ++--- .../simplefactions/utils/EconomicImpact.java | 14 +++--- .../simplefactions/utils/LoreWriter.java | 2 +- .../objects/handler/TaxHandlerTariffTest.java | 49 +++++++++++++++++++ 7 files changed, 86 insertions(+), 28 deletions(-) create mode 100644 src/test/java/net/tfminecraft/simplefactions/objects/handler/TaxHandlerTariffTest.java diff --git a/src/main/java/net/tfminecraft/simplefactions/government/proposal/Proposal.java b/src/main/java/net/tfminecraft/simplefactions/government/proposal/Proposal.java index 4c76b266..a8dd05cb 100644 --- a/src/main/java/net/tfminecraft/simplefactions/government/proposal/Proposal.java +++ b/src/main/java/net/tfminecraft/simplefactions/government/proposal/Proposal.java @@ -267,7 +267,7 @@ public ItemStack getAsBook(Player p) { } else if (isTaxProposal() && tax != null) { TaxTarget target = tax.getTarget(); if (target == TaxTarget.TARIFFS || target == TaxTarget.TARIFF_ID) { - EconomicImpact.applyTariffImpact(econ, p, f, tax.getNewTax(), true, meta, true); + EconomicImpact.applyTariffImpact(econ, p, f, tax.getId(), tax.getNewTax(), true, meta, true); } else { EconomicImpact.applyTaxImpact(econ, p, f, target, tax.getId(), tax.getNewTax(), true, meta, true); } diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/ProvinceManager.java b/src/main/java/net/tfminecraft/simplefactions/managers/ProvinceManager.java index e9e96256..9004782f 100644 --- a/src/main/java/net/tfminecraft/simplefactions/managers/ProvinceManager.java +++ b/src/main/java/net/tfminecraft/simplefactions/managers/ProvinceManager.java @@ -25,6 +25,7 @@ import net.tfminecraft.simplefactions.war.resolution.PillageTradeHit; import net.tfminecraft.simplefactions.enums.GuildModifier; import net.tfminecraft.simplefactions.government.proposal.TaxTarget; +import net.tfminecraft.simplefactions.objects.handler.TaxHandler; import net.tfminecraft.simplefactions.laws.Law; import net.tfminecraft.simplefactions.laws.LawGroup; @@ -166,10 +167,10 @@ public double getIncome(Guild guild, boolean save) { Faction owner = TitleManager.getByProvince(province.getId()); if(owner != null) { if(save) guild.getTradeBreakdown().registerIncome(owner, provinceIncome); - if(owner.getTaxHandler().hasTariffs() && !RelationManager.sameRealm(owner, guild.getFaction())){ + if(!RelationManager.sameRealm(owner, guild.getFaction())){ double provinceTariffs = provinceIncome*owner.getTaxRate(TaxTarget.TARIFFS, guild.getFaction().getId(), true)/100.0; tariffs+=provinceTariffs; - if(save) { + if(save && provinceTariffs > 0) { guild.getTradeBreakdown().registerTariffs(owner, provinceTariffs); } } @@ -247,19 +248,19 @@ public void copyAllDataFrom(ProvinceManager source) { * since tariffs don't affect trade distribution. * * @param faction The faction changing its tariff rate + * @param targetId The faction a specific tariff applies to, or null for the base rate * @param newTariffRate The new tariff rate (0-100) * @return Map of guilds to their tariff impact (negative = lose income, positive = gain income) */ - public Map previewTariffRateChange(Faction faction, double newTariffRate) { + public Map previewTariffRateChange(Faction faction, String targetId, double newTariffRate) { Map impacts = new HashMap<>(); + TaxHandler taxHandler = faction.getTaxHandler(); // Initialize all guilds with 0 impact for (Guild guild : FactionManager.getAllGuilds()) { impacts.put(guild, 0.0); } - double oldTariffRate = faction.getTaxHandler().getTariffs(); - // Loop through all provinces for (Province province : provinces.values()) { Faction owner = TitleManager.getByProvince(province.getId()); @@ -270,11 +271,16 @@ public Map previewTariffRateChange(Faction faction, double newTar for (Guild guild : FactionManager.getAllGuilds()) { // Skip guilds in same realm (no tariffs within realm) if (RelationManager.sameRealm(faction, guild.getFaction())) continue; + String guildFactionId = guild.getFaction().getId(); + // A specific tariff only hits its target; a base change skips factions with their own rate + if (targetId != null ? !targetId.equalsIgnoreCase(guildFactionId) + : taxHandler.hasSpecificTax(TaxTarget.TARIFFS, guildFactionId)) continue; double provinceIncome = province.getIncome(guild); if (provinceIncome == 0) continue; // Calculate tariff impact delta + double oldTariffRate = taxHandler.getTaxRate(TaxTarget.TARIFFS, guildFactionId, false); double oldTariff = provinceIncome * (oldTariffRate / 100.0); double newTariff = provinceIncome * (newTariffRate / 100.0); double tariffDelta = -(newTariff - oldTariff); // Negative because it reduces guild income diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GuildCreator.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GuildCreator.java index e5b0a06c..4e086727 100644 --- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GuildCreator.java +++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/GuildCreator.java @@ -657,15 +657,24 @@ public ItemStack createLedgerTariffsItem(Guild g) { lore.add(StringFormatter.formatHex("#7a706aTop tariff payers")); lore.add(""); - int count = 0; - for (Faction f : g.getTradeBreakdown().getFactionsByIncomeDesc()) { - double paid = g.getTradeBreakdown().getTariffsByFaction(f); + // payerFaction -> tariffs its guilds pay us + HashMap received = new HashMap<>(); + for (Guild payer : FactionManager.getAllGuilds()) { + if (payer == null || payer.getFaction() == null) continue; + double paid = payer.getTradeBreakdown().getTariffsByFaction(g.getFaction()); if (paid <= 0) continue; + received.merge(payer.getFaction(), paid, Double::sum); + } + + List> top = new ArrayList<>(received.entrySet()); + top.sort((a, b) -> Double.compare(b.getValue(), a.getValue())); + int count = 0; + for (var e : top) { lore.add(StringFormatter.formatHex( - "#d4c9ae" + f.getName() + "#d4c9ae" + e.getKey().getName() + "#7a706a: #7fbd73+" - + String.format("%.2f", paid) + "d" + + String.format("%.2f", e.getValue()) + "d" )); if (++count >= 5) break; diff --git a/src/main/java/net/tfminecraft/simplefactions/objects/handler/TaxHandler.java b/src/main/java/net/tfminecraft/simplefactions/objects/handler/TaxHandler.java index 4f13e5d1..8c364ca8 100644 --- a/src/main/java/net/tfminecraft/simplefactions/objects/handler/TaxHandler.java +++ b/src/main/java/net/tfminecraft/simplefactions/objects/handler/TaxHandler.java @@ -36,14 +36,6 @@ public TaxHandler(Faction f, double citizenTax, double guildTax, double vassalTa this.tariffs = tariffs; } - public boolean hasTariffs() { - IncomePreviewContext context = IncomePreviewContext.current(); - if (context != null && context.affects(f)) { - return context.adjustTax(f, this, TaxTarget.TARIFFS, null, tariffs) > 0; - } - return tariffs > 0; - } - public void setTariffs(double tariffs) { this.tariffs = tariffs; } @@ -128,7 +120,8 @@ public double getTaxRate(TaxTarget target, String id, boolean effective) { double rate = switch (target) { case CITIZENS -> citizenTax; case DIVIDENDS -> dividendTax; - case TARIFFS -> tariffs; + case TARIFFS -> (id != null && hasSpecificTax(target, id)) + ? getSpecificTax(target, id) : tariffs; case GUILDS -> (id != null && hasSpecificTax(target, id)) ? getSpecificTax(target, id) : guildTax; @@ -196,6 +189,7 @@ public void applyBracket(TaxTarget target, Bracket bracket) { case TARIFFS: tariffs = applyBracket(tariffs, bracket); + applySpecificBracket(target, bracket); break; default: diff --git a/src/main/java/net/tfminecraft/simplefactions/utils/EconomicImpact.java b/src/main/java/net/tfminecraft/simplefactions/utils/EconomicImpact.java index f93d05e4..0352fe38 100644 --- a/src/main/java/net/tfminecraft/simplefactions/utils/EconomicImpact.java +++ b/src/main/java/net/tfminecraft/simplefactions/utils/EconomicImpact.java @@ -62,26 +62,26 @@ public static void applyTaxImpact( write(lore, f.getTaxHandler().getTaxChangeEffects(target, id, rate), us, shortForm); } - public static void applyTariffImpact(List lore, Player p, Faction f, double newTariffRate) { - applyTariffImpact(lore, p, f, newTariffRate, false, null, false); + public static void applyTariffImpact(List lore, Player p, Faction f, String targetId, double newTariffRate) { + applyTariffImpact(lore, p, f, targetId, newTariffRate, false, null, false); } - public static void applyTariffImpact(List lore, Player p, Faction f, double newTariffRate, boolean shortForm) { - applyTariffImpact(lore, p, f, newTariffRate, shortForm, null, false); + public static void applyTariffImpact(List lore, Player p, Faction f, String targetId, double newTariffRate, boolean shortForm) { + applyTariffImpact(lore, p, f, targetId, newTariffRate, shortForm, null, false); } public static void applyTariffImpact( - List lore, Player p, Faction f, double newTariffRate, + List lore, Player p, Faction f, String targetId, double newTariffRate, boolean shortForm, ItemMeta meta, boolean book) { Guild us = viewer(p); if (us == null) { return; } if (defer(lore, p, us, shortForm, meta, book, - prepared -> EconomicPreview.copyOf(prepared).previewTariffRateChange(f, newTariffRate))) { + prepared -> EconomicPreview.copyOf(prepared).previewTariffRateChange(f, targetId, newTariffRate))) { return; } - write(lore, SimpleFactions.getInstance().getProvinceManager().previewTariffRateChange(f, newTariffRate), us, shortForm); + write(lore, SimpleFactions.getInstance().getProvinceManager().previewTariffRateChange(f, targetId, newTariffRate), us, shortForm); } public static void applyFavourRepressChange(List lore, Player p, Faction f, Guild g, boolean favour) { diff --git a/src/main/java/net/tfminecraft/simplefactions/utils/LoreWriter.java b/src/main/java/net/tfminecraft/simplefactions/utils/LoreWriter.java index 0371231b..7f5e56b3 100644 --- a/src/main/java/net/tfminecraft/simplefactions/utils/LoreWriter.java +++ b/src/main/java/net/tfminecraft/simplefactions/utils/LoreWriter.java @@ -83,7 +83,7 @@ public static void applyProposalLore(Proposal proposal, List lore, Playe lore.add(StringFormatter.formatHex("#3f4040(#767a77Base Rate: #928d7a"+baseRate+"%#3f4040)")); } if(target == TaxTarget.TARIFFS || target == TaxTarget.TARIFF_ID) { - EconomicImpact.applyTariffImpact(lore, p, f, taxChange.getNewTax(), false, meta, false); + EconomicImpact.applyTariffImpact(lore, p, f, taxChange.getId(), taxChange.getNewTax(), false, meta, false); } else { EconomicImpact.applyTaxImpact(lore, p, f, target, taxChange.getId(), taxChange.getNewTax(), false, meta, false); } diff --git a/src/test/java/net/tfminecraft/simplefactions/objects/handler/TaxHandlerTariffTest.java b/src/test/java/net/tfminecraft/simplefactions/objects/handler/TaxHandlerTariffTest.java new file mode 100644 index 00000000..eaee0027 --- /dev/null +++ b/src/test/java/net/tfminecraft/simplefactions/objects/handler/TaxHandlerTariffTest.java @@ -0,0 +1,49 @@ +package net.tfminecraft.simplefactions.objects.handler; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.mockito.Mockito.mock; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import net.tfminecraft.simplefactions.government.proposal.TaxTarget; +import net.tfminecraft.simplefactions.objects.Bracket; +import net.tfminecraft.simplefactions.objects.Faction; + +class TaxHandlerTariffTest { + private TaxHandler handler; + + @BeforeEach + void setUp() { + handler = new TaxHandler(mock(Faction.class), 0, 0, 0, 0, 15.0); + } + + @Test + void factionSpecificTariffOverridesTheBaseRate() { + handler.setTaxRate(TaxTarget.TARIFF_ID, "fig", 0.0); + + assertEquals(0.0, handler.getTaxRate(TaxTarget.TARIFFS, "fig", false)); + assertEquals(15.0, handler.getTaxRate(TaxTarget.TARIFFS, "other", false)); + assertEquals(15.0, handler.getTaxRate(TaxTarget.TARIFFS, null, false)); + } + + @Test + void factionSpecificTariffAppliesWhenTheBaseRateIsZero() { + handler.setTaxRate(TaxTarget.TARIFFS, null, 0.0); + handler.setTaxRate(TaxTarget.TARIFF_ID, "rival", 20.0); + + assertEquals(20.0, handler.getTaxRate(TaxTarget.TARIFFS, "rival", false)); + assertEquals(0.0, handler.getTaxRate(TaxTarget.TARIFFS, "fig", false)); + } + + @Test + void closingTariffsClampsFactionSpecificRates() { + handler.setTaxRate(TaxTarget.TARIFF_ID, "rival", 30.0); + + handler.applyBracket(TaxTarget.TARIFFS, new Bracket(0, 0)); + + assertEquals(0.0, handler.getTaxRate(TaxTarget.TARIFFS, "rival", false)); + assertFalse(handler.hasSpecificTax(TaxTarget.TARIFFS, "rival")); + } +}