diff --git a/backend/src/objects/player-character-physical.ts b/backend/src/objects/player-character-physical.ts index 47fe473..87301fe 100644 --- a/backend/src/objects/player-character-physical.ts +++ b/backend/src/objects/player-character-physical.ts @@ -95,12 +95,14 @@ export class PlayerCharacterPhysical constructor( name: string, + killCount: number, + deathCount: number, public readonly colorIndex: number, team: CharacterTeam, private readonly container: PhysicalContainer, startPosition: vec2, ) { - super(id(), name, colorIndex, team, settings.playerMaxHealth); + super(id(), name, killCount, deathCount, colorIndex, team, settings.playerMaxHealth); this.head = new CirclePhysical( vec2.add(vec2.create(), startPosition, PlayerCharacterPhysical.headOffset), @@ -133,7 +135,13 @@ export class PlayerCharacterPhysical } public calculateUpdates(): UpdateObjectMessage { - return new UpdateGameObjectMessage(this, ['head', 'leftFoot', 'rightFoot', 'health']); + return new UpdateGameObjectMessage(this, [ + 'head', + 'leftFoot', + 'rightFoot', + 'health', + 'killCount', + ]); } public handleMovementAction(c: MoveActionCommand) { @@ -141,11 +149,16 @@ export class PlayerCharacterPhysical } public onCollision(other: GameObject) { - if (other instanceof ProjectilePhysical && other.team !== this.team) { + if ( + other instanceof ProjectilePhysical && + other.team !== this.team && + other.isAlive + ) { other.destroy(); this.health -= other.strength; if (this.health <= 0) { this.destroy(); + other.originator.killCount++; } } } @@ -167,6 +180,7 @@ export class PlayerCharacterPhysical strength, this.team, velocity, + this, this.container, ); this.container.addObject(projectile); diff --git a/backend/src/objects/projectile-physical.ts b/backend/src/objects/projectile-physical.ts index bded869..6a9662c 100644 --- a/backend/src/objects/projectile-physical.ts +++ b/backend/src/objects/projectile-physical.ts @@ -38,6 +38,7 @@ export class ProjectilePhysical public strength: number, public team: CharacterTeam, private velocity: vec2, + public readonly originator: PlayerCharacterPhysical, readonly container: PhysicalContainer, ) { super(id(), center, radius, colorIndex, strength); @@ -46,6 +47,10 @@ export class ProjectilePhysical this.moveOutsideOfObject(); } + public get isAlive(): boolean { + return !this.isDestroyed; + } + private moveOutsideOfObject() { let wasCollision = true; const delta = vec2.scale( diff --git a/backend/src/players/player.ts b/backend/src/players/player.ts index 7ced660..cdc53fa 100644 --- a/backend/src/players/player.ts +++ b/backend/src/players/player.ts @@ -37,6 +37,9 @@ export class Player extends CommandReceiver { private aspectRatio: number = 16 / 9; private isActive = true; + private sumKills = 0; + private sumDeaths = 0; + private objectsPreviouslyInViewArea: Array = []; private pingTime?: number; @@ -64,17 +67,20 @@ export class Player extends CommandReceiver { }; private findEmptyPositionForPlayer(): vec2 { - let possibleCenter = this.players.find((p) => p.team === this.team)?.center; + let possibleCenter = this.players.find( + (p) => p.character?.isAlive && p.team === this.team, + )?.center; + if (!possibleCenter) { possibleCenter = vec2.create(); } - let rotation = Math.atan2(possibleCenter.y, possibleCenter.x); - let radius = vec2.length(possibleCenter); + let rotation = 0; + let radius = 0; for (;;) { const playerPosition = vec2.fromValues( - radius * Math.cos(rotation), - radius * Math.sin(rotation), + radius * Math.cos(rotation) + possibleCenter.x, + radius * Math.sin(rotation) + possibleCenter.y, ); const playerBoundingCircle = new Circle( @@ -121,6 +127,8 @@ export class Player extends CommandReceiver { private createCharacter() { this.character = new PlayerCharacterPhysical( this.playerInfo.name.slice(0, 20), + this.sumKills, + this.sumDeaths, this.colorIndex, this.team, this.objects, @@ -143,6 +151,9 @@ export class Player extends CommandReceiver { this.center = this.character?.center; if (!this.character.isAlive) { + this.sumDeaths++; + this.sumKills = this.character.killCount; + this.socket.emit( TransportEvents.ServerToPlayer, serialize(new PlayerDiedCommand(settings.playerDiedTimeout)), diff --git a/frontend/src/main.scss b/frontend/src/main.scss index 67b923f..008d497 100644 --- a/frontend/src/main.scss +++ b/frontend/src/main.scss @@ -10,7 +10,7 @@ margin: 0; box-sizing: border-box; color: white; - font-family: 'Open Sans', sans-serif; + font-family: 'Open Sans', 'Segoe UI Emoji', sans-serif; &::selection { color: white; @@ -43,10 +43,6 @@ h1 { } } -h2 { - font-size: 4rem; -} - .red { color: $red; &::selection { @@ -97,10 +93,6 @@ body { left: 0; } - h2 { - margin: $large-padding 0; - } - .player-tag { transform: translateX(-50%) translateY(-50%) rotate(-15deg); transition: left 200ms, top 200ms; @@ -108,8 +100,7 @@ body { &.decla { color: $bright-decla; - - div { + div:first-child { background-color: $bright-decla; &:before { background-color: $bright-decla; @@ -120,7 +111,7 @@ body { &.red { color: $bright-red; - div { + div:first-child { background-color: $bright-red; &:before { background-color: $bright-red; @@ -129,7 +120,7 @@ body { } } - div { + div:first-child { position: relative; height: 5px; border-radius: 1000px; @@ -143,6 +134,11 @@ body { border-radius: 1000px; } } + + div:not(:first-child) { + letter-spacing: 2px; + font-size: 0.8em; + } } .ownership { @@ -190,6 +186,17 @@ body { } } + .announcement { + top: 50%; + left: 50%; + transform: translateX(-50%) translateY(-50%); + font-size: 3rem; + @include background; + z-index: 1000; + padding: $medium-padding; + border-radius: 16px; + } + .planet-progress { position: absolute; top: $small-padding; diff --git a/frontend/src/scripts/commands/generators/touch-joystick-listener.ts b/frontend/src/scripts/commands/generators/touch-joystick-listener.ts index 0887190..00ee238 100644 --- a/frontend/src/scripts/commands/generators/touch-joystick-listener.ts +++ b/frontend/src/scripts/commands/generators/touch-joystick-listener.ts @@ -73,11 +73,19 @@ export class TouchJoystickListener extends CommandGenerator { this.joystickButton.style.transform = `translateX(${movement.x}px) translateY(${movement.y}px) translateX(-50%) translateY(-50%)`; vec2.set(movement, movement.x, -movement.y); - if (vec2.squaredLength(movement) > 0) { - vec2.normalize(movement, movement); - + if (length > 10) { this.sendCommandToSubcribers( - new MoveActionCommand(movement, OptionsHandler.options.relativeMovementEnabled), + new MoveActionCommand( + vec2.normalize(movement, movement), + OptionsHandler.options.relativeMovementEnabled, + ), + ); + } else { + this.sendCommandToSubcribers( + new MoveActionCommand( + vec2.create(), + OptionsHandler.options.relativeMovementEnabled, + ), ); } }); diff --git a/frontend/src/scripts/game.ts b/frontend/src/scripts/game.ts index 2e2da55..e11db3f 100644 --- a/frontend/src/scripts/game.ts +++ b/frontend/src/scripts/game.ts @@ -41,12 +41,14 @@ export class Game { private declaPlanetCountElement = document.createElement('div'); private redPlanetCountElement = document.createElement('div'); private neutralPlanetCountElement = document.createElement('div'); + private announcementText = document.createElement('h2'); constructor( private readonly playerDecision: PlayerDecision, private readonly canvas: HTMLCanvasElement, private readonly overlay: HTMLElement, ) { + this.announcementText.className = 'announcement'; const progressBar = document.createElement('div'); progressBar.className = 'planet-progress'; overlay.appendChild(progressBar); @@ -75,7 +77,7 @@ export class Game { if (OptionsHandler.options.vibrationEnabled) { navigator.vibrate(150); } - this.overlay.appendChild(this.announcmentText); + this.overlay.appendChild(this.announcementText); } else if (command instanceof UpdateGameState) { const all = command.declaCount + command.redCount + command.neutralCount; this.declaPlanetCountElement.style.width = (command.declaCount / all) * 100 + '%'; @@ -84,13 +86,13 @@ export class Game { this.redPlanetCountElement.style.width = (command.redCount / all) * 100 + '%'; if (command.declaCount > all * 0.5) { - this.overlay.appendChild(this.announcmentText); - this.announcmentText.innerText = 'Decla team won 🎉'; + this.overlay.appendChild(this.announcementText); + this.announcementText.innerText = 'Decla team won 🎉'; } if (command.redCount > all * 0.5) { - this.overlay.appendChild(this.announcmentText); - this.announcmentText.innerText = 'Red team won 🎉'; + this.overlay.appendChild(this.announcementText); + this.announcementText.innerText = 'Red team won 🎉'; } this.arrowElements @@ -233,7 +235,6 @@ export class Game { this.isActive = false; } - private announcmentText = document.createElement('h2'); private gameLoop( renderer: Renderer, currentTime: DOMHighResTimeStamp, @@ -242,9 +243,9 @@ export class Game { this.renderer = renderer; if ((this.deadTimeout -= deltaTime / 1000) > 0) { - this.announcmentText.innerText = `Respawning in ${Math.floor(this.deadTimeout)} …`; + this.announcementText.innerText = `Reviving in ${Math.floor(this.deadTimeout)}…`; } else { - this.announcmentText.parentElement?.removeChild(this.announcmentText); + this.announcementText.parentElement?.removeChild(this.announcementText); } this.gameObjects.stepObjects(deltaTime); diff --git a/frontend/src/scripts/objects/game-object-container.ts b/frontend/src/scripts/objects/game-object-container.ts index f21b1e8..cd076f5 100644 --- a/frontend/src/scripts/objects/game-object-container.ts +++ b/frontend/src/scripts/objects/game-object-container.ts @@ -21,6 +21,9 @@ export class GameObjectContainer extends CommandReceiver { protected commandExecutors: CommandExecutors = { [CreatePlayerCommand.type]: (c: CreatePlayerCommand) => { + if (this.camera) { + this.deleteObject(this.camera.id); + } this.player = c.character as PlayerCharacterView; this.camera = new Camera(this.game); this.addObject(this.player); diff --git a/frontend/src/scripts/objects/player-character-view.ts b/frontend/src/scripts/objects/player-character-view.ts index be6ca0c..977cd55 100644 --- a/frontend/src/scripts/objects/player-character-view.ts +++ b/frontend/src/scripts/objects/player-character-view.ts @@ -8,14 +8,17 @@ import { ViewObject } from './view-object'; export class PlayerCharacterView extends PlayerCharacterBase implements ViewObject { private shape: BlobShape; - private nameElement: HTMLElement; - private healthElement: HTMLElement; + private nameElement: HTMLElement = document.createElement('div'); + private statsElement: HTMLElement = document.createElement('div'); + private healthElement: HTMLElement = document.createElement('div'); private timeSinceLastNameElementUpdate = 0; private previousHealth; constructor( id: Id, name: string, + killCount: number, + deathCount: number, colorIndex: number, team: CharacterTeam, health: number, @@ -23,15 +26,25 @@ export class PlayerCharacterView extends PlayerCharacterBase implements ViewObje leftFoot?: Circle, rightFoot?: Circle, ) { - super(id, name, colorIndex, team, health, head, leftFoot, rightFoot); + super( + id, + name, + killCount, + deathCount, + colorIndex, + team, + health, + head, + leftFoot, + rightFoot, + ); this.shape = new BlobShape(colorIndex); this.previousHealth = this.health; - this.nameElement = document.createElement('div'); this.nameElement.className = 'player-tag ' + this.team; this.nameElement.innerText = this.name; - this.healthElement = document.createElement('div'); this.nameElement.appendChild(this.healthElement); + this.nameElement.appendChild(this.statsElement); } public get position(): vec2 { @@ -41,10 +54,11 @@ export class PlayerCharacterView extends PlayerCharacterBase implements ViewObje public step(deltaTimeInMilliseconds: number): void { this.timeSinceLastNameElementUpdate += deltaTimeInMilliseconds; this.healthElement.style.width = (50 * this.health) / settings.playerMaxHealth + 'px'; + this.statsElement.innerText = this.getStatsText(); if (this.previousHealth > this.health) { this.previousHealth = this.health; if (OptionsHandler.options.vibrationEnabled) { - navigator.vibrate(50); + navigator.vibrate(75); } } this.previousHealth = this.health; @@ -72,6 +86,10 @@ export class PlayerCharacterView extends PlayerCharacterBase implements ViewObje renderer.addDrawable(this.shape); } + private getStatsText(): string { + return `${this.killCount}⚔/${this.deathCount}☠`; + } + private calculateTextPosition(): vec2 { const footAverage = vec2.add( vec2.create(), @@ -82,7 +100,7 @@ export class PlayerCharacterView extends PlayerCharacterBase implements ViewObje const headFeetDelta = vec2.subtract(footAverage, this.head!.center, footAverage); vec2.normalize(headFeetDelta, headFeetDelta); - const textOffset = vec2.scale(headFeetDelta, headFeetDelta, this.head!.radius + 60); + const textOffset = vec2.scale(headFeetDelta, headFeetDelta, this.head!.radius + 80); return vec2.add(textOffset, this.head!.center, textOffset); } } diff --git a/frontend/src/styles/settings.scss b/frontend/src/styles/settings.scss index 91d1eb6..839bbf4 100644 --- a/frontend/src/styles/settings.scss +++ b/frontend/src/styles/settings.scss @@ -43,6 +43,7 @@ top: 0; right: $medium-padding + $small-icon + $small-padding; } + pointer-events: none; overflow: hidden; } @@ -61,11 +62,10 @@ opacity: 0; transition: transform $animation-time, opacity $animation-time; - pointer-events: none; &.open { transform: none; opacity: 1; - pointer-events: inherit; + pointer-events: all; } img, diff --git a/shared/src/objects/types/player-character-base.ts b/shared/src/objects/types/player-character-base.ts index ec65fac..eb713da 100644 --- a/shared/src/objects/types/player-character-base.ts +++ b/shared/src/objects/types/player-character-base.ts @@ -9,6 +9,8 @@ export class PlayerCharacterBase extends CharacterBase { constructor( id: Id, public name: string, + public killCount: number, + public deathCount: number, colorIndex: number, team: CharacterTeam, health: number, @@ -20,7 +22,17 @@ export class PlayerCharacterBase extends CharacterBase { } public toArray(): Array { - const { id, name, colorIndex, team, health, head, leftFoot, rightFoot } = this; - return [id, name, colorIndex, team, health, head, leftFoot, rightFoot]; + return [ + this.id, + this.name, + this.killCount, + this.deathCount, + this.colorIndex, + this.team, + this.health, + this.head, + this.leftFoot, + this.rightFoot, + ]; } }