mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-26 13:04:35 +00:00
Merge pull request #100 from d3n0x8/scrollAttackRatio
add shift + scroll command in HelpModal hotkeys table
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { LitElement, html, css } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import "./components/Difficulties";
|
||||
import "./components/Maps";
|
||||
|
||||
@@ -236,6 +236,10 @@ export class HelpModal extends LitElement {
|
||||
<td>1 / 2</td>
|
||||
<td>Decrease/Increase attack ratio</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Shift + scroll down / scroll up</td>
|
||||
<td>Decrease/Increase attack ratio</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ALT + R</td>
|
||||
<td>Reset graphics</td>
|
||||
|
||||
@@ -100,6 +100,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) {}
|
||||
}
|
||||
@@ -151,6 +159,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),
|
||||
);
|
||||
@@ -397,6 +408,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",
|
||||
|
||||
@@ -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
|
||||
>;
|
||||
@@ -133,6 +135,7 @@ const BaseIntentSchema = z.object({
|
||||
"emoji",
|
||||
"troop_ratio",
|
||||
"build_unit",
|
||||
"embargo",
|
||||
]),
|
||||
clientID: ID,
|
||||
playerID: ID,
|
||||
@@ -196,6 +199,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,
|
||||
@@ -229,6 +239,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 {
|
||||
|
||||
@@ -95,6 +95,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());
|
||||
}
|
||||
|
||||
@@ -62,6 +62,8 @@ export class PlayerImpl implements Player {
|
||||
|
||||
isTraitor_ = false;
|
||||
|
||||
private embargoes: Set<PlayerID> = new Set();
|
||||
|
||||
public _borderTiles: Set<TileRef> = new Set();
|
||||
|
||||
public _units: UnitImpl[] = [];
|
||||
@@ -123,6 +125,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(),
|
||||
@@ -502,6 +505,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 this._gold;
|
||||
}
|
||||
@@ -588,7 +613,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,
|
||||
@@ -597,6 +627,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 = (this.mg.unitInfo(_type).maxHealth ?? 2) * 0.6;
|
||||
@@ -141,4 +142,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