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
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class RelationType {

private String link;
private double baseCost;
private double blocScaling;

private List<FactionModifier> giveModifiers = new ArrayList<>();
private List<FactionModifier> recieveModifiers = new ArrayList<>();
Expand All @@ -51,6 +52,7 @@ public RelationType(String key, ConfigurationSection config) {
name = StringFormatter.formatHex(config.getString("name", "None"));
prefix = StringFormatter.formatHex(config.getString("prefix", "#a89977Our "));
baseCost = config.getDouble("cost", 0.0);
blocScaling = Math.max(0.0, config.getDouble("bloc-scaling", 0.0));
target = config.getInt("target", 0);
limit = config.getInt("limit", -1);
def = config.getBoolean("default", false);
Expand Down Expand Up @@ -116,6 +118,11 @@ public boolean canPickForWar() {
public double getBaseCost() {
return baseCost;
}

/** How strongly the cost grows with the two factions' share of all prestige; 0 turns it off. */
public double getBlocScaling() {
return blocScaling;
}

public boolean hasThreshold() {
return threshold != null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ public static double getDiplomaticCost(Faction from, Faction to, RelationType r)
if(r.isVassalage()) {
cost/=3.0;
}
if(cost != 0 && r.getBlocScaling() > 0) {
cost *= 1 + r.getBlocScaling() * blocShare(from, to);
}
return cost;
}

Expand All @@ -95,6 +98,23 @@ private static double prestigeCostScale(double prestige) {
return Math.sqrt(nonNegativePrestige);
}

/** The share of all factions' prestige that these two hold together, from 0 to 1. */
static double blocShare(Faction from, Faction to) {
double total = 0;
for (Faction faction : FactionManager.factions) {
if (faction == null) continue;
total += Math.max(0, prestigeOf(faction));
}
if (total <= 0) {
return 0;
}
double bloc = Math.max(0, prestigeOf(from));
if (from != to) {
bloc += Math.max(0, prestigeOf(to));
}
return Math.min(1, bloc / total);
}

private static double prestigeOf(Faction faction) {
Double prestige = faction.getPrestige();
return prestige == null ? 0 : prestige;
Expand Down
5 changes: 4 additions & 1 deletion src/main/resources/diplomacy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ types:
ally:
name: "#975bbdAlly"
target: 30
cost: 3.5
cost: 1.5
# Multiplies the cost by 1 + bloc-scaling * (both factions' prestige / all factions' prestige),
# so an alliance between the strongest factions costs far more than one between small ones.
bloc-scaling: 8
mutual: true #Do both parties need to agree to this relationship?
threshold:
mode: higher_than_or_equal_to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,25 @@ void missingCanPickForWar_defaultsTrue() throws Exception {
assertTrue(type.isVassalage());
}

@Test
void blocScaling_isReadAndDefaultsToOff() throws Exception {
YamlConfiguration config = new YamlConfiguration();
config.loadFromString("""
name: Ally
cost: 1.5
bloc-scaling: 8
""");
assertEquals(8.0, new RelationType("ally", config).getBlocScaling(), 1e-9);

YamlConfiguration plain = new YamlConfiguration();
plain.loadFromString("""
name: Subject
bloc-scaling: -3
""");
assertEquals(0.0, new RelationType("subject", plain).getBlocScaling(), 1e-9);
assertEquals(0.0, new RelationType("none", new YamlConfiguration()).getBlocScaling(), 1e-9);
}

@Test
void canPickForWarFalse_isNotPickable() throws Exception {
YamlConfiguration config = new YamlConfiguration();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package net.tfminecraft.simplefactions.managers;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.List;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import net.tfminecraft.simplefactions.diplomacy.Attitude;
Expand All @@ -12,6 +18,20 @@

class RelationManagerDiplomaticCostTest {

private List<Faction> previousFactions;

@BeforeEach
void clearFactions() {
previousFactions = new ArrayList<>(FactionManager.factions);
FactionManager.factions.clear();
}

@AfterEach
void restoreFactions() {
FactionManager.factions.clear();
FactionManager.factions.addAll(previousFactions);
}

@Test
void relationCost_usesDiminishingReturnsAbove100Prestige() {
Faction origin = mock(Faction.class);
Expand Down Expand Up @@ -55,4 +75,66 @@ void zeroAttitudeCost_isZero() {
when(attitude.getBaseCost()).thenReturn(0.0);
assertEquals(0.0, RelationManager.getDiplomaticCost(origin, target, attitude), 1e-9);
}

@Test
void blocScaling_raisesCostByTheBlocsShareOfAllPrestige() {
Faction origin = factionWithPrestige(300.0);
Faction target = factionWithPrestige(900.0);
factionWithPrestige(1200.0);
RelationType type = allianceType(1.5, 8.0);
// The two hold 1200 of 2400 prestige, so the multiplier is 1 + 8 * 0.5.
assertEquals(1.5 * Math.sqrt(900.0) * 5.0, RelationManager.getDiplomaticCost(origin, target, type), 1e-9);
}

@Test
void blocScaling_chargesSmallBlocsLessThanLargeOnes() {
Faction small = factionWithPrestige(400.0);
Faction alsoSmall = factionWithPrestige(400.0);
Faction large = factionWithPrestige(3000.0);
Faction alsoLarge = factionWithPrestige(3000.0);
RelationType type = allianceType(1.5, 8.0);
double smallCost = RelationManager.getDiplomaticCost(small, alsoSmall, type);
double largeCost = RelationManager.getDiplomaticCost(large, alsoLarge, type);
// Without bloc scaling the large pair would pay sqrt(3000/400) times more; with it they pay more still.
assertTrue(largeCost / smallCost > Math.sqrt(3000.0 / 400.0) * 2);
}

@Test
void blocScaling_isIgnoredForRelationsWithoutIt() {
Faction origin = factionWithPrestige(300.0);
Faction target = factionWithPrestige(900.0);
RelationType type = allianceType(2.0, 0.0);
assertEquals(2.0 * Math.sqrt(900.0), RelationManager.getDiplomaticCost(origin, target, type), 1e-9);
}

@Test
void blocShare_ignoresNegativePrestigeAndNeverExceedsOne() {
Faction origin = factionWithPrestige(500.0);
Faction target = factionWithPrestige(500.0);
factionWithPrestige(-200.0);
assertEquals(1.0, RelationManager.blocShare(origin, target), 1e-9);
}

@Test
void blocShare_isZeroWhenNoFactionHasPrestige() {
Faction origin = factionWithPrestige(0.0);
Faction target = factionWithPrestige(0.0);
assertEquals(0.0, RelationManager.blocShare(origin, target), 1e-9);
}

private static Faction factionWithPrestige(double prestige) {
Faction faction = mock(Faction.class);
when(faction.getPrestige()).thenReturn(prestige);
FactionManager.factions.add(faction);
return faction;
}

private static RelationType allianceType(double baseCost, double blocScaling) {
RelationType type = mock(RelationType.class);
when(type.getBaseCost()).thenReturn(baseCost);
when(type.getBlocScaling()).thenReturn(blocScaling);
when(type.isSettable()).thenReturn(true);
when(type.isVassalage()).thenReturn(false);
return type;
}
}
Loading