mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 07:50:45 +00:00
make radial menu boat button
This commit is contained in:
@@ -139,9 +139,12 @@
|
||||
* make radial menu buttons grayed out DONE 9/27/2024
|
||||
* add request alliance radial button DONE 9/27/2024
|
||||
* add break alliance radial button DONE 9/27/2024
|
||||
* add send boat radial button
|
||||
* add send boat radial button DONE 9/27/2024
|
||||
* attack radial center button only on enemy
|
||||
* Make buttons icons
|
||||
* better color scheme radial menu
|
||||
* test on mobile
|
||||
* make event box work on mobile
|
||||
* add emoji button
|
||||
* buttons greyed out when not active
|
||||
* make alliance request mobile friendly
|
||||
|
||||
@@ -122,7 +122,6 @@ export class ClientGame {
|
||||
this.join()
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public start() {
|
||||
@@ -216,79 +215,15 @@ export class ClientGame {
|
||||
return
|
||||
}
|
||||
|
||||
let bordersOcean = false
|
||||
let bordersEnemy = false
|
||||
if (tile.isLand()) {
|
||||
const bordersWithDists: Tile[] = []
|
||||
for (const border of this.myPlayer.borderTiles()) {
|
||||
if (border.isOceanShore()) {
|
||||
bordersOcean = true
|
||||
}
|
||||
for (const n of border.neighbors()) {
|
||||
if (n.owner() == tile.owner()) {
|
||||
bordersWithDists.push(n)
|
||||
bordersEnemy = true
|
||||
this.eventBus.emit(new SendAttackIntentEvent(targetID))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Border with enemy sorted by distance to click tile.
|
||||
const borderWithDists = bordersWithDists.map(t => ({
|
||||
dist: manhattanDist(t.cell(), tile.cell()),
|
||||
tile: t
|
||||
})).sort((a, b) => a.dist - b.dist);
|
||||
|
||||
const enemyShoreDists = Array.from(bfs(
|
||||
tile,
|
||||
and((t) => t.isLand() && t.owner() == tile.owner(), dist(tile, 10))
|
||||
)).filter(t => t.isOceanShore()).map(t => ({
|
||||
dist: manhattanDist(t.cell(), tile.cell()),
|
||||
tile: t
|
||||
})).sort((a, b) => a.dist - b.dist);
|
||||
|
||||
if (!bordersEnemy && !bordersOcean) {
|
||||
return
|
||||
}
|
||||
|
||||
let borderTileClosest = 10000000
|
||||
let enemyShoreClosest = 10000
|
||||
if (borderWithDists.length == 0 && enemyShoreDists.length == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (bordersWithDists.length > 0) {
|
||||
borderTileClosest = borderWithDists[0].dist
|
||||
}
|
||||
if (enemyShoreDists.length > 0) {
|
||||
enemyShoreClosest = enemyShoreDists[0].dist
|
||||
}
|
||||
if (enemyShoreClosest < borderTileClosest / 6) {
|
||||
this.eventBus.emit(new SendBoatAttackIntentEvent(
|
||||
targetID,
|
||||
enemyShoreDists[0].tile.cell(),
|
||||
this.gs.config().boatAttackAmount(this.myPlayer, owner)
|
||||
))
|
||||
} else {
|
||||
this.eventBus.emit(new SendAttackIntentEvent(targetID))
|
||||
}
|
||||
}
|
||||
|
||||
if (tile.isOcean()) {
|
||||
const bordersOcean = Array.from(this.myPlayer.borderTiles()).filter(t => t.isOceanShore()).length > 0
|
||||
if (!bordersOcean) {
|
||||
return
|
||||
}
|
||||
const tn = Array.from(bfs(tile, dist(tile, 10)))
|
||||
.filter(t => t.isOceanShore())
|
||||
.filter(t => t.owner() != this.myPlayer)
|
||||
.sort((a, b) => manhattanDist(tile.cell(), a.cell()) - manhattanDist(tile.cell(), b.cell()))
|
||||
if (tn.length > 0) {
|
||||
this.eventBus.emit(new SendBoatAttackIntentEvent(
|
||||
tn[0].owner().id(),
|
||||
tn[0].cell(),
|
||||
this.gs.config().boatAttackAmount(this.myPlayer, owner)
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import {EventBus} from "../../../core/EventBus";
|
||||
import {Cell, Game, Player, PlayerID} from "../../../core/game/Game";
|
||||
import {ClientID} from "../../../core/Schemas";
|
||||
import {manhattanDist, sourceDstOceanShore} from "../../../core/Util";
|
||||
import {ContextMenuEvent, MouseUpEvent} from "../../InputHandler";
|
||||
import {SendAllianceRequestIntentEvent, SendAttackIntentEvent, SendBreakAllianceIntentEvent} from "../../Transport";
|
||||
import {SendAllianceRequestIntentEvent, SendAttackIntentEvent, SendBoatAttackIntentEvent, SendBreakAllianceIntentEvent} from "../../Transport";
|
||||
import {TransformHandler} from "../TransformHandler";
|
||||
import {MessageType} from "./EventsDisplay";
|
||||
import {Layer} from "./Layer";
|
||||
@@ -21,8 +22,8 @@ export class RadialMenu implements Layer {
|
||||
private isVisible: boolean = false;
|
||||
private readonly menuItems = new Map([
|
||||
[RadialElement.RequestAlliance, {name: "alliance", color: "#3498db", disabled: true, action: () => { }}],
|
||||
[RadialElement.BreakAlliance, {name: "breakAlliance", color: "#3498db", disabled: true, action: () => { }}],
|
||||
[RadialElement.BoatAttack, {name: "boat", color: "#3498db", disabled: true, action: () => { }}],
|
||||
[RadialElement.BreakAlliance, {name: "breakAlliance", color: "#3498db", disabled: true, action: () => { }}],
|
||||
]);
|
||||
private readonly menuSize = 190;
|
||||
private readonly centerButtonSize = 30;
|
||||
@@ -179,42 +180,87 @@ export class RadialMenu implements Layer {
|
||||
item.disabled = true
|
||||
this.updateMenuItemState(item)
|
||||
}
|
||||
|
||||
const cell = this.transformHandler.screenToWorldCoordinates(event.x, event.y)
|
||||
if (!this.game.isOnMap(cell)) {
|
||||
return
|
||||
}
|
||||
const tile = this.game.tile(cell)
|
||||
if (!tile.hasOwner()) {
|
||||
return
|
||||
}
|
||||
const owner = tile.owner() as Player
|
||||
if (owner.clientID() == this.clientID) {
|
||||
return
|
||||
}
|
||||
const myPlayer = this.game.players().find(p => p.clientID() == this.clientID)
|
||||
if (!myPlayer) {
|
||||
console.warn('my player not found')
|
||||
return
|
||||
}
|
||||
|
||||
if (myPlayer.pendingAllianceRequestWith(owner)) {
|
||||
|
||||
this.clickedCell = this.transformHandler.screenToWorldCoordinates(event.x, event.y)
|
||||
if (!this.game.isOnMap(this.clickedCell)) {
|
||||
return
|
||||
}
|
||||
const tile = this.game.tile(this.clickedCell)
|
||||
const other = tile.owner()
|
||||
|
||||
if (tile.hasOwner()) {
|
||||
const other = tile.owner() as Player
|
||||
if (other.clientID() == this.clientID) {
|
||||
return
|
||||
}
|
||||
|
||||
if (myPlayer.pendingAllianceRequestWith(other)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (myPlayer.isAlliedWith(other)) {
|
||||
this.activateMenuElement(RadialElement.BreakAlliance, () => {
|
||||
this.eventBus.emit(
|
||||
new SendBreakAllianceIntentEvent(myPlayer, other)
|
||||
)
|
||||
})
|
||||
} else {
|
||||
this.activateMenuElement(RadialElement.RequestAlliance, () => {
|
||||
this.eventBus.emit(
|
||||
new SendAllianceRequestIntentEvent(myPlayer, other)
|
||||
)
|
||||
this.game.displayMessage(`sending alliance request to ${other.name()}`, MessageType.INFO, myPlayer.id())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (!tile.isLand()) {
|
||||
return
|
||||
}
|
||||
if (myPlayer.boats().length >= this.game.config().boatMaxNumber()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (myPlayer.isAlliedWith(owner)) {
|
||||
this.activateMenuElement(RadialElement.BreakAlliance, () => {
|
||||
this.eventBus.emit(
|
||||
new SendBreakAllianceIntentEvent(myPlayer, owner)
|
||||
)
|
||||
})
|
||||
let myPlayerBordersOcean = false
|
||||
for (const bt of myPlayer.borderTiles()) {
|
||||
if (bt.isOceanShore()) {
|
||||
myPlayerBordersOcean = true
|
||||
break
|
||||
}
|
||||
}
|
||||
let otherPlayerBordersOcean = false
|
||||
if (!tile.hasOwner()) {
|
||||
otherPlayerBordersOcean = true
|
||||
} else {
|
||||
this.activateMenuElement(RadialElement.RequestAlliance, () => {
|
||||
this.eventBus.emit(
|
||||
new SendAllianceRequestIntentEvent(myPlayer, owner)
|
||||
)
|
||||
this.game.displayMessage(`sending alliance request to ${owner.name()}`, MessageType.INFO, myPlayer.id())
|
||||
})
|
||||
for (const bt of (other as Player).borderTiles()) {
|
||||
if (bt.isOceanShore()) {
|
||||
otherPlayerBordersOcean = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (other.isPlayer() && myPlayer.allianceWith(other)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (myPlayerBordersOcean && otherPlayerBordersOcean) {
|
||||
const [src, dst] = sourceDstOceanShore(this.game, myPlayer, other, this.clickedCell)
|
||||
if (src != null && dst != null) {
|
||||
if (manhattanDist(src.cell(), dst.cell()) < this.game.config().boatMaxDistance()) {
|
||||
this.activateMenuElement(RadialElement.BoatAttack, () => {
|
||||
this.eventBus.emit(
|
||||
new SendBoatAttackIntentEvent(other.id(), this.clickedCell, null)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,4 +312,8 @@ export class RadialMenu implements Layer {
|
||||
const gray = rgb.r * 0.299 + rgb.g * 0.587 + rgb.b * 0.114;
|
||||
return d3.rgb(gray, gray, gray).toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function closestOceanShoreOwnedByPlayer(attacker: any, targetShore: any) {
|
||||
throw new Error("Function not implemented.");
|
||||
}
|
||||
|
||||
+1
-1
@@ -75,7 +75,7 @@ export const BoatAttackIntentSchema = BaseIntentSchema.extend({
|
||||
type: z.literal('boat'),
|
||||
attackerID: z.string(),
|
||||
targetID: z.string().nullable(),
|
||||
troops: z.number(),
|
||||
troops: z.number().nullable(),
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
})
|
||||
|
||||
+36
-1
@@ -1,7 +1,7 @@
|
||||
import {v4 as uuidv4} from 'uuid';
|
||||
|
||||
|
||||
import {Cell, Player, Tile} from "./game/Game";
|
||||
import {Cell, Game, Player, TerraNullius, Tile} from "./game/Game";
|
||||
|
||||
export function manhattanDist(c1: Cell, c2: Cell): number {
|
||||
return Math.abs(c1.x - c2.x) + Math.abs(c1.y - c2.y);
|
||||
@@ -32,6 +32,41 @@ export function and(x: (tile: Tile) => boolean, y: (tile: Tile) => boolean): (ti
|
||||
return (tile: Tile) => x(tile) && y(tile)
|
||||
}
|
||||
|
||||
// TODO: refactor to new file
|
||||
export function sourceDstOceanShore(game: Game, src: Player, dst: Player | TerraNullius, cell: Cell): [Tile | null, Tile | null] {
|
||||
let srcTile = closestOceanShoreFromPlayer(src, cell, game.width())
|
||||
let dstTile: Tile | null = null
|
||||
if (dst.isPlayer()) {
|
||||
dstTile = closestOceanShoreFromPlayer(dst as Player, cell, game.width())
|
||||
} else {
|
||||
dstTile = closestOceanShoreTN(game.tile(cell), 300)
|
||||
}
|
||||
return [srcTile, dstTile]
|
||||
}
|
||||
|
||||
function closestOceanShoreFromPlayer(player: Player, target: Cell, width: number): Tile | null {
|
||||
const shoreTiles = Array.from(player.borderTiles()).filter(t => t.isOceanShore())
|
||||
if (shoreTiles.length == 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return shoreTiles.reduce((closest, current) => {
|
||||
const closestDistance = manhattanDistWrapped(target, closest.cell(), width);
|
||||
const currentDistance = manhattanDistWrapped(target, current.cell(), width);
|
||||
return currentDistance < closestDistance ? current : closest;
|
||||
});
|
||||
}
|
||||
|
||||
function closestOceanShoreTN(tile: Tile, searchDist: number): Tile {
|
||||
const tn = Array.from(bfs(tile, and(t => !t.hasOwner(), dist(tile, searchDist))))
|
||||
.filter(t => t.isOceanShore())
|
||||
.sort((a, b) => manhattanDist(tile.cell(), a.cell()) - manhattanDist(tile.cell(), b.cell()))
|
||||
if (tn.length == 0) {
|
||||
return null
|
||||
}
|
||||
return tn[0]
|
||||
}
|
||||
|
||||
export function bfs(tile: Tile, filter: (tile: Tile) => boolean): Set<Tile> {
|
||||
const seen = new Set<Tile>
|
||||
const q: Tile[] = []
|
||||
|
||||
@@ -7,7 +7,7 @@ export const devConfig = new class extends DefaultConfig {
|
||||
return 95
|
||||
}
|
||||
numSpawnPhaseTurns(): number {
|
||||
return 80
|
||||
return 40
|
||||
}
|
||||
gameCreationRate(): number {
|
||||
return 2 * 1000
|
||||
@@ -27,9 +27,9 @@ export const devConfig = new class extends DefaultConfig {
|
||||
// return 10 * 10
|
||||
// }
|
||||
|
||||
// numFakeHumans(gameID: GameID): number {
|
||||
// return 0
|
||||
// }
|
||||
numFakeHumans(gameID: GameID): number {
|
||||
return 0
|
||||
}
|
||||
|
||||
// startTroops(playerInfo: PlayerInfo): number {
|
||||
// if (playerInfo.isBot) {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import {PriorityQueue} from "@datastructures-js/priority-queue";
|
||||
import {Boat, Cell, Execution, MutableBoat, MutableGame, MutablePlayer, Player, PlayerID, TerraNullius, Tile, TileEvent} from "../game/Game";
|
||||
import {manhattanDist, manhattanDistWrapped} from "../Util";
|
||||
import {and, bfs, manhattanDistWrapped, sourceDstOceanShore} from "../Util";
|
||||
import {AttackExecution} from "./AttackExecution";
|
||||
import {Config} from "../configuration/Config";
|
||||
import {EventBus} from "../EventBus";
|
||||
import {DisplayMessageEvent, MessageType} from "../../client/graphics/layers/EventsDisplay";
|
||||
|
||||
export class BoatAttackExecution implements Execution {
|
||||
@@ -21,8 +19,8 @@ export class BoatAttackExecution implements Execution {
|
||||
|
||||
// TODO make private
|
||||
public path: Tile[]
|
||||
private src: Tile
|
||||
private dst: Tile
|
||||
private src: Tile | null
|
||||
private dst: Tile | null
|
||||
|
||||
private currTileIndex: number = 0
|
||||
|
||||
@@ -37,7 +35,7 @@ export class BoatAttackExecution implements Execution {
|
||||
private attackerID: PlayerID,
|
||||
private targetID: PlayerID | null,
|
||||
private cell: Cell,
|
||||
private troops: number,
|
||||
private troops: number | null,
|
||||
) { }
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
@@ -63,11 +61,16 @@ export class BoatAttackExecution implements Execution {
|
||||
this.target = mg.player(this.targetID)
|
||||
}
|
||||
|
||||
if (this.troops == null) {
|
||||
this.troops = this.mg.config().boatAttackAmount(this.attacker, this.target)
|
||||
}
|
||||
|
||||
this.troops = Math.min(this.troops, this.attacker.troops())
|
||||
this.attacker.removeTroops(this.troops)
|
||||
|
||||
this.src = this.closestShoreTile(this.attacker, this.cell)
|
||||
this.dst = this.mg.tile(this.cell)
|
||||
const [srcTile, dstTile]: [Tile | null, Tile | null] = sourceDstOceanShore(this.mg, this.attacker, this.target, this.cell);
|
||||
this.src = srcTile
|
||||
this.dst = dstTile
|
||||
|
||||
if (this.src == null || this.dst == null) {
|
||||
this.active = false
|
||||
@@ -135,18 +138,6 @@ export class BoatAttackExecution implements Execution {
|
||||
return this.active
|
||||
}
|
||||
|
||||
private closestShoreTile(player: Player, target: Cell): Tile | null {
|
||||
const shoreTiles = Array.from(player.borderTiles()).filter(t => t.isOceanShore())
|
||||
if (shoreTiles.length == 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return shoreTiles.reduce((closest, current) => {
|
||||
const closestDistance = manhattanDistWrapped(target, closest.cell(), this.mg.width());
|
||||
const currentDistance = manhattanDistWrapped(target, current.cell(), this.mg.width());
|
||||
return currentDistance < closestDistance ? current : closest;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class AStar {
|
||||
|
||||
Reference in New Issue
Block a user