mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-01 16:43:25 +00:00
Merge branch 'openfrontio:main' into main
This commit is contained in:
@@ -15,6 +15,7 @@ import warshipIcon from "../../../../resources/images/BattleshipIconWhite.svg";
|
||||
import missileSiloIcon from "../../../../resources/images/MissileSiloIconWhite.svg";
|
||||
import goldCoinIcon from "../../../../resources/images/GoldCoinIcon.svg";
|
||||
import portIcon from "../../../../resources/images/PortIcon.svg";
|
||||
import mirvIcon from "../../../../resources/images/MIRVIcon.svg";
|
||||
import cityIcon from "../../../../resources/images/CityIconWhite.svg";
|
||||
import shieldIcon from "../../../../resources/images/ShieldIconWhite.svg";
|
||||
import { renderNumber } from "../../Utils";
|
||||
@@ -28,7 +29,8 @@ interface BuildItemDisplay {
|
||||
const buildTable: BuildItemDisplay[][] = [
|
||||
[
|
||||
{ unitType: UnitType.AtomBomb, icon: atomBombIcon },
|
||||
{ unitType: UnitType.MIRV, icon: hydrogenBombIcon },
|
||||
{ unitType: UnitType.MIRV, icon: mirvIcon },
|
||||
{ unitType: UnitType.HydrogenBomb, icon: hydrogenBombIcon },
|
||||
{ unitType: UnitType.Warship, icon: warshipIcon },
|
||||
{ unitType: UnitType.Port, icon: portIcon },
|
||||
{ unitType: UnitType.MissileSilo, icon: missileSiloIcon },
|
||||
|
||||
@@ -195,7 +195,7 @@ export class ControlPanel extends LitElement implements Layer {
|
||||
type="range"
|
||||
min="1"
|
||||
max="100"
|
||||
.value=${this.targetTroopRatio * 100}
|
||||
.value=${(this.targetTroopRatio * 100).toString()}
|
||||
@input=${(e: Event) => {
|
||||
this.targetTroopRatio =
|
||||
parseInt((e.target as HTMLInputElement).value) / 100;
|
||||
@@ -225,7 +225,7 @@ export class ControlPanel extends LitElement implements Layer {
|
||||
type="range"
|
||||
min="1"
|
||||
max="100"
|
||||
.value=${this.attackRatio * 100}
|
||||
.value=${(this.attackRatio * 100).toString()}
|
||||
@input=${(e: Event) => {
|
||||
this.attackRatio =
|
||||
parseInt((e.target as HTMLInputElement).value) / 100;
|
||||
|
||||
@@ -80,6 +80,17 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
this.events = remainingEvents;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
const myPlayer = this.game.myPlayer();
|
||||
if (!myPlayer) {
|
||||
return;
|
||||
}
|
||||
myPlayer.incomingAttacks().forEach((a) => {
|
||||
// console.log(`got incoming attack: ${JSON.stringify(a)}`);
|
||||
});
|
||||
myPlayer.outgoingAttacks().forEach((a) => {
|
||||
// console.log(`got outgoing attack: ${JSON.stringify(a)}`);
|
||||
});
|
||||
}
|
||||
|
||||
private addEvent(event: Event) {
|
||||
@@ -125,10 +136,10 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
}
|
||||
|
||||
const requestor = this.game.playerBySmallID(
|
||||
update.requestorID,
|
||||
update.requestorID
|
||||
) as PlayerView;
|
||||
const recipient = this.game.playerBySmallID(
|
||||
update.recipientID,
|
||||
update.recipientID
|
||||
) as PlayerView;
|
||||
|
||||
this.addEvent({
|
||||
@@ -139,7 +150,7 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
className: "btn",
|
||||
action: () =>
|
||||
this.eventBus.emit(
|
||||
new SendAllianceReplyIntentEvent(requestor, recipient, true),
|
||||
new SendAllianceReplyIntentEvent(requestor, recipient, true)
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -147,7 +158,7 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
className: "btn-info",
|
||||
action: () =>
|
||||
this.eventBus.emit(
|
||||
new SendAllianceReplyIntentEvent(requestor, recipient, false),
|
||||
new SendAllianceReplyIntentEvent(requestor, recipient, false)
|
||||
),
|
||||
},
|
||||
],
|
||||
@@ -156,7 +167,7 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
createdAt: this.game.ticks(),
|
||||
onDelete: () =>
|
||||
this.eventBus.emit(
|
||||
new SendAllianceReplyIntentEvent(requestor, recipient, false),
|
||||
new SendAllianceReplyIntentEvent(requestor, recipient, false)
|
||||
),
|
||||
});
|
||||
}
|
||||
@@ -168,7 +179,7 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
}
|
||||
|
||||
const recipient = this.game.playerBySmallID(
|
||||
update.request.recipientID,
|
||||
update.request.recipientID
|
||||
) as PlayerView;
|
||||
|
||||
this.addEvent({
|
||||
@@ -213,8 +224,8 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
update.player1ID === myPlayer.smallID()
|
||||
? update.player2ID
|
||||
: update.player2ID === myPlayer.smallID()
|
||||
? update.player1ID
|
||||
: null;
|
||||
? update.player1ID
|
||||
: null;
|
||||
const other = this.game.playerBySmallID(otherID) as PlayerView;
|
||||
if (!other || !myPlayer.isAlive() || !other.isAlive()) return;
|
||||
|
||||
@@ -250,7 +261,7 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
? AllPlayers
|
||||
: this.game.playerBySmallID(update.emoji.recipientID);
|
||||
const sender = this.game.playerBySmallID(
|
||||
update.emoji.senderID,
|
||||
update.emoji.senderID
|
||||
) as PlayerView;
|
||||
|
||||
if (recipient == myPlayer) {
|
||||
@@ -306,7 +317,7 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
(event, index) => html`
|
||||
<tr
|
||||
class="border-b border-opacity-0 ${this.getMessageTypeClasses(
|
||||
event.type,
|
||||
event.type
|
||||
)}"
|
||||
>
|
||||
<td class="lg:p-3 p-1 text-left">
|
||||
@@ -331,14 +342,14 @@ export class EventsDisplay extends LitElement implements Layer {
|
||||
>
|
||||
${btn.text}
|
||||
</button>
|
||||
`,
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</td>
|
||||
</tr>
|
||||
`,
|
||||
`
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -147,8 +147,10 @@
|
||||
<div class="flex sm:flex-row flex-col sm:gap-8 gap-2">
|
||||
<a href="https://youtu.be/jvHEvbko3uw?si=znspkP84P76B1w5I"
|
||||
class="text-white/70 hover:text-white transition-colors duration-300" target="_blank">How to Play</a>
|
||||
<a href="https://discord.gg/k22YrnAzGp" class="text-white/70 hover:text-white transition-colors duration-300"
|
||||
target="_blank">Discord</a>
|
||||
<a href="https://discord.gg/k22YrnAzGp" class="text-white/70 hover:text-white transition-colors duration-300"
|
||||
target="_blank">Discord</a>
|
||||
<a href="https://openfront.fandom.com/wiki/Openfront_Wiki" class="text-white/70 hover:text-white transition-colors duration-300"
|
||||
target="_blank">Wiki</a>
|
||||
</div>
|
||||
<div class="text-white/70">
|
||||
© 2025
|
||||
|
||||
@@ -129,7 +129,7 @@ export class DefaultConfig implements Config {
|
||||
};
|
||||
case UnitType.MIRV:
|
||||
return {
|
||||
cost: () => 5_000_000,
|
||||
cost: () => 10_000_000,
|
||||
territoryBound: false,
|
||||
};
|
||||
case UnitType.MIRVWarhead:
|
||||
@@ -351,14 +351,28 @@ export class DefaultConfig implements Config {
|
||||
}
|
||||
|
||||
maxPopulation(player: Player | PlayerView): number {
|
||||
let maxPop = Math.pow(player.numTilesOwned(), 0.6) * 1000 + 50000;
|
||||
let maxPop =
|
||||
2 * (Math.pow(player.numTilesOwned(), 0.6) * 1000 + 50000) +
|
||||
player.units(UnitType.City).length * this.cityPopulationIncrease();
|
||||
|
||||
if (player.type() == PlayerType.Bot) {
|
||||
return maxPop / 2;
|
||||
}
|
||||
|
||||
if (player.type() == PlayerType.Human) {
|
||||
return maxPop;
|
||||
}
|
||||
return (
|
||||
maxPop * 2 +
|
||||
player.units(UnitType.City).length * this.cityPopulationIncrease()
|
||||
);
|
||||
|
||||
switch (this._gameConfig.difficulty) {
|
||||
case Difficulty.Easy:
|
||||
return maxPop * 0.5;
|
||||
case Difficulty.Medium:
|
||||
return maxPop * 0.7;
|
||||
case Difficulty.Hard:
|
||||
return maxPop * 1;
|
||||
case Difficulty.Impossible:
|
||||
return maxPop * 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
populationIncreaseRate(player: Player): number {
|
||||
@@ -372,24 +386,6 @@ export class DefaultConfig implements Config {
|
||||
if (player.type() == PlayerType.Bot) {
|
||||
toAdd *= 0.7;
|
||||
}
|
||||
let difficultyMultiplier = 1;
|
||||
switch (this._gameConfig.difficulty) {
|
||||
case Difficulty.Easy:
|
||||
difficultyMultiplier = 0.3;
|
||||
break;
|
||||
case Difficulty.Medium:
|
||||
difficultyMultiplier = 0.5;
|
||||
break;
|
||||
case Difficulty.Hard:
|
||||
difficultyMultiplier = 1;
|
||||
break;
|
||||
case Difficulty.Impossible:
|
||||
difficultyMultiplier = 1.2;
|
||||
break;
|
||||
}
|
||||
if (player.type() == PlayerType.FakeHuman) {
|
||||
toAdd *= difficultyMultiplier;
|
||||
}
|
||||
|
||||
return Math.min(player.population() + toAdd, max) - player.population();
|
||||
}
|
||||
|
||||
@@ -18,14 +18,14 @@ export class DevConfig extends DefaultConfig {
|
||||
}
|
||||
|
||||
numSpawnPhaseTurns(): number {
|
||||
return this.gameConfig().gameType == GameType.Singleplayer ? 20 : 100;
|
||||
return this.gameConfig().gameType == GameType.Singleplayer ? 40 : 100;
|
||||
// return 100
|
||||
}
|
||||
|
||||
unitInfo(type: UnitType): UnitInfo {
|
||||
const info = super.unitInfo(type);
|
||||
const oldCost = info.cost;
|
||||
info.cost = (p: Player) => oldCost(p) / 1000000000;
|
||||
// info.cost = (p: Player) => oldCost(p) / 1000000000;
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { PriorityQueue } from "@datastructures-js/priority-queue";
|
||||
import {
|
||||
Attack,
|
||||
Cell,
|
||||
Execution,
|
||||
Game,
|
||||
@@ -37,8 +38,10 @@ export class AttackExecution implements Execution {
|
||||
|
||||
private border = new Set<TileRef>();
|
||||
|
||||
private attack: Attack = null;
|
||||
|
||||
constructor(
|
||||
private troops: number | null,
|
||||
private startTroops: number | null = null,
|
||||
private _ownerID: PlayerID,
|
||||
private _targetID: PlayerID | null,
|
||||
private sourceTile: TileRef | null,
|
||||
@@ -80,57 +83,49 @@ export class AttackExecution implements Execution {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.troops == null) {
|
||||
this.troops = this.mg.config().attackAmount(this._owner, this.target);
|
||||
if (this.startTroops == null) {
|
||||
this.startTroops = this.mg
|
||||
.config()
|
||||
.attackAmount(this._owner, this.target);
|
||||
}
|
||||
this.troops = Math.min(this._owner.troops(), this.troops);
|
||||
this.startTroops = Math.min(this._owner.troops(), this.startTroops);
|
||||
if (this.removeTroops) {
|
||||
this._owner.removeTroops(this.troops);
|
||||
this._owner.removeTroops(this.startTroops);
|
||||
}
|
||||
this.attack = this._owner.createAttack(
|
||||
this.target,
|
||||
this.startTroops,
|
||||
this.sourceTile
|
||||
);
|
||||
|
||||
for (const exec of mg.executions()) {
|
||||
if (exec.isActive() && exec instanceof AttackExecution && exec != this) {
|
||||
const otherAttack = exec as AttackExecution;
|
||||
for (const incoming of this._owner.incomingAttacks()) {
|
||||
if (incoming.attacker() == this.target) {
|
||||
// Target has opposing attack, cancel them out
|
||||
if (
|
||||
this.target.isPlayer() &&
|
||||
otherAttack._targetID == this._ownerID &&
|
||||
this._targetID == otherAttack._ownerID
|
||||
) {
|
||||
if (otherAttack.troops > this.troops) {
|
||||
otherAttack.troops -= this.troops;
|
||||
// otherAttack.calculateToConquer()
|
||||
this.active = false;
|
||||
return;
|
||||
} else {
|
||||
this.troops -= otherAttack.troops;
|
||||
otherAttack.active = false;
|
||||
}
|
||||
}
|
||||
// Existing attack on same target, add troops
|
||||
if (
|
||||
otherAttack._owner == this._owner &&
|
||||
otherAttack._targetID == this._targetID &&
|
||||
this.sourceTile == otherAttack.sourceTile
|
||||
) {
|
||||
otherAttack.troops += this.troops;
|
||||
otherAttack.refreshToConquer();
|
||||
if (incoming.troops() > this.attack.troops()) {
|
||||
incoming.setTroops(incoming.troops() - this.attack.troops());
|
||||
this.attack.delete();
|
||||
this.active = false;
|
||||
return;
|
||||
} else {
|
||||
this.attack.setTroops(this.attack.troops() - incoming.troops());
|
||||
incoming.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
this._owner.type() != PlayerType.Bot &&
|
||||
this.target.isPlayer() &&
|
||||
this.target.type() == PlayerType.Human
|
||||
) {
|
||||
mg.displayMessage(
|
||||
`You are being attacked by ${this._owner.displayName()}`,
|
||||
MessageType.ERROR,
|
||||
this._targetID
|
||||
);
|
||||
for (const outgoing of this._owner.outgoingAttacks()) {
|
||||
if (
|
||||
outgoing != this.attack &&
|
||||
outgoing.target() == this.attack.target() &&
|
||||
outgoing.sourceTile() == this.attack.sourceTile()
|
||||
) {
|
||||
// Existing attack on same target, add troops
|
||||
outgoing.setTroops(outgoing.troops() + this.attack.troops());
|
||||
this.active = false;
|
||||
this.attack.delete();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.sourceTile != null) {
|
||||
this.addNeighbors(this.sourceTile);
|
||||
} else {
|
||||
@@ -155,9 +150,11 @@ export class AttackExecution implements Execution {
|
||||
}
|
||||
|
||||
tick(ticks: number) {
|
||||
if (!this.active) {
|
||||
if (!this.attack.isActive()) {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const alliance = this._owner.allianceWith(this.target as Player);
|
||||
if (this.breakAlliance && alliance != null) {
|
||||
this.breakAlliance = false;
|
||||
@@ -165,7 +162,8 @@ export class AttackExecution implements Execution {
|
||||
}
|
||||
if (this.target.isPlayer() && this._owner.isAlliedWith(this.target)) {
|
||||
// In this case a new alliance was created AFTER the attack started.
|
||||
this._owner.addTroops(this.troops);
|
||||
this._owner.addTroops(this.attack.troops());
|
||||
this.attack.delete();
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
@@ -173,7 +171,7 @@ export class AttackExecution implements Execution {
|
||||
let numTilesPerTick = this.mg
|
||||
.config()
|
||||
.attackTilesPerTick(
|
||||
this.troops,
|
||||
this.attack.troops(),
|
||||
this._owner,
|
||||
this.target,
|
||||
this.border.size + this.random.nextInt(0, 5)
|
||||
@@ -182,7 +180,8 @@ export class AttackExecution implements Execution {
|
||||
// consolex.log(`num execs: ${this.mg.executions().length}`)
|
||||
|
||||
while (numTilesPerTick > 0) {
|
||||
if (this.troops < 1) {
|
||||
if (this.attack.troops() < 1) {
|
||||
this.attack.delete();
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
@@ -190,7 +189,8 @@ export class AttackExecution implements Execution {
|
||||
if (this.toConquer.size() == 0) {
|
||||
this.refreshToConquer();
|
||||
this.active = false;
|
||||
this._owner.addTroops(this.troops);
|
||||
this._owner.addTroops(this.attack.troops());
|
||||
this.attack.delete();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -209,13 +209,13 @@ export class AttackExecution implements Execution {
|
||||
.config()
|
||||
.attackLogic(
|
||||
this.mg,
|
||||
this.troops,
|
||||
this.attack.troops(),
|
||||
this._owner,
|
||||
this.target,
|
||||
tileToConquer
|
||||
);
|
||||
numTilesPerTick -= tilesPerTickUsed;
|
||||
this.troops -= attackerTroopLoss;
|
||||
this.attack.setTroops(this.attack.troops() - attackerTroopLoss);
|
||||
if (this.target.isPlayer()) {
|
||||
this.target.removeTroops(defenderTroopLoss);
|
||||
}
|
||||
|
||||
@@ -26,9 +26,8 @@ export class MirvExecution implements Execution {
|
||||
|
||||
private nuke: Unit;
|
||||
|
||||
private mirvRange = 500;
|
||||
private warheadCount = 1000;
|
||||
// private warheadRange = 5;
|
||||
private mirvRange = 1500;
|
||||
private warheadCount = 500;
|
||||
|
||||
private random: PseudoRandom;
|
||||
|
||||
@@ -92,7 +91,7 @@ export class MirvExecution implements Execution {
|
||||
|
||||
private separate() {
|
||||
const dsts: TileRef[] = [this.dst];
|
||||
let attempts = 1000;
|
||||
let attempts = 10000;
|
||||
while (attempts > 0 && dsts.length < this.warheadCount) {
|
||||
attempts--;
|
||||
const potential = this.randomLand(this.dst);
|
||||
@@ -106,6 +105,7 @@ export class MirvExecution implements Execution {
|
||||
(a, b) =>
|
||||
this.mg.manhattanDist(b, this.dst) - this.mg.manhattanDist(a, this.dst)
|
||||
);
|
||||
console.log(`got ${dsts.length} dsts!!`);
|
||||
|
||||
for (const [i, dst] of dsts.entries()) {
|
||||
this.mg.addExecution(
|
||||
|
||||
@@ -104,13 +104,13 @@ export class NukeExecution implements Execution {
|
||||
let magnitude;
|
||||
switch (this.type) {
|
||||
case UnitType.MIRVWarhead:
|
||||
magnitude = { inner: 10, outer: 14 };
|
||||
magnitude = { inner: 20, outer: 25 };
|
||||
break;
|
||||
case UnitType.AtomBomb:
|
||||
magnitude = { inner: 15, outer: 40 };
|
||||
break;
|
||||
case UnitType.HydrogenBomb:
|
||||
magnitude = { inner: 140, outer: 160 };
|
||||
magnitude = { inner: 120, outer: 140 };
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Attack, Player, TerraNullius } from "./Game";
|
||||
import { TileRef } from "./GameMap";
|
||||
import { PlayerImpl } from "./PlayerImpl";
|
||||
|
||||
export class AttackImpl implements Attack {
|
||||
private _isActive = true;
|
||||
|
||||
constructor(
|
||||
private _target: Player | TerraNullius,
|
||||
private _attacker: Player,
|
||||
private _troops: number,
|
||||
private _sourceTile: TileRef | null
|
||||
) {}
|
||||
|
||||
sourceTile(): TileRef | null {
|
||||
return this._sourceTile;
|
||||
}
|
||||
|
||||
target(): Player | TerraNullius {
|
||||
return this._target;
|
||||
}
|
||||
attacker(): Player {
|
||||
return this._attacker;
|
||||
}
|
||||
troops(): number {
|
||||
return this._troops;
|
||||
}
|
||||
setTroops(troops: number) {
|
||||
this._troops = troops;
|
||||
}
|
||||
|
||||
isActive() {
|
||||
return this._isActive;
|
||||
}
|
||||
|
||||
delete() {
|
||||
if (this._target.isPlayer()) {
|
||||
(this._target as PlayerImpl)._incomingAttacks = (
|
||||
this._target as PlayerImpl
|
||||
)._incomingAttacks.filter((a) => a != this);
|
||||
}
|
||||
|
||||
(this._attacker as PlayerImpl)._outgoingAttacks = (
|
||||
this._attacker as PlayerImpl
|
||||
)._outgoingAttacks.filter((a) => a != this);
|
||||
|
||||
this._isActive = false;
|
||||
}
|
||||
}
|
||||
+21
-3
@@ -135,6 +135,17 @@ export interface Execution {
|
||||
owner(): Player;
|
||||
}
|
||||
|
||||
export interface Attack {
|
||||
target(): Player | TerraNullius;
|
||||
attacker(): Player;
|
||||
troops(): number;
|
||||
setTroops(troops: number): void;
|
||||
isActive(): boolean;
|
||||
delete(): void;
|
||||
// The tile the attack originated from, mostly used for boat attacks.
|
||||
sourceTile(): TileRef | null;
|
||||
}
|
||||
|
||||
export interface AllianceRequest {
|
||||
accept(): void;
|
||||
reject(): void;
|
||||
@@ -284,12 +295,21 @@ export interface Player {
|
||||
canDonate(recipient: Player): boolean;
|
||||
donate(recipient: Player, troops: number): void;
|
||||
|
||||
// Attacking.
|
||||
canAttack(tile: TileRef): boolean;
|
||||
createAttack(
|
||||
target: Player | TerraNullius,
|
||||
troops: number,
|
||||
sourceTile: TileRef
|
||||
): Attack;
|
||||
outgoingAttacks(): Attack[];
|
||||
incomingAttacks(): Attack[];
|
||||
|
||||
// Misc
|
||||
executions(): Execution[];
|
||||
toUpdate(): PlayerUpdate;
|
||||
playerProfile(): PlayerProfile;
|
||||
canBoat(tile: TileRef): boolean;
|
||||
canAttack(tile: TileRef);
|
||||
}
|
||||
|
||||
export interface Game extends GameMap {
|
||||
@@ -324,8 +344,6 @@ export interface Game extends GameMap {
|
||||
unitInfo(type: UnitType): UnitInfo;
|
||||
nearbyDefensePosts(tile: TileRef): Unit[];
|
||||
|
||||
// Events & Messages
|
||||
executions(): Execution[];
|
||||
addExecution(...exec: Execution[]): void;
|
||||
displayMessage(
|
||||
message: string,
|
||||
|
||||
@@ -70,6 +70,12 @@ export interface UnitUpdate {
|
||||
constructionType?: UnitType;
|
||||
}
|
||||
|
||||
export interface AttackUpdate {
|
||||
attackerID: number;
|
||||
targetID: number;
|
||||
troops: number;
|
||||
}
|
||||
|
||||
export interface PlayerUpdate {
|
||||
type: GameUpdateType.Player;
|
||||
nameViewData?: NameViewData;
|
||||
@@ -90,6 +96,8 @@ export interface PlayerUpdate {
|
||||
isTraitor: boolean;
|
||||
targets: number[];
|
||||
outgoingEmojis: EmojiMessage[];
|
||||
outgoingAttacks: AttackUpdate[];
|
||||
incomingAttacks: AttackUpdate[];
|
||||
}
|
||||
|
||||
export interface AllianceRequestUpdate {
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
PlayerProfile,
|
||||
Unit,
|
||||
} from "./Game";
|
||||
import { PlayerUpdate } from "./GameUpdates";
|
||||
import { AttackUpdate, PlayerUpdate } from "./GameUpdates";
|
||||
import { UnitUpdate } from "./GameUpdates";
|
||||
import { NameViewData } from "./Game";
|
||||
import { GameUpdateType } from "./GameUpdates";
|
||||
@@ -106,6 +106,14 @@ export class PlayerView {
|
||||
);
|
||||
}
|
||||
|
||||
outgoingAttacks(): AttackUpdate[] {
|
||||
return this.data.outgoingAttacks;
|
||||
}
|
||||
|
||||
incomingAttacks(): AttackUpdate[] {
|
||||
return this.data.incomingAttacks;
|
||||
}
|
||||
|
||||
units(...types: UnitType[]): UnitView[] {
|
||||
return this.game
|
||||
.units(...types)
|
||||
|
||||
@@ -17,8 +17,9 @@ import {
|
||||
Relation,
|
||||
EmojiMessage,
|
||||
PlayerProfile,
|
||||
Attack,
|
||||
} from "./Game";
|
||||
import { PlayerUpdate } from "./GameUpdates";
|
||||
import { AttackUpdate, PlayerUpdate } from "./GameUpdates";
|
||||
import { GameUpdateType } from "./GameUpdates";
|
||||
import { ClientID } from "../Schemas";
|
||||
import {
|
||||
@@ -37,6 +38,7 @@ import { renderTroops } from "../../client/Utils";
|
||||
import { TerraNulliusImpl } from "./TerraNulliusImpl";
|
||||
import { andFN, manhattanDistFN, TileRef } from "./GameMap";
|
||||
import { Emoji } from "discord.js";
|
||||
import { AttackImpl } from "./AttackImpl";
|
||||
|
||||
interface Target {
|
||||
tick: Tick;
|
||||
@@ -75,6 +77,9 @@ export class PlayerImpl implements Player {
|
||||
|
||||
private relations = new Map<Player, number>();
|
||||
|
||||
public _incomingAttacks: Attack[] = [];
|
||||
public _outgoingAttacks: Attack[] = [];
|
||||
|
||||
constructor(
|
||||
private mg: GameImpl,
|
||||
private _smallID: number,
|
||||
@@ -111,6 +116,22 @@ export class PlayerImpl implements Player {
|
||||
isTraitor: this.isTraitor(),
|
||||
targets: this.targets().map((p) => p.smallID()),
|
||||
outgoingEmojis: this.outgoingEmojis(),
|
||||
outgoingAttacks: this._outgoingAttacks.map(
|
||||
(a) =>
|
||||
({
|
||||
attackerID: a.attacker().smallID(),
|
||||
targetID: a.target().smallID(),
|
||||
troops: a.troops(),
|
||||
} as AttackUpdate)
|
||||
),
|
||||
incomingAttacks: this._incomingAttacks.map(
|
||||
(a) =>
|
||||
({
|
||||
attackerID: a.attacker().smallID(),
|
||||
targetID: a.target().smallID(),
|
||||
troops: a.troops(),
|
||||
} as AttackUpdate)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -759,6 +780,25 @@ export class PlayerImpl implements Player {
|
||||
}
|
||||
}
|
||||
|
||||
createAttack(
|
||||
target: Player | TerraNullius,
|
||||
troops: number,
|
||||
sourceTile: TileRef
|
||||
): Attack {
|
||||
const attack = new AttackImpl(target, this, troops, sourceTile);
|
||||
this._outgoingAttacks.push(attack);
|
||||
if (target.isPlayer()) {
|
||||
(target as PlayerImpl)._incomingAttacks.push(attack);
|
||||
}
|
||||
return attack;
|
||||
}
|
||||
outgoingAttacks(): Attack[] {
|
||||
return this._outgoingAttacks;
|
||||
}
|
||||
incomingAttacks(): Attack[] {
|
||||
return this._incomingAttacks;
|
||||
}
|
||||
|
||||
public canAttack(tile: TileRef): boolean {
|
||||
if (
|
||||
this.mg.hasOwner(tile) &&
|
||||
|
||||
Reference in New Issue
Block a user