diff --git a/src/core/execution/DonateGoldExecution.ts b/src/core/execution/DonateGoldExecution.ts index 51b60ce0a1..87f80f27dd 100644 --- a/src/core/execution/DonateGoldExecution.ts +++ b/src/core/execution/DonateGoldExecution.ts @@ -1,8 +1,16 @@ -import { Execution, Game, Gold, Player, PlayerID } from "../game/Game"; +import { + Difficulty, + Execution, + Game, + Gold, + Player, + PlayerID, +} from "../game/Game"; import { toInt } from "../Util"; export class DonateGoldExecution implements Execution { private recipient: Player; + private mg: Game; private active = true; private gold: Gold; @@ -16,8 +24,12 @@ export class DonateGoldExecution implements Execution { } init(mg: Game, ticks: number): void { + this.mg = mg; + if (!mg.hasPlayer(this.recipientID)) { - console.warn(`DonateExecution recipient ${this.recipientID} not found`); + console.warn( + `DonateGoldExecution recipient ${this.recipientID} not found`, + ); this.active = false; return; } @@ -32,7 +44,11 @@ export class DonateGoldExecution implements Execution { this.sender.canDonateGold(this.recipient) && this.sender.donateGold(this.recipient, this.gold) ) { - this.recipient.updateRelation(this.sender, 50); + // Give relation points based on how much gold was donated + const relationUpdate = this.calculateRelationUpdate(this.gold, ticks); + if (relationUpdate > 0) { + this.recipient.updateRelation(this.sender, relationUpdate); + } } else { console.warn( `cannot send gold from ${this.sender.name()} to ${this.recipient.name()}`, @@ -41,6 +57,39 @@ export class DonateGoldExecution implements Execution { this.active = false; } + private getGoldChunkSize(): number { + const { difficulty } = this.mg.config().gameConfig(); + switch (difficulty) { + case Difficulty.Easy: + return 2_500; + case Difficulty.Medium: + return 5_000; + case Difficulty.Hard: + return 12_500; + case Difficulty.Impossible: + return 25_000; + default: + return 2_500; + } + } + + private calculateRelationUpdate(goldSent: Gold, ticks: number): number { + const chunkSize = this.getGoldChunkSize(); + // For every 5 minutes that pass, multiply the chunk size to scale with game progression + const chunkSizeMultiplier = + ticks / (3000 + this.mg.config().numSpawnPhaseTurns()); + const adjustedChunkSize = BigInt( + Math.round(chunkSize + chunkSize * chunkSizeMultiplier), + ); + // Calculate how many complete chunks were donated + const chunks = Number(goldSent / adjustedChunkSize); + // Each chunk gives 5 relation points + const relationUpdate = chunks * 5; + // Cap at 100 relation points + if (relationUpdate > 100) return 100; + return relationUpdate; + } + isActive(): boolean { return this.active; } diff --git a/src/core/execution/DonateTroopExecution.ts b/src/core/execution/DonateTroopExecution.ts index 00af5de7c1..bab9ad5744 100644 --- a/src/core/execution/DonateTroopExecution.ts +++ b/src/core/execution/DonateTroopExecution.ts @@ -1,7 +1,10 @@ -import { Execution, Game, Player, PlayerID } from "../game/Game"; +import { Difficulty, Execution, Game, Player, PlayerID } from "../game/Game"; +import { PseudoRandom } from "../PseudoRandom"; export class DonateTroopsExecution implements Execution { private recipient: Player; + private random: PseudoRandom; + private mg: Game; private active = true; @@ -12,8 +15,13 @@ export class DonateTroopsExecution implements Execution { ) {} init(mg: Game, ticks: number): void { + this.mg = mg; + this.random = new PseudoRandom(mg.ticks()); + if (!mg.hasPlayer(this.recipientID)) { - console.warn(`DonateExecution recipient ${this.recipientID} not found`); + console.warn( + `DonateTroopExecution recipient ${this.recipientID} not found`, + ); this.active = false; return; } @@ -27,11 +35,17 @@ export class DonateTroopsExecution implements Execution { tick(ticks: number): void { if (this.troops === null) throw new Error("not initialized"); + + const minTroops = this.getMinTroopsForRelationUpdate(); + if ( this.sender.canDonateTroops(this.recipient) && this.sender.donateTroops(this.recipient, this.troops) ) { - this.recipient.updateRelation(this.sender, 50); + // Prevent players from just buying a good relation by sending 1% troops. Instead, a minimum is needed, and it's random. + if (this.troops >= minTroops) { + this.recipient.updateRelation(this.sender, 50); + } } else { console.warn( `cannot send troops from ${this.sender} to ${this.recipient}`, @@ -40,6 +54,43 @@ export class DonateTroopsExecution implements Execution { this.active = false; } + private getMinTroopsForRelationUpdate(): number { + const { difficulty } = this.mg.config().gameConfig(); + const recipientMaxTroops = this.mg.config().maxTroops(this.recipient); + + switch (difficulty) { + // ~7.7k - ~9.1k troops (for 100k troops) + case Difficulty.Easy: + return this.random.nextInt( + recipientMaxTroops / 13, + recipientMaxTroops / 11, + ); + // ~9.1k - ~11.1k troops (for 100k troops) + case Difficulty.Medium: + return this.random.nextInt( + recipientMaxTroops / 11, + recipientMaxTroops / 9, + ); + // ~11.1k - ~14.3k troops (for 100k troops) + case Difficulty.Hard: + return this.random.nextInt( + recipientMaxTroops / 9, + recipientMaxTroops / 7, + ); + // ~14.3k - ~20k troops (for 100k troops) + case Difficulty.Impossible: + return this.random.nextInt( + recipientMaxTroops / 7, + recipientMaxTroops / 5, + ); + default: + return this.random.nextInt( + recipientMaxTroops / 13, + recipientMaxTroops / 11, + ); + } + } + isActive(): boolean { return this.active; }