mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-30 10:42:13 +00:00
Implement Stop/Start trading with all (#2278)
## Description: fixes #2275 Added global Start/Stop trading; use your **player panel** to trigger it. <img width="370" height="540" alt="Screenshot 2025-10-23 184447" src="https://github.com/user-attachments/assets/c3b7967e-ffdd-4f37-ba67-b60a602278ce" /> ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: abodcraft1
This commit is contained in:
@@ -606,6 +606,8 @@
|
||||
"nuke": "Nukes sent by them to you",
|
||||
"start_trade": "Start Trading",
|
||||
"stop_trade": "Stop Trading",
|
||||
"stop_trade_all": "Stop Trading with All",
|
||||
"start_trade_all": "Start Trading with All",
|
||||
"alliances": "Alliances",
|
||||
"flag": "Flag",
|
||||
"chat": "Chat",
|
||||
|
||||
@@ -132,6 +132,10 @@ export class SendEmbargoIntentEvent implements GameEvent {
|
||||
) {}
|
||||
}
|
||||
|
||||
export class SendEmbargoAllIntentEvent implements GameEvent {
|
||||
constructor(public readonly action: "start" | "stop") {}
|
||||
}
|
||||
|
||||
export class SendDeleteUnitIntentEvent implements GameEvent {
|
||||
constructor(public readonly unitId: number) {}
|
||||
}
|
||||
@@ -226,6 +230,9 @@ export class Transport {
|
||||
this.eventBus.on(SendEmbargoIntentEvent, (e) =>
|
||||
this.onSendEmbargoIntent(e),
|
||||
);
|
||||
this.eventBus.on(SendEmbargoAllIntentEvent, (e) =>
|
||||
this.onSendEmbargoAllIntent(e),
|
||||
);
|
||||
this.eventBus.on(BuildUnitIntentEvent, (e) => this.onBuildUnitIntent(e));
|
||||
|
||||
this.eventBus.on(PauseGameEvent, (e) => this.onPauseGameEvent(e));
|
||||
@@ -528,6 +535,14 @@ export class Transport {
|
||||
});
|
||||
}
|
||||
|
||||
private onSendEmbargoAllIntent(event: SendEmbargoAllIntentEvent) {
|
||||
this.sendIntent({
|
||||
type: "embargo_all",
|
||||
clientID: this.lobbyConfig.clientID,
|
||||
action: event.action,
|
||||
});
|
||||
}
|
||||
|
||||
private onBuildUnitIntent(event: BuildUnitIntentEvent) {
|
||||
this.sendIntent({
|
||||
type: "build_unit",
|
||||
|
||||
@@ -28,6 +28,7 @@ import { CloseViewEvent, MouseUpEvent } from "../../InputHandler";
|
||||
import {
|
||||
SendAllianceRequestIntentEvent,
|
||||
SendBreakAllianceIntentEvent,
|
||||
SendEmbargoAllIntentEvent,
|
||||
SendEmbargoIntentEvent,
|
||||
SendEmojiIntentEvent,
|
||||
SendTargetPlayerIntentEvent,
|
||||
@@ -223,6 +224,16 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
private onStopTradingAllClick(e: Event) {
|
||||
e.stopPropagation();
|
||||
this.eventBus.emit(new SendEmbargoAllIntentEvent("start"));
|
||||
}
|
||||
|
||||
private onStartTradingAllClick(e: Event) {
|
||||
e.stopPropagation();
|
||||
this.eventBus.emit(new SendEmbargoAllIntentEvent("stop"));
|
||||
}
|
||||
|
||||
private handleEmojiClick(e: Event, myPlayer: PlayerView, other: PlayerView) {
|
||||
e.stopPropagation();
|
||||
this.emojiTable.showTable((emoji: string) => {
|
||||
@@ -709,6 +720,37 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
})
|
||||
: ""}
|
||||
</div>
|
||||
|
||||
${other === my
|
||||
? html`<div class="grid auto-cols-fr grid-flow-col gap-1">
|
||||
${actionButton({
|
||||
onClick: (e: MouseEvent) => this.onStopTradingAllClick(e),
|
||||
icon: stopTradingIcon,
|
||||
iconAlt: "Stop Trading With All",
|
||||
title: !this.actions?.canEmbargoAll
|
||||
? `${translateText("player_panel.stop_trade_all")} - ${translateText("cooldown")}`
|
||||
: translateText("player_panel.stop_trade_all"),
|
||||
label: !this.actions?.canEmbargoAll
|
||||
? `${translateText("player_panel.stop_trade_all")} ⏳`
|
||||
: translateText("player_panel.stop_trade_all"),
|
||||
type: "yellow",
|
||||
disabled: !this.actions?.canEmbargoAll,
|
||||
})}
|
||||
${actionButton({
|
||||
onClick: (e: MouseEvent) => this.onStartTradingAllClick(e),
|
||||
icon: startTradingIcon,
|
||||
iconAlt: "Start Trading With All",
|
||||
title: !this.actions?.canEmbargoAll
|
||||
? `${translateText("player_panel.start_trade_all")} - ${translateText("cooldown")}`
|
||||
: translateText("player_panel.start_trade_all"),
|
||||
label: !this.actions?.canEmbargoAll
|
||||
? `${translateText("player_panel.start_trade_all")} ⏳`
|
||||
: translateText("player_panel.start_trade_all"),
|
||||
type: "green",
|
||||
disabled: !this.actions?.canEmbargoAll,
|
||||
})}
|
||||
</div>`
|
||||
: ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -189,6 +189,7 @@ export class GameRunner {
|
||||
canAttack: tile !== null && player.canAttack(tile),
|
||||
buildableUnits: player.buildableUnits(tile),
|
||||
canSendEmojiAllPlayers: player.canSendEmoji(AllPlayers),
|
||||
canEmbargoAll: player.canEmbargoAll(),
|
||||
} as PlayerActions;
|
||||
|
||||
if (tile !== null && this.game.hasOwner(tile)) {
|
||||
|
||||
@@ -43,6 +43,7 @@ export type Intent =
|
||||
| QuickChatIntent
|
||||
| MoveWarshipIntent
|
||||
| MarkDisconnectedIntent
|
||||
| EmbargoAllIntent
|
||||
| UpgradeStructureIntent
|
||||
| DeleteUnitIntent
|
||||
| KickPlayerIntent;
|
||||
@@ -51,6 +52,7 @@ export type AttackIntent = z.infer<typeof AttackIntentSchema>;
|
||||
export type CancelAttackIntent = z.infer<typeof CancelAttackIntentSchema>;
|
||||
export type SpawnIntent = z.infer<typeof SpawnIntentSchema>;
|
||||
export type BoatAttackIntent = z.infer<typeof BoatAttackIntentSchema>;
|
||||
export type EmbargoAllIntent = z.infer<typeof EmbargoAllIntentSchema>;
|
||||
export type CancelBoatIntent = z.infer<typeof CancelBoatIntentSchema>;
|
||||
export type AllianceRequestIntent = z.infer<typeof AllianceRequestIntentSchema>;
|
||||
export type AllianceRequestReplyIntent = z.infer<
|
||||
@@ -276,6 +278,11 @@ export const EmbargoIntentSchema = BaseIntentSchema.extend({
|
||||
action: z.union([z.literal("start"), z.literal("stop")]),
|
||||
});
|
||||
|
||||
export const EmbargoAllIntentSchema = BaseIntentSchema.extend({
|
||||
type: z.literal("embargo_all"),
|
||||
action: z.union([z.literal("start"), z.literal("stop")]),
|
||||
});
|
||||
|
||||
export const DonateGoldIntentSchema = BaseIntentSchema.extend({
|
||||
type: z.literal("donate_gold"),
|
||||
recipient: ID,
|
||||
@@ -355,6 +362,7 @@ const IntentSchema = z.discriminatedUnion("type", [
|
||||
BuildUnitIntentSchema,
|
||||
UpgradeStructureIntentSchema,
|
||||
EmbargoIntentSchema,
|
||||
EmbargoAllIntentSchema,
|
||||
MoveWarshipIntentSchema,
|
||||
QuickChatIntentSchema,
|
||||
AllianceExtensionIntentSchema,
|
||||
|
||||
@@ -131,6 +131,7 @@ export interface Config {
|
||||
emojiMessageCooldown(): Tick;
|
||||
emojiMessageDuration(): Tick;
|
||||
donateCooldown(): Tick;
|
||||
embargoAllCooldown(): Tick;
|
||||
deletionMarkDuration(): Tick;
|
||||
deleteUnitCooldown(): Tick;
|
||||
defaultDonationAmount(sender: Player): number;
|
||||
|
||||
@@ -573,6 +573,9 @@ export class DefaultConfig implements Config {
|
||||
donateCooldown(): Tick {
|
||||
return 10 * 10;
|
||||
}
|
||||
embargoAllCooldown(): Tick {
|
||||
return 10 * 10;
|
||||
}
|
||||
deletionMarkDuration(): Tick {
|
||||
return 15 * 10;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Execution, Game, Player, PlayerType } from "../game/Game";
|
||||
|
||||
export class EmbargoAllExecution implements Execution {
|
||||
constructor(
|
||||
private readonly player: Player,
|
||||
private readonly action: "start" | "stop",
|
||||
) {}
|
||||
|
||||
init(mg: Game, _: number): void {
|
||||
if (!this.player.canEmbargoAll()) {
|
||||
return;
|
||||
}
|
||||
const me = this.player;
|
||||
for (const p of mg.players()) {
|
||||
if (p.id() === me.id()) continue;
|
||||
if (p.type() === PlayerType.Bot) continue;
|
||||
if (me.isOnSameTeam(p)) continue;
|
||||
|
||||
if (this.action === "start") {
|
||||
if (!me.hasEmbargoAgainst(p)) me.addEmbargo(p, false);
|
||||
} else {
|
||||
if (me.hasEmbargoAgainst(p)) me.stopEmbargo(p);
|
||||
}
|
||||
}
|
||||
|
||||
this.player.recordEmbargoAll();
|
||||
}
|
||||
|
||||
tick(_: number): void {}
|
||||
|
||||
isActive(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import { ConstructionExecution } from "./ConstructionExecution";
|
||||
import { DeleteUnitExecution } from "./DeleteUnitExecution";
|
||||
import { DonateGoldExecution } from "./DonateGoldExecution";
|
||||
import { DonateTroopsExecution } from "./DonateTroopExecution";
|
||||
import { EmbargoAllExecution } from "./EmbargoAllExecution";
|
||||
import { EmbargoExecution } from "./EmbargoExecution";
|
||||
import { EmojiExecution } from "./EmojiExecution";
|
||||
import { FakeHumanExecution } from "./FakeHumanExecution";
|
||||
@@ -100,6 +101,8 @@ export class Executor {
|
||||
return new DonateGoldExecution(player, intent.recipient, intent.gold);
|
||||
case "embargo":
|
||||
return new EmbargoExecution(player, intent.targetID, intent.action);
|
||||
case "embargo_all":
|
||||
return new EmbargoAllExecution(player, intent.action);
|
||||
case "build_unit":
|
||||
return new ConstructionExecution(player, intent.unit, intent.tile);
|
||||
case "allianceExtension": {
|
||||
|
||||
@@ -624,6 +624,8 @@ export interface Player {
|
||||
donateGold(recipient: Player, gold: Gold): boolean;
|
||||
canDeleteUnit(): boolean;
|
||||
recordDeleteUnit(): void;
|
||||
canEmbargoAll(): boolean;
|
||||
recordEmbargoAll(): void;
|
||||
|
||||
// Embargo
|
||||
hasEmbargoAgainst(other: Player): boolean;
|
||||
@@ -746,6 +748,7 @@ export interface PlayerActions {
|
||||
canAttack: boolean;
|
||||
buildableUnits: BuildableUnit[];
|
||||
canSendEmojiAllPlayers: boolean;
|
||||
canEmbargoAll?: boolean;
|
||||
interaction?: PlayerInteraction;
|
||||
}
|
||||
|
||||
|
||||
@@ -94,6 +94,7 @@ export class PlayerImpl implements Player {
|
||||
private relations = new Map<Player, number>();
|
||||
|
||||
private lastDeleteUnitTick: Tick = -1;
|
||||
private lastEmbargoAllTick: Tick = -1;
|
||||
|
||||
public _incomingAttacks: Attack[] = [];
|
||||
public _outgoingAttacks: Attack[] = [];
|
||||
@@ -689,6 +690,28 @@ export class PlayerImpl implements Player {
|
||||
this.lastDeleteUnitTick = this.mg.ticks();
|
||||
}
|
||||
|
||||
canEmbargoAll(): boolean {
|
||||
// Cooldown gate
|
||||
if (
|
||||
this.mg.ticks() - this.lastEmbargoAllTick <
|
||||
this.mg.config().embargoAllCooldown()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
// At least one eligible player exists
|
||||
for (const p of this.mg.players()) {
|
||||
if (p.id() === this.id()) continue;
|
||||
if (p.type() === PlayerType.Bot) continue;
|
||||
if (this.isOnSameTeam(p)) continue;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
recordEmbargoAll(): void {
|
||||
this.lastEmbargoAllTick = this.mg.ticks();
|
||||
}
|
||||
|
||||
hasEmbargoAgainst(other: Player): boolean {
|
||||
return this.embargoes.has(other.id());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user