diff --git a/resources/images/NukeIconWhite.png b/resources/images/NukeIconWhite.png new file mode 100644 index 000000000..625e7768e Binary files /dev/null and b/resources/images/NukeIconWhite.png differ diff --git a/src/client/Transport.ts b/src/client/Transport.ts index 8ac43e989..dfaa72d5f 100644 --- a/src/client/Transport.ts +++ b/src/client/Transport.ts @@ -67,6 +67,14 @@ export class SendDonateIntentEvent implements GameEvent { ) { } } +export class SendNukeIntentEvent implements GameEvent { + constructor( + public readonly sender: Player, + public readonly cell: Cell, + public readonly magnitude: number | null, + ) { } +} + export class Transport { private socket: WebSocket @@ -97,6 +105,7 @@ export class Transport { this.eventBus.on(SendTargetPlayerIntentEvent, (e) => this.onSendTargetPlayerIntent(e)) this.eventBus.on(SendEmojiIntentEvent, (e) => this.onSendEmojiIntent(e)) this.eventBus.on(SendDonateIntentEvent, (e) => this.onSendDonateIntent(e)) + this.eventBus.on(SendNukeIntentEvent, (e) => this.onSendNukeIntent(e)) } connect(onconnect: () => void, onmessage: (message: ServerMessage) => void) { @@ -276,6 +285,17 @@ export class Transport { }) } + private onSendNukeIntent(event: SendNukeIntentEvent) { + this.sendIntent({ + type: "nuke", + clientID: this.clientID, + sender: event.sender.id(), + x: event.cell.x, + y: event.cell.y, + magnitude: event.magnitude, + }) + } + private sendIntent(intent: Intent) { if (this.isLocal || this.socket.readyState === WebSocket.OPEN) { const msg = ClientIntentMessageSchema.parse({ diff --git a/src/client/graphics/layers/radial/RadialMenu.ts b/src/client/graphics/layers/radial/RadialMenu.ts index a2d373192..3edcbcd47 100644 --- a/src/client/graphics/layers/radial/RadialMenu.ts +++ b/src/client/graphics/layers/radial/RadialMenu.ts @@ -3,7 +3,7 @@ import {AllPlayers, Cell, Game, Player} from "../../../../core/game/Game"; import {ClientID} from "../../../../core/Schemas"; import {and, bfs, dist, manhattanDist, manhattanDistWrapped, sourceDstOceanShore} from "../../../../core/Util"; import {ContextMenuEvent, MouseUpEvent} from "../../../InputHandler"; -import {SendAllianceRequestIntentEvent, SendAttackIntentEvent, SendBoatAttackIntentEvent, SendBreakAllianceIntentEvent, SendDonateIntentEvent, SendEmojiIntentEvent, SendSpawnIntentEvent, SendTargetPlayerIntentEvent} from "../../../Transport"; +import {SendAllianceRequestIntentEvent, SendAttackIntentEvent, SendBoatAttackIntentEvent, SendBreakAllianceIntentEvent, SendDonateIntentEvent, SendEmojiIntentEvent, SendNukeIntentEvent, SendSpawnIntentEvent, SendTargetPlayerIntentEvent} from "../../../Transport"; import {TransformHandler} from "../../TransformHandler"; import {Layer} from "../Layer"; import * as d3 from 'd3'; @@ -15,6 +15,7 @@ import targetIcon from '../../../../../resources/images/TargetIconWhite.png'; import emojiIcon from '../../../../../resources/images/EmojiIconWhite.png'; import disabledIcon from '../../../../../resources/images/DisabledIcon.png'; import donateIcon from '../../../../../resources/images/DonateIconWhite.png'; +import nukeIcon from '../../../../../resources/images/NukeIconWhite.png'; import {EmojiTable} from "./EmojiTable"; @@ -22,7 +23,8 @@ enum Slot { Alliance, Boat, Target, - Emoji + Emoji, + Nuke, } export class RadialMenu implements Layer { @@ -35,6 +37,7 @@ export class RadialMenu implements Layer { [Slot.Boat, {name: "boat", disabled: true, action: () => { }, color: null, icon: null}], [Slot.Target, {name: "target", disabled: true, action: () => { }}], [Slot.Emoji, {name: "emoji", disabled: true, action: () => { }}], + [Slot.Nuke, {name: "nuke", disabled: true, action: () => { }}], ]); private readonly menuSize = 190; @@ -225,10 +228,14 @@ export class RadialMenu implements Layer { return } + this.activateMenuElement(Slot.Nuke, "#ebe250", nukeIcon, () => { + this.eventBus.emit(new SendNukeIntentEvent(myPlayer, this.clickedCell, null)) + }) + if (tile.hasOwner()) { const target = tile.owner() == myPlayer ? AllPlayers : (tile.owner() as Player) if (myPlayer.canSendEmoji(target)) { - this.activateMenuElement(Slot.Emoji, "#ebe250", emojiIcon, () => { + this.activateMenuElement(Slot.Emoji, "#00a6a4", emojiIcon, () => { this.emojiTable.onEmojiClicked = (emoji: string) => { this.emojiTable.hideTable() this.eventBus.emit(new SendEmojiIntentEvent(target, emoji)) diff --git a/src/client/index.html b/src/client/index.html index d77aea1ba..eafa93a49 100644 --- a/src/client/index.html +++ b/src/client/index.html @@ -28,7 +28,7 @@

OpenFront.io

-

(v0.7.0)

+

(v0.7.1)

diff --git a/src/core/Util.ts b/src/core/Util.ts index 7e8f2cf9f..7d353c867 100644 --- a/src/core/Util.ts +++ b/src/core/Util.ts @@ -7,6 +7,10 @@ export function manhattanDist(c1: Cell, c2: Cell): number { return Math.abs(c1.x - c2.x) + Math.abs(c1.y - c2.y); } +export function euclideanDist(c1: Cell, c2: Cell): number { + return Math.sqrt(Math.pow(c1.x - c2.x, 2) + Math.pow(c1.y - c2.y, 2)); +} + export function manhattanDistWrapped(c1: Cell, c2: Cell, width: number): number { // Calculate x distance let dx = Math.abs(c1.x - c2.x); diff --git a/src/core/execution/NukeExecution.ts b/src/core/execution/NukeExecution.ts index 424e095ad..5f9463528 100644 --- a/src/core/execution/NukeExecution.ts +++ b/src/core/execution/NukeExecution.ts @@ -1,5 +1,6 @@ import {Cell, Execution, MutableGame, MutablePlayer, PlayerID, Tile} from "../game/Game"; -import {bfs, dist} from "../Util"; +import {PseudoRandom} from "../PseudoRandom"; +import {bfs, dist, euclideanDist, manhattanDist} from "../Util"; export class NukeExecution implements Execution { @@ -22,12 +23,17 @@ export class NukeExecution implements Execution { this.mg = mg this.sender = mg.player(this.senderID) if (this.magnitude == null) { - this.magnitude = 50 + this.magnitude = 70 } + const rand = new PseudoRandom(mg.ticks()) const tile = mg.tile(this.cell) - this.toDestroy = bfs(tile, dist(tile, this.magnitude)) + this.toDestroy = bfs(tile, (n: Tile) => { + const d = euclideanDist(tile.cell(), n.cell()) + return (d <= this.magnitude || rand.chance(2)) && d <= this.magnitude + 30 + }) } + tick(ticks: number): void { for (const tile of this.toDestroy) { const owner = tile.owner()