Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
50 changes: 47 additions & 3 deletions src/core/execution/DonateGoldExecution.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
import { Execution, Game, Gold, Player, PlayerID } from "../game/Game";
import {
Difficulty,
Execution,
Game,
Gold,
Player,
PlayerID,
} from "../game/Game";
import { PseudoRandom } from "../PseudoRandom";
import { toInt } from "../Util";

export class DonateGoldExecution implements Execution {
private recipient: Player;
private random: PseudoRandom;
private mg: Game;

private active = true;
private gold: Gold;
Expand All @@ -16,8 +26,13 @@ export class DonateGoldExecution 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(
`DonateGoldExecution recipient ${this.recipientID} not found`,
);
this.active = false;
return;
}
Expand All @@ -32,7 +47,9 @@ 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(Number(this.gold));
this.recipient.updateRelation(this.sender, relationUpdate);
} else {
console.warn(
`cannot send gold from ${this.sender.name()} to ${this.recipient.name()}`,
Expand All @@ -41,6 +58,33 @@ export class DonateGoldExecution implements Execution {
this.active = false;
}

getGoldChunkSize(): number {
const { difficulty } = this.mg.config().gameConfig();
switch (difficulty) {
Copy link
Contributor

@Lavodan Lavodan Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to put this into defaultConfig?

You could also use the unused difficultyModifier function if you want, but it's not needed.

Some goes for the troop switch

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uh I don't know... Putting something in defaultConfig is probably most helpful if its used in multiple places?

If we want all constants in defaultConfig then a lot is missing there

case Difficulty.Easy:
return this.random.nextInt(1, 2_500);
case Difficulty.Medium:
return this.random.nextInt(2_500, 5_000);
case Difficulty.Hard:
return this.random.nextInt(5_000, 12_500);
case Difficulty.Impossible:
return this.random.nextInt(12_500, 25_000);
default:
return 2_500;
}
}

calculateRelationUpdate(goldSent: number): number {
const chunkSize = this.getGoldChunkSize();
// Calculate how many complete chunks were donated
const chunks = Math.floor(goldSent / chunkSize);
// 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;
}
Expand Down
54 changes: 51 additions & 3 deletions src/core/execution/DonateTroopExecution.ts
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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;
}
Expand All @@ -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();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this variable can be inlined

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had it inlined, but then coderabbit rightfully said that we need to calculate the troops BEFORE donating them.


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}`,
Expand All @@ -40,6 +54,40 @@ export class DonateTroopsExecution implements Execution {
this.active = false;
}

getMinTroopsForRelationUpdate(): number {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use a switch case for this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

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 0;
}
}

isActive(): boolean {
return this.active;
}
Expand Down
Loading