diff --git a/backend/src/players/player.ts b/backend/src/players/player.ts index 9e9da6d..7ced660 100644 --- a/backend/src/players/player.ts +++ b/backend/src/players/player.ts @@ -17,10 +17,11 @@ import { Circle, PlayerInformation, CharacterTeam, - UpdatePlanetOwnershipCommand, + UpdateGameState, GameObject, Command, UpdateObjectMessage, + OtherPlayerDirection, } from 'shared'; import { getTimeInMilliseconds } from '../helper/get-time-in-milliseconds'; import { BoundingBox } from '../physics/bounding-boxes/bounding-box'; @@ -189,14 +190,46 @@ export class Player extends CommandReceiver { ); this.sendToPlayer( - new UpdatePlanetOwnershipCommand( + new UpdateGameState( PlanetPhysical.declaPlanetCount, PlanetPhysical.redPlanetCount, PlanetPhysical.neutralPlanetCount, + this.getOtherPlayers(), ), ); } + private getOtherPlayers(): Array { + if (!this.character) { + return []; + } + + const viewArea = calculateViewArea(this.center, this.aspectRatio, 0.9); + const bb = new BoundingBox(); + bb.topLeft = viewArea.topLeft; + bb.size = viewArea.size; + + const playersInViewArea = this.objects + .findIntersecting(bb) + .map((o) => o.gameObject) + .filter((g) => g instanceof PlayerCharacterPhysical); + + const otherPlayers = this.players.filter( + (p) => playersInViewArea.indexOf(p.character!) < 0, + ); + + return otherPlayers.map( + (p) => + new OtherPlayerDirection( + vec2.normalize( + vec2.create(), + vec2.subtract(vec2.create(), p.center, this.character!.center), + ), + p.team, + ), + ); + } + private sendToPlayer(command: Command) { this.socket.emit(TransportEvents.ServerToPlayer, serialize(command)); } diff --git a/frontend/src/main.scss b/frontend/src/main.scss index e99d743..ca5a121 100644 --- a/frontend/src/main.scss +++ b/frontend/src/main.scss @@ -145,7 +145,24 @@ body { @include square(50px); border-radius: 1000px; - mask: url('../static/mask.svg'); + mask-image: url('../static/mask.svg'); + } + + .other-player-arrow { + @include square($large-icon); + @media (max-width: $breakpoint) { + @include square($small-icon); + } + + mask-image: url('../static/chevron.svg'); + mask-size: contain; + + &.decla { + background-color: $bright-decla; + } + &.red { + background-color: $bright-red; + } } .planet-progress { @@ -165,8 +182,10 @@ body { height: $height; } + border-radius: 100px; + overflow: hidden; + div:nth-child(1) { - border-radius: 100px 0 0 100px; background: $bright-decla; } @@ -175,7 +194,6 @@ body { } div:nth-child(3) { - border-radius: 0 100px 100px 0; background: $bright-red; } } diff --git a/frontend/src/scripts/game.ts b/frontend/src/scripts/game.ts index fc0f794..b5f52bc 100644 --- a/frontend/src/scripts/game.ts +++ b/frontend/src/scripts/game.ts @@ -18,7 +18,8 @@ import { SetAspectRatioActionCommand, PlayerInformation, PlayerDiedCommand, - UpdatePlanetOwnershipCommand, + UpdateGameState, + clamp, } from 'shared'; import io from 'socket.io-client'; import { KeyboardListener } from './commands/generators/keyboard-listener'; @@ -54,6 +55,7 @@ export class Game { progressBar.appendChild(this.redPlanetCountElement); } + private arrowElements: Array = []; private async setupCommunication(serverUrl: string): Promise { this.socket = io(serverUrl, { reconnectionDelayMax: 10000, @@ -74,7 +76,7 @@ export class Game { navigator.vibrate(150); } this.overlay.appendChild(this.announcmentText); - } else if (command instanceof UpdatePlanetOwnershipCommand) { + } else if (command instanceof UpdateGameState) { const all = command.declaCount + command.redCount + command.neutralCount; this.declaPlanetCountElement.style.width = (command.declaCount / all) * 100 + '%'; this.neutralPlanetCountElement.style.width = @@ -90,6 +92,53 @@ export class Game { this.overlay.appendChild(this.announcmentText); this.announcmentText.innerText = 'Red team won 🎉'; } + + this.arrowElements + .splice(command.otherPlayerDirections.length, this.arrowElements.length) + .forEach((e) => e.parentElement?.removeChild(e)); + + for ( + let i = this.arrowElements.length; + i < command.otherPlayerDirections.length; + i++ + ) { + const element = document.createElement('div'); + this.arrowElements.push(element); + this.overlay.appendChild(element); + } + + this.arrowElements.forEach((e, i) => { + const direction = command.otherPlayerDirections[i].direction; + const team = command.otherPlayerDirections[i].team; + const angle = Math.atan2(direction.y, direction.x); + e.className = 'other-player-arrow ' + team; + + const { width, height } = this.overlay.getBoundingClientRect(); + const aspectRatio = width / height; + const directionRatio = direction.x / direction.y; + + let deltaX: number, deltaY: number; + if (aspectRatio < Math.abs(directionRatio)) { + deltaX = (width / 2) * Math.sign(direction.x); + deltaY = deltaX / directionRatio; + } else { + deltaY = (height / 2) * Math.sign(direction.y); + deltaX = deltaY * directionRatio; + } + + const delta = vec2.fromValues(deltaX, deltaY); + const center = vec2.fromValues(width / 2, height / 2); + const p = vec2.add(center, center, delta); + const arrowPadding = 16; + vec2.set( + p, + clamp(p.x, arrowPadding, width - 3 * arrowPadding), + clamp(height - p.y, arrowPadding, height - 3 * arrowPadding), + ); + e.style.transform = `translateX(${p.x}px) translateY(${p.y}px) rotate(${ + -angle + Math.PI / 2 + }rad) translateX(-50%) translateY(-50%)`; + }); } else this.gameObjects.sendCommand(command); }); diff --git a/frontend/src/scripts/objects/planet-view.ts b/frontend/src/scripts/objects/planet-view.ts index b1c120b..d3281c0 100644 --- a/frontend/src/scripts/objects/planet-view.ts +++ b/frontend/src/scripts/objects/planet-view.ts @@ -30,12 +30,18 @@ export class PlanetView extends PlanetBase implements ViewObject { private getGradient(): string { const sideDecla = this.ownership < 0.5; const sidePercent = (Math.abs(this.ownership - 0.5) / 0.5) * 100; - const color = sideDecla ? 'var(--bright-decla)' : 'var(--bright-red)'; - return `conic-gradient( - ${color} ${sidePercent}%, - ${color} ${sidePercent}%, + return sideDecla + ? `conic-gradient( + var(--bright-decla) ${sidePercent}%, + var(--bright-decla) ${sidePercent}%, var(--bright-neutral) ${sidePercent}%, var(--bright-neutral) 100% + )` + : `conic-gradient( + var(--bright-neutral) 0%, + var(--bright-neutral) ${100 - sidePercent}%, + var(--bright-red) ${100 - sidePercent}%, + var(--bright-red) 100% )`; } public beforeDestroy(): void { diff --git a/frontend/src/scripts/shapes/blob-shape.ts b/frontend/src/scripts/shapes/blob-shape.ts index f79f87f..5d0f793 100644 --- a/frontend/src/scripts/shapes/blob-shape.ts +++ b/frontend/src/scripts/shapes/blob-shape.ts @@ -15,8 +15,8 @@ export class BlobShape extends Drawable { float blobSmoothMin(float a, float b) { - const float k = 300.0; - float res = exp2(-k * a) + exp2(-k * b); + const highp float k = 300.0; + highp float res = exp2(-k * a) + exp2(-k * b); return -log2(res) / k; } diff --git a/frontend/src/scripts/shapes/planet-shape.ts b/frontend/src/scripts/shapes/planet-shape.ts index e637e29..b28e75d 100644 --- a/frontend/src/scripts/shapes/planet-shape.ts +++ b/frontend/src/scripts/shapes/planet-shape.ts @@ -52,7 +52,9 @@ export class PlanetShape extends PolygonFactory(settings.planetEdgeCount, 0) { vec2 center = planetCenters[j]; float l = planetLengths[j]; float randomOffset = planetRandoms[j]; - vec2 targetTangent = normalize(target - center); + vec2 targetCenterDelta = target - center; + float targetDistance = length(targetCenterDelta); + vec2 targetTangent = targetCenterDelta / clamp(targetDistance, 0.01, 1000.0); vec2 noisyTarget = target - ( targetTangent * planetTerrain(vec2( l * abs(atan(targetTangent.y, targetTangent.x)), diff --git a/frontend/static/chevron.svg b/frontend/static/chevron.svg new file mode 100644 index 0000000..0178fd9 --- /dev/null +++ b/frontend/static/chevron.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/shared/src/commands/types/update-game-state.ts b/shared/src/commands/types/update-game-state.ts new file mode 100644 index 0000000..f9d231e --- /dev/null +++ b/shared/src/commands/types/update-game-state.ts @@ -0,0 +1,37 @@ +import { vec2 } from 'gl-matrix'; +import { CharacterTeam } from '../../objects/types/character-team'; +import { serializable } from '../../transport/serialization/serializable'; +import { Command } from '../command'; + +@serializable +export class OtherPlayerDirection { + public constructor( + public readonly direction: vec2, + public readonly team: CharacterTeam, + ) {} + + public toArray(): Array { + return [this.direction, this.team]; + } +} + +@serializable +export class UpdateGameState extends Command { + public constructor( + public readonly declaCount: number, + public readonly redCount: number, + public readonly neutralCount: number, + public readonly otherPlayerDirections: Array, + ) { + super(); + } + + public toArray(): Array { + return [ + this.declaCount, + this.redCount, + this.neutralCount, + this.otherPlayerDirections, + ]; + } +} diff --git a/shared/src/commands/types/update-planet-ownership.ts b/shared/src/commands/types/update-planet-ownership.ts deleted file mode 100644 index e2b3c1c..0000000 --- a/shared/src/commands/types/update-planet-ownership.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { serializable } from '../../transport/serialization/serializable'; -import { Command } from '../command'; - -@serializable -export class UpdatePlanetOwnershipCommand extends Command { - public constructor( - public readonly declaCount: number, - public readonly redCount: number, - public readonly neutralCount: number, - ) { - super(); - } - - public toArray(): Array { - return [this.declaCount, this.redCount, this.neutralCount]; - } -} diff --git a/shared/src/main.ts b/shared/src/main.ts index 81235b1..bcb12f8 100644 --- a/shared/src/main.ts +++ b/shared/src/main.ts @@ -7,9 +7,10 @@ export * from './commands/types/move-action'; export * from './commands/types/primary-action'; export * from './commands/types/player-died'; export * from './commands/types/update-objects'; -export * from './commands/types/update-planet-ownership'; +export * from './commands/types/update-game-state'; export * from './commands/types/secondary-action'; export * from './commands/command-receiver'; +export * from './commands/types/update-game-state'; export * from './commands/command-executors'; export * from './commands/command-generator'; export * from './commands/broadcast-commands'; @@ -45,7 +46,6 @@ export * from './objects/types/player-character-base'; export * from './objects/types/lamp-base'; export * from './objects/types/planet-base'; export * from './objects/types/projectile-base'; -export * from './objects/update-message'; export * from './objects/update-object-message'; export * from './settings'; export * from './transport/transport-events';