mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 15:00:43 +00:00
give Battleships & Destroyers health, make shells more frequent & larger
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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`)
|
||||
|
||||
Reference in New Issue
Block a user