diff --git a/src/core/configuration/Config.ts b/src/core/configuration/Config.ts index 796e5143ee..55fbab613a 100644 --- a/src/core/configuration/Config.ts +++ b/src/core/configuration/Config.ts @@ -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; diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index 6682b92bad..27eaa8c08c 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -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 { diff --git a/src/core/execution/PortExecution.ts b/src/core/execution/PortExecution.ts index 1b042a4a50..9483f1b701 100644 --- a/src/core/execution/PortExecution.ts +++ b/src/core/execution/PortExecution.ts @@ -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; @@ -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; } diff --git a/src/core/execution/TradeShipExecution.ts b/src/core/execution/TradeShipExecution.ts index 2de607b690..23a905940a 100644 --- a/src/core/execution/TradeShipExecution.ts +++ b/src/core/execution/TradeShipExecution.ts @@ -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());