Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/main/java/net/tfminecraft/simplefactions/Cache.java
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,14 @@ public static boolean requireProvinces(org.bukkit.command.CommandSender sender)

public static double supplyHubMaxTax = 50.0;

public static double infrastructureFull = 20;
public static double infrastructureTarget = 0.75;
public static double infrastructureWildernessSpread = 0.25;
public static double infrastructureSpreadFloor = 0.5;
public static double infrastructureStation = 10;
public static double infrastructurePort = 10;
public static double infrastructureAirport = 5;

public static Map<Scope, LawEffect> baseEffects = new HashMap<>();

public static Map<Terrain, Double> tradeCarry = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public enum FactionModifiers {
STABILITY_INFLUENCE(true, false),
TRADE_POWER(true, true),
PRODUCTION(true, true),
INFRASTRUCTURE_ACCESS(true, true),
DIPLOMATIC_CAPACITY_MULTIPLIER(true, false),
ADMIN_POWER_MULTIPLIER(true, false),
ADMIN_POWER_GAIN_MULTIPLIER(true, false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public enum GuildModifier {
TRADE_POWER("#92d665Trade Power", true),
TRADE_UPKEEP("#d6645aTrade Upkeep", false),
PRODUCTION("#f2c94cProduction", true),
INFRASTRUCTURE("#86d1b0Infrastructure", true),
TRADE_CARRY("#86d1b0Trade Carry", true),
HUB_LIMIT("#c99b70Supply Hub Limit", true),
HUB_UPKEEP("#d6645aSupply Hub Upkeep", false),
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/net/tfminecraft/simplefactions/laws/LawEffect.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import net.tfminecraft.simplefactions.objects.Bracket;
import net.tfminecraft.simplefactions.objects.FactionModifier;
import net.tfminecraft.simplefactions.enums.Brackets;
import net.tfminecraft.simplefactions.enums.FactionModifiers;
import net.tfminecraft.simplefactions.enums.Region;
import net.tfminecraft.simplefactions.enums.Rules;
import net.tfminecraft.simplefactions.enums.Scope;
Expand Down Expand Up @@ -171,6 +172,19 @@ public Map<Region, List<FactionModifier>> getRegionModifiers() {
return regionModifiers;
}

/** Null means absent; an explicit zero still overrides a default. */
public Double getModifierAmount(FactionModifiers type, Region region) {
Double amount = null;
List<FactionModifier> modifiers = new ArrayList<>(globalModifiers);
modifiers.addAll(regionModifiers.getOrDefault(region, List.of()));
for (FactionModifier modifier : modifiers) {
if (modifier.getType() == type) {
amount = (amount == null ? 0 : amount) + modifier.getAmount();
}
}
return amount;
}

public Map<Rules, Boolean> getRules() {
return rules;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ public void loadConfig(File configFile) {
Cache.mercenaryDefaultBreachRefund = config.getDouble("mercenary-default-breach-refund", 500.0);
net.tfminecraft.simplefactions.guild.hub.SupplyHubService.loadConfig(config);
net.tfminecraft.simplefactions.guild.hub.HubTransport.loadConfig(config);
Cache.infrastructureFull = config.getDouble("infrastructure.full", 20);
Cache.infrastructureTarget = config.getDouble("infrastructure.target", 0.75);
Cache.infrastructureWildernessSpread = config.getDouble("infrastructure.wilderness-spread", 0.25);
double infrastructureSpreadFloor = config.getDouble("infrastructure.spread-floor", 0.5);
Cache.infrastructureSpreadFloor = Double.isFinite(infrastructureSpreadFloor) && infrastructureSpreadFloor > 0
? infrastructureSpreadFloor : 0.5;
Cache.infrastructureStation = config.getDouble("infrastructure.station", 10);
Cache.infrastructurePort = config.getDouble("infrastructure.port", 10);
Cache.infrastructureAirport = config.getDouble("infrastructure.airport", 5);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Cache.settlementLargePopulationThreshold = config.getInt("settlement-large-population-threshold", 8);
Cache.portSeaProximityBlocks = config.getInt("port-sea-proximity-blocks", 20);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.bukkit.Bukkit;
Expand All @@ -18,6 +19,9 @@
import net.tfminecraft.simplefactions.guild.hub.HubTransport.Link;
import net.tfminecraft.simplefactions.map.provinces.Province;
import net.tfminecraft.simplefactions.map.provinces.ProvinceDataEntry;
import net.tfminecraft.simplefactions.map.infra.InfrastructureAccess;
import net.tfminecraft.simplefactions.map.infra.InfrastructureSources;
import net.tfminecraft.simplefactions.map.infra.InfrastructureSpread;
import net.tfminecraft.simplefactions.objects.Bracket;
import net.tfminecraft.simplefactions.objects.Faction;
import net.tfminecraft.simplefactions.Cache;
Expand Down Expand Up @@ -107,6 +111,7 @@ public void recalculate() {
return;
}
HubNetwork.refreshIfLive(this);
recalculateInfrastructure();
dropMissingGuilds();
for(Guild g : FactionManager.getAllGuilds()) {
if (!g.hasCapital()) continue;
Expand All @@ -124,6 +129,7 @@ public void recalculateForSingleGuild(Guild g, boolean save) {
if (!Cache.provincesEnabled) {
return;
}
recalculateInfrastructure();
if (!g.hasCapital()) return;
HubNetwork.refreshIfLive(this);
dropMissingGuilds();
Expand All @@ -137,6 +143,24 @@ public void recalculateForSingleGuild(Guild g, boolean save) {
recalculateProsperity();
}

private void recalculateInfrastructure() {
Map<Integer, InfrastructureSpread.Node> graph = new HashMap<>();
for (Province province : provinces.values()) {
Faction realm = InfrastructureAccess.topRealm(province.getOwner());
String origin = realm == null ? null : realm.getId().toLowerCase(Locale.ROOT);
graph.put(province.getId(), new InfrastructureSpread.Node(
province.getTradeCarry(), !province.isSea(), origin, province.getNeighbours()));
}
Map<Integer, Double> sources = InfrastructureSources.collect(
provinces, FactionManager.getAllGuilds(), FactionManager.getCopy());
Map<Integer, InfrastructureSpread.Arrival> infrastructure = InfrastructureSpread.spread(
graph, sources, Cache.infrastructureWildernessSpread, Cache.infrastructureSpreadFloor);
for (Province province : provinces.values()) {
InfrastructureSpread.Arrival arrival = infrastructure.get(province.getId());
province.setInfrastructure(arrival == null ? 0 : arrival.amount());
}
}

private void recalculateProsperity() {
for (Province p : provinces.values()) {
p.calculateProsperity();
Expand Down Expand Up @@ -326,6 +350,7 @@ public void copyAllDataFrom(ProvinceManager source) {
}

dst.setProsperity(src.getProsperity());
dst.setInfrastructure(src.getInfrastructure());
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package net.tfminecraft.simplefactions.map.infra;

public final class EffectiveTerrain {
private EffectiveTerrain() {}

public static double calculate(double terrain, double infrastructure, double full, double target, double access) {
if (terrain >= target || infrastructure <= 0 || full <= 0) return terrain;
double fill = Math.min(1, infrastructure / full);
return terrain + (target - terrain) * fill * access;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package net.tfminecraft.simplefactions.map.infra;

import java.util.HashSet;
import java.util.Set;

import net.tfminecraft.simplefactions.enums.FactionModifiers;
import net.tfminecraft.simplefactions.enums.Region;
import net.tfminecraft.simplefactions.enums.Scope;
import net.tfminecraft.simplefactions.guild.income.IncomePreviewContext;
import net.tfminecraft.simplefactions.laws.Law;
import net.tfminecraft.simplefactions.laws.LawEffect;
import net.tfminecraft.simplefactions.laws.LawGroup;
import net.tfminecraft.simplefactions.objects.Faction;

public final class InfrastructureAccess {
private InfrastructureAccess() {}

public static double forGuild(Faction guildFaction, Faction owner) {
if (owner == null) return 1;
if (guildFaction != null && sameFaction(topRealm(guildFaction), topRealm(owner))) return 1;
double grant = amount(economyLaw(owner), Scope.FOREIGN_GUILDS, Region.OUR_TERRITORY);
double reach = amount(economyLaw(guildFaction), Scope.DOMESTIC_GUILDS, Region.FOREIGN_TERRITORY);
return Math.max(0, Math.min(1, grant + reach));
}

public static Faction topRealm(Faction faction) {
Set<Faction> seen = new HashSet<>();
Faction current = faction;
while (current != null) {
// Ignore a cyclic overlord chain rather than granting access through it.
if (!seen.add(current)) return faction;
Faction overlord = current.getOverlord();
if (overlord == null) return current;
current = overlord;
}
return null;
}

private static boolean sameFaction(Faction first, Faction second) {
return first == second || (first != null && second != null && first.getId() != null
&& first.getId().equalsIgnoreCase(second.getId()));
}

private static Law economyLaw(Faction faction) {
if (faction == null || faction.getLawHandler() == null) return null;
LawGroup group = faction.getLawHandler().getGroup("economy");
if (group == null) return null;
IncomePreviewContext context = IncomePreviewContext.current();
Law overlay = context != null && context.previewsLaw(faction)
? IncomePreviewContext.overlayLaw(group) : null;
return overlay != null ? overlay : group.getCurrent();
}

public static double amount(Law law, Scope scope, Region region) {
if (law == null) return 0;
LawEffect effect = law.getScopedEffects().get(scope);
Double explicit = effect == null ? null : effect.getModifierAmount(FactionModifiers.INFRASTRUCTURE_ACCESS, region);
if (explicit != null) return explicit;
boolean grant = scope == Scope.FOREIGN_GUILDS && region == Region.OUR_TERRITORY;
boolean reach = scope == Scope.DOMESTIC_GUILDS && region == Region.FOREIGN_TERRITORY;
if (!grant && !reach) return 0;
return switch (law.getId()) {
case "free_trade" -> grant ? 0.80 : 0.20;
case "mercantilism" -> grant ? 0.50 : 0.20;
case "decentralized" -> grant ? 0.50 : 0.00;
case "protectionism" -> grant ? 0.20 : -0.10;
case "isolationism" -> grant ? 0.00 : -0.30;
default -> 0;
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package net.tfminecraft.simplefactions.map.infra;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import net.tfminecraft.simplefactions.Cache;
import net.tfminecraft.simplefactions.enums.GuildModifier;
import net.tfminecraft.simplefactions.guild.Guild;
import net.tfminecraft.simplefactions.guild.GuildModifierOverride;
import net.tfminecraft.simplefactions.installation.Installation;
import net.tfminecraft.simplefactions.map.provinces.Province;
import net.tfminecraft.simplefactions.objects.Faction;

public final class InfrastructureSources {
private InfrastructureSources() {}

public static Map<Integer, Double> collect(
Map<Integer, Province> provinces, Collection<Guild> guilds, Collection<Faction> factions) {
Map<Integer, Double> sources = new HashMap<>();
for (Guild guild : guilds) {
if (guild == null || !guild.isBase() || !guild.hasCapital()) continue;
add(provinces, sources, guild.getCapital(), GuildModifierOverride.resolve(guild, GuildModifier.INFRASTRUCTURE));
}
for (Faction faction : factions) {
if (faction == null || faction.getInstallationHandler() == null) continue;
for (Installation installation : faction.getInstallationHandler().getAll()) {
double amount = switch (installation.getKind()) {
case TRAIN_STATION -> Cache.infrastructureStation;
case PORT -> Cache.infrastructurePort;
case AIRPORT -> Cache.infrastructureAirport;
default -> 0;
};
add(provinces, sources, installation.getProvince(), amount);
}
}
return sources;
}

private static void add(Map<Integer, Province> provinces, Map<Integer, Double> sources, int id, double amount) {
Province province = provinces.get(id);
if (province == null || province.isSea() || amount <= 0) return;
sources.merge(id, amount, Double::sum);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package net.tfminecraft.simplefactions.map.infra;

import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.PriorityQueue;

public final class InfrastructureSpread {
private InfrastructureSpread() {}

public record Node(double terrain, boolean land, String realm, Collection<Integer> neighbours) {}
public record Arrival(double amount, String origin) {}
private record Step(int province, Arrival arrival) {}

public static Map<Integer, Arrival> spread(
Map<Integer, Node> provinces, Map<Integer, Double> sources, double wilderness, double floor) {
Map<Integer, Arrival> best = new HashMap<>();
PriorityQueue<Step> pending = new PriorityQueue<>(
Comparator.comparingDouble((Step step) -> step.arrival().amount()).reversed()
.thenComparingInt(Step::province));
for (Map.Entry<Integer, Double> source : sources.entrySet()) {
Node province = provinces.get(source.getKey());
if (province == null || !province.land() || source.getValue() <= 0) continue;
Arrival arrival = new Arrival(source.getValue(), province.realm());
best.put(source.getKey(), arrival);
pending.add(new Step(source.getKey(), arrival));
}
while (!pending.isEmpty()) {
Step step = pending.remove();
Arrival here = step.arrival();
if (!here.equals(best.get(step.province())) || here.amount() < floor) continue;
for (Integer neighbour : provinces.get(step.province()).neighbours()) {
Node entered = provinces.get(neighbour);
if (entered == null || !entered.land()) continue;
if (entered.realm() != null && !Objects.equals(entered.realm(), here.origin())) continue;
double amount = here.amount() * entered.terrain();
if (entered.realm() == null) amount *= wilderness;
Arrival previous = best.get(neighbour);
if (amount < floor || (previous != null && amount <= previous.amount())) continue;
Arrival arrival = new Arrival(amount, here.origin());
best.put(neighbour, arrival);
pending.add(new Step(neighbour, arrival));
}
}
return best;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import net.tfminecraft.simplefactions.managers.TitleManager;
import net.tfminecraft.simplefactions.objects.Faction;
import net.tfminecraft.simplefactions.objects.FactionModifier;
import net.tfminecraft.simplefactions.map.infra.EffectiveTerrain;
import net.tfminecraft.simplefactions.map.infra.InfrastructureAccess;
import net.tfminecraft.simplefactions.utils.ModifierMerger;
import net.tfminecraft.simplefactions.enums.FactionModifiers;
import net.tfminecraft.simplefactions.enums.GuildModifier;
Expand All @@ -32,6 +34,7 @@ public class Province {
private int centerX;
private int centerZ;
private double prosperity = 0;
private double infrastructure = 0;
private final Set<Integer> neighbours = new HashSet<>();

public Province() {
Expand Down Expand Up @@ -88,7 +91,7 @@ public void calculateTrade(
double amount;
double carry = GuildModifierOverride.resolve(guild, GuildModifier.TRADE_CARRY);
double effectiveDistance = distance / Math.pow(carry, 1.1);
double factor = Math.pow(getTradeCarry(), effectiveDistance);
double factor = Math.pow(getTradeCarry(guild), effectiveDistance);
if (prev == -1) {
// Capital province
amount = net.tfminecraft.simplefactions.government.stability.StabilityDebuffs.realmSeed(
Expand Down Expand Up @@ -130,7 +133,7 @@ public void calculateProduction(
int distance
) {
double amount;
double terrainFactor = Math.pow(getTradeCarry(), 0.5);
double terrainFactor = Math.pow(getTradeCarry(guild), 0.5);
double factor = terrainFactor*getTradeFactor(guild);

if (prev == null) {
Expand Down Expand Up @@ -167,7 +170,7 @@ public void calculateProduction(
public double getTradeFactor(Guild guild) {
double trade = getGuildTrade(guild);
if(trade == 0) return 0.05;
double K = 2.5 / Math.pow(getTradeCarry(), 0.5);
double K = 2.5 / Math.pow(getTradeCarry(guild), 0.5);
return (trade / (trade + K));
}

Expand Down Expand Up @@ -320,7 +323,7 @@ private void spreadHubProduction(
ProvinceDataEntry prev,
int distance
) {
double factor = Math.pow(getTradeCarry(), 0.5) * getTradeFactor(guild);
double factor = Math.pow(getTradeCarry(guild), 0.5) * getTradeFactor(guild);
double amount = prev.getProduction() * factor;
if (amount < 0.1) return;

Expand All @@ -346,6 +349,16 @@ private void spreadHubProduction(
public double getTradeCarry() {
return Cache.tradeCarry.getOrDefault(terrain, 0.5);
}

public double getTradeCarry(Guild guild) {
double terrain = getTradeCarry();
if (guild == null || isSea() || infrastructure <= 0 || terrain >= Cache.infrastructureTarget) return terrain;
return EffectiveTerrain.calculate(terrain, infrastructure, Cache.infrastructureFull,
Cache.infrastructureTarget, InfrastructureAccess.forGuild(guild.getFaction(), getOwner()));
}

public double getInfrastructure() { return infrastructure; }
public void setInfrastructure(double amount) { infrastructure = amount; }
public Set<Integer> getNeighbours() {
return Collections.unmodifiableSet(neighbours);
}
Expand Down
Loading
Loading