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
5 changes: 2 additions & 3 deletions src/core/configuration/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,10 @@ export interface Config {
defaultDonationAmount(sender: Player): number;
unitInfo(type: UnitType): UnitInfo;
tradeShipShortRangeDebuff(): number;
tradeShipGold(dist: number, numPorts: number): Gold;
tradeShipGold(dist: number): Gold;
tradeShipSpawnRate(
tradeShipSpawnRejections: number,
numTradeShips: number,
numPlayerPorts: number,
numPlayerTradeShips: number,
): number;
trainGold(rel: "self" | "team" | "ally" | "other"): Gold;
trainSpawnRate(numPlayerFactories: number): number;
Expand Down
43 changes: 10 additions & 33 deletions src/core/configuration/DefaultConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,52 +297,29 @@ export class DefaultConfig implements Config {
return 120;
}

tradeShipGold(dist: number, numPorts: number): Gold {
tradeShipGold(dist: number): Gold {
// Sigmoid: concave start, sharp S-curve middle, linear end - heavily punishes trades under range debuff.
const debuff = this.tradeShipShortRangeDebuff();
const baseGold =
100_000 / (1 + Math.exp(-0.03 * (dist - debuff))) + 100 * dist;
const numPortBonus = numPorts - 1;
// Hyperbolic decay, midpoint at 5 ports, 3x bonus max.
const bonus = 1 + 2 * (numPortBonus / (numPortBonus + 5));
50_000 / (1 + Math.exp(-0.03 * (dist - debuff))) + 50 * dist;
const multiplier = this.goldMultiplier();
return BigInt(Math.floor(baseGold * bonus * multiplier));
return BigInt(Math.floor(baseGold * multiplier));
}

// Probability of trade ship spawn = 1 / tradeShipSpawnRate
tradeShipSpawnRate(
tradeShipSpawnRejections: number,
numTradeShips: number,
numPlayerPorts: number,
numPlayerTradeShips: number,
): number {
// Geometric mean of base spawn rate and port multiplier
const combined = Math.sqrt(
this.tradeShipBaseSpawn(numTradeShips, numPlayerTradeShips) *
this.tradeShipPortMultiplier(numPlayerPorts),
);
const decayRate = Math.LN2 / 50;

return Math.floor(25 / combined);
}
// Approaches 0 as numTradeShips increase
const baseSpawnRate = 1 - sigmoid(numTradeShips, decayRate, 200);

private tradeShipBaseSpawn(
numTradeShips: number,
numPlayerTradeShips: number,
): number {
if (numPlayerTradeShips < 3) {
// If other players have many ports, then they can starve out smaller players.
// So this prevents smaller players from being completely starved out.
return 1;
}
const decayRate = Math.LN2 / 10;
return 1 - sigmoid(numTradeShips, decayRate, 55);
}
// Pity timer: increases spawn chance after consecutive rejections
const rejectionModifier = 1 / (tradeShipSpawnRejections + 1);

private tradeShipPortMultiplier(numPlayerPorts: number): number {
// Hyperbolic decay function with midpoint at 10 ports
// Expected trade ship spawn rate is proportional to numPlayerPorts * multiplier
// Gradual decay prevents scenario where more ports => fewer ships
const decayRate = 1 / 10;
return 1 / (1 + decayRate * numPlayerPorts);
return Math.floor((100 * rejectionModifier) / baseSpawnRate);
}

unitInfo(type: UnitType): UnitInfo {
Expand Down
9 changes: 4 additions & 5 deletions src/core/execution/PortExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export class PortExecution implements Execution {
private port: Unit;
private random: PseudoRandom;
private checkOffset: number;
private tradeShipSpawnRejections = 0;

constructor(port: Unit) {
this.port = port;
Expand Down Expand Up @@ -69,17 +70,15 @@ export class PortExecution implements Execution {

shouldSpawnTradeShip(): boolean {
const numTradeShips = this.mg.unitCount(UnitType.TradeShip);
const numPlayerPorts = this.port!.owner().unitCount(UnitType.Port);
const numPlayerTradeShips = this.port!.owner().unitCount(
UnitType.TradeShip,
);
const spawnRate = this.mg
.config()
.tradeShipSpawnRate(numTradeShips, numPlayerPorts, numPlayerTradeShips);
.tradeShipSpawnRate(this.tradeShipSpawnRejections, numTradeShips);
for (let i = 0; i < this.port!.level(); i++) {
if (this.random.chance(spawnRate)) {
this.tradeShipSpawnRejections = 0;
return true;
}
this.tradeShipSpawnRejections++;
}
return false;
}
Expand Down
7 changes: 1 addition & 6 deletions src/core/execution/TradeShipExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,7 @@ export class TradeShipExecution implements Execution {
private complete() {
this.active = false;
this.tradeShip!.delete(false);
const gold = this.mg
.config()
.tradeShipGold(
this.tilesTraveled,
this.tradeShip!.owner().unitCount(UnitType.Port),
);
const gold = this.mg.config().tradeShipGold(this.tilesTraveled);

if (this.wasCaptured) {
this.tradeShip!.owner().addGold(gold, this._dstPort.tile());
Expand Down
Loading