mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 11:30:43 +00:00
Merge pull request #95 from ilan-schemoul/embargoes
feat: everyone trade with everyone, anyone can embargo a player to stop trade
This commit is contained in:
Executable
+29
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M2285 4675 c-640 -84 -1211 -457 -1551 -1015 -278 -456 -372 -1020
|
||||
-258 -1550 86 -400 284 -760 579 -1055 351 -352 787 -560 1284 -615 171 -19
|
||||
442 -8 615 24 423 80 802 282 1111 591 407 407 625 931 625 1505 0 574 -218
|
||||
1098 -625 1505 -348 349 -786 561 -1270 615 -122 13 -387 11 -510 -5z m574
|
||||
-436 c375 -64 738 -269 989 -560 504 -583 557 -1420 129 -2067 -33 -50 -63
|
||||
-92 -67 -92 -3 0 -68 63 -145 140 -77 77 -144 140 -150 140 -13 0 -295 -282
|
||||
-295 -295 0 -6 63 -73 140 -150 77 -77 140 -142 140 -145 0 -12 -195 -134
|
||||
-289 -181 -399 -198 -882 -229 -1302 -83 -595 206 -1020 703 -1130 1320 -30
|
||||
166 -30 422 -1 585 48 265 145 487 324 744 4 6 68 -50 152 -134 l145 -145 22
|
||||
20 c57 49 279 272 279 280 0 5 -64 73 -142 152 -83 83 -139 146 -133 150 297
|
||||
207 556 308 878 342 113 12 320 2 456 -21z"/>
|
||||
<path d="M2350 3715 l0 -126 -45 -19 c-128 -55 -251 -173 -315 -303 -143 -291
|
||||
-60 -569 230 -764 25 -17 132 -76 238 -131 195 -102 284 -159 302 -192 28 -52
|
||||
1 -148 -57 -202 -58 -55 -77 -58 -388 -58 l-285 0 0 -215 0 -215 160 0 160 0
|
||||
0 -105 0 -105 210 0 210 0 0 125 0 126 45 19 c232 100 398 365 382 610 -12
|
||||
179 -112 332 -299 458 -25 18 -132 76 -237 131 -193 100 -283 158 -301 191
|
||||
-28 52 -1 148 57 202 58 55 77 58 388 58 l285 0 0 215 0 215 -160 0 -160 0 0
|
||||
105 0 105 -210 0 -210 0 0 -125z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -102,6 +102,14 @@ export class SendDonateIntentEvent implements GameEvent {
|
||||
) {}
|
||||
}
|
||||
|
||||
export class SendEmbargoIntentEvent implements GameEvent {
|
||||
constructor(
|
||||
public readonly sender: PlayerView,
|
||||
public readonly target: PlayerView,
|
||||
public readonly action: "start" | "stop",
|
||||
) {}
|
||||
}
|
||||
|
||||
export class SendSetTargetTroopRatioEvent implements GameEvent {
|
||||
constructor(public readonly ratio: number) {}
|
||||
}
|
||||
@@ -159,6 +167,9 @@ export class Transport {
|
||||
);
|
||||
this.eventBus.on(SendEmojiIntentEvent, (e) => this.onSendEmojiIntent(e));
|
||||
this.eventBus.on(SendDonateIntentEvent, (e) => this.onSendDonateIntent(e));
|
||||
this.eventBus.on(SendEmbargoIntentEvent, (e) =>
|
||||
this.onSendEmbargoIntent(e),
|
||||
);
|
||||
this.eventBus.on(SendSetTargetTroopRatioEvent, (e) =>
|
||||
this.onSendSetTargetTroopRatioEvent(e),
|
||||
);
|
||||
@@ -409,6 +420,16 @@ export class Transport {
|
||||
});
|
||||
}
|
||||
|
||||
private onSendEmbargoIntent(event: SendEmbargoIntentEvent) {
|
||||
this.sendIntent({
|
||||
type: "embargo",
|
||||
clientID: this.lobbyConfig.clientID,
|
||||
playerID: this.lobbyConfig.playerID,
|
||||
targetID: event.target.id(),
|
||||
action: event.action,
|
||||
});
|
||||
}
|
||||
|
||||
private onSendSetTargetTroopRatioEvent(event: SendSetTargetTroopRatioEvent) {
|
||||
this.sendIntent({
|
||||
type: "troop_ratio",
|
||||
|
||||
@@ -14,6 +14,7 @@ import allianceIcon from "../../../../resources/images/AllianceIcon.svg";
|
||||
import allianceRequestIcon from "../../../../resources/images/AllianceRequestIcon.svg";
|
||||
import crownIcon from "../../../../resources/images/CrownIcon.svg";
|
||||
import targetIcon from "../../../../resources/images/TargetIcon.svg";
|
||||
import embargoIcon from "../../../../resources/images/EmbargoIcon.svg";
|
||||
import { ClientID } from "../../../core/Schemas";
|
||||
import { GameView, PlayerView } from "../../../core/game/GameView";
|
||||
import { createCanvas, renderTroops } from "../../Utils";
|
||||
@@ -45,6 +46,7 @@ export class NameLayer implements Layer {
|
||||
private allianceIconImage: HTMLImageElement;
|
||||
private targetIconImage: HTMLImageElement;
|
||||
private crownIconImage: HTMLImageElement;
|
||||
private embargoIconImage: HTMLImageElement;
|
||||
private container: HTMLDivElement;
|
||||
private myPlayer: PlayerView | null = null;
|
||||
private firstPlace: PlayerView | null = null;
|
||||
@@ -65,6 +67,8 @@ export class NameLayer implements Layer {
|
||||
this.crownIconImage.src = crownIcon;
|
||||
this.targetIconImage = new Image();
|
||||
this.targetIconImage.src = targetIcon;
|
||||
this.embargoIconImage = new Image();
|
||||
this.embargoIconImage.src = embargoIcon;
|
||||
}
|
||||
|
||||
resizeCanvas() {
|
||||
@@ -381,6 +385,24 @@ export class NameLayer implements Layer {
|
||||
existingEmoji.remove();
|
||||
}
|
||||
|
||||
const existingEmbargo = iconsDiv.querySelector('[data-icon="embargo"]');
|
||||
const hasEmbargo =
|
||||
render.player.hasEmbargoAgainst(myPlayer) ||
|
||||
myPlayer.hasEmbargoAgainst(render.player);
|
||||
if (myPlayer && hasEmbargo) {
|
||||
if (!existingEmbargo) {
|
||||
iconsDiv.appendChild(
|
||||
this.createIconElement(
|
||||
this.embargoIconImage.src,
|
||||
iconSize,
|
||||
"embargo",
|
||||
),
|
||||
);
|
||||
}
|
||||
} else if (existingEmbargo) {
|
||||
existingEmbargo.remove();
|
||||
}
|
||||
|
||||
// Update all icon sizes
|
||||
const icons = iconsDiv.getElementsByTagName("img");
|
||||
for (const icon of icons) {
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
SendDonateIntentEvent,
|
||||
SendEmojiIntentEvent,
|
||||
SendTargetPlayerIntentEvent,
|
||||
SendEmbargoIntentEvent,
|
||||
} from "../../Transport";
|
||||
import { EmojiTable } from "./EmojiTable";
|
||||
|
||||
@@ -76,6 +77,26 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
private handleEmbargoClick(
|
||||
e: Event,
|
||||
myPlayer: PlayerView,
|
||||
other: PlayerView,
|
||||
) {
|
||||
e.stopPropagation();
|
||||
this.eventBus.emit(new SendEmbargoIntentEvent(myPlayer, other, "start"));
|
||||
this.hide();
|
||||
}
|
||||
|
||||
private handleStopEmbargoClick(
|
||||
e: Event,
|
||||
myPlayer: PlayerView,
|
||||
other: PlayerView,
|
||||
) {
|
||||
e.stopPropagation();
|
||||
this.eventBus.emit(new SendEmbargoIntentEvent(myPlayer, other, "stop"));
|
||||
this.hide();
|
||||
}
|
||||
|
||||
private handleEmojiClick(e: Event, myPlayer: PlayerView, other: PlayerView) {
|
||||
e.stopPropagation();
|
||||
this.emojiTable.showTable((emoji: string) => {
|
||||
@@ -131,6 +152,7 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
: this.actions.interaction?.canSendEmoji;
|
||||
const canBreakAlliance = this.actions.interaction?.canBreakAlliance;
|
||||
const canTarget = this.actions.interaction?.canTarget;
|
||||
const canEmbargo = this.actions.interaction?.canEmbargo;
|
||||
|
||||
return html`
|
||||
<div
|
||||
@@ -190,6 +212,15 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="text-white text-opacity-80 text-sm px-2">
|
||||
Embargo against you
|
||||
</div>
|
||||
<div class="bg-opacity-50 bg-gray-700 rounded p-2 text-white">
|
||||
${other.hasEmbargoAgainst(myPlayer) ? "Yes" : "No"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<div class="flex justify-center gap-2">
|
||||
${canTarget
|
||||
@@ -249,6 +280,27 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
</button>`
|
||||
: ""}
|
||||
</div>
|
||||
${canEmbargo && other != myPlayer
|
||||
? html`<button
|
||||
@click=${(e) => this.handleEmbargoClick(e, myPlayer, other)}
|
||||
class="w-100 h-10 flex items-center justify-center
|
||||
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
|
||||
text-white rounded-lg transition-colors"
|
||||
>
|
||||
Start embargo
|
||||
</button>`
|
||||
: ""}
|
||||
${!canEmbargo && other != myPlayer
|
||||
? html`<button
|
||||
@click=${(e) =>
|
||||
this.handleStopEmbargoClick(e, myPlayer, other)}
|
||||
class="w-100 h-10 flex items-center justify-center
|
||||
bg-opacity-50 bg-gray-700 hover:bg-opacity-70
|
||||
text-white rounded-lg transition-colors"
|
||||
>
|
||||
Stop embargo
|
||||
</button>`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -167,6 +167,7 @@ export class GameRunner {
|
||||
canSendAllianceRequest: player.canSendAllianceRequest(other),
|
||||
canBreakAlliance: player.isAlliedWith(other),
|
||||
canDonate: player.canDonate(other),
|
||||
canEmbargo: !player.hasEmbargoAgainst(other),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+12
-1
@@ -22,7 +22,8 @@ export type Intent =
|
||||
| EmojiIntent
|
||||
| DonateIntent
|
||||
| TargetTroopRatioIntent
|
||||
| BuildUnitIntent;
|
||||
| BuildUnitIntent
|
||||
| EmbargoIntent;
|
||||
|
||||
export type AttackIntent = z.infer<typeof AttackIntentSchema>;
|
||||
export type SpawnIntent = z.infer<typeof SpawnIntentSchema>;
|
||||
@@ -35,6 +36,7 @@ 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 EmbargoIntent = z.infer<typeof EmbargoIntentSchema>;
|
||||
export type TargetTroopRatioIntent = z.infer<
|
||||
typeof TargetTroopRatioIntentSchema
|
||||
>;
|
||||
@@ -139,6 +141,7 @@ const BaseIntentSchema = z.object({
|
||||
"emoji",
|
||||
"troop_ratio",
|
||||
"build_unit",
|
||||
"embargo",
|
||||
]),
|
||||
clientID: ID,
|
||||
playerID: ID,
|
||||
@@ -202,6 +205,13 @@ export const EmojiIntentSchema = BaseIntentSchema.extend({
|
||||
emoji: EmojiSchema,
|
||||
});
|
||||
|
||||
export const EmbargoIntentSchema = BaseIntentSchema.extend({
|
||||
type: z.literal("embargo"),
|
||||
playerID: ID,
|
||||
targetID: ID,
|
||||
action: z.union([z.literal("start"), z.literal("stop")]),
|
||||
});
|
||||
|
||||
export const DonateIntentSchema = BaseIntentSchema.extend({
|
||||
type: z.literal("donate"),
|
||||
playerID: ID,
|
||||
@@ -235,6 +245,7 @@ const IntentSchema = z.union([
|
||||
DonateIntentSchema,
|
||||
TargetTroopRatioIntentSchema,
|
||||
BuildUnitIntentSchema,
|
||||
EmbargoIntentSchema,
|
||||
]);
|
||||
|
||||
export const TurnSchema = z.object({
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { consolex } from "../Consolex";
|
||||
import { Execution, Game, Player, PlayerID } from "../game/Game";
|
||||
|
||||
export class EmbargoExecution implements Execution {
|
||||
private active = true;
|
||||
|
||||
constructor(
|
||||
private player: Player,
|
||||
private targetID: PlayerID,
|
||||
private readonly action: "start" | "stop",
|
||||
) {}
|
||||
|
||||
init(mg: Game, _: number): void {
|
||||
if (!mg.hasPlayer(this.player.id())) {
|
||||
console.warn(`EmbargoExecution: sender ${this.player.id()} not found`);
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
if (!mg.hasPlayer(this.targetID)) {
|
||||
console.warn(`EmbargoExecution recipient ${this.targetID} not found`);
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
tick(_: number): void {
|
||||
if (this.action == "start") this.player.addEmbargo(this.targetID);
|
||||
else this.player.stopEmbargo(this.targetID);
|
||||
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ import { SetTargetTroopRatioExecution } from "./SetTargetTroopRatioExecution";
|
||||
import { ConstructionExecution } from "./ConstructionExecution";
|
||||
import { fixProfaneUsername, isProfaneUsername } from "../validations/username";
|
||||
import { NoOpExecution } from "./NoOpExecution";
|
||||
import { EmbargoExecution } from "./EmbargoExecution";
|
||||
|
||||
export class Executor {
|
||||
// private random = new PseudoRandom(999)
|
||||
@@ -53,6 +54,7 @@ export class Executor {
|
||||
}
|
||||
|
||||
createExec(intent: Intent): Execution {
|
||||
let player: Player;
|
||||
if (intent.type != "spawn") {
|
||||
if (!this.mg.hasPlayer(intent.playerID)) {
|
||||
console.warn(
|
||||
@@ -60,7 +62,7 @@ export class Executor {
|
||||
);
|
||||
return new NoOpExecution();
|
||||
}
|
||||
const player = this.mg.player(intent.playerID);
|
||||
player = this.mg.player(intent.playerID);
|
||||
if (player.clientID() != intent.clientID) {
|
||||
console.warn(
|
||||
`intent ${intent.type} has incorrect clientID ${intent.clientID} for player ${player.name()} with clientID ${player.clientID()}`,
|
||||
@@ -68,6 +70,7 @@ export class Executor {
|
||||
return new NoOpExecution();
|
||||
}
|
||||
}
|
||||
|
||||
switch (intent.type) {
|
||||
case "attack": {
|
||||
return new AttackExecution(
|
||||
@@ -124,6 +127,8 @@ export class Executor {
|
||||
);
|
||||
case "troop_ratio":
|
||||
return new SetTargetTroopRatioExecution(intent.playerID, intent.ratio);
|
||||
case "embargo":
|
||||
return new EmbargoExecution(player, intent.targetID, intent.action);
|
||||
case "build_unit":
|
||||
return new ConstructionExecution(
|
||||
intent.playerID,
|
||||
|
||||
@@ -69,22 +69,20 @@ export class PortExecution implements Execution {
|
||||
return;
|
||||
}
|
||||
|
||||
const alliedPorts = this.player()
|
||||
.alliances()
|
||||
.map((a) => a.other(this.player()))
|
||||
const tradingPartnersPorts = this.player()
|
||||
.tradingPartners()
|
||||
.flatMap((p) => p.units(UnitType.Port));
|
||||
const alliedPortsSet = new Set(alliedPorts);
|
||||
const tradingPartnersPortsSet = new Set(tradingPartnersPorts);
|
||||
|
||||
const allyConnections = new Set(
|
||||
const tradingPartnersConnections = new Set(
|
||||
Array.from(this.portPaths.keys()).map((p) => p.owner()),
|
||||
);
|
||||
allyConnections;
|
||||
|
||||
for (const port of alliedPorts) {
|
||||
if (allyConnections.has(port.owner())) {
|
||||
for (const port of tradingPartnersPorts) {
|
||||
if (tradingPartnersConnections.has(port.owner())) {
|
||||
continue;
|
||||
}
|
||||
allyConnections.add(port.owner());
|
||||
tradingPartnersConnections.add(port.owner());
|
||||
if (this.computingPaths.has(port)) {
|
||||
const aStar = this.computingPaths.get(port);
|
||||
switch (aStar.compute()) {
|
||||
@@ -114,7 +112,7 @@ export class PortExecution implements Execution {
|
||||
}
|
||||
|
||||
for (const port of this.portPaths.keys()) {
|
||||
if (!port.isActive() || !alliedPortsSet.has(port)) {
|
||||
if (!port.isActive() || !tradingPartnersPortsSet.has(port)) {
|
||||
this.portPaths.delete(port);
|
||||
this.computingPaths.delete(port);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export class TradeShipExecution implements Execution {
|
||||
constructor(
|
||||
private _owner: PlayerID,
|
||||
private srcPort: Unit,
|
||||
private dstPort: Unit,
|
||||
private _dstPort: Unit,
|
||||
private pathFinder: PathFinder,
|
||||
// don't modify
|
||||
private path: TileRef[],
|
||||
@@ -49,7 +49,12 @@ export class TradeShipExecution implements Execution {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
this.tradeShip = this.origOwner.buildUnit(UnitType.TradeShip, 0, spawn);
|
||||
this.tradeShip = this.origOwner.buildUnit(
|
||||
UnitType.TradeShip,
|
||||
0,
|
||||
spawn,
|
||||
this._dstPort,
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.tradeShip.isActive()) {
|
||||
@@ -64,8 +69,8 @@ export class TradeShipExecution implements Execution {
|
||||
|
||||
if (
|
||||
!this.wasCaptured &&
|
||||
(!this.dstPort.isActive() ||
|
||||
!this.tradeShip.owner().isAlliedWith(this.dstPort.owner()))
|
||||
(!this._dstPort.isActive() ||
|
||||
!this.tradeShip.owner().canTrade(this._dstPort.owner()))
|
||||
) {
|
||||
this.tradeShip.delete(false);
|
||||
this.active = false;
|
||||
@@ -122,17 +127,17 @@ export class TradeShipExecution implements Execution {
|
||||
const gold = this.mg
|
||||
.config()
|
||||
.tradeShipGold(
|
||||
this.mg.manhattanDist(this.srcPort.tile(), this.dstPort.tile()),
|
||||
this.mg.manhattanDist(this.srcPort.tile(), this._dstPort.tile()),
|
||||
);
|
||||
this.srcPort.owner().addGold(gold);
|
||||
this.dstPort.owner().addGold(gold);
|
||||
this._dstPort.owner().addGold(gold);
|
||||
this.mg.displayMessage(
|
||||
`Received ${renderNumber(gold)} gold from trade with ${this.srcPort.owner().displayName()}`,
|
||||
MessageType.SUCCESS,
|
||||
this.dstPort.owner().id(),
|
||||
this._dstPort.owner().id(),
|
||||
);
|
||||
this.mg.displayMessage(
|
||||
`Received ${renderNumber(gold)} gold from trade with ${this.dstPort.owner().displayName()}`,
|
||||
`Received ${renderNumber(gold)} gold from trade with ${this._dstPort.owner().displayName()}`,
|
||||
MessageType.SUCCESS,
|
||||
this.srcPort.owner().id(),
|
||||
);
|
||||
@@ -154,4 +159,8 @@ export class TradeShipExecution implements Execution {
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
dstPort(): TileRef {
|
||||
return this._dstPort.tile();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,11 @@ export class WarshipExecution implements Execution {
|
||||
.filter((u) => u.owner() != this.warship.owner())
|
||||
.filter((u) => u != this.warship)
|
||||
.filter((u) => !u.owner().isAlliedWith(this.warship.owner()))
|
||||
.filter((u) => !this.alreadySentShell.has(u));
|
||||
.filter((u) => !this.alreadySentShell.has(u))
|
||||
.filter(
|
||||
(u) =>
|
||||
u.type() != UnitType.TradeShip || u.dstPort().owner() != this.owner(),
|
||||
);
|
||||
|
||||
this.target =
|
||||
ships.sort((a, b) => {
|
||||
|
||||
+18
-2
@@ -214,6 +214,9 @@ export interface Unit {
|
||||
|
||||
// Updates
|
||||
toUpdate(): UnitUpdate;
|
||||
|
||||
// Only for some types, otherwise return null
|
||||
dstPort(): Unit;
|
||||
}
|
||||
|
||||
export interface TerraNullius {
|
||||
@@ -267,7 +270,12 @@ export interface Player {
|
||||
units(...types: UnitType[]): Unit[];
|
||||
unitsIncludingConstruction(type: UnitType): Unit[];
|
||||
canBuild(type: UnitType, targetTile: TileRef): TileRef | false;
|
||||
buildUnit(type: UnitType, troops: number, tile: TileRef): Unit;
|
||||
buildUnit(
|
||||
type: UnitType,
|
||||
troops: number,
|
||||
tile: TileRef,
|
||||
dstPort?: Unit,
|
||||
): Unit;
|
||||
captureUnit(unit: Unit): void;
|
||||
|
||||
// Relations & Diplomacy
|
||||
@@ -300,10 +308,17 @@ export interface Player {
|
||||
outgoingEmojis(): EmojiMessage[];
|
||||
sendEmoji(recipient: Player | typeof AllPlayers, emoji: string): void;
|
||||
|
||||
// Trading
|
||||
// Donation
|
||||
canDonate(recipient: Player): boolean;
|
||||
donate(recipient: Player, troops: number): void;
|
||||
|
||||
// Embargo
|
||||
hasEmbargoAgainst(other: Player): boolean;
|
||||
tradingPartners(): Player[];
|
||||
addEmbargo(other: PlayerID): void;
|
||||
stopEmbargo(other: PlayerID): void;
|
||||
canTrade(other: Player): boolean;
|
||||
|
||||
// Attacking.
|
||||
canAttack(tile: TileRef): boolean;
|
||||
createAttack(
|
||||
@@ -392,6 +407,7 @@ export interface PlayerInteraction {
|
||||
canBreakAlliance: boolean;
|
||||
canTarget: boolean;
|
||||
canDonate: boolean;
|
||||
canEmbargo: boolean;
|
||||
}
|
||||
|
||||
export interface EmojiMessage {
|
||||
|
||||
@@ -97,6 +97,7 @@ export interface PlayerUpdate {
|
||||
troops: number;
|
||||
targetTroopRatio: number;
|
||||
allies: number[];
|
||||
embargoes: Set<PlayerID>;
|
||||
isTraitor: boolean;
|
||||
targets: number[];
|
||||
outgoingEmojis: EmojiMessage[];
|
||||
|
||||
@@ -191,6 +191,10 @@ export class PlayerView {
|
||||
return this.data.outgoingAllianceRequests.some((id) => other.id() == id);
|
||||
}
|
||||
|
||||
hasEmbargoAgainst(other: PlayerView): boolean {
|
||||
return this.data.embargoes.has(other.id());
|
||||
}
|
||||
|
||||
profile(): Promise<PlayerProfile> {
|
||||
return this.game.worker.playerProfile(this.smallID());
|
||||
}
|
||||
|
||||
@@ -66,6 +66,8 @@ export class PlayerImpl implements Player {
|
||||
|
||||
isTraitor_ = false;
|
||||
|
||||
private embargoes: Set<PlayerID> = new Set();
|
||||
|
||||
public _borderTiles: Set<TileRef> = new Set();
|
||||
|
||||
public _units: UnitImpl[] = [];
|
||||
@@ -127,6 +129,7 @@ export class PlayerImpl implements Player {
|
||||
troops: this.troops(),
|
||||
targetTroopRatio: this.targetTroopRatio(),
|
||||
allies: this.alliances().map((a) => a.other(this).smallID()),
|
||||
embargoes: this.embargoes,
|
||||
isTraitor: this.isTraitor(),
|
||||
targets: this.targets().map((p) => p.smallID()),
|
||||
outgoingEmojis: this.outgoingEmojis(),
|
||||
@@ -506,6 +509,28 @@ export class PlayerImpl implements Player {
|
||||
);
|
||||
}
|
||||
|
||||
hasEmbargoAgainst(other: Player): boolean {
|
||||
return this.embargoes.has(other.id());
|
||||
}
|
||||
|
||||
canTrade(other: Player): boolean {
|
||||
return !other.hasEmbargoAgainst(this) && !this.hasEmbargoAgainst(other);
|
||||
}
|
||||
|
||||
addEmbargo(other: PlayerID): void {
|
||||
this.embargoes.add(other);
|
||||
}
|
||||
|
||||
stopEmbargo(other: PlayerID): void {
|
||||
this.embargoes.delete(other);
|
||||
}
|
||||
|
||||
tradingPartners(): Player[] {
|
||||
return this.mg
|
||||
.players()
|
||||
.filter((other) => other != this && this.canTrade(other));
|
||||
}
|
||||
|
||||
gold(): Gold {
|
||||
return Number(this._gold);
|
||||
}
|
||||
@@ -592,7 +617,12 @@ export class PlayerImpl implements Player {
|
||||
);
|
||||
}
|
||||
|
||||
buildUnit(type: UnitType, troops: number, spawnTile: TileRef): UnitImpl {
|
||||
buildUnit(
|
||||
type: UnitType,
|
||||
troops: number,
|
||||
spawnTile: TileRef,
|
||||
dstPort?: Unit,
|
||||
): UnitImpl {
|
||||
const cost = this.mg.unitInfo(type).cost(this);
|
||||
const b = new UnitImpl(
|
||||
type,
|
||||
@@ -601,6 +631,7 @@ export class PlayerImpl implements Player {
|
||||
troops,
|
||||
this.mg.nextUnitID(),
|
||||
this,
|
||||
dstPort,
|
||||
);
|
||||
this._units.push(b);
|
||||
this.removeGold(cost);
|
||||
|
||||
@@ -21,6 +21,7 @@ export class UnitImpl implements Unit {
|
||||
private _troops: number,
|
||||
private _id: number,
|
||||
public _owner: PlayerImpl,
|
||||
private _dstPort?: Unit,
|
||||
) {
|
||||
// default to 60% health (or 1.2 is no health specified)
|
||||
this._health = toInt((this.mg.unitInfo(_type).maxHealth ?? 2) * 0.6);
|
||||
@@ -145,4 +146,8 @@ export class UnitImpl implements Unit {
|
||||
toString(): string {
|
||||
return `Unit:${this._type},owner:${this.owner().name()}`;
|
||||
}
|
||||
|
||||
dstPort(): Unit {
|
||||
return this._dstPort;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user