Added Gold & Troop donate button (#373)

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
- [x] I understand that submitting code with bugs that could have been
caught through manual testing blocks releases and new features for all
contributors

Changed the icon for the troop donation button, as it looked like it was
donating gold.
I replaced it with a soldier icon to better reflect its function.
Additionally, I updated the icon below it to clearly represent gold
donation instead.

## Please put your Discord username so you can be contacted if a bug or
regression is found:
aotumuri

The cat is because of an extension I have put in. Please ignore it.
<img width="345" alt="スクリーンショット 2025-03-30 10 13 06"
src="https://github.com/user-attachments/assets/9088be1a-57b4-4d10-9507-ca8dc0fcc68c"
/>
<img width="396" alt="スクリーンショット 2025-03-30 10 18 10"
src="https://github.com/user-attachments/assets/2555fd24-0bbc-4b40-8f80-ccf43a1e9a75"
/>

---------

Co-authored-by: Evan <evanpelle@gmail.com>
This commit is contained in:
Aotumuri
2025-03-31 10:51:05 -07:00
committed by GitHub
co-authored by Evan
parent d0e220c0a2
commit 745017aee2
18 changed files with 331 additions and 33 deletions
+14 -5
View File
@@ -23,7 +23,8 @@ export type Intent =
| BreakAllianceIntent
| TargetPlayerIntent
| EmojiIntent
| DonateIntent
| DonateGoldIntent
| DonateTroopsIntent
| TargetTroopRatioIntent
| BuildUnitIntent
| EmbargoIntent
@@ -40,7 +41,8 @@ export type AllianceRequestReplyIntent = z.infer<
export type BreakAllianceIntent = z.infer<typeof BreakAllianceIntentSchema>;
export type TargetPlayerIntent = z.infer<typeof TargetPlayerIntentSchema>;
export type EmojiIntent = z.infer<typeof EmojiIntentSchema>;
export type DonateIntent = z.infer<typeof DonateIntentSchema>;
export type DonateGoldIntent = z.infer<typeof DonateGoldIntentSchema>;
export type DonateTroopsIntent = z.infer<typeof DonateTroopIntentSchema>;
export type EmbargoIntent = z.infer<typeof EmbargoIntentSchema>;
export type TargetTroopRatioIntent = z.infer<
typeof TargetTroopRatioIntentSchema
@@ -232,8 +234,14 @@ export const EmbargoIntentSchema = BaseIntentSchema.extend({
action: z.union([z.literal("start"), z.literal("stop")]),
});
export const DonateIntentSchema = BaseIntentSchema.extend({
type: z.literal("donate"),
export const DonateGoldIntentSchema = BaseIntentSchema.extend({
type: z.literal("donate_gold"),
recipient: ID,
gold: z.number().nullable(),
});
export const DonateTroopIntentSchema = BaseIntentSchema.extend({
type: z.literal("donate_troops"),
recipient: ID,
troops: z.number().nullable(),
});
@@ -271,7 +279,8 @@ const IntentSchema = z.union([
BreakAllianceIntentSchema,
TargetPlayerIntentSchema,
EmojiIntentSchema,
DonateIntentSchema,
DonateGoldIntentSchema,
DonateTroopIntentSchema,
TargetTroopRatioIntentSchema,
BuildUnitIntentSchema,
EmbargoIntentSchema,
+58
View File
@@ -0,0 +1,58 @@
import { consolex } from "../Consolex";
import { Execution, Game, Player, PlayerID, Gold } from "../game/Game";
export class DonateGoldExecution implements Execution {
private sender: Player;
private recipient: Player;
private active = true;
constructor(
private senderID: PlayerID,
private recipientID: PlayerID,
private gold: number | null,
) {}
init(mg: Game, ticks: number): void {
if (!mg.hasPlayer(this.senderID)) {
console.warn(`DonateExecution: sender ${this.senderID} not found`);
this.active = false;
return;
}
if (!mg.hasPlayer(this.recipientID)) {
console.warn(`DonateExecution recipient ${this.recipientID} not found`);
this.active = false;
return;
}
this.sender = mg.player(this.senderID);
this.recipient = mg.player(this.recipientID);
if (this.gold == null) {
this.gold = Math.round(this.sender.gold() / 3);
}
}
tick(ticks: number): void {
if (this.sender.canDonate(this.recipient)) {
this.sender.donateGold(this.recipient, this.gold);
this.recipient.updateRelation(this.sender, 50);
} else {
consolex.warn(
`cannot send gold from ${this.sender.name()} to ${this.recipient.name()}`,
);
}
this.active = false;
}
owner(): Player {
return null;
}
isActive(): boolean {
return this.active;
}
activeDuringSpawnPhase(): boolean {
return false;
}
}
@@ -1,7 +1,7 @@
import { consolex } from "../Consolex";
import { Execution, Game, Player, PlayerID } from "../game/Game";
import { Execution, Game, Player, PlayerID, Gold } from "../game/Game";
export class DonateExecution implements Execution {
export class DonateTroopsExecution implements Execution {
private sender: Player;
private recipient: Player;
@@ -34,7 +34,7 @@ export class DonateExecution implements Execution {
tick(ticks: number): void {
if (this.sender.canDonate(this.recipient)) {
this.sender.donate(this.recipient, this.troops);
this.sender.donateTroops(this.recipient, this.troops);
this.recipient.updateRelation(this.sender, 50);
} else {
consolex.warn(
+10 -3
View File
@@ -29,7 +29,8 @@ import { AllianceRequestReplyExecution } from "./alliance/AllianceRequestReplyEx
import { BreakAllianceExecution } from "./alliance/BreakAllianceExecution";
import { TargetPlayerExecution } from "./TargetPlayerExecution";
import { EmojiExecution } from "./EmojiExecution";
import { DonateExecution } from "./DonateExecution";
import { DonateTroopsExecution } from "./DonateTroopExecution";
import { DonateGoldExecution } from "./DonateGoldExecution";
import { SetTargetTroopRatioExecution } from "./SetTargetTroopRatioExecution";
import { ConstructionExecution } from "./ConstructionExecution";
import { fixProfaneUsername, isProfaneUsername } from "../validations/username";
@@ -111,8 +112,14 @@ export class Executor {
return new TargetPlayerExecution(playerID, intent.target);
case "emoji":
return new EmojiExecution(playerID, intent.recipient, intent.emoji);
case "donate":
return new DonateExecution(playerID, intent.recipient, intent.troops);
case "donate_troops":
return new DonateTroopsExecution(
playerID,
intent.recipient,
intent.troops,
);
case "donate_gold":
return new DonateGoldExecution(playerID, intent.recipient, intent.gold);
case "troop_ratio":
return new SetTargetTroopRatioExecution(playerID, intent.ratio);
case "embargo":
+2 -1
View File
@@ -384,7 +384,8 @@ export interface Player {
// Donation
canDonate(recipient: Player): boolean;
donate(recipient: Player, troops: number): void;
donateTroops(recipient: Player, troops: number): void;
donateGold(recipient: Player, gold: number): void;
// Embargo
hasEmbargoAgainst(other: Player): boolean;
+22 -8
View File
@@ -39,7 +39,7 @@ import {
import { CellString, GameImpl } from "./GameImpl";
import { UnitImpl } from "./UnitImpl";
import { MessageType } from "./Game";
import { renderTroops } from "../../client/Utils";
import { renderTroops, renderNumber } from "../../client/Utils";
import { TerraNulliusImpl } from "./TerraNulliusImpl";
import { andFN, manhattanDistFN, TileRef } from "./GameMap";
import { AttackImpl } from "./AttackImpl";
@@ -523,7 +523,7 @@ export class PlayerImpl implements Player {
return true;
}
donate(recipient: Player, troops: number): void {
donateTroops(recipient: Player, troops: number): void {
this.sentDonations.push(new Donation(recipient, this.mg.ticks()));
recipient.addTroops(this.removeTroops(troops));
this.mg.displayMessage(
@@ -537,6 +537,20 @@ export class PlayerImpl implements Player {
recipient.id(),
);
}
donateGold(recipient: Player, gold: number): void {
this.sentDonations.push(new Donation(recipient, this.mg.ticks()));
recipient.addGold(this.removeGold(gold));
this.mg.displayMessage(
`Sent ${renderNumber(gold)} gold to ${recipient.name()}`,
MessageType.INFO,
this.id(),
);
this.mg.displayMessage(
`Recieved ${renderNumber(gold)} gold from ${this.name()}`,
MessageType.SUCCESS,
recipient.id(),
);
}
hasEmbargoAgainst(other: Player): boolean {
return this.embargoes.has(other.id());
@@ -588,13 +602,13 @@ export class PlayerImpl implements Player {
this._gold += toInt(toAdd);
}
removeGold(toRemove: Gold): void {
if (toRemove > this._gold) {
throw Error(
`Player ${this} does not enough gold (${toRemove} vs ${this._gold}))`,
);
removeGold(toRemove: Gold): number {
if (toRemove <= 1) {
return 0;
}
this._gold -= toInt(toRemove);
const actualRemoved = minInt(this._gold, toInt(toRemove));
this._gold -= actualRemoved;
return Number(actualRemoved);
}
population(): number {