mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-30 13:22:51 +00:00
combine battleship + destroyer => warship.
This commit is contained in:
@@ -22,7 +22,7 @@ import { renderNumber, renderTroops } from "../../Utils";
|
||||
function euclideanDistWorld(
|
||||
coord: { x: number; y: number },
|
||||
tileRef: TileRef,
|
||||
game: GameView,
|
||||
game: GameView
|
||||
): number {
|
||||
const x = game.x(tileRef);
|
||||
const y = game.y(tileRef);
|
||||
@@ -71,7 +71,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
||||
|
||||
init() {
|
||||
this.eventBus.on(MouseMoveEvent, (e: MouseMoveEvent) =>
|
||||
this.onMouseEvent(e),
|
||||
this.onMouseEvent(e)
|
||||
);
|
||||
this._isActive = true;
|
||||
}
|
||||
@@ -111,7 +111,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
||||
this.setVisible(true);
|
||||
} else if (!this.game.isLand(tile)) {
|
||||
const units = this.game
|
||||
.units(UnitType.Destroyer, UnitType.Battleship, UnitType.TradeShip)
|
||||
.units(UnitType.Warship, UnitType.TradeShip, UnitType.TransportShip)
|
||||
.filter((u) => euclideanDistWorld(worldCoord, u.tile(), this.game) < 50)
|
||||
.sort(distSortUnitWorld(worldCoord, this.game));
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ export class UnitLayer implements Layer {
|
||||
constructor(
|
||||
private game: GameView,
|
||||
private eventBus: EventBus,
|
||||
private clientID: ClientID,
|
||||
private clientID: ClientID
|
||||
) {
|
||||
this.theme = game.config().theme();
|
||||
}
|
||||
@@ -65,7 +65,7 @@ export class UnitLayer implements Layer {
|
||||
-this.game.width() / 2,
|
||||
-this.game.height() / 2,
|
||||
this.game.width(),
|
||||
this.game.height(),
|
||||
this.game.height()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -103,11 +103,8 @@ export class UnitLayer implements Layer {
|
||||
case UnitType.TransportShip:
|
||||
this.handleBoatEvent(unit);
|
||||
break;
|
||||
case UnitType.Destroyer:
|
||||
this.handleDestroyerEvent(unit);
|
||||
break;
|
||||
case UnitType.Battleship:
|
||||
this.handleBattleshipEvent(unit);
|
||||
case UnitType.Warship:
|
||||
this.handleWarShipEvent(unit);
|
||||
break;
|
||||
case UnitType.Shell:
|
||||
this.handleShellEvent(unit);
|
||||
@@ -122,54 +119,13 @@ export class UnitLayer implements Layer {
|
||||
}
|
||||
}
|
||||
|
||||
private handleDestroyerEvent(unit: UnitView) {
|
||||
private handleWarShipEvent(unit: UnitView) {
|
||||
const rel = this.relationship(unit);
|
||||
|
||||
// Clear previous area
|
||||
for (const t of this.game.bfs(
|
||||
unit.lastTile(),
|
||||
euclDistFN(unit.lastTile(), 4),
|
||||
)) {
|
||||
this.clearCell(this.game.x(t), this.game.y(t));
|
||||
}
|
||||
|
||||
if (!unit.isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Paint border
|
||||
for (const t of this.game.bfs(unit.tile(), euclDistFN(unit.tile(), 4))) {
|
||||
this.paintCell(
|
||||
this.game.x(t),
|
||||
this.game.y(t),
|
||||
rel,
|
||||
this.theme.borderColor(unit.owner().info()),
|
||||
255,
|
||||
);
|
||||
}
|
||||
|
||||
// Paint territory
|
||||
for (const t of this.game.bfs(
|
||||
unit.tile(),
|
||||
manhattanDistFN(unit.tile(), 3),
|
||||
)) {
|
||||
this.paintCell(
|
||||
this.game.x(t),
|
||||
this.game.y(t),
|
||||
rel,
|
||||
this.theme.territoryColor(unit.owner().info()),
|
||||
255,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private handleBattleshipEvent(unit: UnitView) {
|
||||
const rel = this.relationship(unit);
|
||||
|
||||
// Clear previous area
|
||||
for (const t of this.game.bfs(
|
||||
unit.lastTile(),
|
||||
euclDistFN(unit.lastTile(), 6),
|
||||
euclDistFN(unit.lastTile(), 6)
|
||||
)) {
|
||||
this.clearCell(this.game.x(t), this.game.y(t));
|
||||
}
|
||||
@@ -185,21 +141,21 @@ export class UnitLayer implements Layer {
|
||||
this.game.y(t),
|
||||
rel,
|
||||
this.theme.territoryColor(unit.owner().info()),
|
||||
255,
|
||||
255
|
||||
);
|
||||
}
|
||||
|
||||
// Paint border
|
||||
for (const t of this.game.bfs(
|
||||
unit.tile(),
|
||||
manhattanDistFN(unit.tile(), 4),
|
||||
manhattanDistFN(unit.tile(), 4)
|
||||
)) {
|
||||
this.paintCell(
|
||||
this.game.x(t),
|
||||
this.game.y(t),
|
||||
rel,
|
||||
this.theme.borderColor(unit.owner().info()),
|
||||
255,
|
||||
255
|
||||
);
|
||||
}
|
||||
|
||||
@@ -210,7 +166,7 @@ export class UnitLayer implements Layer {
|
||||
this.game.y(t),
|
||||
rel,
|
||||
this.theme.territoryColor(unit.owner().info()),
|
||||
255,
|
||||
255
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -236,14 +192,14 @@ export class UnitLayer implements Layer {
|
||||
this.game.y(unit.tile()),
|
||||
rel,
|
||||
this.theme.borderColor(unit.owner().info()),
|
||||
255,
|
||||
255
|
||||
);
|
||||
this.paintCell(
|
||||
this.game.x(unit.lastTile()),
|
||||
this.game.y(unit.lastTile()),
|
||||
rel,
|
||||
this.theme.borderColor(unit.owner().info()),
|
||||
255,
|
||||
255
|
||||
);
|
||||
}
|
||||
|
||||
@@ -253,7 +209,7 @@ export class UnitLayer implements Layer {
|
||||
// Clear previous area
|
||||
for (const t of this.game.bfs(
|
||||
unit.lastTile(),
|
||||
euclDistFN(unit.lastTile(), 2),
|
||||
euclDistFN(unit.lastTile(), 2)
|
||||
)) {
|
||||
this.clearCell(this.game.x(t), this.game.y(t));
|
||||
}
|
||||
@@ -266,7 +222,7 @@ export class UnitLayer implements Layer {
|
||||
this.game.y(t),
|
||||
rel,
|
||||
this.theme.borderColor(unit.owner().info()),
|
||||
255,
|
||||
255
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -278,7 +234,7 @@ export class UnitLayer implements Layer {
|
||||
// Clear previous area
|
||||
for (const t of this.game.bfs(
|
||||
unit.lastTile(),
|
||||
euclDistFN(unit.lastTile(), 3),
|
||||
euclDistFN(unit.lastTile(), 3)
|
||||
)) {
|
||||
this.clearCell(this.game.x(t), this.game.y(t));
|
||||
}
|
||||
@@ -287,28 +243,28 @@ export class UnitLayer implements Layer {
|
||||
// Paint territory
|
||||
for (const t of this.game.bfs(
|
||||
unit.tile(),
|
||||
manhattanDistFN(unit.tile(), 2),
|
||||
manhattanDistFN(unit.tile(), 2)
|
||||
)) {
|
||||
this.paintCell(
|
||||
this.game.x(t),
|
||||
this.game.y(t),
|
||||
rel,
|
||||
this.theme.territoryColor(unit.owner().info()),
|
||||
255,
|
||||
255
|
||||
);
|
||||
}
|
||||
|
||||
// Paint border
|
||||
for (const t of this.game.bfs(
|
||||
unit.tile(),
|
||||
manhattanDistFN(unit.tile(), 1),
|
||||
manhattanDistFN(unit.tile(), 1)
|
||||
)) {
|
||||
this.paintCell(
|
||||
this.game.x(t),
|
||||
this.game.y(t),
|
||||
rel,
|
||||
this.theme.borderColor(unit.owner().info()),
|
||||
255,
|
||||
255
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -326,7 +282,7 @@ export class UnitLayer implements Layer {
|
||||
// Clear previous area
|
||||
for (const t of this.game.bfs(
|
||||
unit.lastTile(),
|
||||
manhattanDistFN(unit.lastTile(), 3),
|
||||
manhattanDistFN(unit.lastTile(), 3)
|
||||
)) {
|
||||
this.clearCell(this.game.x(t), this.game.y(t));
|
||||
}
|
||||
@@ -339,35 +295,35 @@ export class UnitLayer implements Layer {
|
||||
this.game.y(t),
|
||||
rel,
|
||||
this.theme.territoryColor(unit.owner().info()),
|
||||
150,
|
||||
150
|
||||
);
|
||||
}
|
||||
|
||||
// Paint border
|
||||
for (const t of this.game.bfs(
|
||||
unit.tile(),
|
||||
manhattanDistFN(unit.tile(), 2),
|
||||
manhattanDistFN(unit.tile(), 2)
|
||||
)) {
|
||||
this.paintCell(
|
||||
this.game.x(t),
|
||||
this.game.y(t),
|
||||
rel,
|
||||
this.theme.borderColor(unit.owner().info()),
|
||||
255,
|
||||
255
|
||||
);
|
||||
}
|
||||
|
||||
// Paint territory
|
||||
for (const t of this.game.bfs(
|
||||
unit.tile(),
|
||||
manhattanDistFN(unit.tile(), 1),
|
||||
manhattanDistFN(unit.tile(), 1)
|
||||
)) {
|
||||
this.paintCell(
|
||||
this.game.x(t),
|
||||
this.game.y(t),
|
||||
rel,
|
||||
this.theme.territoryColor(unit.owner().info()),
|
||||
255,
|
||||
255
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -383,7 +339,7 @@ export class UnitLayer implements Layer {
|
||||
y: number,
|
||||
relationship: Relationship,
|
||||
color: Colord,
|
||||
alpha: number,
|
||||
alpha: number
|
||||
) {
|
||||
this.clearCell(x, y);
|
||||
if (this.alternateView) {
|
||||
|
||||
@@ -11,15 +11,13 @@ import {
|
||||
import { BuildUnitIntentEvent } from "../../../Transport";
|
||||
import atomBombIcon from "../../../../../resources/images/NukeIconWhite.svg";
|
||||
import hydrogenBombIcon from "../../../../../resources/images/MushroomCloudIconWhite.svg";
|
||||
import destroyerIcon from "../../../../../resources/images/DestroyerIconWhite.svg";
|
||||
import battleshipIcon from "../../../../../resources/images/BattleshipIconWhite.svg";
|
||||
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 shieldIcon from "../../../../../resources/images/ShieldIconWhite.svg";
|
||||
import cityIcon from "../../../../../resources/images/CityIconWhite.svg";
|
||||
import { renderNumber } from "../../../Utils";
|
||||
import { ContextMenuEvent } from "../../../InputHandler";
|
||||
import { GameView, PlayerView } from "../../../../core/game/GameView";
|
||||
|
||||
interface BuildItemDisplay {
|
||||
@@ -31,8 +29,7 @@ const buildTable: BuildItemDisplay[][] = [
|
||||
[
|
||||
{ unitType: UnitType.AtomBomb, icon: atomBombIcon },
|
||||
{ unitType: UnitType.HydrogenBomb, icon: hydrogenBombIcon },
|
||||
{ unitType: UnitType.Destroyer, icon: destroyerIcon },
|
||||
{ unitType: UnitType.Battleship, icon: battleshipIcon },
|
||||
{ unitType: UnitType.Warship, icon: warshipIcon },
|
||||
{ unitType: UnitType.Port, icon: portIcon },
|
||||
{ unitType: UnitType.MissileSilo, icon: missileSiloIcon },
|
||||
// { unitType: UnitType.DefensePost, icon: shieldIcon },
|
||||
@@ -201,7 +198,7 @@ export class BuildMenu extends LitElement {
|
||||
|
||||
public onBuildSelected = (item: BuildItemDisplay) => {
|
||||
this.eventBus.emit(
|
||||
new BuildUnitIntentEvent(item.unitType, this.clickedCell),
|
||||
new BuildUnitIntentEvent(item.unitType, this.clickedCell)
|
||||
);
|
||||
this.hideMenu();
|
||||
};
|
||||
@@ -233,7 +230,7 @@ export class BuildMenu extends LitElement {
|
||||
? this.game
|
||||
.unitInfo(item.unitType)
|
||||
.cost(this.myPlayer)
|
||||
: 0,
|
||||
: 0
|
||||
)}
|
||||
<img
|
||||
src=${goldCoinIcon}
|
||||
@@ -244,10 +241,10 @@ export class BuildMenu extends LitElement {
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
`,
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`,
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -33,7 +33,7 @@ export abstract class DefaultServerConfig implements ServerConfig {
|
||||
export class DefaultConfig implements Config {
|
||||
constructor(
|
||||
private _serverConfig: ServerConfig,
|
||||
private _gameConfig: GameConfig,
|
||||
private _gameConfig: GameConfig
|
||||
) {}
|
||||
|
||||
gameConfig(): GameConfig {
|
||||
@@ -88,20 +88,12 @@ export class DefaultConfig implements Config {
|
||||
cost: () => 0,
|
||||
territoryBound: false,
|
||||
};
|
||||
case UnitType.Destroyer:
|
||||
case UnitType.Warship:
|
||||
return {
|
||||
cost: (p: Player) =>
|
||||
(p.units(UnitType.Destroyer).length + 1) * 250_000,
|
||||
cost: (p: Player) => (p.units(UnitType.Warship).length + 1) * 250_000,
|
||||
territoryBound: false,
|
||||
maxHealth: 1000,
|
||||
};
|
||||
case UnitType.Battleship:
|
||||
return {
|
||||
cost: (p: Player) =>
|
||||
(p.units(UnitType.Battleship).length + 1) * 500_000,
|
||||
territoryBound: false,
|
||||
maxHealth: 5000,
|
||||
};
|
||||
case UnitType.Shell:
|
||||
return {
|
||||
cost: () => 0,
|
||||
@@ -113,7 +105,7 @@ export class DefaultConfig implements Config {
|
||||
cost: (p: Player) =>
|
||||
Math.min(
|
||||
1_000_000,
|
||||
Math.pow(2, p.units(UnitType.Port).length) * 250_000,
|
||||
Math.pow(2, p.units(UnitType.Port).length) * 250_000
|
||||
),
|
||||
territoryBound: true,
|
||||
};
|
||||
@@ -142,7 +134,7 @@ export class DefaultConfig implements Config {
|
||||
cost: (p: Player) =>
|
||||
Math.min(
|
||||
250_000,
|
||||
(p.units(UnitType.DefensePost).length + 1) * 50_000,
|
||||
(p.units(UnitType.DefensePost).length + 1) * 50_000
|
||||
),
|
||||
territoryBound: true,
|
||||
};
|
||||
@@ -151,7 +143,7 @@ export class DefaultConfig implements Config {
|
||||
cost: (p: Player) =>
|
||||
Math.min(
|
||||
1_000_000,
|
||||
Math.pow(2, p.units(UnitType.City).length) * 125_000,
|
||||
Math.pow(2, p.units(UnitType.City).length) * 125_000
|
||||
),
|
||||
territoryBound: true,
|
||||
};
|
||||
@@ -207,7 +199,7 @@ export class DefaultConfig implements Config {
|
||||
attackTroops: number,
|
||||
attacker: Player,
|
||||
defender: Player | TerraNullius,
|
||||
tileToConquer: TileRef,
|
||||
tileToConquer: TileRef
|
||||
): {
|
||||
attackerTroopLoss: number;
|
||||
defenderTroopLoss: number;
|
||||
@@ -271,7 +263,7 @@ export class DefaultConfig implements Config {
|
||||
tilesPerTickUsed: within(
|
||||
(2000 * Math.max(10, speed)) / attackTroops,
|
||||
5,
|
||||
100,
|
||||
100
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -281,7 +273,7 @@ export class DefaultConfig implements Config {
|
||||
attackTroops: number,
|
||||
attacker: Player,
|
||||
defender: Player | TerraNullius,
|
||||
numAdjacentTilesWithEnemy: number,
|
||||
numAdjacentTilesWithEnemy: number
|
||||
): number {
|
||||
if (defender.isPlayer()) {
|
||||
return (
|
||||
|
||||
@@ -29,6 +29,10 @@ export class DevConfig extends DefaultConfig {
|
||||
return info;
|
||||
}
|
||||
|
||||
tradeShipSpawnRate(): number {
|
||||
return 10;
|
||||
}
|
||||
|
||||
// percentageTilesOwnedToWin(): number {
|
||||
// return 1
|
||||
// }
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
import {
|
||||
Cell,
|
||||
Execution,
|
||||
Game,
|
||||
Player,
|
||||
Unit,
|
||||
PlayerID,
|
||||
TerrainType,
|
||||
UnitType,
|
||||
} from "../game/Game";
|
||||
import { PathFinder } from "../pathfinding/PathFinding";
|
||||
import { PathFindResultType } from "../pathfinding/AStar";
|
||||
import { PseudoRandom } from "../PseudoRandom";
|
||||
import { distSort, distSortUnit } from "../Util";
|
||||
import { ShellExecution } from "./ShellExecution";
|
||||
import { consolex } from "../Consolex";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
|
||||
export class BattleshipExecution implements Execution {
|
||||
private random: PseudoRandom;
|
||||
|
||||
private _owner: Player;
|
||||
private active = true;
|
||||
private battleship: Unit = null;
|
||||
private mg: Game = null;
|
||||
|
||||
private pathfinder: PathFinder;
|
||||
|
||||
private patrolTile: TileRef;
|
||||
|
||||
// TODO: put in config
|
||||
private searchRange = 100;
|
||||
private attackRate = 5;
|
||||
private lastAttack = 0;
|
||||
|
||||
private alreadyTargeted = new Set<Unit>();
|
||||
|
||||
constructor(
|
||||
private playerID: PlayerID,
|
||||
private patrolCenterTile: TileRef,
|
||||
) {}
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.pathfinder = PathFinder.Mini(mg, 5000, false);
|
||||
this._owner = mg.player(this.playerID);
|
||||
this.mg = mg;
|
||||
this.patrolTile = this.patrolCenterTile;
|
||||
this.random = new PseudoRandom(mg.ticks());
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
this.alreadyTargeted.forEach((u) => {
|
||||
if (!u.isActive()) {
|
||||
this.alreadyTargeted.delete(u);
|
||||
}
|
||||
});
|
||||
if (this.battleship == null) {
|
||||
const spawn = this._owner.canBuild(UnitType.Battleship, this.patrolTile);
|
||||
if (spawn == false) {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
this.battleship = this._owner.buildUnit(UnitType.Battleship, 0, spawn);
|
||||
return;
|
||||
}
|
||||
if (!this.battleship.isActive()) {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.mg.ticks() % 2 == 0) {
|
||||
const result = this.pathfinder.nextTile(
|
||||
this.battleship.tile(),
|
||||
this.patrolTile,
|
||||
);
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
this.patrolTile = this.randomTile();
|
||||
break;
|
||||
case PathFindResultType.NextTile:
|
||||
this.battleship.move(result.tile);
|
||||
break;
|
||||
case PathFindResultType.Pending:
|
||||
return;
|
||||
case PathFindResultType.PathNotFound:
|
||||
consolex.log(`path not found to patrol tile`);
|
||||
this.patrolTile = this.randomTile();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.mg.ticks() - this.lastAttack < this.attackRate) {
|
||||
return;
|
||||
}
|
||||
|
||||
let ships = this.mg
|
||||
.units(
|
||||
UnitType.TransportShip,
|
||||
UnitType.Destroyer,
|
||||
UnitType.TradeShip,
|
||||
UnitType.Battleship,
|
||||
)
|
||||
.filter(
|
||||
(u) => this.mg.manhattanDist(u.tile(), this.battleship.tile()) < 100,
|
||||
)
|
||||
.filter((u) => u.owner() != this.battleship.owner())
|
||||
.filter((u) => u != this.battleship)
|
||||
.filter((u) => !u.owner().isAlliedWith(this.battleship.owner()))
|
||||
.filter((u) => !this.alreadyTargeted.has(u))
|
||||
.sort(distSortUnit(this.mg, this.battleship));
|
||||
|
||||
const friendlyDestroyerNearby =
|
||||
this.battleship
|
||||
.owner()
|
||||
.units(UnitType.Destroyer)
|
||||
.filter(
|
||||
(d) => this.mg.manhattanDist(d.tile(), this.battleship.tile()) < 120,
|
||||
).length > 0;
|
||||
|
||||
if (friendlyDestroyerNearby) {
|
||||
// Don't attack trade ships to allow friendly destroyer to capture them
|
||||
ships = ships.filter((s) => s.type() != UnitType.TradeShip);
|
||||
}
|
||||
|
||||
if (ships.length > 0) {
|
||||
const toAttack = ships[0];
|
||||
if (!toAttack.hasHealth()) {
|
||||
// Don't send multiple shells to target if it can be one-shotted.
|
||||
this.alreadyTargeted.add(toAttack);
|
||||
}
|
||||
this.lastAttack = this.mg.ticks();
|
||||
this.mg.addExecution(
|
||||
new ShellExecution(
|
||||
this.battleship.tile(),
|
||||
this.battleship.owner(),
|
||||
this.battleship,
|
||||
toAttack,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return this._owner;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
randomTile(): TileRef {
|
||||
while (true) {
|
||||
const x =
|
||||
this.mg.x(this.patrolCenterTile) +
|
||||
this.random.nextInt(-this.searchRange / 2, this.searchRange / 2);
|
||||
const y =
|
||||
this.mg.y(this.patrolCenterTile) +
|
||||
this.random.nextInt(-this.searchRange / 2, this.searchRange / 2);
|
||||
if (!this.mg.isValidCoord(x, y)) {
|
||||
continue;
|
||||
}
|
||||
const tile = this.mg.ref(x, y);
|
||||
if (!this.mg.isOcean(tile)) {
|
||||
continue;
|
||||
}
|
||||
return tile;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,10 +31,9 @@ import { EmojiExecution } from "./EmojiExecution";
|
||||
import { DonateExecution } from "./DonateExecution";
|
||||
import { NukeExecution } from "./NukeExecution";
|
||||
import { SetTargetTroopRatioExecution } from "./SetTargetTroopRatioExecution";
|
||||
import { DestroyerExecution } from "./DestroyerExecution";
|
||||
import { WarshipExecution } from "./WarshipExecution";
|
||||
import { PortExecution } from "./PortExecution";
|
||||
import { MissileSiloExecution } from "./MissileSiloExecution";
|
||||
import { BattleshipExecution } from "./BattleshipExecution";
|
||||
import { DefensePostExecution } from "./DefensePostExecution";
|
||||
import { CityExecution } from "./CityExecution";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
@@ -43,10 +42,7 @@ export class Executor {
|
||||
// private random = new PseudoRandom(999)
|
||||
private random: PseudoRandom = null;
|
||||
|
||||
constructor(
|
||||
private mg: Game,
|
||||
private gameID: GameID,
|
||||
) {
|
||||
constructor(private mg: Game, private gameID: GameID) {
|
||||
// Add one to avoid id collisions with bots.
|
||||
this.random = new PseudoRandom(simpleHash(gameID) + 1);
|
||||
}
|
||||
@@ -62,7 +58,7 @@ export class Executor {
|
||||
intent.troops,
|
||||
intent.attackerID,
|
||||
intent.targetID,
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
case "spawn":
|
||||
@@ -71,16 +67,16 @@ export class Executor {
|
||||
sanitize(intent.name),
|
||||
intent.playerType,
|
||||
intent.clientID,
|
||||
intent.playerID,
|
||||
intent.playerID
|
||||
),
|
||||
this.mg.ref(intent.x, intent.y),
|
||||
this.mg.ref(intent.x, intent.y)
|
||||
);
|
||||
case "boat":
|
||||
return new TransportShipExecution(
|
||||
intent.attackerID,
|
||||
intent.targetID,
|
||||
this.mg.ref(intent.x, intent.y),
|
||||
intent.troops,
|
||||
intent.troops
|
||||
);
|
||||
case "allianceRequest":
|
||||
return new AllianceRequestExecution(intent.requestor, intent.recipient);
|
||||
@@ -88,7 +84,7 @@ export class Executor {
|
||||
return new AllianceRequestReplyExecution(
|
||||
intent.requestor,
|
||||
intent.recipient,
|
||||
intent.accept,
|
||||
intent.accept
|
||||
);
|
||||
case "breakAlliance":
|
||||
return new BreakAllianceExecution(intent.requestor, intent.recipient);
|
||||
@@ -98,13 +94,13 @@ export class Executor {
|
||||
return new EmojiExecution(
|
||||
intent.sender,
|
||||
intent.recipient,
|
||||
intent.emoji,
|
||||
intent.emoji
|
||||
);
|
||||
case "donate":
|
||||
return new DonateExecution(
|
||||
intent.sender,
|
||||
intent.recipient,
|
||||
intent.troops,
|
||||
intent.troops
|
||||
);
|
||||
case "troop_ratio":
|
||||
return new SetTargetTroopRatioExecution(intent.player, intent.ratio);
|
||||
@@ -115,37 +111,32 @@ export class Executor {
|
||||
return new NukeExecution(
|
||||
intent.unit,
|
||||
intent.player,
|
||||
this.mg.ref(intent.x, intent.y),
|
||||
this.mg.ref(intent.x, intent.y)
|
||||
);
|
||||
case UnitType.Destroyer:
|
||||
return new DestroyerExecution(
|
||||
case UnitType.Warship:
|
||||
return new WarshipExecution(
|
||||
intent.player,
|
||||
this.mg.ref(intent.x, intent.y),
|
||||
);
|
||||
case UnitType.Battleship:
|
||||
return new BattleshipExecution(
|
||||
intent.player,
|
||||
this.mg.ref(intent.x, intent.y),
|
||||
this.mg.ref(intent.x, intent.y)
|
||||
);
|
||||
case UnitType.Port:
|
||||
return new PortExecution(
|
||||
intent.player,
|
||||
this.mg.ref(intent.x, intent.y),
|
||||
this.mg.ref(intent.x, intent.y)
|
||||
);
|
||||
case UnitType.MissileSilo:
|
||||
return new MissileSiloExecution(
|
||||
intent.player,
|
||||
this.mg.ref(intent.x, intent.y),
|
||||
this.mg.ref(intent.x, intent.y)
|
||||
);
|
||||
case UnitType.DefensePost:
|
||||
return new DefensePostExecution(
|
||||
intent.player,
|
||||
this.mg.ref(intent.x, intent.y),
|
||||
this.mg.ref(intent.x, intent.y)
|
||||
);
|
||||
case UnitType.City:
|
||||
return new CityExecution(
|
||||
intent.player,
|
||||
this.mg.ref(intent.x, intent.y),
|
||||
this.mg.ref(intent.x, intent.y)
|
||||
);
|
||||
default:
|
||||
throw Error(`unit type ${intent.unit} not supported`);
|
||||
@@ -171,14 +162,14 @@ export class Executor {
|
||||
nation.name,
|
||||
PlayerType.FakeHuman,
|
||||
null,
|
||||
this.random.nextID(),
|
||||
this.random.nextID()
|
||||
),
|
||||
nation.cell,
|
||||
nation.strength *
|
||||
this.mg
|
||||
.config()
|
||||
.difficultyModifier(this.mg.config().gameConfig().difficulty),
|
||||
),
|
||||
.difficultyModifier(this.mg.config().gameConfig().difficulty)
|
||||
)
|
||||
);
|
||||
}
|
||||
return execs;
|
||||
|
||||
@@ -18,8 +18,7 @@ import { AttackExecution } from "./AttackExecution";
|
||||
import { TransportShipExecution } from "./TransportShipExecution";
|
||||
import { SpawnExecution } from "./SpawnExecution";
|
||||
import { PortExecution } from "./PortExecution";
|
||||
import { DestroyerExecution } from "./DestroyerExecution";
|
||||
import { BattleshipExecution } from "./BattleshipExecution";
|
||||
import { WarshipExecution } from "./WarshipExecution";
|
||||
import { GameID } from "../Schemas";
|
||||
import { consolex } from "../Consolex";
|
||||
import { CityExecution } from "./CityExecution";
|
||||
@@ -321,10 +320,7 @@ export class FakeHumanExecution implements Execution {
|
||||
2,
|
||||
(t) => new CityExecution(this.player.id(), t)
|
||||
);
|
||||
if (this.maybeSpawnWarship(UnitType.Destroyer)) {
|
||||
return;
|
||||
}
|
||||
if (this.maybeSpawnWarship(UnitType.Battleship)) {
|
||||
if (this.maybeSpawnWarship()) {
|
||||
return;
|
||||
}
|
||||
this.maybeSpawnStructure(
|
||||
@@ -359,41 +355,28 @@ export class FakeHumanExecution implements Execution {
|
||||
this.mg.addExecution(build(tile));
|
||||
}
|
||||
|
||||
private maybeSpawnWarship(
|
||||
shipType: UnitType.Destroyer | UnitType.Battleship
|
||||
): boolean {
|
||||
private maybeSpawnWarship(): boolean {
|
||||
if (!this.random.chance(50)) {
|
||||
return false;
|
||||
}
|
||||
const ports = this.player.units(UnitType.Port);
|
||||
const ships = this.player.units(shipType);
|
||||
const ships = this.player.units(UnitType.Warship);
|
||||
if (
|
||||
ports.length > 0 &&
|
||||
ships.length == 0 &&
|
||||
this.player.gold() > this.cost(shipType)
|
||||
this.player.gold() > this.cost(UnitType.Warship)
|
||||
) {
|
||||
const port = this.random.randElement(ports);
|
||||
const targetTile = this.warshipSpawnTile(port.tile());
|
||||
if (targetTile == null) {
|
||||
return false;
|
||||
}
|
||||
const canBuild = this.player.canBuild(UnitType.Destroyer, targetTile);
|
||||
const canBuild = this.player.canBuild(UnitType.Warship, targetTile);
|
||||
if (canBuild == false) {
|
||||
consolex.warn("cannot spawn destroyer");
|
||||
return false;
|
||||
}
|
||||
switch (shipType) {
|
||||
case UnitType.Destroyer:
|
||||
this.mg.addExecution(
|
||||
new DestroyerExecution(this.player.id(), targetTile)
|
||||
);
|
||||
break;
|
||||
case UnitType.Battleship:
|
||||
this.mg.addExecution(
|
||||
new BattleshipExecution(this.player.id(), targetTile)
|
||||
);
|
||||
break;
|
||||
}
|
||||
this.mg.addExecution(new WarshipExecution(this.player.id(), targetTile));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -25,10 +25,7 @@ export class PortExecution implements Execution {
|
||||
private portPaths = new Map<Unit, TileRef[]>();
|
||||
private computingPaths = new Map<Unit, MiniAStar>();
|
||||
|
||||
constructor(
|
||||
private _owner: PlayerID,
|
||||
private tile: TileRef,
|
||||
) {}
|
||||
constructor(private _owner: PlayerID, private tile: TileRef) {}
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.mg = mg;
|
||||
@@ -49,7 +46,7 @@ export class PortExecution implements Execution {
|
||||
.filter((t) => this.mg.isOceanShore(t) && this.mg.owner(t) == player)
|
||||
.sort(
|
||||
(a, b) =>
|
||||
this.mg.manhattanDist(a, tile) - this.mg.manhattanDist(b, tile),
|
||||
this.mg.manhattanDist(a, tile) - this.mg.manhattanDist(b, tile)
|
||||
);
|
||||
|
||||
if (spawns.length == 0) {
|
||||
@@ -71,7 +68,7 @@ export class PortExecution implements Execution {
|
||||
const alliedPortsSet = new Set(alliedPorts);
|
||||
|
||||
const allyConnections = new Set(
|
||||
Array.from(this.portPaths.keys()).map((p) => p.owner()),
|
||||
Array.from(this.portPaths.keys()).map((p) => p.owner())
|
||||
);
|
||||
allyConnections;
|
||||
|
||||
@@ -103,7 +100,7 @@ export class PortExecution implements Execution {
|
||||
port.tile(),
|
||||
(tr: TileRef) => this.mg.miniMap().isOcean(tr),
|
||||
10_000,
|
||||
25,
|
||||
25
|
||||
);
|
||||
this.computingPaths.set(port, pf);
|
||||
}
|
||||
@@ -124,9 +121,9 @@ export class PortExecution implements Execution {
|
||||
const port = this.random.randElement(portConnections);
|
||||
const path = this.portPaths.get(port);
|
||||
if (path != null) {
|
||||
const pf = PathFinder.Mini(this.mg, 10, false);
|
||||
const pf = PathFinder.Mini(this.mg, 10000, false);
|
||||
this.mg.addExecution(
|
||||
new TradeShipExecution(this.player().id(), this.port, port, pf, path),
|
||||
new TradeShipExecution(this.player().id(), this.port, port, pf, path)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,14 @@ import { PseudoRandom } from "../PseudoRandom";
|
||||
import { distSort, distSortUnit } from "../Util";
|
||||
import { consolex } from "../Consolex";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
import { ShellExecution } from "./ShellExecution";
|
||||
|
||||
export class DestroyerExecution implements Execution {
|
||||
export class WarshipExecution implements Execution {
|
||||
private random: PseudoRandom;
|
||||
|
||||
private _owner: Player;
|
||||
private active = true;
|
||||
private destroyer: Unit = null;
|
||||
private warship: Unit = null;
|
||||
private mg: Game = null;
|
||||
|
||||
private target: Unit = null;
|
||||
@@ -31,10 +32,12 @@ export class DestroyerExecution implements Execution {
|
||||
// TODO: put in config
|
||||
private searchRange = 100;
|
||||
|
||||
constructor(
|
||||
private playerID: PlayerID,
|
||||
private patrolCenterTile: TileRef,
|
||||
) {}
|
||||
private shellAttackRate = 5;
|
||||
private lastShellAttack = 0;
|
||||
|
||||
private alreadySentShell = new Set<Unit>();
|
||||
|
||||
constructor(private playerID: PlayerID, private patrolCenterTile: TileRef) {}
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.pathfinder = PathFinder.Mini(mg, 5000, false);
|
||||
@@ -45,16 +48,16 @@ export class DestroyerExecution implements Execution {
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.destroyer == null) {
|
||||
const spawn = this._owner.canBuild(UnitType.Destroyer, this.patrolTile);
|
||||
if (this.warship == null) {
|
||||
const spawn = this._owner.canBuild(UnitType.Warship, this.patrolTile);
|
||||
if (spawn == false) {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
this.destroyer = this._owner.buildUnit(UnitType.Destroyer, 0, spawn);
|
||||
this.warship = this._owner.buildUnit(UnitType.Warship, 0, spawn);
|
||||
return;
|
||||
}
|
||||
if (!this.destroyer.isActive()) {
|
||||
if (!this.warship.isActive()) {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
@@ -63,34 +66,28 @@ export class DestroyerExecution implements Execution {
|
||||
}
|
||||
if (this.target == null) {
|
||||
const ships = this.mg
|
||||
.units(
|
||||
UnitType.TransportShip,
|
||||
UnitType.Destroyer,
|
||||
UnitType.TradeShip,
|
||||
UnitType.Battleship,
|
||||
)
|
||||
.units(UnitType.TransportShip, UnitType.Warship, UnitType.TradeShip)
|
||||
.filter(
|
||||
(u) => this.mg.manhattanDist(u.tile(), this.destroyer.tile()) < 100,
|
||||
(u) => this.mg.manhattanDist(u.tile(), this.warship.tile()) < 100
|
||||
)
|
||||
.filter(
|
||||
(u) =>
|
||||
u.type() != UnitType.Destroyer ||
|
||||
u.health() < this.destroyer.health(),
|
||||
) // only attack Destroyers weaker than it.
|
||||
.filter((u) => u.owner() != this.destroyer.owner())
|
||||
.filter((u) => u != this.destroyer)
|
||||
.filter((u) => !u.owner().isAlliedWith(this.destroyer.owner()));
|
||||
if (ships.length == 0) {
|
||||
.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));
|
||||
|
||||
this.target = ships.sort(distSortUnit(this.mg, this.warship))[0] ?? null;
|
||||
if (this.target == null || this.target.type() != UnitType.TradeShip) {
|
||||
// Patrol unless we are hunting down a tradeship
|
||||
const result = this.pathfinder.nextTile(
|
||||
this.destroyer.tile(),
|
||||
this.patrolTile,
|
||||
this.warship.tile(),
|
||||
this.patrolTile
|
||||
);
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
this.patrolTile = this.randomTile();
|
||||
break;
|
||||
case PathFindResultType.NextTile:
|
||||
this.destroyer.move(result.tile);
|
||||
this.warship.move(result.tile);
|
||||
break;
|
||||
case PathFindResultType.Pending:
|
||||
return;
|
||||
@@ -99,42 +96,53 @@ export class DestroyerExecution implements Execution {
|
||||
this.patrolTile = this.randomTile();
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.target = ships.sort(distSortUnit(this.mg, this.destroyer))[0];
|
||||
}
|
||||
if (!this.target.isActive() || this.target.owner() == this._owner) {
|
||||
// Incase another destroyer captured or destroyed target
|
||||
if (
|
||||
this.target == null ||
|
||||
!this.target.isActive() ||
|
||||
this.target.owner() == this._owner
|
||||
) {
|
||||
// In case another destroyer captured or destroyed target
|
||||
this.target = null;
|
||||
return;
|
||||
}
|
||||
if (this.target.type() != UnitType.TradeShip) {
|
||||
if (this.mg.ticks() - this.lastShellAttack > this.shellAttackRate) {
|
||||
this.lastShellAttack = this.mg.ticks();
|
||||
this.mg.addExecution(
|
||||
new ShellExecution(
|
||||
this.warship.tile(),
|
||||
this.warship.owner(),
|
||||
this.warship,
|
||||
this.target
|
||||
)
|
||||
);
|
||||
if (!this.target.hasHealth()) {
|
||||
// Don't send multiple shells to target that can be oneshotted
|
||||
this.alreadySentShell.add(this.target);
|
||||
this.target = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Only hunt down tradeships
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < 2; i++) {
|
||||
// target is trade ship so capture it.
|
||||
const result = this.pathfinder.nextTile(
|
||||
this.destroyer.tile(),
|
||||
this.warship.tile(),
|
||||
this.target.tile(),
|
||||
5,
|
||||
5
|
||||
);
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
switch (this.target.type()) {
|
||||
case UnitType.TransportShip:
|
||||
case UnitType.Battleship:
|
||||
this.target.delete();
|
||||
break;
|
||||
case UnitType.TradeShip:
|
||||
this.owner().captureUnit(this.target);
|
||||
break;
|
||||
case UnitType.Destroyer:
|
||||
const health = this.target.health();
|
||||
this.target.modifyHealth(-this.destroyer.health());
|
||||
this.destroyer.modifyHealth(-health);
|
||||
break;
|
||||
}
|
||||
this.owner().captureUnit(this.target);
|
||||
this.target = null;
|
||||
return;
|
||||
case PathFindResultType.NextTile:
|
||||
this.destroyer.move(result.tile);
|
||||
this.warship.move(result.tile);
|
||||
break;
|
||||
case PathFindResultType.Pending:
|
||||
break;
|
||||
@@ -62,8 +62,7 @@ export interface UnitInfo {
|
||||
|
||||
export enum UnitType {
|
||||
TransportShip = "Transport",
|
||||
Destroyer = "Destroyer",
|
||||
Battleship = "Battleship",
|
||||
Warship = "Warship",
|
||||
Shell = "Shell",
|
||||
Port = "Port",
|
||||
AtomBomb = "Atom Bomb",
|
||||
|
||||
+14
-12
@@ -42,10 +42,7 @@ export class UnitView {
|
||||
public _wasUpdated = true;
|
||||
public lastPos: MapPos[] = [];
|
||||
|
||||
constructor(
|
||||
private gameView: GameView,
|
||||
private data: UnitUpdate,
|
||||
) {
|
||||
constructor(private gameView: GameView, private data: UnitUpdate) {
|
||||
this.lastPos.push(data.pos);
|
||||
}
|
||||
|
||||
@@ -101,14 +98,14 @@ export class PlayerView {
|
||||
constructor(
|
||||
private game: GameView,
|
||||
public data: PlayerUpdate,
|
||||
public nameData: NameViewData,
|
||||
public nameData: NameViewData
|
||||
) {}
|
||||
|
||||
async actions(tile: TileRef): Promise<PlayerActions> {
|
||||
return this.game.worker.playerInteraction(
|
||||
this.id(),
|
||||
this.game.x(tile),
|
||||
this.game.y(tile),
|
||||
this.game.y(tile)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -151,12 +148,12 @@ export class PlayerView {
|
||||
}
|
||||
allies(): PlayerView[] {
|
||||
return this.data.allies.map(
|
||||
(a) => this.game.playerBySmallID(a) as PlayerView,
|
||||
(a) => this.game.playerBySmallID(a) as PlayerView
|
||||
);
|
||||
}
|
||||
targets(): PlayerView[] {
|
||||
return this.data.targets.map(
|
||||
(id) => this.game.playerBySmallID(id) as PlayerView,
|
||||
(id) => this.game.playerBySmallID(id) as PlayerView
|
||||
);
|
||||
}
|
||||
gold(): Gold {
|
||||
@@ -211,7 +208,7 @@ export class GameView implements GameMap {
|
||||
public worker: WorkerClient,
|
||||
private _config: Config,
|
||||
private _map: GameMap,
|
||||
private _myClientID: ClientID,
|
||||
private _myClientID: ClientID
|
||||
) {
|
||||
this.lastUpdate = {
|
||||
tick: 0,
|
||||
@@ -242,7 +239,7 @@ export class GameView implements GameMap {
|
||||
} else {
|
||||
this._players.set(
|
||||
pu.id,
|
||||
new PlayerView(this, pu, gu.playerNameViewData[pu.id]),
|
||||
new PlayerView(this, pu, gu.playerNameViewData[pu.id])
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -321,7 +318,12 @@ export class GameView implements GameMap {
|
||||
return this._config;
|
||||
}
|
||||
units(...types: UnitType[]): UnitView[] {
|
||||
return Array.from(this._units.values());
|
||||
if (types.length == 0) {
|
||||
return Array.from(this._units.values());
|
||||
}
|
||||
return Array.from(this._units.values()).filter((u) =>
|
||||
types.includes(u.type())
|
||||
);
|
||||
}
|
||||
unit(id: number): UnitView {
|
||||
return this._units.get(id);
|
||||
@@ -416,7 +418,7 @@ export class GameView implements GameMap {
|
||||
}
|
||||
bfs(
|
||||
tile: TileRef,
|
||||
filter: (gm: GameMap, tile: TileRef) => boolean,
|
||||
filter: (gm: GameMap, tile: TileRef) => boolean
|
||||
): Set<TileRef> {
|
||||
return this._map.bfs(tile, filter);
|
||||
}
|
||||
|
||||
@@ -567,8 +567,7 @@ export class PlayerImpl implements Player {
|
||||
return this.nukeSpawn(targetTile);
|
||||
case UnitType.Port:
|
||||
return this.portSpawn(targetTile);
|
||||
case UnitType.Destroyer:
|
||||
case UnitType.Battleship:
|
||||
case UnitType.Warship:
|
||||
return this.warshipSpawn(targetTile);
|
||||
case UnitType.Shell:
|
||||
return targetTile;
|
||||
|
||||
@@ -18,7 +18,7 @@ export class UnitImpl implements Unit {
|
||||
private _tile: TileRef,
|
||||
private _troops: number,
|
||||
private _id: number,
|
||||
public _owner: PlayerImpl,
|
||||
public _owner: PlayerImpl
|
||||
) {
|
||||
// default to half health (or 1 is no health specified)
|
||||
this._health = (this.mg.unitInfo(_type).maxHealth ?? 2) / 2;
|
||||
@@ -35,6 +35,7 @@ export class UnitImpl implements Unit {
|
||||
isActive: this._active,
|
||||
pos: { x: this.mg.x(this._tile), y: this.mg.y(this._tile) },
|
||||
lastPos: { x: this.mg.x(this._lastTile), y: this.mg.y(this._lastTile) },
|
||||
health: this.hasHealth() ? this._health : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -85,7 +86,7 @@ export class UnitImpl implements Unit {
|
||||
this.mg.displayMessage(
|
||||
`Your ${this.type()} was captured by ${newOwner.displayName()}`,
|
||||
MessageType.ERROR,
|
||||
oldOwner.id(),
|
||||
oldOwner.id()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -104,7 +105,7 @@ export class UnitImpl implements Unit {
|
||||
this.mg.displayMessage(
|
||||
`Your ${this.type()} was destroyed`,
|
||||
MessageType.ERROR,
|
||||
this.owner().id(),
|
||||
this.owner().id()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user