diff --git a/src/main/java/net/tfminecraft/simplefactions/enums/Brackets.java b/src/main/java/net/tfminecraft/simplefactions/enums/Brackets.java index b71cac0b..27fce511 100644 --- a/src/main/java/net/tfminecraft/simplefactions/enums/Brackets.java +++ b/src/main/java/net/tfminecraft/simplefactions/enums/Brackets.java @@ -7,6 +7,7 @@ public enum Brackets { VASSAL_TAX("Vassal Tax"), DIVIDEND_TAX("Dividend Tax"), TARIFFS("Tariffs"), + HUB_TAX("Hub Tax"), VEHICLE_TAX("Vehicle Tax (% of upkeep)"), REGISTRATION_FEE("Registration Fee (x upkeep)"), TRANSFER_FEE("Transfer Fee (x upkeep)"); diff --git a/src/main/java/net/tfminecraft/simplefactions/enums/Rules.java b/src/main/java/net/tfminecraft/simplefactions/enums/Rules.java index 7d1defb9..9d54a4f7 100644 --- a/src/main/java/net/tfminecraft/simplefactions/enums/Rules.java +++ b/src/main/java/net/tfminecraft/simplefactions/enums/Rules.java @@ -14,6 +14,8 @@ public enum Rules { GUILD_TAX("Can Collect Guild Taxes", true), //Implemented DIVIDEND_TAX("Can Collect Dividend Taxes", true), //Implemented TARIFFS("Can Impose Tariffs", true), //Implemented + HUB_TAX("Can Collect Hub Taxes", true), //Implemented + SUPPLY_HUBS("Can Build Supply Hubs", true), //Implemented VEHICLE_TAX("Can Collect Vehicle Tax", true), //Implemented REGISTRATION_FEE("Can Charge Vehicle Registration Fees", true), //Implemented TRANSFER_FEE("Can Charge Vehicle Transfer Fees", true), //Implemented diff --git a/src/main/java/net/tfminecraft/simplefactions/guild/hub/HubNetwork.java b/src/main/java/net/tfminecraft/simplefactions/guild/hub/HubNetwork.java index b552a696..562134cd 100644 --- a/src/main/java/net/tfminecraft/simplefactions/guild/hub/HubNetwork.java +++ b/src/main/java/net/tfminecraft/simplefactions/guild/hub/HubNetwork.java @@ -51,7 +51,8 @@ public static List linksFor(Guild guild) { if (guild == null || guild.getId() == null) { return List.of(); } - return links.getOrDefault(guild.getId(), List.of()); + List cached = links.getOrDefault(guild.getId(), List.of()); + return cached.isEmpty() || SupplyHubService.allowsSupplyHubs(guild) ? cached : List.of(); } /** Rebuilds every guild's connections. Server thread only. */ @@ -179,9 +180,6 @@ private static List activeSites(Guild guild, List guilds) { List sites = new ArrayList<>(); String guildFactionId = guild.getFaction() == null ? null : guild.getFaction().getId(); for (SupplyHub hub : guild.getSupplyHubs()) { - if (SupplyHubService.beyondGuildLimit(guild, hub)) { - continue; - } Installation installation = SupplyHubService.findInstallation(hub.ownerFactionId(), hub.installationId()); if (installation == null) { @@ -190,7 +188,7 @@ private static List activeSites(Guild guild, List guilds) { Faction owner = FactionManager.getByString(hub.ownerFactionId()); boolean permit = owner != null && owner.hasHubPermit(guild.getId()); HubStanding standing = SupplyHubService.standing( - hub, + guild, hub, true, SupplyHubService.ownerAllows(guildFactionId, hub.ownerFactionId(), permit), InstallationConfigLoader.getHubSlots(installation.getKind(), installation.getLevel()), diff --git a/src/main/java/net/tfminecraft/simplefactions/guild/hub/HubTaxService.java b/src/main/java/net/tfminecraft/simplefactions/guild/hub/HubTaxService.java index ab686a42..820d5aa6 100644 --- a/src/main/java/net/tfminecraft/simplefactions/guild/hub/HubTaxService.java +++ b/src/main/java/net/tfminecraft/simplefactions/guild/hub/HubTaxService.java @@ -49,7 +49,7 @@ public static HubTaxBreakdown assess(ProvinceManager provinces, Guild guild, Lis boolean allowed = SupplyHubService.ownerAllows( guild.getFaction().getId(), host.getId(), host.hasHubPermit(guild.getId())); if (!SupplyHubService.standing( - hub, true, allowed, + guild, hub, true, allowed, InstallationConfigLoader.getHubSlots(installation.getKind(), installation.getLevel()), SupplyHubService.atInstallation(host.getId(), installation.getId(), guilds)).active()) { continue; diff --git a/src/main/java/net/tfminecraft/simplefactions/guild/hub/SupplyHubCommands.java b/src/main/java/net/tfminecraft/simplefactions/guild/hub/SupplyHubCommands.java index 02baf58f..fba455bc 100644 --- a/src/main/java/net/tfminecraft/simplefactions/guild/hub/SupplyHubCommands.java +++ b/src/main/java/net/tfminecraft/simplefactions/guild/hub/SupplyHubCommands.java @@ -186,6 +186,10 @@ private static boolean build(Player player) { player.sendMessage("§cOnly the guild leader can build a supply hub"); return true; } + if (!SupplyHubService.allowsSupplyHubs(guild)) { + player.sendMessage(SupplyHubService.buildFailureMessage(BuildFailure.ECONOMY_DISALLOWS, null, 0)); + return true; + } List covering = covering(player.getLocation()); if (covering.isEmpty()) { player.sendMessage("§cYou are not standing inside an installation"); @@ -305,10 +309,6 @@ private static void listPermits(Player player, Faction faction) { } private static String statusOf(Guild guild, SupplyHub hub, Installation installation) { - if (SupplyHubService.beyondGuildLimit(guild, hub)) { - return SupplyHubService.statusText(new HubStanding( - false, SupplyHubService.DormantReason.BEYOND_GUILD_LIMIT)); - } String guildFactionId = guild.getFaction() == null ? null : guild.getFaction().getId(); Faction owner = FactionManager.getByString(hub.ownerFactionId()); boolean permit = owner != null && owner.hasHubPermit(guild.getId()); @@ -319,7 +319,7 @@ private static String statusOf(Guild guild, SupplyHub hub, Installation installa List atInstallation = SupplyHubService.atInstallation( hub.ownerFactionId(), hub.installationId(), SupplyHubService.allGuilds()); HubStanding standing = SupplyHubService.standing( - hub, + guild, hub, installation != null, SupplyHubService.ownerAllows(guildFactionId, hub.ownerFactionId(), permit), slots, diff --git a/src/main/java/net/tfminecraft/simplefactions/guild/hub/SupplyHubService.java b/src/main/java/net/tfminecraft/simplefactions/guild/hub/SupplyHubService.java index ef08e87e..c8f089ab 100644 --- a/src/main/java/net/tfminecraft/simplefactions/guild/hub/SupplyHubService.java +++ b/src/main/java/net/tfminecraft/simplefactions/guild/hub/SupplyHubService.java @@ -13,6 +13,8 @@ import net.tfminecraft.simplefactions.guild.Guild; import net.tfminecraft.simplefactions.guild.GuildModifierOverride; import net.tfminecraft.simplefactions.enums.GuildModifier; +import net.tfminecraft.simplefactions.enums.Rules; +import net.tfminecraft.simplefactions.guild.income.IncomePreviewContext; import net.tfminecraft.simplefactions.installation.Installation; import net.tfminecraft.simplefactions.installation.InstallationKind; import net.tfminecraft.simplefactions.managers.FactionManager; @@ -39,6 +41,7 @@ private SupplyHubService() { } public enum BuildFailure { + ECONOMY_DISALLOWS, KIND_DISALLOWS, ALREADY_HAS_HUB, HUB_LIMIT, @@ -48,6 +51,7 @@ public enum BuildFailure { } public enum DormantReason { + ECONOMY_DISALLOWS, INSTALLATION_GONE, NO_PERMIT, BEYOND_HUB_SLOTS, @@ -195,6 +199,8 @@ public static BuildFailure checkBuild( public static String buildFailureMessage(BuildFailure failure, String kindDisplayName, int limit) { String kind = kindDisplayName == null || kindDisplayName.isBlank() ? "installation" : kindDisplayName; switch (failure) { + case ECONOMY_DISALLOWS: + return "§cYour faction's economy does not allow supply hubs"; case KIND_DISALLOWS: return "§cA " + kind + " cannot host a supply hub"; case ALREADY_HAS_HUB: @@ -246,6 +252,32 @@ public static boolean togglePermit(List permits, String guildId) { return true; } + public static boolean allowsSupplyHubs(Guild guild) { + Faction faction = guild == null ? null : guild.getFaction(); + if (faction == null) { + return true; + } + IncomePreviewContext context = IncomePreviewContext.current(); + return context != null ? context.allowsHubRule(faction, Rules.SUPPLY_HUBS) + : faction.hasFactionRule(Rules.SUPPLY_HUBS); + } + + public static HubStanding standing( + Guild guild, + SupplyHub hub, + boolean installationExists, + boolean allowed, + int hubSlots, + List atInstallationOldestFirst) { + if (!allowsSupplyHubs(guild)) { + return new HubStanding(false, DormantReason.ECONOMY_DISALLOWS); + } + if (beyondGuildLimit(guild, hub)) { + return new HubStanding(false, DormantReason.BEYOND_GUILD_LIMIT); + } + return standing(hub, installationExists, allowed, hubSlots, atInstallationOldestFirst); + } + public static HubStanding standing( SupplyHub hub, boolean installationExists, @@ -278,6 +310,8 @@ public static String dormantReasonText(DormantReason reason) { return "dormant"; } switch (reason) { + case ECONOMY_DISALLOWS: + return "your faction's economy does not allow supply hubs"; case INSTALLATION_GONE: return "its installation no longer exists"; case NO_PERMIT: diff --git a/src/main/java/net/tfminecraft/simplefactions/guild/income/IncomePreviewContext.java b/src/main/java/net/tfminecraft/simplefactions/guild/income/IncomePreviewContext.java index 9dcbac2e..f40cb21f 100644 --- a/src/main/java/net/tfminecraft/simplefactions/guild/income/IncomePreviewContext.java +++ b/src/main/java/net/tfminecraft/simplefactions/guild/income/IncomePreviewContext.java @@ -249,7 +249,54 @@ private boolean replaces(TaxHandler handler, TaxTarget query, String id) { return true; } + public boolean allowsHubRule(Faction owner, Rules rule) { + if (!previewsLaw(owner)) { + return owner.hasFactionRule(rule); + } + LawEffect proposed = law.getScopedEffects().get(Scope.FACTION); + if (proposed != null && Boolean.FALSE.equals(proposed.getRules().get(rule))) { + return false; + } + boolean foundTrue = proposed != null && Boolean.TRUE.equals(proposed.getRules().get(rule)); + if (owner.getLawHandler() != null) { + for (Law current : owner.getLawHandler().getCurrentLaws()) { + if (current.getGroup().equals(law.getGroup())) continue; + LawEffect effect = current.getScopedEffects().get(Scope.FACTION); + if (effect != null && effect.getRules().containsKey(rule)) { + if (!effect.getRules().get(rule)) return false; + foundTrue = true; + } + } + } + if (foundTrue) return true; + Faction overlord = owner.getOverlord(); + return overlord == null || !Boolean.FALSE.equals(overlord.getExplicitRule(Scope.VASSALS, rule)); + } + + private Bracket hubTaxBracket() { + Bracket bracket = TaxHandler.DEFAULT_HUB_TAX_BRACKET; + boolean foundGroup = false; + if (faction.getLawHandler() != null) { + for (Law current : faction.getLawHandler().getCurrentLaws()) { + if (current.getGroup().equals(law.getGroup())) { + current = law; + foundGroup = true; + } + LawEffect effect = current.getScopedEffects().get(Scope.FACTION); + if (effect != null && effect.getBrackets().containsKey(Brackets.HUB_TAX)) { + bracket = effect.getBrackets().get(Brackets.HUB_TAX); + } + } + } + LawEffect proposed = law.getScopedEffects().get(Scope.FACTION); + return !foundGroup && proposed != null + ? proposed.getBrackets().getOrDefault(Brackets.HUB_TAX, bracket) : bracket; + } + private double clampForLaw(TaxTarget target, double rate) { + if (target == TaxTarget.HUB_TAX) { + return allowsHubRule(faction, Rules.HUB_TAX) ? clamp(rate, hubTaxBracket()) : 0; + } LawEffect effect = law.getScopedEffects().get(Scope.FACTION); if (effect == null) { return rate; @@ -261,8 +308,7 @@ private double clampForLaw(TaxTarget target, double rate) { } if (effect.hasBrackets()) { for (Map.Entry entry : effect.getBrackets().entrySet()) { - if ((BracketToTaxTarget.convert(entry.getKey()) == family - || (family == TaxTarget.HUB_TAX && entry.getKey() == Brackets.TARIFFS)) + if (BracketToTaxTarget.convert(entry.getKey()) == family && entry.getValue() != null) { rate = clamp(rate, entry.getValue()); } @@ -309,7 +355,8 @@ private static Rules ruleFor(TaxTarget target) { case GUILDS, GUILD_ID -> Rules.GUILD_TAX; case VASSALS, VASSAL_ID -> Rules.VASSAL_TAX; case DIVIDENDS -> Rules.DIVIDEND_TAX; - case TARIFFS, TARIFF_ID, HUB_TAX -> Rules.TARIFFS; + case TARIFFS, TARIFF_ID -> Rules.TARIFFS; + case HUB_TAX -> Rules.HUB_TAX; default -> null; }; } diff --git a/src/main/java/net/tfminecraft/simplefactions/guild/income/Ledger.java b/src/main/java/net/tfminecraft/simplefactions/guild/income/Ledger.java index fff714d3..12792495 100644 --- a/src/main/java/net/tfminecraft/simplefactions/guild/income/Ledger.java +++ b/src/main/java/net/tfminecraft/simplefactions/guild/income/Ledger.java @@ -33,6 +33,7 @@ import net.tfminecraft.simplefactions.utils.Formatter; import net.tfminecraft.simplefactions.utils.PostSettlementPayouts.PlayerUuidLookup; import net.tfminecraft.simplefactions.enums.FactionModifiers; +import net.tfminecraft.simplefactions.enums.Rules; import net.tfminecraft.simplefactions.government.proposal.TaxTarget; import net.tfminecraft.simplefactions.mercenary.company.MercenaryCompany; import net.tfminecraft.simplefactions.mercenary.contract.MercenaryEngagements; @@ -425,8 +426,17 @@ public Map getPayableHubTaxes() { if (hubTax == null || guild.getFaction() == null || skipsMoneyMovement()) { return Map.of(); } + IncomePreviewContext context = IncomePreviewContext.current(); + if (context != null && context.previewsLaw(guild.getFaction()) + && !context.allowsHubRule(guild.getFaction(), Rules.SUPPLY_HUBS)) { + return Map.of(); + } Map payable = new HashMap<>(); for (Map.Entry entry : hubTax.getTaxesByFaction().entrySet()) { + boolean allowed = context != null + ? context.allowsHubRule(entry.getKey(), Rules.HUB_TAX) + : entry.getKey().hasFactionRule(Rules.HUB_TAX); + if (!allowed) continue; Guild receiver = entry.getKey().getOrCreateMainGuild(); if (entry.getValue() > 0 && receiver != null && receiver.getLedger() != null && !receiver.getLedger().skipsMoneyMovement()) { diff --git a/src/main/java/net/tfminecraft/simplefactions/laws/LawEffect.java b/src/main/java/net/tfminecraft/simplefactions/laws/LawEffect.java index c2686455..ef5ff9c4 100644 --- a/src/main/java/net/tfminecraft/simplefactions/laws/LawEffect.java +++ b/src/main/java/net/tfminecraft/simplefactions/laws/LawEffect.java @@ -216,7 +216,9 @@ public boolean affectsEconomy() { for(Rules r : rules.keySet()) { if(r == Rules.VASSAL_TAX || r == Rules.GUILD_TAX - || r == Rules.TARIFFS) { + || r == Rules.TARIFFS + || r == Rules.HUB_TAX + || r == Rules.SUPPLY_HUBS) { return true; } } diff --git a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/SupplyHubView.java b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/SupplyHubView.java index c979db37..42aa20fc 100644 --- a/src/main/java/net/tfminecraft/simplefactions/managers/inventory/SupplyHubView.java +++ b/src/main/java/net/tfminecraft/simplefactions/managers/inventory/SupplyHubView.java @@ -268,9 +268,6 @@ private static boolean isLeader(Guild guild, Player player) { } private static HubStanding standing(Guild guild, SupplyHub hub) { - if (SupplyHubService.beyondGuildLimit(guild, hub)) { - return new HubStanding(false, SupplyHubService.DormantReason.BEYOND_GUILD_LIMIT); - } Installation installation = SupplyHubService.findInstallation( hub.ownerFactionId(), hub.installationId()); Faction owner = FactionManager.getByString(hub.ownerFactionId()); @@ -280,7 +277,7 @@ private static HubStanding standing(Guild guild, SupplyHub hub) { int slots = installation == null ? 0 : InstallationConfigLoader.getHubSlots(installation.getKind(), installation.getLevel()); - return SupplyHubService.standing(hub, installation != null, allowed, slots, + return SupplyHubService.standing(guild, hub, installation != null, allowed, slots, SupplyHubService.atInstallation( hub.ownerFactionId(), hub.installationId(), SupplyHubService.allGuilds())); } diff --git a/src/main/java/net/tfminecraft/simplefactions/objects/Faction.java b/src/main/java/net/tfminecraft/simplefactions/objects/Faction.java index ae619ab4..f69a542e 100644 --- a/src/main/java/net/tfminecraft/simplefactions/objects/Faction.java +++ b/src/main/java/net/tfminecraft/simplefactions/objects/Faction.java @@ -1042,6 +1042,7 @@ public void applyLaw(Law law, LawGroup group) { LawEffect effect = law.getScopedEffects().get(Scope.FACTION); if (effect == null) { + refreshHubTaxBracket(); cancelInvalidElections(); return; } @@ -1087,6 +1088,10 @@ public void applyLaw(Law law, LawGroup group) { if (!value) taxHandler.applyBracket(TaxTarget.TARIFFS, new Bracket(0, 0)); break; + case HUB_TAX: + if (!value) + taxHandler.applyBracket(TaxTarget.HUB_TAX, new Bracket(0, 0)); + break; case VEHICLE_TAX: case REGISTRATION_FEE: case TRANSFER_FEE: @@ -1099,6 +1104,8 @@ public void applyLaw(Law law, LawGroup group) { } } + refreshHubTaxBracket(); + // --- council structure --- if (effect.affectsCouncilSize() || effect.affectsCouncilType()) { government.getCouncil().reorganize(); @@ -1118,6 +1125,18 @@ public void applyLaw(Law law, LawGroup group) { } } + private void refreshHubTaxBracket() { + Bracket bracket = TaxHandler.DEFAULT_HUB_TAX_BRACKET; + for (Law current : lawHandler.getCurrentLaws()) { + LawEffect effect = current.getScopedEffects().get(Scope.FACTION); + if (effect != null && effect.getBrackets().containsKey(Brackets.HUB_TAX)) { + bracket = effect.getBrackets().get(Brackets.HUB_TAX); + } + } + taxHandler.applyBracket(TaxTarget.HUB_TAX, + hasFactionRule(Rules.HUB_TAX) ? bracket : new Bracket(0, 0)); + } + public void applyPoliticalAction(Cause cause, Proposal proposal) { if(!proposal.isPoliticalActionProposal()) return; PoliticalAction politicalAction = proposal.getPoliticalAction(); 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 5f68aeec..5633d12d 100644 --- a/src/main/java/net/tfminecraft/simplefactions/objects/handler/TaxHandler.java +++ b/src/main/java/net/tfminecraft/simplefactions/objects/handler/TaxHandler.java @@ -15,6 +15,7 @@ import net.tfminecraft.simplefactions.enums.Rules; public class TaxHandler { + public static final Bracket DEFAULT_HUB_TAX_BRACKET = new Bracket(0, 10); private Faction f; private TaxSnapshot savedSnapshot; @@ -51,14 +52,15 @@ public double getHubTax() { } private double clampHubTax(double rate) { - return clampHubTax(rate, taxBrackets.get(TaxTarget.TARIFFS)); + return canCollectTax(TaxTarget.HUB_TAX) + ? clampHubTax(rate, getBracket(TaxTarget.HUB_TAX)) : 0; } private double clampHubTax(double rate, Bracket bracket) { if (!Double.isFinite(rate)) { return 0; } - double max = Math.min(Cache.supplyHubMaxTax, bracket == null ? 100.0 : bracket.getMax()); + double max = Math.min(Cache.supplyHubMaxTax, bracket == null ? DEFAULT_HUB_TAX_BRACKET.getMax() : bracket.getMax()); double min = Math.min(max, bracket == null ? 0.0 : bracket.getMin()); return Math.max(min, Math.min(max, rate)); } @@ -147,7 +149,7 @@ public double getTaxRate(TaxTarget target, String id, boolean effective) { double rate = switch (target) { case CITIZENS -> citizenTax; case DIVIDENDS -> dividendTax; - case HUB_TAX -> getHubTax(); + case HUB_TAX -> hubTax; case TARIFFS -> (id != null && hasSpecificTax(target, id)) ? getSpecificTax(target, id) : tariffs; @@ -173,10 +175,11 @@ public double getTaxRate(TaxTarget target, String id, boolean effective) { rate = context.adjustTax(f, this, target, id, rate); } if (target == TaxTarget.HUB_TAX) { - // adjustTax already applied the proposed law's bracket, as it does for tariffs. - Bracket bracket = context != null && context.previewsLaw(f) - ? null : taxBrackets.get(TaxTarget.TARIFFS); - rate = clampHubTax(rate, bracket); + if (context != null && context.previewsLaw(f)) { + rate = clampHubTax(rate, new Bracket(0, Cache.supplyHubMaxTax)); + } else { + rate = clampHubTax(rate); + } } return effective ? Formatter.formatDouble(rate * f.getGovernment().getTaxEfficiency()) : rate; } @@ -221,9 +224,12 @@ public void applyBracket(TaxTarget target, Bracket bracket) { dividendTax = applyBracket(dividendTax, bracket); break; + case HUB_TAX: + setHubTax(hubTax); + break; + case TARIFFS: tariffs = applyBracket(tariffs, bracket); - setHubTax(hubTax); applySpecificBracket(target, bracket); break; @@ -233,12 +239,13 @@ public void applyBracket(TaxTarget target, Bracket bracket) { } public Bracket getBracket(TaxTarget target) { - return taxBrackets.get(target); + return target == TaxTarget.HUB_TAX + ? taxBrackets.getOrDefault(target, DEFAULT_HUB_TAX_BRACKET) : taxBrackets.get(target); } public double getMin(TaxTarget target) { if(!canCollectTax(target)) return 0.0; - Bracket bracket = taxBrackets.get(target == TaxTarget.HUB_TAX ? TaxTarget.TARIFFS : target); + Bracket bracket = getBracket(target); if (bracket == null) return 0.0; if (target == TaxTarget.HUB_TAX) { return Math.min(bracket.getMin(), getMax(target)); @@ -248,9 +255,9 @@ public double getMin(TaxTarget target) { public double getMax(TaxTarget target) { if(!canCollectTax(target)) return 0.0; - Bracket bracket = taxBrackets.get(target == TaxTarget.HUB_TAX ? TaxTarget.TARIFFS : target); + Bracket bracket = getBracket(target); if (target == TaxTarget.HUB_TAX) { - return Math.min(Cache.supplyHubMaxTax, bracket == null ? 100.0 : bracket.getMax()); + return Math.min(Cache.supplyHubMaxTax, bracket == null ? DEFAULT_HUB_TAX_BRACKET.getMax() : bracket.getMax()); } if (bracket == null) return 100.0; return bracket.getMax(); @@ -270,8 +277,9 @@ public boolean canCollectTax(TaxTarget target) { return f.hasFactionRule(Rules.DIVIDEND_TAX); case TARIFFS: case TARIFF_ID: - case HUB_TAX: return f.hasFactionRule(Rules.TARIFFS); + case HUB_TAX: + return f.hasFactionRule(Rules.HUB_TAX); default: return false; } diff --git a/src/main/java/net/tfminecraft/simplefactions/utils/BracketToTaxTarget.java b/src/main/java/net/tfminecraft/simplefactions/utils/BracketToTaxTarget.java index e7aa77f8..14b611df 100644 --- a/src/main/java/net/tfminecraft/simplefactions/utils/BracketToTaxTarget.java +++ b/src/main/java/net/tfminecraft/simplefactions/utils/BracketToTaxTarget.java @@ -14,6 +14,8 @@ public static TaxTarget convert(Brackets bracket) { return TaxTarget.GUILDS; case TARIFFS: return TaxTarget.TARIFFS; + case HUB_TAX: + return TaxTarget.HUB_TAX; case VASSAL_TAX: return TaxTarget.VASSALS; default: diff --git a/src/main/resources/laws.yml b/src/main/resources/laws.yml index 5364d627..669cd868 100644 --- a/src/main/resources/laws.yml +++ b/src/main/resources/laws.yml @@ -664,6 +664,8 @@ economy: faction: rules: - tariffs false + - hub_tax false + - supply_hubs false foreign_guilds: our_territory: - trade_power(5.0) @@ -688,6 +690,7 @@ economy: faction: brackets: tariffs: "0-75" + hub_tax: "0-50" foreign_guilds: our_territory: - trade_power(-80.0) @@ -717,6 +720,7 @@ economy: faction: brackets: tariffs: "0-50" + hub_tax: "0-40" foreign_guilds: our_territory: - trade_power(-25.0) @@ -747,6 +751,7 @@ economy: faction: brackets: tariffs: "0-25" + hub_tax: "0-25" domestic_guilds: foreign_territory: - trade_power(10.0) @@ -766,6 +771,8 @@ economy: - decentralized 1 effects: faction: + brackets: + hub_tax: "0-10" rules: - tariffs false foreign_guilds: diff --git a/src/test/java/net/tfminecraft/simplefactions/guild/hub/HubTaxServiceTest.java b/src/test/java/net/tfminecraft/simplefactions/guild/hub/HubTaxServiceTest.java index cd88fbad..b63f049d 100644 --- a/src/test/java/net/tfminecraft/simplefactions/guild/hub/HubTaxServiceTest.java +++ b/src/test/java/net/tfminecraft/simplefactions/guild/hub/HubTaxServiceTest.java @@ -18,6 +18,7 @@ import net.tfminecraft.simplefactions.Cache; import net.tfminecraft.simplefactions.enums.GuildModifier; +import net.tfminecraft.simplefactions.enums.Rules; import net.tfminecraft.simplefactions.enums.Terrain; import net.tfminecraft.simplefactions.government.proposal.TaxTarget; import net.tfminecraft.simplefactions.guild.Guild; @@ -86,6 +87,7 @@ void snapshotsAssessForeignHubsWithoutChangingLiveDataOrTheNetwork() { Faction home = mock(Faction.class); Faction host = mock(Faction.class); when(home.getId()).thenReturn("home"); + when(home.hasFactionRule(Rules.SUPPLY_HUBS)).thenReturn(true); when(host.getId()).thenReturn("host"); when(host.hasHubPermit("guild")).thenReturn(true); when(host.getTaxRate(TaxTarget.HUB_TAX, null, true)).thenReturn(50.0); diff --git a/src/test/java/net/tfminecraft/simplefactions/guild/hub/SupplyHubServiceTest.java b/src/test/java/net/tfminecraft/simplefactions/guild/hub/SupplyHubServiceTest.java index e731bd40..9bf216d0 100644 --- a/src/test/java/net/tfminecraft/simplefactions/guild/hub/SupplyHubServiceTest.java +++ b/src/test/java/net/tfminecraft/simplefactions/guild/hub/SupplyHubServiceTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -30,6 +31,7 @@ import net.tfminecraft.simplefactions.database.SupplyHubData; import net.tfminecraft.simplefactions.guild.Guild; import net.tfminecraft.simplefactions.enums.GuildModifier; +import net.tfminecraft.simplefactions.enums.Rules; import net.tfminecraft.simplefactions.guild.hub.SupplyHubService.BuildFailure; import net.tfminecraft.simplefactions.guild.hub.SupplyHubService.DormantReason; import net.tfminecraft.simplefactions.guild.hub.SupplyHubService.HubCandidate; @@ -62,6 +64,46 @@ void restore() { Cache.supplyHubUpkeepGrowth = previousGrowth; } + @Test + void forbiddenEconomyRefusesBuildBeforeCheckingTheInstallation() { + boolean enabled = Cache.provincesEnabled; + Cache.provincesEnabled = true; + Guild guild = mock(Guild.class); + Faction faction = mock(Faction.class); + Player player = mock(Player.class); + when(player.getName()).thenReturn("leader"); + when(guild.getLeader()).thenReturn("leader"); + when(guild.getFaction()).thenReturn(faction); + when(faction.hasFactionRule(Rules.SUPPLY_HUBS)).thenReturn(false); + try (MockedStatic factions = mockStatic(FactionManager.class)) { + factions.when(() -> FactionManager.getGuildByMember("leader")).thenReturn(guild); + assertTrue(SupplyHubCommands.guild(player, new String[] {"hub", "build"})); + verify(player).sendMessage("§cYour faction's economy does not allow supply hubs"); + verify(player, never()).getLocation(); + } finally { + Cache.provincesEnabled = enabled; + } + } + + @Test + void forbiddenEconomyMakesExistingHubsDormantButKeepsUpkeepAndSlots() { + SupplyHub hub = hub("decentralized", "station", 1); + Guild guild = guildWith(hub); + Faction faction = mock(Faction.class); + when(guild.getFaction()).thenReturn(faction); + HubStanding standing = SupplyHubService.standing(guild, hub, true, true, 1, List.of(hub)); + assertFalse(standing.active()); + assertEquals(DormantReason.ECONOMY_DISALLOWS, standing.reason()); + assertEquals("§cDormant §7(your faction's economy does not allow supply hubs)", + SupplyHubService.statusText(standing)); + assertEquals(SupplyHubService.baseUpkeep(), SupplyHubService.dailyCost(guild)); + assertEquals(1, SupplyHubService.countAt("decentralized", "station", List.of(guild))); + + // The host's economy does not forbid a foreign guild that may build hubs. + when(faction.hasFactionRule(Rules.SUPPLY_HUBS)).thenReturn(true); + assertTrue(SupplyHubService.standing(guild, hub, true, true, 1, List.of(hub)).active()); + } + @Test void build_eachPreconditionFailsOnItsOwn() { assertEquals(BuildFailure.KIND_DISALLOWS, SupplyHubService.checkBuild(0, false, 0, 2, 0, 1, true)); diff --git a/src/test/java/net/tfminecraft/simplefactions/guild/income/BranchIncomePreviewTest.java b/src/test/java/net/tfminecraft/simplefactions/guild/income/BranchIncomePreviewTest.java index 12c71f6c..f9194797 100644 --- a/src/test/java/net/tfminecraft/simplefactions/guild/income/BranchIncomePreviewTest.java +++ b/src/test/java/net/tfminecraft/simplefactions/guild/income/BranchIncomePreviewTest.java @@ -24,6 +24,7 @@ import net.tfminecraft.simplefactions.Cache; import net.tfminecraft.simplefactions.enums.GuildModifier; +import net.tfminecraft.simplefactions.enums.Rules; import net.tfminecraft.simplefactions.enums.Terrain; import net.tfminecraft.simplefactions.government.proposal.TaxTarget; import net.tfminecraft.simplefactions.guild.Guild; @@ -67,6 +68,7 @@ void setUp() { live.start(Map.of(capital.getId(), capital, neighbour.getId(), neighbour)); faction = mock(Faction.class); + when(faction.hasFactionRule(Rules.SUPPLY_HUBS)).thenReturn(true); when(faction.getTaxRate(TaxTarget.GUILDS, "fields", true)).thenReturn(0.0); guild = mock(Guild.class); diff --git a/src/test/java/net/tfminecraft/simplefactions/guild/income/LedgerHubTaxTest.java b/src/test/java/net/tfminecraft/simplefactions/guild/income/LedgerHubTaxTest.java index 3d14a7a6..5182c1fc 100644 --- a/src/test/java/net/tfminecraft/simplefactions/guild/income/LedgerHubTaxTest.java +++ b/src/test/java/net/tfminecraft/simplefactions/guild/income/LedgerHubTaxTest.java @@ -17,6 +17,11 @@ import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; +import net.tfminecraft.simplefactions.enums.Rules; +import net.tfminecraft.simplefactions.objects.Bracket; +import net.tfminecraft.simplefactions.laws.Law; +import net.tfminecraft.simplefactions.laws.LawGroup; +import org.bukkit.configuration.file.YamlConfiguration; import net.tfminecraft.simplefactions.database.GuildData; import net.tfminecraft.simplefactions.database.JsonUtil; import net.tfminecraft.simplefactions.government.Government; @@ -54,6 +59,8 @@ void setUp() { when(host.getGovernment()).thenReturn(government); when(government.getTaxEfficiency()).thenReturn(1.0); taxes = new TaxHandler(host, 0, 0, 0, 0, 0); + when(host.hasFactionRule(Rules.HUB_TAX)).thenReturn(true); + taxes.applyBracket(TaxTarget.HUB_TAX, new Bracket(0, 50)); taxes.setHubTax(20); when(host.getTaxHandler()).thenReturn(taxes); when(host.getTaxRate(any(TaxTarget.class), nullable(String.class), anyBoolean())) @@ -163,6 +170,52 @@ void ratePreviewUsesStoredBasesAndLeavesTheAssessmentAndRateAlone() { assertEquals(50, assessment.getTaxableIncome(host)); } + @Test + void forbiddenHostCollectsNothingEvenFromAnExistingAssessment() { + when(host.hasFactionRule(Rules.HUB_TAX)).thenReturn(false); + assertEquals(0, taxes.getHubTax()); + assertEquals(0, payer.getLedger().getIncome(Cashflow.HUB_TAX_PAYMENTS)); + assertEquals(0, receiver.getLedger().getIncome(Cashflow.HUB_TAX)); + assertEquals(200, payer.getLedger().getNetIncome()); + assertTrue(Ledger.collectHistoryDay(List.of(payer, receiver)).isEmpty()); + DailyGuildTransfers transfers = new DailyGuildTransfers(); + payer.getLedger().populateDailyTransfers(transfers); + assertTrue(transfers.getTransfers().isEmpty()); + } + + @Test + void lawPreviewUsesIndependentBracketAndRuleOnBothLedgerSides() { + YamlConfiguration config = new YamlConfiguration(); + config.set("effects.faction.rules", List.of("tariffs false")); + config.set("effects.faction.brackets.hub_tax", "0-10"); + LawGroup group = mock(LawGroup.class); + IncomePreviewContext.open(IncomePreviewContext.law(host, group, new Law("economy", "free_trade", config))); + assertEquals(-5, payer.getLedger().getIncome(Cashflow.HUB_TAX_PAYMENTS)); + assertEquals(5, receiver.getLedger().getIncome(Cashflow.HUB_TAX)); + IncomePreviewContext.clear(); + + config.set("effects.faction.rules", List.of("hub_tax false", "supply_hubs false")); + IncomePreviewContext.open(IncomePreviewContext.law(host, group, new Law("economy", "decentralized", config))); + assertEquals(0, payer.getLedger().getIncome(Cashflow.HUB_TAX_PAYMENTS)); + assertEquals(0, receiver.getLedger().getIncome(Cashflow.HUB_TAX)); + IncomePreviewContext.clear(); + assertEquals(20, taxes.getHubTax()); + assertEquals(10, assessment.getTotalTax()); + } + + @Test + void dormantHubLawPreviewRemovesPaymentsWithoutChangingTheStoredAssessment() { + YamlConfiguration config = new YamlConfiguration(); + config.set("effects.faction.rules", List.of("supply_hubs false")); + IncomePreviewContext.open(IncomePreviewContext.law(payer.getFaction(), mock(LawGroup.class), + new Law("economy", "decentralized", config))); + assertEquals(0, payer.getLedger().getIncome(Cashflow.HUB_TAX_PAYMENTS)); + assertEquals(0, receiver.getLedger().getIncome(Cashflow.HUB_TAX)); + IncomePreviewContext.clear(); + assertEquals(10, assessment.getTotalTax()); + assertEquals(-10, payer.getLedger().getIncome(Cashflow.HUB_TAX_PAYMENTS)); + } + private static Guild guild(Faction faction, boolean base) { Guild guild = mock(Guild.class); Bank bank = mock(Bank.class); diff --git a/src/test/java/net/tfminecraft/simplefactions/laws/HubLawTest.java b/src/test/java/net/tfminecraft/simplefactions/laws/HubLawTest.java new file mode 100644 index 00000000..d9249dd0 --- /dev/null +++ b/src/test/java/net/tfminecraft/simplefactions/laws/HubLawTest.java @@ -0,0 +1,172 @@ +package net.tfminecraft.simplefactions.laws; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.bukkit.configuration.file.YamlConfiguration; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import net.tfminecraft.simplefactions.Cache; +import net.tfminecraft.simplefactions.enums.Brackets; +import net.tfminecraft.simplefactions.enums.Rules; +import net.tfminecraft.simplefactions.enums.Scope; +import net.tfminecraft.simplefactions.government.Government; +import net.tfminecraft.simplefactions.government.proposal.TaxTarget; +import net.tfminecraft.simplefactions.guild.income.IncomePreviewContext; +import net.tfminecraft.simplefactions.objects.Faction; +import net.tfminecraft.simplefactions.objects.handler.LawHandler; +import net.tfminecraft.simplefactions.objects.handler.TaxHandler; +import net.tfminecraft.simplefactions.utils.BracketToTaxTarget; +import net.tfminecraft.simplefactions.utils.LoreWriter; +import net.tfminecraft.tlibs.objects.api.subapi.StringFormatter; + +class HubLawTest { + private final double previousCap = Cache.supplyHubMaxTax; + private Faction faction; + private TaxHandler taxes; + private LawGroup group; + + @BeforeEach + void setUp() throws Exception { + Cache.supplyHubMaxTax = 50; + group = new LawGroup("economy", bundled().getConfigurationSection("economy")); + faction = mock(Faction.class); + LawHandler laws = mock(LawHandler.class); + when(faction.getLawHandler()).thenReturn(laws); + when(laws.getCurrentLaws()).thenAnswer(call -> List.of(group.getCurrent())); + when(faction.getGovernment()).thenReturn(mock(Government.class)); + doCallRealMethod().when(faction).hasFactionRule(any(Rules.class)); + doCallRealMethod().when(faction).getExplicitRule(any(Scope.class), any(Rules.class)); + doCallRealMethod().when(faction).applyLaw(any(Law.class), any(LawGroup.class)); + taxes = new TaxHandler(faction, 0, 0, 0, 0, 20); + setField("lawHandler", laws); + setField("taxHandler", taxes); + group.setCurrent(group.getLaw("isolationism")); + faction.applyLaw(group.getCurrent(), group); + } + + @AfterEach + void restore() { + IncomePreviewContext.clear(); + Cache.supplyHubMaxTax = previousCap; + } + + @Test + void bundledEconomyLawsHaveIndependentSettingsAndLore() { + assertEquals(TaxTarget.HUB_TAX, BracketToTaxTarget.convert(Brackets.HUB_TAX)); + for (Map.Entry setting : Map.of( + "free_trade", 10.0, "mercantilism", 25.0, + "protectionism", 40.0, "isolationism", 50.0).entrySet()) { + faction.applyLaw(group.getLaw(setting.getKey()), group); + LawEffect effect = group.getCurrent().getScopedEffects().get(Scope.FACTION); + assertEquals(0, effect.getBrackets().get(Brackets.HUB_TAX).getMin()); + assertEquals(setting.getValue(), taxes.getMax(TaxTarget.HUB_TAX)); + assertTrue(faction.hasFactionRule(Rules.HUB_TAX)); + assertTrue(faction.hasFactionRule(Rules.SUPPLY_HUBS)); + List lore = new ArrayList<>(); + LoreWriter.writeEffect(Scope.FACTION, effect, lore); + assertTrue(lore.stream().anyMatch(line -> line.contains("Hub Tax") && line.contains("Range:"))); + } + faction.applyLaw(group.getLaw("decentralized"), group); + LawEffect effect = group.getCurrent().getScopedEffects().get(Scope.FACTION); + assertFalse(effect.getRules().get(Rules.HUB_TAX)); + assertFalse(effect.getRules().get(Rules.SUPPLY_HUBS)); + assertTrue(effect.affectsEconomy()); + List lore = new ArrayList<>(); + LoreWriter.writeEffect(Scope.FACTION, effect, lore); + assertTrue(lore.contains(StringFormatter.formatHex(" #d65c5c✖ #d4c9aeCan Collect Hub Taxes"))); + assertTrue(lore.contains(StringFormatter.formatHex(" #d65c5c✖ #d4c9aeCan Build Supply Hubs"))); + } + + @Test + void freeTradeAllowsTenPercentHubTaxWhileTariffsStayOff() { + taxes.setHubTax(45); + taxes.setSpecificTax(TaxTarget.TARIFFS, "foreign", 35); + faction.applyLaw(group.getLaw("free_trade"), group); + assertEquals(10, taxes.getHubTax()); + assertTrue(taxes.canCollectTax(TaxTarget.HUB_TAX)); + assertEquals(0, taxes.getTariffs()); + assertEquals(0, taxes.getTaxRate(TaxTarget.TARIFFS, "foreign", false)); + assertFalse(taxes.canCollectTax(TaxTarget.TARIFFS)); + } + + @Test + void lawChangesPullDownTheSavedRateAndRestoreDefaultsAfterABan() { + taxes.setHubTax(50); + faction.applyLaw(group.getLaw("protectionism"), group); + assertEquals(40, taxes.getHubTax()); + faction.applyLaw(group.getLaw("mercantilism"), group); + assertEquals(25, taxes.getHubTax()); + faction.applyLaw(group.getLaw("decentralized"), group); + assertEquals(0, taxes.getHubTax()); + assertEquals(0, taxes.getMax(TaxTarget.HUB_TAX)); + assertFalse(faction.hasFactionRule(Rules.SUPPLY_HUBS)); + faction.applyLaw(group.getLaw("free_trade"), group); + assertEquals(10, taxes.getMax(TaxTarget.HUB_TAX)); + taxes.setHubTax(8); + assertEquals(8, taxes.getHubTax()); + assertTrue(faction.hasFactionRule(Rules.SUPPLY_HUBS)); + } + + @Test + void oldLawsFilesDefaultBothRulesToAllowedAndTheBracketToZeroToTen() { + YamlConfiguration config = bundled(); + for (String id : config.getConfigurationSection("economy.laws").getKeys(false)) { + String path = "economy.laws." + id + ".effects.faction"; + config.set(path + ".brackets.hub_tax", null); + List rules = new ArrayList<>(config.getStringList(path + ".rules")); + rules.removeIf(rule -> rule.startsWith("hub_tax ") || rule.startsWith("supply_hubs ")); + config.set(path + ".rules", rules); + } + group = new LawGroup("economy", config.getConfigurationSection("economy")); + for (Law law : group.getLaws().values()) { + faction.applyLaw(law, group); + assertTrue(faction.hasFactionRule(Rules.HUB_TAX), law.getId()); + assertTrue(faction.hasFactionRule(Rules.SUPPLY_HUBS), law.getId()); + assertEquals(0, taxes.getMin(TaxTarget.HUB_TAX)); + assertEquals(10, taxes.getMax(TaxTarget.HUB_TAX)); + taxes.setHubTax(50); + assertEquals(10, taxes.getHubTax()); + } + } + + @Test + void previewsUseProposedSettingsAndLeaveLiveLawsAndRateAlone() { + taxes.setHubTax(40); + IncomePreviewContext.open(IncomePreviewContext.law(faction, group, group.getLaw("free_trade"))); + assertEquals(10, taxes.getTaxRate(TaxTarget.HUB_TAX, null, false)); + IncomePreviewContext.clear(); + assertEquals(40, taxes.getHubTax()); + IncomePreviewContext.open(IncomePreviewContext.law(faction, group, group.getLaw("decentralized"))); + assertEquals(0, taxes.getTaxRate(TaxTarget.HUB_TAX, null, false)); + assertFalse(faction.hasFactionRule(Rules.SUPPLY_HUBS)); + IncomePreviewContext.clear(); + assertEquals("isolationism", group.getCurrent().getId()); + assertEquals(40, taxes.getHubTax()); + } + + private void setField(String name, Object value) throws Exception { + Field field = Faction.class.getDeclaredField(name); + field.setAccessible(true); + field.set(faction, value); + } + + private static YamlConfiguration bundled() { + return YamlConfiguration.loadConfiguration(new InputStreamReader( + HubLawTest.class.getResourceAsStream("/laws.yml"), StandardCharsets.UTF_8)); + } +} diff --git a/src/test/java/net/tfminecraft/simplefactions/managers/ProvinceManagerHubTransportTest.java b/src/test/java/net/tfminecraft/simplefactions/managers/ProvinceManagerHubTransportTest.java index 7465b007..600b2f0f 100644 --- a/src/test/java/net/tfminecraft/simplefactions/managers/ProvinceManagerHubTransportTest.java +++ b/src/test/java/net/tfminecraft/simplefactions/managers/ProvinceManagerHubTransportTest.java @@ -18,10 +18,15 @@ import net.tfminecraft.simplefactions.Cache; import net.tfminecraft.simplefactions.enums.GuildModifier; +import net.tfminecraft.simplefactions.enums.Rules; import net.tfminecraft.simplefactions.diplomacy.DiplomacyHandler; import net.tfminecraft.simplefactions.enums.Terrain; import net.tfminecraft.simplefactions.government.Government; import net.tfminecraft.simplefactions.guild.Guild; +import net.tfminecraft.simplefactions.guild.income.IncomePreviewContext; +import net.tfminecraft.simplefactions.laws.Law; +import net.tfminecraft.simplefactions.laws.LawGroup; +import org.bukkit.configuration.file.YamlConfiguration; import net.tfminecraft.simplefactions.guild.hub.HubNetwork; import net.tfminecraft.simplefactions.guild.hub.HubTransport; import net.tfminecraft.simplefactions.guild.hub.HubTransport.Link; @@ -59,6 +64,7 @@ void setUp() { Faction host = mock(Faction.class); when(host.getId()).thenReturn("host"); + when(host.hasFactionRule(Rules.SUPPLY_HUBS)).thenReturn(true); guild = mock(Guild.class); when(guild.getId()).thenReturn("guild"); when(guild.getFaction()).thenReturn(host); @@ -80,6 +86,34 @@ void tearDown() { Cache.provincesEnabled = provincesWereEnabled; } + @Test + void forbiddenSupplyHubsCarryNothingAndLawPreviewSuppressesCachedLinks() { + link(new Link(1, 22, Mode.RAIL, 0, 0.7, 0.25)); + List cached = HubNetwork.linksFor(guild); + recalculate(); + assertEquals(14, trade(22), 1e-9); + YamlConfiguration config = new YamlConfiguration(); + config.set("effects.faction.rules", List.of("supply_hubs false")); + Law proposed = new Law("economy", "decentralized", config); + IncomePreviewContext.open(IncomePreviewContext.law(guild.getFaction(), mock(LawGroup.class), proposed)); + try { + ProvinceManager snapshot = provinces.createSnapshotShell(); + snapshot.copyAllDataFrom(provinces); + snapshot.recalculateForSingleGuild(guild, false); + assertTrue(HubNetwork.linksFor(guild).isEmpty()); + assertEquals(0, snapshot.get(22).getStoredGuildTrade(guild)); + assertEquals(0, snapshot.get(22).getGuildProduction(guild)); + assertEquals(14, trade(22), 1e-9); + } finally { + IncomePreviewContext.clear(); + } + assertEquals(cached, HubNetwork.linksFor(guild)); + when(guild.getFaction().hasFactionRule(Rules.SUPPLY_HUBS)).thenReturn(false); + recalculate(); + assertEquals(0, trade(22)); + assertEquals(0, production(22)); + } + @Test void withoutHubs_powerFadesAlongTheRoad() { recalculate(); diff --git a/src/test/java/net/tfminecraft/simplefactions/objects/handler/TaxHandlerHubTaxTest.java b/src/test/java/net/tfminecraft/simplefactions/objects/handler/TaxHandlerHubTaxTest.java index a022a7be..253fb09a 100644 --- a/src/test/java/net/tfminecraft/simplefactions/objects/handler/TaxHandlerHubTaxTest.java +++ b/src/test/java/net/tfminecraft/simplefactions/objects/handler/TaxHandlerHubTaxTest.java @@ -41,6 +41,7 @@ void setUp() { faction = mock(Faction.class); Government government = mock(Government.class); when(faction.hasFactionRule(Rules.TARIFFS)).thenReturn(true); + when(faction.hasFactionRule(Rules.HUB_TAX)).thenReturn(true); when(faction.getGovernment()).thenReturn(government); when(government.getTaxEfficiency()).thenReturn(0.8); handler = new TaxHandler(faction, 0, 0, 0, 0, 15); @@ -56,38 +57,40 @@ void tearDown() { } @Test - void defaultsToZeroAndCapsAtFiftyWithTariffEfficiency() { + void defaultsToZeroAndCapsAtTenWithTaxEfficiency() { assertEquals(0, handler.getHubTax()); - assertEquals(50, handler.getMax(TaxTarget.HUB_TAX)); + assertEquals(10, handler.getMax(TaxTarget.HUB_TAX)); handler.setTaxRate(TaxTarget.HUB_TAX, null, 90); - assertEquals(50, handler.getTaxRate(TaxTarget.HUB_TAX, "any", false)); - assertEquals(40, handler.getTaxRate(TaxTarget.HUB_TAX, null, true)); + assertEquals(10, handler.getTaxRate(TaxTarget.HUB_TAX, "any", false)); + assertEquals(8, handler.getTaxRate(TaxTarget.HUB_TAX, null, true)); handler.setHubTax(-1); assertEquals(0, handler.getHubTax()); } @Test - void configuredCapAndTariffBracketsBothApply() { + void configuredCapAndHubTaxBracketsBothApply() { YamlConfiguration config = new YamlConfiguration(); config.set("supply-hubs.max-tax", 25); SupplyHubService.loadConfig(config); + handler.applyBracket(TaxTarget.HUB_TAX, new Bracket(0, 50)); handler.setHubTax(80); assertEquals(25, handler.getHubTax()); assertEquals(25, handler.getMax(TaxTarget.HUB_TAX)); - handler.applyBracket(TaxTarget.TARIFFS, new Bracket(5, 20)); + handler.applyBracket(TaxTarget.HUB_TAX, new Bracket(5, 20)); assertEquals(20, handler.getHubTax()); assertEquals(5, handler.getMin(TaxTarget.HUB_TAX)); assertEquals(20, handler.getMax(TaxTarget.HUB_TAX)); - handler.applyBracket(TaxTarget.TARIFFS, new Bracket(0, 0)); + handler.applyBracket(TaxTarget.HUB_TAX, new Bracket(0, 0)); assertEquals(0, handler.getHubTax()); } @Test - void lawPreviewUsesTheProposedTariffBracketAndConfiguredCap() { + void lawPreviewUsesTheProposedHubTaxBracketAndConfiguredCap() { handler.applyBracket(TaxTarget.TARIFFS, new Bracket(0, 10)); handler.setHubTax(5); YamlConfiguration config = new YamlConfiguration(); config.set("effects.FACTION.brackets.TARIFFS", "20-80"); + config.set("effects.FACTION.brackets.HUB_TAX", "20-40"); Law proposed = new Law("taxes", "proposed", config); LawGroup group = mock(LawGroup.class); @@ -105,8 +108,46 @@ void lawPreviewUsesTheProposedTariffBracketAndConfiguredCap() { assertEquals(10, handler.getTaxRate(TaxTarget.HUB_TAX, null, false)); } + @Test + void tariffsHaveNoEffectOnHubTaxAndForbiddenHubTaxCannotBeSet() { + handler.setHubTax(9); + handler.applyBracket(TaxTarget.TARIFFS, new Bracket(0, 0)); + when(faction.hasFactionRule(Rules.TARIFFS)).thenReturn(false); + assertEquals(9, handler.getHubTax()); + assertEquals(10, handler.getMax(TaxTarget.HUB_TAX)); + assertFalse(handler.canCollectTax(TaxTarget.TARIFFS)); + + when(faction.hasFactionRule(Rules.HUB_TAX)).thenReturn(false); + handler.setTaxRate(TaxTarget.HUB_TAX, null, 10); + assertEquals(0, handler.getHubTax()); + assertEquals(0, handler.getTaxRate(TaxTarget.HUB_TAX, null, true)); + assertEquals(0, handler.getMax(TaxTarget.HUB_TAX)); + assertEquals(0, handler.getMin(TaxTarget.HUB_TAX)); + assertFalse(handler.canCollectTax(TaxTarget.HUB_TAX)); + } + + @Test + void lawPreviewUsesHubTaxRuleAndDefaultsWithoutChangingLiveRate() { + handler.applyBracket(TaxTarget.HUB_TAX, new Bracket(0, 50)); + handler.setHubTax(40); + YamlConfiguration config = new YamlConfiguration(); + config.set("effects.faction.rules", java.util.List.of("hub_tax false")); + LawGroup group = mock(LawGroup.class); + IncomePreviewContext.open(IncomePreviewContext.law(faction, group, new Law("economy", "banned", config))); + assertEquals(0, handler.getTaxRate(TaxTarget.HUB_TAX, null, false)); + IncomePreviewContext.clear(); + assertEquals(40, handler.getHubTax()); + + config.set("effects.faction.rules", java.util.List.of("tariffs false")); + IncomePreviewContext.open(IncomePreviewContext.law(faction, group, new Law("economy", "legacy", config))); + assertEquals(10, handler.getTaxRate(TaxTarget.HUB_TAX, null, false)); + IncomePreviewContext.clear(); + assertEquals(40, handler.getHubTax()); + } + @Test void hubTaxCannotAcquireSpecificRatesAndRestoresItsSavedState() { + handler.applyBracket(TaxTarget.HUB_TAX, new Bracket(0, 50)); handler.setHubTax(12); handler.setSpecificTax(TaxTarget.HUB_TAX, "foreign", 30); assertFalse(handler.hasSpecificTax(TaxTarget.HUB_TAX, "foreign")); @@ -119,6 +160,7 @@ void hubTaxCannotAcquireSpecificRatesAndRestoresItsSavedState() { @Test void factionRateRoundTripsAndOldSavesDefaultToZero() { + handler.applyBracket(TaxTarget.HUB_TAX, new Bracket(0, 50)); handler.setHubTax(23.5); FactionData data = new FactionData(); data.hubTax = handler.getHubTax();