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();