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
3 changes: 0 additions & 3 deletions src/main/java/net/tfminecraft/simplefactions/Cache.java
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,6 @@ public static boolean requireProvinces(org.bukkit.command.CommandSender sender)
public static int mercenaryMaxContractDays = 14;
public static double mercenaryDefaultBreachRefund = 500.0;

public static int supplyHubBaseLimit = 2;
public static double supplyHubBaseUpkeep = 15.0;
public static double supplyHubUpkeepGrowth = 1.5;
public static double supplyHubMaxTax = 50.0;

public static Map<Scope, LawEffect> baseEffects = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ public void saveFaction(Faction f) {
gd.depositsToday = depositsToday.isEmpty() ? null : depositsToday;
gd.company = g.getCompany() != null ? g.getCompany().serialize() : null;
gd.supplyHubs = SupplyHubService.toData(g.getSupplyHubs());
gd.supplyHubTutorialDismissals = g.getSupplyHubTutorialDismissals();

// --- Bank ---
if (g.getBank() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,7 @@ public class GuildData {

@SerializedName("supply hubs")
public List<SupplyHubData> supplyHubs = new ArrayList<>();

@SerializedName("supply hub tutorial dismissals")
public List<String> supplyHubTutorialDismissals = new ArrayList<>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public enum GuildModifier {
PRODUCTION("#f2c94cProduction", true),
TRADE_CARRY("#86d1b0Trade Carry", true),
HUB_LIMIT("#c99b70Supply Hub Limit", true),
HUB_UPKEEP("#d6645aSupply Hub Upkeep", false),
HUB_TRADE("#b5835aHub Trade Transfer", true),
HUB_PRODUCTION("#a9744fHub Production Transfer", true),
DIPLOMATIC_CAPACITY("#56ccf2Diplomatic Capacity", true),
Expand Down Expand Up @@ -34,4 +35,8 @@ public String getName() {
public boolean isPositive() {
return positive;
}

public boolean scalesWithInactivity() {
return positive && this != HUB_LIMIT;
}
}
25 changes: 23 additions & 2 deletions src/main/java/net/tfminecraft/simplefactions/guild/Guild.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ public class Guild {
private List<String> dividendEligible = new ArrayList<>();
private MercenaryCompany company;
private final List<SupplyHub> supplyHubs = new ArrayList<>();
private final Set<String> supplyHubTutorialDismissals = new HashSet<>();

public Guild(Faction f) {
host = f;
Expand Down Expand Up @@ -246,6 +247,9 @@ public Guild(GuildData data, Faction host) {
this, data.company, MercenaryCompany.cloneMercenaryRegiment());
}
this.supplyHubs.addAll(SupplyHubService.fromData(data.supplyHubs));
if (data.supplyHubTutorialDismissals != null) {
supplyHubTutorialDismissals.addAll(data.supplyHubTutorialDismissals);
}
if(data.pillageHits != null) {
for (StabilityModifierData smd : data.pillageHits) {
if (smd == null || smd.name == null) {
Expand Down Expand Up @@ -427,6 +431,20 @@ public boolean consumeInvite(String p) {
public List<SupplyHub> getSupplyHubs() {
return supplyHubs;
}

public boolean hasDismissedSupplyHubTutorial(String playerId) {
return playerId != null && supplyHubTutorialDismissals.contains(playerId);
}

public void dismissSupplyHubTutorial(String playerId) {
if (playerId != null) {
supplyHubTutorialDismissals.add(playerId);
}
}

public List<String> getSupplyHubTutorialDismissals() {
return new ArrayList<>(supplyHubTutorialDismissals);
}
public String getName() { return isBase() ? host.getName() : name; }

/** Backing name, even while this guild is a faction base (display name follows the host). */
Expand Down Expand Up @@ -459,8 +477,11 @@ public Branch getBranch(int i) {
return branches.getOrDefault(i, null);
}
public Branch getBranch(String id) {
// A renamed branch is still found by the id it was saved under.
Branch definition = BranchLoader.getByString(id);
String current = definition == null ? id : definition.getId();
for(Branch b : branches.values()) {
if(b.getId().equalsIgnoreCase(id)) return b;
if(b.getId().equalsIgnoreCase(current)) return b;
}
return null;
}
Expand Down Expand Up @@ -693,7 +714,7 @@ public double getModifier(GuildModifier m) {
amount += u.getAmount(m);
}
// Levels stay. While the guild is inactive, the benefit of those levels fades.
if (m != null && m.isPositive()) {
if (m != null && m.scalesWithInactivity()) {
amount *= net.tfminecraft.simplefactions.inactivity.InactivityService.outputFactor(this);
}
return amount;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import net.tfminecraft.simplefactions.guild.hub.HubTransport.Rates;
import net.tfminecraft.simplefactions.guild.hub.SupplyHubService.HubStanding;
import net.tfminecraft.simplefactions.installation.Installation;
import net.tfminecraft.simplefactions.installation.InstallationKind;
import net.tfminecraft.simplefactions.loaders.InstallationConfigLoader;
import net.tfminecraft.simplefactions.managers.FactionManager;
import net.tfminecraft.simplefactions.managers.ProvinceManager;
Expand All @@ -40,7 +41,9 @@ public interface RailRoutes {
}

private static volatile Map<String, List<Link>> links = Map.of();
private static final Map<String, OptionalDouble> railDistances = new ConcurrentHashMap<>();
private record CachedRoute(OptionalDouble distance, long expiresAt) { }
private static final Map<String, CachedRoute> railDistances = new ConcurrentHashMap<>();
private static final long ROUTE_MISS_TTL_MILLIS = 30_000L;
private static RailRoutes railRoutes = HubNetwork::trackDistance;

private HubNetwork() {
Expand Down Expand Up @@ -122,35 +125,58 @@ public static Link connect(Installation from, Installation to, ProvinceManager p
if (from == null || to == null || from == to || from.getProvince() == to.getProvince()) {
return null;
}
Mode mode = HubTransport.modeBetween(from.getKind(), to.getKind());
if (mode == null) {
if (!hubKind(from) || !hubKind(to)) {
return null;
}
String fromFactionId = SupplyHubService.owningFactionId(from);
String toFactionId = SupplyHubService.owningFactionId(to);
if (fromFactionId == null) {
fromFactionId = "";
}
if (toFactionId == null) {
toFactionId = "";
}
Link best = modeLink(from, fromFactionId, to, toFactionId, Mode.RAIL, provinces);
Mode ownMode = HubTransport.modeBetween(from.getKind(), to.getKind());
if (ownMode != null && ownMode != Mode.RAIL) {
Link own = modeLink(from, fromFactionId, to, toFactionId, ownMode, provinces);
if (own != null && (best == null || own.tradeFactor() > best.tradeFactor())) {
best = own;
}
}
return best;
}

private static boolean hubKind(Installation installation) {
return installation.getKind() == InstallationKind.PORT
|| installation.getKind() == InstallationKind.AIRPORT
|| installation.getKind() == InstallationKind.TRAIN_STATION;
}

private static Link modeLink(
Installation from, String fromFactionId, Installation to, String toFactionId,
Mode mode, ProvinceManager provinces) {
Rates rates = HubTransport.rates(mode);
double distance;
switch (mode) {
case RAIL:
OptionalDouble track = railDistance(from, to);
if (track.isEmpty()) {
return null;
}
distance = track.getAsDouble();
break;
case SEA:
if (SeaConnectivity.sharedSeaProvinces(
provinces, List.of(from.getProvince()), List.of(to.getProvince())).isEmpty()) {
return null;
}
distance = straightLine(from, to);
break;
default:
distance = straightLine(from, to);
break;
if (mode == Mode.RAIL) {
OptionalDouble track = railDistance(from, to);
if (track.isEmpty()) {
return null;
}
distance = track.getAsDouble();
} else if (mode == Mode.SEA) {
if (SeaConnectivity.sharedSeaProvinces(
provinces, List.of(from.getProvince()), List.of(to.getProvince())).isEmpty()) {
return null;
}
distance = straightLine(from, to);
} else {
distance = straightLine(from, to);
}
if (!Double.isFinite(distance) || distance < 0 || !HubTransport.inRange(rates, distance)) {
return null;
}
return HubTransport.link(from.getProvince(), to.getProvince(), mode, distance);
return HubTransport.link(from, fromFactionId, to, toFactionId, mode, distance);
}

public static void setLinksForTests(Map<String, List<Link>> replacement) {
Expand Down Expand Up @@ -201,19 +227,17 @@ private static List<Installation> activeSites(Guild guild, List<Guild> guilds) {
}

private static OptionalDouble railDistance(Installation from, Installation to) {
String a = from.getProvince() + ":" + from.getId();
String b = to.getProvince() + ":" + to.getId();
String a = SupplyHubService.owningFactionId(from) + ":" + from.getProvince() + ":" + from.getId();
String b = SupplyHubService.owningFactionId(to) + ":" + to.getProvince() + ":" + to.getId();
String key = a.compareTo(b) <= 0 ? a + "|" + b : b + "|" + a;
OptionalDouble known = railDistances.get(key);
if (known != null) {
return known;
long now = System.currentTimeMillis();
CachedRoute known = railDistances.get(key);
if (known != null && (known.distance().isPresent() || known.expiresAt() > now)) {
return known.distance();
}
// Only a found route is remembered: stations with no track yet are asked about again,
// so a line connects as soon as it is finished rather than at the next daily check.
OptionalDouble measured = railRoutes.distance(from, to);
if (measured.isPresent()) {
railDistances.put(key, measured);
}
railDistances.put(key, new CachedRoute(
measured, measured.isPresent() ? Long.MAX_VALUE : now + ROUTE_MISS_TTL_MILLIS));
return measured;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public static HubTaxBreakdown assess(ProvinceManager provinces, Guild guild, Lis
continue;
}
List<Link> remaining = links.stream()
.filter(link -> !touches(link, installation))
.filter(link -> !touches(link, hub))
.toList();
without.put(hub, incomeWith(provinces, guild, remaining));
hosts.put(hub, host);
Expand Down Expand Up @@ -99,10 +99,15 @@ public static <T> Map<T, Double> taxableIncome(double base, double full, Map<T,
return marginal;
}

static boolean touches(Link link, Installation installation) {
return link.mode().getKind() == installation.getKind()
&& (link.fromProvince() == installation.getProvince()
|| link.toProvince() == installation.getProvince());
static boolean touches(Link link, SupplyHub hub) {
return hub != null && (matches(link.fromFactionId(), link.fromInstallationId(), hub)
|| matches(link.toFactionId(), link.toInstallationId(), hub));
}

private static boolean matches(String factionId, String installationId, SupplyHub hub) {
return factionId != null && installationId != null
&& factionId.equalsIgnoreCase(hub.ownerFactionId())
&& installationId.equalsIgnoreCase(hub.installationId());
}

private static double incomeWith(ProvinceManager source, Guild guild, List<Link> links) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;

import net.tfminecraft.simplefactions.installation.Installation;
import net.tfminecraft.simplefactions.installation.InstallationKind;

/**
Expand Down Expand Up @@ -59,11 +60,21 @@ public record Rates(double trade, double production, double keptPer1000, double
public record Link(
int fromProvince,
int toProvince,
String fromFactionId,
String fromInstallationId,
String toFactionId,
String toInstallationId,
Mode mode,
double distance,
double tradeFactor,
double productionFactor) {

public Link(int fromProvince, int toProvince, Mode mode, double distance,
double tradeFactor, double productionFactor) {
this(fromProvince, toProvince, null, null, null, null, mode,
distance, tradeFactor, productionFactor);
}

/** The boosted share is capped before the cached distance loss is applied. */
public double boostedTradeFactor(double bonus) {
return boostedFactor(tradeFactor, mode, distance, bonus, true);
Expand Down Expand Up @@ -142,17 +153,28 @@ public static double delivered(double share, double keptPer1000, double distance
return share * Math.pow(keptPer1000, Math.max(0, distance) / 1000.0);
}

public static Link link(int fromProvince, int toProvince, Mode mode, double distance) {
public static Link link(Installation from, String fromFactionId, Installation to, String toFactionId, Mode mode, double distance) {
Rates modeRates = rates(mode);
return new Link(
fromProvince,
toProvince,
from.getProvince(),
to.getProvince(),
fromFactionId,
from.getId(),
toFactionId,
to.getId(),
mode,
distance,
delivered(modeRates.trade(), modeRates.keptPer1000(), distance),
delivered(modeRates.production(), modeRates.keptPer1000(), distance));
}

public static Link link(int fromProvince, int toProvince, Mode mode, double distance) {
Rates modeRates = rates(mode);
return new Link(fromProvince, toProvince, mode, distance,
delivered(modeRates.trade(), modeRates.keptPer1000(), distance),
delivered(modeRates.production(), modeRates.keptPer1000(), distance));
}

private static double boostedFactor(double factor, Mode mode, double distance, double bonus, boolean trade) {
Rates modeRates = rates(mode);
double baseShare = trade ? modeRates.trade() : modeRates.production();
Expand Down
Loading
Loading