make radial menu boat button

This commit is contained in:
evanpelle
2024-09-28 12:58:49 -07:00
parent c06009f64e
commit c4c23de1ec
7 changed files with 137 additions and 123 deletions
+4 -1
View File
@@ -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
+2 -67
View File
@@ -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)
))
}
}
}
}
+79 -29
View File
@@ -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
View File
@@ -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
View File
@@ -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[] = []
+4 -4
View File
@@ -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) {
+11 -20
View File
@@ -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 {