give Battleships & Destroyers health, make shells more frequent & larger

This commit is contained in:
Evan
2024-12-20 16:43:24 -08:00
parent 17d75324f8
commit 5307285d8b
11 changed files with 96 additions and 33 deletions
+11 -6
View File
@@ -245,21 +245,26 @@
* send client logs back to server DONE 12/18/2024
* render more info in info overlay DONE 12/19/2024
* create alternate view for freinds enemies DONE 12/19/2024
* give naval units health
* give naval units health DONE 12/20/2024
* make shells larger DONE 12/20/2024
* make shells more frequent & less attack DONE 12/20/2024
* bug: NPCs don't have money
* have game start every 15 mins
* create more prominant discord link
* right click brings up player info menu
* create new view for enemies & personal units
* send client logs back to server DONE 12/17/2024
* make info panel merge with X, display more info
* make attack bonus based on current attack size
* make fallout harder to capture, h-bombs little smaller
* make atom bombs a bit cheaper & smaller
* make defense post stronger & larger radius
* right click brings up player info menu
* seperate server config from client config
* make event box wider
* highlight player spawn
* bug: player names not updating sometimes
* make player editeable configs
* couldn't scroll left on build menu to deploy bombs (mobile)
* UI/test too big on mobile
* bug: build city 25k bump doesn't match troop/worker ratio
* bug: mobile: if you don't have enough money can't get rid of build menu
* highlight player spawn
* show players joined username in private lobby
* have bots build cities & defense posts
* allow longer names and allow them to be displayed in the Rank UI not be cut
+12 -11
View File
@@ -1,7 +1,7 @@
import { LitElement, html, css } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { Layer } from './Layer';
import { Game, Player, Unit } from '../../../core/game/Game';
import { Game, Player, Unit, UnitType } from '../../../core/game/Game';
import { ClientID } from '../../../core/Schemas';
import { EventBus } from '../../../core/EventBus';
import { TransformHandler } from '../TransformHandler';
@@ -55,7 +55,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
this.player = owner;
this.setVisible(true);
} else if (!tile.isLand()) {
const units = this.game.units()
const units = this.game.units(UnitType.Destroyer, UnitType.Battleship)
.filter(u => euclideanDist(worldCoord, u.tile().cell()) < 50)
.sort(distSortUnit(tile));
@@ -71,9 +71,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
}
tick() {
if (this.game.ticks() % 10 == 0) {
this.requestUpdate()
}
this.requestUpdate()
// Implementation for Layer interface
}
@@ -111,13 +109,16 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
private renderUnitInfo(unit: Unit) {
const isAlly = (unit.owner() == this.myPlayer() || this.myPlayer()?.isAlliedWith(unit.owner())) ?? false;
return html`
<div class="info-content">
<div class="player-name ${isAlly ? 'ally' : ''}">${unit.owner().name()}</div>
<div class="unit-details">
<div class="type-label">${unit.type()}</div>
</div>
<div class="info-content">
<div class="player-name ${isAlly ? 'ally' : ''}">${unit.owner().name()}</div>
<div class="unit-details">
<div class="type-label">${unit.type()}</div>
${unit.hasHealth() ? html`
<div class="type-label">Health: ${unit.health()}</div>
` : ''}
</div>
`
</div>
`
}
render() {
+9
View File
@@ -25,6 +25,8 @@ export class UnitLayer implements Layer {
private myPlayer: Player | null = null
private oldShellTile = new Map<Unit, Tile>()
constructor(private game: Game, private eventBus: EventBus, private clientID: ClientID) {
this.theme = game.config().theme();
@@ -143,11 +145,18 @@ export class UnitLayer implements Layer {
private handleShellEvent(event: UnitEvent) {
const rel = this.relationship(event.unit)
this.clearCell(event.oldTile.cell())
if (this.oldShellTile.has(event.unit)) {
this.clearCell(this.oldShellTile.get(event.unit).cell())
}
this.oldShellTile.set(event.unit, event.oldTile)
if (!event.unit.isActive()) {
return
}
this.paintCell(event.unit.tile().cell(), rel, this.theme.borderColor(event.unit.owner().info()), 255)
this.paintCell(event.oldTile.cell(), rel, this.theme.borderColor(event.unit.owner().info()), 255)
}
+7 -4
View File
@@ -53,22 +53,25 @@ export abstract class DefaultConfig implements Config {
case UnitType.TransportShip:
return {
cost: () => 0,
territoryBound: false
territoryBound: false,
}
case UnitType.Destroyer:
return {
cost: (p: Player) => (p.units(UnitType.Destroyer).length + 1) * 250_000,
territoryBound: false
territoryBound: false,
maxHealth: 1000,
}
case UnitType.Battleship:
return {
cost: (p: Player) => (p.units(UnitType.Battleship).length + 1) * 500_000,
territoryBound: false
territoryBound: false,
maxHealth: 5000
}
case UnitType.Shell:
return {
cost: () => 0,
territoryBound: false
territoryBound: false,
damage: 250
}
case UnitType.Port:
return {
+1 -1
View File
@@ -16,7 +16,7 @@ export const devConfig = new class extends DefaultConfig {
return 95
}
numSpawnPhaseTurns(gameType: GameType): number {
return gameType == GameType.Singleplayer ? 40 : 100
return gameType == GameType.Singleplayer ? 40 : 200
// return 100
}
gameCreationRate(): number {
+16 -3
View File
@@ -1,4 +1,4 @@
import { Cell, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, TerrainType, Tile, UnitType } from "../game/Game";
import { Cell, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, TerrainType, Tile, Unit, UnitType } from "../game/Game";
import { PathFinder } from "../pathfinding/PathFinding";
import { PathFindResultType } from "../pathfinding/AStar";
import { SerialAStar } from "../pathfinding/SerialAStar";
@@ -22,9 +22,11 @@ export class BattleshipExecution implements Execution {
// TODO: put in config
private searchRange = 100
private attackRate = 20
private attackRate = 5
private lastAttack = 0
private alreadyTargeted = new Set<Unit>()
constructor(
private playerID: PlayerID,
private cell: Cell,
@@ -41,6 +43,11 @@ export class BattleshipExecution implements Execution {
}
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) {
@@ -82,11 +89,17 @@ export class BattleshipExecution implements Execution {
.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.battleship));
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(), ships[0]))
this.mg.addExecution(new ShellExecution(this.battleship.tile(), this.battleship.owner(), this.battleship, toAttack))
}
}
+5 -2
View File
@@ -58,6 +58,7 @@ export class DestroyerExecution implements Execution {
if (this.target == null) {
const ships = this.mg.units(UnitType.TransportShip, UnitType.Destroyer, UnitType.TradeShip, UnitType.Battleship)
.filter(u => manhattanDist(u.tile().cell(), this.destroyer.tile().cell()) < 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()))
@@ -93,14 +94,16 @@ export class DestroyerExecution implements Execution {
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:
this.target.delete()
this.destroyer.delete()
const health = this.target.health()
this.target.modifyHealth(-this.destroyer.health())
this.destroyer.modifyHealth(-health)
break
}
this.target = null
+5
View File
@@ -30,6 +30,11 @@ export class PlayerExecution implements Execution {
tick(ticks: number) {
this.player.units().forEach(u => {
if (u.health() <= 0) {
u.delete()
return
}
u.modifyHealth(1)
const tileOwner = u.tile().owner()
if (u.info().territoryBound) {
if (tileOwner.isPlayer()) {
+4 -4
View File
@@ -9,7 +9,7 @@ export class ShellExecution implements Execution {
private pathFinder: PathFinder
private shell: MutableUnit
constructor(private spawn: Tile, private _owner: MutablePlayer, private target: MutableUnit) {
constructor(private spawn: Tile, private _owner: MutablePlayer, private ownerUnit: Unit, private target: MutableUnit) {
}
@@ -25,17 +25,17 @@ export class ShellExecution implements Execution {
this.active = false
return
}
if (!this.target.isActive()) {
if (!this.target.isActive() || !this.ownerUnit.isActive()) {
this.shell.delete(false)
this.active = false
return
}
for (let i = 0; i < 3; i++) {
const result = this.pathFinder.nextTile(this.shell.tile(), this.target.tile())
const result = this.pathFinder.nextTile(this.shell.tile(), this.target.tile(), 3)
switch (result.type) {
case PathFindResultType.Completed:
this.active = false
this.target.delete()
this.target.modifyHealth(-this.shell.info().damage)
this.shell.delete(false)
return
case PathFindResultType.NextTile:
+5
View File
@@ -35,6 +35,8 @@ export interface UnitInfo {
cost: (player: Player) => Gold
// Determines if its owner changes when its tile is conquered.
territoryBound: boolean
maxHealth?: number,
damage?: number
}
export enum UnitType {
@@ -193,6 +195,8 @@ export interface Unit {
owner(): Player
isActive(): boolean
info(): UnitInfo
hasHealth(): boolean
health(): number
}
export interface MutableUnit extends Unit {
@@ -200,6 +204,7 @@ export interface MutableUnit extends Unit {
owner(): MutablePlayer
setTroops(troops: number): void
delete(displayerMessage?: boolean): void
modifyHealth(delta: number): void
}
export interface TerraNullius {
+21 -2
View File
@@ -1,5 +1,5 @@
import { MessageType } from "../../client/graphics/layers/EventsDisplay";
import { simpleHash } from "../Util";
import { simpleHash, within } from "../Util";
import { MutableUnit, Tile, TerraNullius, UnitType, Player, UnitInfo } from "./Game";
import { GameImpl } from "./GameImpl";
import { PlayerImpl } from "./PlayerImpl";
@@ -8,6 +8,7 @@ import { TerraNulliusImpl } from "./TerraNulliusImpl";
export class UnitImpl implements MutableUnit {
private _active = true;
private _health: number
constructor(
private _type: UnitType,
@@ -15,7 +16,10 @@ export class UnitImpl implements MutableUnit {
private _tile: Tile,
private _troops: number,
public _owner: PlayerImpl,
) { }
) {
// default to half health (or 1 is no health specified)
this._health = (this.g.unitInfo(_type).maxHealth ?? 2) / 2
}
type(): UnitType {
return this._type
@@ -35,6 +39,12 @@ export class UnitImpl implements MutableUnit {
troops(): number {
return this._troops;
}
health(): number {
return this._health
}
hasHealth(): boolean {
return this.info().maxHealth != undefined
}
tile(): Tile {
return this._tile;
}
@@ -57,6 +67,15 @@ export class UnitImpl implements MutableUnit {
)
}
modifyHealth(delta: number): void {
this._health = within(
this._health + delta,
0,
this.info().maxHealth ?? 1
)
}
delete(displayMessage: boolean = true): void {
if (!this.isActive()) {
throw new Error(`cannot delete ${this} not active`)