Add npcs
This commit is contained in:
parent
a66fa63b4b
commit
efa838a2ad
20 changed files with 1691 additions and 368 deletions
|
|
@ -3,8 +3,8 @@ import { Options } from './options';
|
||||||
export const defaultOptions: Options = {
|
export const defaultOptions: Options = {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
name: 'Test server',
|
name: 'Test server',
|
||||||
playerLimit: 16,
|
playerLimit: 8,
|
||||||
seed: Math.random(),
|
seed: 0,
|
||||||
scoreLimit: 500,
|
scoreLimit: 500,
|
||||||
worldSize: 15000,
|
worldSize: 15000,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import {
|
||||||
settings,
|
settings,
|
||||||
ServerInformation,
|
ServerInformation,
|
||||||
PlayerInformation,
|
PlayerInformation,
|
||||||
serializable,
|
|
||||||
UpdateGameState,
|
UpdateGameState,
|
||||||
serialize,
|
serialize,
|
||||||
CharacterTeam,
|
CharacterTeam,
|
||||||
|
|
@ -18,7 +17,7 @@ import { DeltaTimeCalculator } from './helper/delta-time-calculator';
|
||||||
import { Options } from './options';
|
import { Options } from './options';
|
||||||
import { PlayerContainer } from './players/player-container';
|
import { PlayerContainer } from './players/player-container';
|
||||||
|
|
||||||
const playerCountSubscribedRoom = 'playerCountUpdates';
|
const gameStateSubscribedRoom = 'gameStateSubscribedRoom';
|
||||||
|
|
||||||
export class GameServer {
|
export class GameServer {
|
||||||
private objects!: PhysicalContainer;
|
private objects!: PhysicalContainer;
|
||||||
|
|
@ -38,15 +37,15 @@ export class GameServer {
|
||||||
private initialize() {
|
private initialize() {
|
||||||
const previousPlayers = this.players;
|
const previousPlayers = this.players;
|
||||||
this.objects = new PhysicalContainer();
|
this.objects = new PhysicalContainer();
|
||||||
this.players = new PlayerContainer(this.objects);
|
createWorld(this.objects, this.options.worldSize);
|
||||||
|
this.objects.initialize();
|
||||||
|
this.players = new PlayerContainer(this.objects, this.options.playerLimit);
|
||||||
this.deltaTimeCalculator = new DeltaTimeCalculator();
|
this.deltaTimeCalculator = new DeltaTimeCalculator();
|
||||||
this.deltaTimes = [];
|
this.deltaTimes = [];
|
||||||
this.declaPoints = 0;
|
this.declaPoints = 0;
|
||||||
this.redPoints = 0;
|
this.redPoints = 0;
|
||||||
this.isInEndGame = false;
|
this.isInEndGame = false;
|
||||||
this.timeScaling = 1;
|
this.timeScaling = 1;
|
||||||
createWorld(this.objects, this.options.worldSize);
|
|
||||||
this.objects.initialize();
|
|
||||||
previousPlayers?.sendOnSocket(serialize(new GameStart()));
|
previousPlayers?.sendOnSocket(serialize(new GameStart()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,6 +57,7 @@ export class GameServer {
|
||||||
|
|
||||||
io.on('connection', (socket: SocketIO.Socket) => {
|
io.on('connection', (socket: SocketIO.Socket) => {
|
||||||
socket.on(TransportEvents.PlayerJoining, (playerInfo: PlayerInformation) => {
|
socket.on(TransportEvents.PlayerJoining, (playerInfo: PlayerInformation) => {
|
||||||
|
try {
|
||||||
const player = this.players.createPlayer(playerInfo, socket);
|
const player = this.players.createPlayer(playerInfo, socket);
|
||||||
socket.on(TransportEvents.PlayerToServer, (json: string) => {
|
socket.on(TransportEvents.PlayerToServer, (json: string) => {
|
||||||
const command = deserialize(json);
|
const command = deserialize(json);
|
||||||
|
|
@ -71,10 +71,13 @@ export class GameServer {
|
||||||
this.players.deletePlayer(player);
|
this.players.deletePlayer(player);
|
||||||
this.sendServerStateUpdate();
|
this.sendServerStateUpdate();
|
||||||
});
|
});
|
||||||
|
} catch {
|
||||||
|
socket.disconnect();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on(TransportEvents.SubscribeForServerInfoUpdates, () => {
|
socket.on(TransportEvents.SubscribeForServerInfoUpdates, () => {
|
||||||
socket.join(playerCountSubscribedRoom);
|
socket.join(gameStateSubscribedRoom);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -82,7 +85,7 @@ export class GameServer {
|
||||||
private timeSinceLastServerStateUpdate = 0;
|
private timeSinceLastServerStateUpdate = 0;
|
||||||
public sendServerStateUpdate() {
|
public sendServerStateUpdate() {
|
||||||
this.io
|
this.io
|
||||||
.to(playerCountSubscribedRoom)
|
.to(gameStateSubscribedRoom)
|
||||||
.emit(TransportEvents.ServerInfoUpdate, [this.players.count, this.gameProgress]);
|
.emit(TransportEvents.ServerInfoUpdate, [this.players.count, this.gameProgress]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ export class PlanetPhysical
|
||||||
}
|
}
|
||||||
|
|
||||||
public get team(): CharacterTeam {
|
public get team(): CharacterTeam {
|
||||||
return this.ownership === 0.5
|
return Math.abs(this.ownership - 0.5) < 0.1
|
||||||
? CharacterTeam.neutral
|
? CharacterTeam.neutral
|
||||||
: this.ownership < 0.5
|
: this.ownership < 0.5
|
||||||
? CharacterTeam.decla
|
? CharacterTeam.decla
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,9 @@ export class PlayerCharacterPhysical
|
||||||
}
|
}
|
||||||
|
|
||||||
public get center(): vec2 {
|
public get center(): vec2 {
|
||||||
return this.head.center;
|
const bodyCenter = vec2.add(vec2.create(), this.head.center, this.leftFoot.center);
|
||||||
|
vec2.add(bodyCenter, bodyCenter, this.rightFoot.center);
|
||||||
|
return vec2.scale(bodyCenter, bodyCenter, 1 / 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
public distance(target: vec2): number {
|
public distance(target: vec2): number {
|
||||||
|
|
@ -199,7 +201,7 @@ export class PlayerCharacterPhysical
|
||||||
this.head.distance(target),
|
this.head.distance(target),
|
||||||
this.leftFoot.distance(target),
|
this.leftFoot.distance(target),
|
||||||
this.rightFoot.distance(target),
|
this.rightFoot.distance(target),
|
||||||
) - 20
|
) - 5
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -222,7 +224,7 @@ export class PlayerCharacterPhysical
|
||||||
this.movementActions = [];
|
this.movementActions = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return direction;
|
return vec2.normalize(direction, direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
public step(deltaTime: number) {
|
public step(deltaTime: number) {
|
||||||
|
|
@ -251,16 +253,28 @@ export class PlayerCharacterPhysical
|
||||||
this.rightFoot.center,
|
this.rightFoot.center,
|
||||||
);
|
);
|
||||||
vec2.scale(feetCenter, feetCenter, 0.5);
|
vec2.scale(feetCenter, feetCenter, 0.5);
|
||||||
const actualGravity = forceAtPosition(feetCenter, intersectingWithForcefield);
|
const leftFootGravity = forceAtPosition(
|
||||||
|
this.leftFoot.center,
|
||||||
|
intersectingWithForcefield,
|
||||||
|
);
|
||||||
|
const rightFootGravity = forceAtPosition(
|
||||||
|
this.rightFoot.center,
|
||||||
|
intersectingWithForcefield,
|
||||||
|
);
|
||||||
|
|
||||||
const direction = this.averageAndResetMovementActions();
|
const direction = this.averageAndResetMovementActions();
|
||||||
const movementForce = vec2.scale(direction, direction, settings.maxAcceleration);
|
const movementForce = vec2.scale(direction, direction, settings.maxAcceleration);
|
||||||
|
|
||||||
if (!this.currentPlanet) {
|
if (!this.currentPlanet) {
|
||||||
this.applyForce(this.leftFoot, actualGravity, deltaTime);
|
this.applyForce(this.leftFoot, leftFootGravity, deltaTime);
|
||||||
this.applyForce(this.rightFoot, actualGravity, deltaTime);
|
this.applyForce(this.rightFoot, rightFootGravity, deltaTime);
|
||||||
|
|
||||||
const sumForce = vec2.subtract(vec2.create(), actualGravity, movementForce);
|
const sumForce = vec2.subtract(
|
||||||
|
vec2.create(),
|
||||||
|
// the next line is intentional
|
||||||
|
vec2.add(vec2.create(), leftFootGravity, rightFootGravity),
|
||||||
|
movementForce,
|
||||||
|
);
|
||||||
|
|
||||||
this.setDirection(
|
this.setDirection(
|
||||||
vec2.length(sumForce) === 0 ? vec2.fromValues(0, -1) : sumForce,
|
vec2.length(sumForce) === 0 ? vec2.fromValues(0, -1) : sumForce,
|
||||||
|
|
@ -274,7 +288,7 @@ export class PlayerCharacterPhysical
|
||||||
vec2.rotate(movementForce, movementForce, vec2.create(), this.direction);
|
vec2.rotate(movementForce, movementForce, vec2.create(), this.direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vec2.dot(movementForce, actualGravity) < -vec2.length(movementForce) * 0.8) {
|
if (vec2.dot(movementForce, leftFootGravity) < -vec2.length(movementForce) * 0.8) {
|
||||||
vec2.scale(leftFootGravity, leftFootGravity, 0.35);
|
vec2.scale(leftFootGravity, leftFootGravity, 0.35);
|
||||||
vec2.scale(rightFootGravity, rightFootGravity, 0.35);
|
vec2.scale(rightFootGravity, rightFootGravity, 0.35);
|
||||||
}
|
}
|
||||||
|
|
@ -283,7 +297,7 @@ export class PlayerCharacterPhysical
|
||||||
|
|
||||||
const headGravity = this.currentPlanet!.getForce(this.head.center);
|
const headGravity = this.currentPlanet!.getForce(this.head.center);
|
||||||
|
|
||||||
if (vec2.length(headGravity) < vec2.length(actualGravity) / 2) {
|
if (vec2.length(headGravity) < vec2.length(leftFootGravity) / 2) {
|
||||||
this.currentPlanet = undefined;
|
this.currentPlanet = undefined;
|
||||||
}
|
}
|
||||||
this.setDirection(headGravity, deltaTime);
|
this.setDirection(headGravity, deltaTime);
|
||||||
|
|
@ -294,7 +308,9 @@ export class PlayerCharacterPhysical
|
||||||
|
|
||||||
this.stepBodyPart(this.leftFoot, deltaTime);
|
this.stepBodyPart(this.leftFoot, deltaTime);
|
||||||
this.stepBodyPart(this.rightFoot, deltaTime);
|
this.stepBodyPart(this.rightFoot, deltaTime);
|
||||||
this.keepPosture();
|
|
||||||
|
this.keepPosture(deltaTime);
|
||||||
|
|
||||||
this.remoteCall('updateCircles', this.head, this.leftFoot, this.rightFoot);
|
this.remoteCall('updateCircles', this.head, this.leftFoot, this.rightFoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -302,27 +318,68 @@ export class PlayerCharacterPhysical
|
||||||
this.direction = interpolateAngles(
|
this.direction = interpolateAngles(
|
||||||
this.direction,
|
this.direction,
|
||||||
Math.atan2(direction.y, direction.x) + Math.PI / 2,
|
Math.atan2(direction.y, direction.x) + Math.PI / 2,
|
||||||
Math.pow(4, deltaTime),
|
Math.pow(2, deltaTime),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private keepPosture() {
|
private keepPosture(deltaTime: number) {
|
||||||
const bodyCenter = vec2.add(vec2.create(), this.head.center, this.leftFoot.center);
|
const center = this.center;
|
||||||
vec2.add(bodyCenter, bodyCenter, this.rightFoot.center);
|
this.springMove(
|
||||||
vec2.scale(bodyCenter, bodyCenter, 1 / 3);
|
this.leftFoot,
|
||||||
this.springMove(this.leftFoot, bodyCenter, PlayerCharacterPhysical.leftFootOffset);
|
center,
|
||||||
this.springMove(this.rightFoot, bodyCenter, PlayerCharacterPhysical.rightFootOffset);
|
PlayerCharacterPhysical.leftFootOffset,
|
||||||
this.springMove(this.head, bodyCenter, PlayerCharacterPhysical.headOffset);
|
deltaTime,
|
||||||
|
);
|
||||||
|
this.springMove(
|
||||||
|
this.rightFoot,
|
||||||
|
center,
|
||||||
|
PlayerCharacterPhysical.rightFootOffset,
|
||||||
|
deltaTime,
|
||||||
|
);
|
||||||
|
/*
|
||||||
|
const feetDelta = vec2.subtract(
|
||||||
|
vec2.create(),
|
||||||
|
this.leftFoot.center,
|
||||||
|
this.rightFoot.center,
|
||||||
|
);
|
||||||
|
const desiredDistance = vec2.dist(
|
||||||
|
PlayerCharacterPhysical.desiredLeftFootOffset,
|
||||||
|
PlayerCharacterPhysical.desiredRightFootOffset,
|
||||||
|
);
|
||||||
|
const actualDistance = vec2.length(feetDelta);
|
||||||
|
const delta = vec2.normalize(feetDelta, feetDelta);
|
||||||
|
vec2.scale(delta, delta, Math.min(actualDistance / 2, deltaTime * 200));
|
||||||
|
let hitObject = this.rightFoot.tryMove(delta);
|
||||||
|
if (hitObject instanceof PlanetPhysical) {
|
||||||
|
this.secondsSinceOnSurface = 0;
|
||||||
|
this.currentPlanet = hitObject;
|
||||||
|
}
|
||||||
|
vec2.scale(delta, delta, -1);
|
||||||
|
hitObject = this.leftFoot.tryMove(delta);
|
||||||
|
if (hitObject instanceof PlanetPhysical) {
|
||||||
|
this.secondsSinceOnSurface = 0;
|
||||||
|
this.currentPlanet = hitObject;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
this.springMove(this.head, center, PlayerCharacterPhysical.headOffset, deltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private springMove(object: CirclePhysical, center: vec2, offset: vec2) {
|
private springMove(
|
||||||
// todo: make time-independent
|
object: CirclePhysical,
|
||||||
const springConstant = 0.55;
|
center: vec2,
|
||||||
|
offset: vec2,
|
||||||
|
deltaTime: number,
|
||||||
|
) {
|
||||||
const desiredPosition = vec2.add(vec2.create(), center, offset);
|
const desiredPosition = vec2.add(vec2.create(), center, offset);
|
||||||
vec2.rotate(desiredPosition, desiredPosition, center, this.direction);
|
vec2.rotate(desiredPosition, desiredPosition, center, this.direction);
|
||||||
const positionDelta = vec2.subtract(desiredPosition, desiredPosition, object.center);
|
const positionDelta = vec2.subtract(desiredPosition, desiredPosition, object.center);
|
||||||
vec2.scale(positionDelta, positionDelta, springConstant);
|
const positionDeltaDirection = vec2.normalize(vec2.create(), positionDelta);
|
||||||
|
const positionDeltaLength = vec2.length(positionDelta);
|
||||||
|
vec2.scale(
|
||||||
|
positionDelta,
|
||||||
|
positionDeltaDirection,
|
||||||
|
Math.min(positionDeltaLength, (positionDeltaLength / 50) * deltaTime * 800),
|
||||||
|
);
|
||||||
const hitObject = object.tryMove(positionDelta);
|
const hitObject = object.tryMove(positionDelta);
|
||||||
|
|
||||||
if (hitObject instanceof PlanetPhysical) {
|
if (hitObject instanceof PlanetPhysical) {
|
||||||
|
|
|
||||||
132
backend/src/players/npc.ts
Normal file
132
backend/src/players/npc.ts
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
import { vec2 } from 'gl-matrix';
|
||||||
|
import {
|
||||||
|
PlayerInformation,
|
||||||
|
CharacterTeam,
|
||||||
|
settings,
|
||||||
|
Circle,
|
||||||
|
Random,
|
||||||
|
MoveActionCommand,
|
||||||
|
} from 'shared';
|
||||||
|
import { PhysicalContainer } from '../physics/containers/physical-container';
|
||||||
|
import { PlayerContainer } from './player-container';
|
||||||
|
import { PlayerBase } from './player-base';
|
||||||
|
import { getBoundingBoxOfCircle } from '../physics/functions/get-bounding-box-of-circle';
|
||||||
|
import { PlayerCharacterPhysical } from '../objects/player-character-physical';
|
||||||
|
import { PlanetPhysical } from '../objects/planet-physical';
|
||||||
|
|
||||||
|
export class NPC extends PlayerBase {
|
||||||
|
private moveTarget: vec2 = vec2.create();
|
||||||
|
private planet?: PlanetPhysical;
|
||||||
|
private timeSinceLastFindTarget = 10000;
|
||||||
|
private timeSinceLastFindShootTarget = 10000;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
playerInfo: PlayerInformation,
|
||||||
|
playerContainer: PlayerContainer,
|
||||||
|
objectContainer: PhysicalContainer,
|
||||||
|
team: CharacterTeam,
|
||||||
|
) {
|
||||||
|
super(playerInfo, playerContainer, objectContainer, team);
|
||||||
|
this.createCharacter();
|
||||||
|
this.step(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private findTarget() {
|
||||||
|
const observableArea = getBoundingBoxOfCircle(new Circle(this.center, 100));
|
||||||
|
const nearObjects = this.objectContainer.findIntersecting(observableArea);
|
||||||
|
|
||||||
|
const enemies = nearObjects.filter(
|
||||||
|
(o) =>
|
||||||
|
o.gameObject instanceof PlayerCharacterPhysical &&
|
||||||
|
o.gameObject.team !== this.team,
|
||||||
|
);
|
||||||
|
const allies = nearObjects.filter(
|
||||||
|
(o) =>
|
||||||
|
o.gameObject instanceof PlayerCharacterPhysical &&
|
||||||
|
o.gameObject.team === this.team,
|
||||||
|
);
|
||||||
|
const notControlledPlanets = nearObjects.filter(
|
||||||
|
(o) => o.gameObject instanceof PlanetPhysical && o.gameObject.team !== this.team,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (enemies.length > allies.length) {
|
||||||
|
console.log('fleeing');
|
||||||
|
const enemiesCenter = enemies.reduce(
|
||||||
|
(sum, e) => vec2.add(sum, sum, (e.gameObject as PlayerCharacterPhysical).center),
|
||||||
|
vec2.create(),
|
||||||
|
);
|
||||||
|
vec2.scale(enemiesCenter, enemiesCenter, 1 / enemies.length);
|
||||||
|
const enemiesDelta = vec2.subtract(enemiesCenter, this.center, enemiesCenter);
|
||||||
|
if (vec2.length(enemiesDelta) > 0) {
|
||||||
|
vec2.scale(enemiesDelta, enemiesDelta, 200 / vec2.length(enemiesDelta));
|
||||||
|
}
|
||||||
|
vec2.add(this.moveTarget, this.center, enemiesDelta);
|
||||||
|
} else if (!this.planet) {
|
||||||
|
if (notControlledPlanets.length > 0) {
|
||||||
|
this.planet = notControlledPlanets[0] as PlanetPhysical;
|
||||||
|
this.moveTarget = this.planet.center;
|
||||||
|
} else {
|
||||||
|
this.moveTarget = vec2.fromValues(
|
||||||
|
Random.getRandomInRange(-5000, 5000),
|
||||||
|
Random.getRandomInRange(-5000, 5000),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private findShootTarget(): vec2 | undefined {
|
||||||
|
const observableArea = getBoundingBoxOfCircle(new Circle(this.center, 1000));
|
||||||
|
const nearObjects = this.objectContainer.findIntersecting(observableArea);
|
||||||
|
|
||||||
|
const enemies = nearObjects.filter(
|
||||||
|
(o) =>
|
||||||
|
o.gameObject instanceof PlayerCharacterPhysical &&
|
||||||
|
o.gameObject.team !== this.team,
|
||||||
|
);
|
||||||
|
if (enemies.length > 0) {
|
||||||
|
return (enemies[0].gameObject as PlayerCharacterPhysical).center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private timeUntilRespawn = 0;
|
||||||
|
public step(deltaTimeInSeconds: number) {
|
||||||
|
if (this.character) {
|
||||||
|
this.center = this.character?.center;
|
||||||
|
|
||||||
|
if (!this.character.isAlive) {
|
||||||
|
this.sumDeaths++;
|
||||||
|
this.sumKills = this.character.killCount;
|
||||||
|
|
||||||
|
this.character = null;
|
||||||
|
this.timeUntilRespawn = settings.playerDiedTimeout;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ((this.timeUntilRespawn -= deltaTimeInSeconds) < 0) {
|
||||||
|
this.createCharacter();
|
||||||
|
this.center = this.character!.center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.planet && Math.abs(this.planet.ownership - 0.5) > 0.3) {
|
||||||
|
this.planet = undefined;
|
||||||
|
this.findTarget();
|
||||||
|
} else {
|
||||||
|
if ((this.timeSinceLastFindTarget += deltaTimeInSeconds) > 1) {
|
||||||
|
this.timeSinceLastFindTarget = 0;
|
||||||
|
this.findTarget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((this.timeSinceLastFindShootTarget += deltaTimeInSeconds) > 0.5) {
|
||||||
|
const shootTarget = this.findShootTarget();
|
||||||
|
if (shootTarget) {
|
||||||
|
this.character?.shootTowards(shootTarget);
|
||||||
|
}
|
||||||
|
this.timeSinceLastFindShootTarget = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetDelta = vec2.subtract(vec2.create(), this.moveTarget, this.center);
|
||||||
|
vec2.normalize(targetDelta, targetDelta);
|
||||||
|
this.character?.handleMovementAction(new MoveActionCommand(targetDelta, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
78
backend/src/players/player-base.ts
Normal file
78
backend/src/players/player-base.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
import { vec2 } from 'gl-matrix';
|
||||||
|
import { CommandReceiver, Circle, PlayerInformation, CharacterTeam } from 'shared';
|
||||||
|
import { PhysicalContainer } from '../physics/containers/physical-container';
|
||||||
|
import { getBoundingBoxOfCircle } from '../physics/functions/get-bounding-box-of-circle';
|
||||||
|
import { isCircleIntersecting } from '../physics/functions/is-circle-intersecting';
|
||||||
|
import { PlayerCharacterPhysical } from '../objects/player-character-physical';
|
||||||
|
import { PlayerContainer } from './player-container';
|
||||||
|
|
||||||
|
export abstract class PlayerBase extends CommandReceiver {
|
||||||
|
public character?: PlayerCharacterPhysical | null;
|
||||||
|
public center: vec2 = vec2.create();
|
||||||
|
|
||||||
|
protected sumKills = 0;
|
||||||
|
protected sumDeaths = 0;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected readonly playerInfo: PlayerInformation,
|
||||||
|
protected readonly playerContainer: PlayerContainer,
|
||||||
|
protected readonly objectContainer: PhysicalContainer,
|
||||||
|
public readonly team: CharacterTeam,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createCharacter() {
|
||||||
|
this.character = new PlayerCharacterPhysical(
|
||||||
|
this.playerInfo.name.slice(0, 20),
|
||||||
|
this.sumKills,
|
||||||
|
this.sumDeaths,
|
||||||
|
this.team,
|
||||||
|
this.objectContainer,
|
||||||
|
this.findEmptyPositionForPlayer(),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.objectContainer.addObject(this.character);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract step(deltaTimeInSeconds: number): void;
|
||||||
|
|
||||||
|
protected findEmptyPositionForPlayer(): vec2 {
|
||||||
|
let possibleCenter = this.playerContainer.players.find(
|
||||||
|
(p) => p.character?.isAlive && p.team === this.team,
|
||||||
|
)?.center;
|
||||||
|
|
||||||
|
if (!possibleCenter) {
|
||||||
|
possibleCenter = vec2.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
let rotation = 0;
|
||||||
|
let radius = 0;
|
||||||
|
for (;;) {
|
||||||
|
const playerPosition = vec2.fromValues(
|
||||||
|
radius * Math.cos(rotation) + possibleCenter.x,
|
||||||
|
radius * Math.sin(rotation) + possibleCenter.y,
|
||||||
|
);
|
||||||
|
|
||||||
|
const playerBoundingCircle = new Circle(
|
||||||
|
playerPosition,
|
||||||
|
PlayerCharacterPhysical.boundRadius,
|
||||||
|
);
|
||||||
|
|
||||||
|
const playerBoundingBox = getBoundingBoxOfCircle(playerBoundingCircle);
|
||||||
|
const possibleIntersectors = this.objectContainer.findIntersecting(
|
||||||
|
playerBoundingBox,
|
||||||
|
);
|
||||||
|
if (!isCircleIntersecting(playerBoundingCircle, possibleIntersectors)) {
|
||||||
|
return playerPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
rotation += Math.PI / 8;
|
||||||
|
radius += 30;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy() {
|
||||||
|
this.character?.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,31 +1,61 @@
|
||||||
import { CharacterTeam, PlayerInformation, Random, TransportEvents } from 'shared';
|
import {
|
||||||
|
CharacterTeam,
|
||||||
|
PlayerInformation,
|
||||||
|
Random,
|
||||||
|
TransportEvents,
|
||||||
|
settings,
|
||||||
|
} from 'shared';
|
||||||
import { PhysicalContainer } from '../physics/containers/physical-container';
|
import { PhysicalContainer } from '../physics/containers/physical-container';
|
||||||
|
import { NPC } from './npc';
|
||||||
import { Player } from './player';
|
import { Player } from './player';
|
||||||
|
import { PlayerBase } from './player-base';
|
||||||
|
|
||||||
export class PlayerContainer {
|
export class PlayerContainer {
|
||||||
private _players: Array<Player> = [];
|
private _players: Array<Player> = [];
|
||||||
|
private _npcs: Array<NPC> = [];
|
||||||
|
|
||||||
constructor(private readonly objects: PhysicalContainer) {}
|
constructor(
|
||||||
|
private readonly objects: PhysicalContainer,
|
||||||
|
private readonly playerMaxCount: number,
|
||||||
|
) {
|
||||||
|
this.createNPCs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public createNPCs() {
|
||||||
|
const newNpcCount = this.playerMaxCount - this._players.length - this._npcs.length;
|
||||||
|
for (let i = 0; i < newNpcCount; i++) {
|
||||||
|
const name = `BOT ${Random.choose(settings.npcNames)}`;
|
||||||
|
this._npcs.push(
|
||||||
|
new NPC({ name }, this, this.objects, this.getTeamOfNextPlayer(true)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public createPlayer(playerInfo: PlayerInformation, socket: SocketIO.Socket): Player {
|
public createPlayer(playerInfo: PlayerInformation, socket: SocketIO.Socket): Player {
|
||||||
const player = new Player(
|
if (this._players.length === this.playerMaxCount) {
|
||||||
playerInfo,
|
throw new Error('Too many players');
|
||||||
this,
|
}
|
||||||
this.objects,
|
|
||||||
socket,
|
const team = this.getTeamOfNextPlayer();
|
||||||
this.getTeamOfNextPlayer(),
|
let npcToReplace = this._npcs.find((n) => n.team === team);
|
||||||
);
|
if (!npcToReplace) {
|
||||||
|
npcToReplace = this._npcs.find((n) => n.team !== team);
|
||||||
|
}
|
||||||
|
npcToReplace?.destroy();
|
||||||
|
this._npcs = this._npcs.filter((n) => n !== npcToReplace);
|
||||||
|
|
||||||
|
const player = new Player(playerInfo, this, this.objects, team, socket);
|
||||||
this._players.push(player);
|
this._players.push(player);
|
||||||
|
|
||||||
return player;
|
return player;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get players(): Array<Player> {
|
public get players(): Array<PlayerBase> {
|
||||||
return this._players;
|
return [...this._players, ...this._npcs];
|
||||||
}
|
}
|
||||||
|
|
||||||
public get count(): number {
|
public get count(): number {
|
||||||
return this.players.length;
|
return this._players.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public step(deltaTimeInSeconds: number) {
|
public step(deltaTimeInSeconds: number) {
|
||||||
|
|
@ -33,12 +63,13 @@ export class PlayerContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
public sendOnSocket(message: any) {
|
public sendOnSocket(message: any) {
|
||||||
this.players.forEach((p) => p.socket.emit(TransportEvents.ServerToPlayer, message));
|
this._players.forEach((p) => p.socket.emit(TransportEvents.ServerToPlayer, message));
|
||||||
}
|
}
|
||||||
|
|
||||||
private getTeamOfNextPlayer(): CharacterTeam {
|
private getTeamOfNextPlayer(isNpc = false): CharacterTeam {
|
||||||
const declaCount = this._players.filter((p) => p.team === CharacterTeam.decla).length;
|
const players = isNpc ? this.players : this._players;
|
||||||
const redCount = this._players.filter((p) => p.team === CharacterTeam.red).length;
|
const declaCount = players.filter((p) => p.team === CharacterTeam.decla).length;
|
||||||
|
const redCount = players.filter((p) => p.team === CharacterTeam.red).length;
|
||||||
|
|
||||||
if ((declaCount === redCount && Random.getRandom() >= 0.5) || declaCount < redCount) {
|
if ((declaCount === redCount && Random.getRandom() >= 0.5) || declaCount < redCount) {
|
||||||
return CharacterTeam.decla;
|
return CharacterTeam.decla;
|
||||||
|
|
@ -49,5 +80,6 @@ export class PlayerContainer {
|
||||||
|
|
||||||
public deletePlayer(player: Player) {
|
public deletePlayer(player: Player) {
|
||||||
this._players = this._players.filter((p) => p !== player);
|
this._players = this._players.filter((p) => p !== player);
|
||||||
|
this.createNPCs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,35 +27,18 @@ import {
|
||||||
import { getTimeInMilliseconds } from '../helper/get-time-in-milliseconds';
|
import { getTimeInMilliseconds } from '../helper/get-time-in-milliseconds';
|
||||||
import { BoundingBox } from '../physics/bounding-boxes/bounding-box';
|
import { BoundingBox } from '../physics/bounding-boxes/bounding-box';
|
||||||
import { PhysicalContainer } from '../physics/containers/physical-container';
|
import { PhysicalContainer } from '../physics/containers/physical-container';
|
||||||
import { getBoundingBoxOfCircle } from '../physics/functions/get-bounding-box-of-circle';
|
|
||||||
import { isCircleIntersecting } from '../physics/functions/is-circle-intersecting';
|
|
||||||
import { PlayerCharacterPhysical } from '../objects/player-character-physical';
|
import { PlayerCharacterPhysical } from '../objects/player-character-physical';
|
||||||
import { PlanetPhysical } from '../objects/planet-physical';
|
|
||||||
import { PlayerContainer } from './player-container';
|
import { PlayerContainer } from './player-container';
|
||||||
|
import { PlayerBase } from './player-base';
|
||||||
|
|
||||||
export class Player extends CommandReceiver {
|
export class Player extends PlayerBase {
|
||||||
private character?: PlayerCharacterPhysical | null;
|
|
||||||
private aspectRatio: number = 16 / 9;
|
private aspectRatio: number = 16 / 9;
|
||||||
private isActive = true;
|
private isActive = true;
|
||||||
|
|
||||||
private sumKills = 0;
|
|
||||||
private sumDeaths = 0;
|
|
||||||
|
|
||||||
private objectsPreviouslyInViewArea: Array<GameObject> = [];
|
private objectsPreviouslyInViewArea: Array<GameObject> = [];
|
||||||
|
|
||||||
private pingTime?: number;
|
private pingTime?: number;
|
||||||
private _latency?: number;
|
private _latency?: number;
|
||||||
public measureLatency() {
|
|
||||||
this.pingTime = getTimeInMilliseconds();
|
|
||||||
this.socket.emit(TransportEvents.Ping);
|
|
||||||
if (this.isActive) {
|
|
||||||
setTimeout(this.measureLatency.bind(this), 10000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public get latency(): number | undefined {
|
|
||||||
return this._latency;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected commandExecutors: CommandExecutors = {
|
protected commandExecutors: CommandExecutors = {
|
||||||
[SetAspectRatioActionCommand.type]: (v: SetAspectRatioActionCommand) =>
|
[SetAspectRatioActionCommand.type]: (v: SetAspectRatioActionCommand) =>
|
||||||
|
|
@ -68,14 +51,13 @@ export class Player extends CommandReceiver {
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly playerInfo: PlayerInformation,
|
playerInfo: PlayerInformation,
|
||||||
private readonly playerContainer: PlayerContainer,
|
playerContainer: PlayerContainer,
|
||||||
private readonly objectContainer: PhysicalContainer,
|
objectContainer: PhysicalContainer,
|
||||||
|
team: CharacterTeam,
|
||||||
public readonly socket: SocketIO.Socket,
|
public readonly socket: SocketIO.Socket,
|
||||||
public readonly team: CharacterTeam,
|
|
||||||
) {
|
) {
|
||||||
super();
|
super(playerInfo, playerContainer, objectContainer, team);
|
||||||
this.team = team;
|
|
||||||
|
|
||||||
this.createCharacter();
|
this.createCharacter();
|
||||||
|
|
||||||
|
|
@ -88,26 +70,25 @@ export class Player extends CommandReceiver {
|
||||||
this.step(0);
|
this.step(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private createCharacter() {
|
public measureLatency() {
|
||||||
this.character = new PlayerCharacterPhysical(
|
this.pingTime = getTimeInMilliseconds();
|
||||||
this.playerInfo.name.slice(0, 20),
|
this.socket.emit(TransportEvents.Ping);
|
||||||
this.sumKills,
|
if (this.isActive) {
|
||||||
this.sumDeaths,
|
setTimeout(this.measureLatency.bind(this), 10000);
|
||||||
this.team,
|
}
|
||||||
this.objectContainer,
|
}
|
||||||
this.findEmptyPositionForPlayer(),
|
|
||||||
);
|
public get latency(): number | undefined {
|
||||||
|
return this._latency;
|
||||||
this.objectContainer.addObject(this.character);
|
}
|
||||||
this.objectsPreviouslyInViewArea.push(this.character);
|
|
||||||
|
protected createCharacter() {
|
||||||
this.socket.emit(
|
super.createCharacter();
|
||||||
TransportEvents.ServerToPlayer,
|
|
||||||
serialize(new CreatePlayerCommand(this.character)),
|
this.objectsPreviouslyInViewArea.push(this.character!);
|
||||||
);
|
this.sendToPlayer(new CreatePlayerCommand(this.character!));
|
||||||
}
|
}
|
||||||
|
|
||||||
private center: vec2 = vec2.create();
|
|
||||||
private timeUntilRespawn = 0;
|
private timeUntilRespawn = 0;
|
||||||
public step(deltaTimeInSeconds: number) {
|
public step(deltaTimeInSeconds: number) {
|
||||||
if (this.character) {
|
if (this.character) {
|
||||||
|
|
@ -173,41 +154,6 @@ export class Player extends CommandReceiver {
|
||||||
this.sendToPlayer(new UpdateOtherPlayerDirections(this.getOtherPlayers()));
|
this.sendToPlayer(new UpdateOtherPlayerDirections(this.getOtherPlayers()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private findEmptyPositionForPlayer(): vec2 {
|
|
||||||
let possibleCenter = this.playerContainer.players.find(
|
|
||||||
(p) => p.character?.isAlive && p.team === this.team,
|
|
||||||
)?.center;
|
|
||||||
|
|
||||||
if (!possibleCenter) {
|
|
||||||
possibleCenter = vec2.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
let rotation = 0;
|
|
||||||
let radius = 0;
|
|
||||||
for (;;) {
|
|
||||||
const playerPosition = vec2.fromValues(
|
|
||||||
radius * Math.cos(rotation) + possibleCenter.x,
|
|
||||||
radius * Math.sin(rotation) + possibleCenter.y,
|
|
||||||
);
|
|
||||||
|
|
||||||
const playerBoundingCircle = new Circle(
|
|
||||||
playerPosition,
|
|
||||||
PlayerCharacterPhysical.boundRadius,
|
|
||||||
);
|
|
||||||
|
|
||||||
const playerBoundingBox = getBoundingBoxOfCircle(playerBoundingCircle);
|
|
||||||
const possibleIntersectors = this.objectContainer.findIntersecting(
|
|
||||||
playerBoundingBox,
|
|
||||||
);
|
|
||||||
if (!isCircleIntersecting(playerBoundingCircle, possibleIntersectors)) {
|
|
||||||
return playerPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
rotation += Math.PI / 8;
|
|
||||||
radius += 30;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getOtherPlayers(): Array<OtherPlayerDirection> {
|
private getOtherPlayers(): Array<OtherPlayerDirection> {
|
||||||
if (!this.character) {
|
if (!this.character) {
|
||||||
return [];
|
return [];
|
||||||
|
|
@ -244,7 +190,7 @@ export class Player extends CommandReceiver {
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy() {
|
public destroy() {
|
||||||
|
super.destroy();
|
||||||
this.isActive = false;
|
this.isActive = false;
|
||||||
this.character?.destroy();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1367
frontend/package-lock.json
generated
1367
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -47,7 +47,7 @@
|
||||||
"resolve-url-loader": "^3.1.1",
|
"resolve-url-loader": "^3.1.1",
|
||||||
"sass": "^1.27.0",
|
"sass": "^1.27.0",
|
||||||
"sass-loader": "^8.0.2",
|
"sass-loader": "^8.0.2",
|
||||||
"sdf-2d": "^0.6.0",
|
"sdf-2d": "^0.6.2",
|
||||||
"shared": "file:../shared",
|
"shared": "file:../shared",
|
||||||
"socket.io-client": "^2.3.1",
|
"socket.io-client": "^2.3.1",
|
||||||
"source-map-loader": "^1.1.1",
|
"source-map-loader": "^1.1.1",
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
import { glMatrix } from 'gl-matrix';
|
import { glMatrix } from 'gl-matrix';
|
||||||
import {
|
import {
|
||||||
CharacterBase,
|
|
||||||
LampBase,
|
LampBase,
|
||||||
overrideDeserialization,
|
overrideDeserialization,
|
||||||
PlanetBase,
|
PlanetBase,
|
||||||
PlayerCharacterBase,
|
PlayerCharacterBase,
|
||||||
ProjectileBase,
|
ProjectileBase,
|
||||||
} from 'shared';
|
} from 'shared';
|
||||||
import { CharacterView } from './scripts/objects/character-view';
|
|
||||||
import { LampView } from './scripts/objects/lamp-view';
|
import { LampView } from './scripts/objects/lamp-view';
|
||||||
import { ProjectileView } from './scripts/objects/projectile-view';
|
import { ProjectileView } from './scripts/objects/projectile-view';
|
||||||
import { PlanetView } from './scripts/objects/planet-view';
|
import { PlanetView } from './scripts/objects/planet-view';
|
||||||
|
|
@ -33,7 +31,6 @@ import { SoundHandler, Sounds } from './scripts/sound-handler';
|
||||||
|
|
||||||
glMatrix.setMatrixArrayType(Array);
|
glMatrix.setMatrixArrayType(Array);
|
||||||
|
|
||||||
overrideDeserialization(CharacterBase, CharacterView);
|
|
||||||
overrideDeserialization(PlayerCharacterBase, PlayerCharacterView);
|
overrideDeserialization(PlayerCharacterBase, PlayerCharacterView);
|
||||||
overrideDeserialization(PlanetBase, PlanetView);
|
overrideDeserialization(PlanetBase, PlanetView);
|
||||||
overrideDeserialization(LampBase, LampView);
|
overrideDeserialization(LampBase, LampView);
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ body {
|
||||||
|
|
||||||
.player-tag {
|
.player-tag {
|
||||||
transform: translateX(-50%) translateY(-50%) rotate(-15deg);
|
transform: translateX(-50%) translateY(-50%) rotate(-15deg);
|
||||||
transition: left 200ms, top 200ms;
|
transition: left 150ms, top 150ms;
|
||||||
border-radius: 1000px;
|
border-radius: 1000px;
|
||||||
|
|
||||||
&.decla {
|
&.decla {
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,12 @@ export class Game extends CommandReceiver {
|
||||||
const angle = Math.atan2(direction.y, direction.x);
|
const angle = Math.atan2(direction.y, direction.x);
|
||||||
e.className = 'other-player-arrow ' + team;
|
e.className = 'other-player-arrow ' + team;
|
||||||
|
|
||||||
const { width, height } = this.overlay.getBoundingClientRect();
|
if (!this.renderer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = this.renderer.canvasSize.x;
|
||||||
|
const height = this.renderer.canvasSize.y;
|
||||||
const aspectRatio = width / height;
|
const aspectRatio = width / height;
|
||||||
const directionRatio = direction.x / direction.y;
|
const directionRatio = direction.x / direction.y;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
import { vec2 } from 'gl-matrix';
|
|
||||||
import { Renderer } from 'sdf-2d';
|
|
||||||
import { CharacterBase, CharacterTeam, Circle, Id, settings } from 'shared';
|
|
||||||
|
|
||||||
import { BlobShape } from '../shapes/blob-shape';
|
|
||||||
import { ViewObject } from './view-object';
|
|
||||||
|
|
||||||
export class CharacterView extends CharacterBase implements ViewObject {
|
|
||||||
private shape: BlobShape;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
id: Id,
|
|
||||||
team: CharacterTeam,
|
|
||||||
health: number,
|
|
||||||
head?: Circle,
|
|
||||||
leftFoot?: Circle,
|
|
||||||
rightFoot?: Circle,
|
|
||||||
) {
|
|
||||||
super(id, team, health, head, leftFoot, rightFoot);
|
|
||||||
this.shape = new BlobShape(settings.colorIndices[team]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get position(): vec2 {
|
|
||||||
return this.head!.center;
|
|
||||||
}
|
|
||||||
|
|
||||||
public beforeDestroy(): void {}
|
|
||||||
|
|
||||||
public step(deltaTimeInSeconds: number): void {}
|
|
||||||
|
|
||||||
public draw(renderer: Renderer, overlay: HTMLElement): void {
|
|
||||||
this.shape.setCircles([this.head!, this.leftFoot!, this.rightFoot!]);
|
|
||||||
renderer.addDrawable(this.shape);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -12,7 +12,6 @@ export class PlayerCharacterView extends PlayerCharacterBase implements ViewObje
|
||||||
private nameElement: HTMLElement = document.createElement('div');
|
private nameElement: HTMLElement = document.createElement('div');
|
||||||
private statsElement: HTMLElement = document.createElement('div');
|
private statsElement: HTMLElement = document.createElement('div');
|
||||||
private healthElement: HTMLElement = document.createElement('div');
|
private healthElement: HTMLElement = document.createElement('div');
|
||||||
private timeSinceLastNameElementUpdate = 0;
|
|
||||||
private previousHealth;
|
private previousHealth;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
|
@ -50,7 +49,6 @@ export class PlayerCharacterView extends PlayerCharacterBase implements ViewObje
|
||||||
}
|
}
|
||||||
|
|
||||||
public step(deltaTimeInSeconds: number): void {
|
public step(deltaTimeInSeconds: number): void {
|
||||||
this.timeSinceLastNameElementUpdate += deltaTimeInSeconds;
|
|
||||||
this.healthElement.style.width = (50 * this.health) / settings.playerMaxHealth + 'px';
|
this.healthElement.style.width = (50 * this.health) / settings.playerMaxHealth + 'px';
|
||||||
this.statsElement.innerText = this.getStatsText();
|
this.statsElement.innerText = this.getStatsText();
|
||||||
if (this.previousHealth > this.health) {
|
if (this.previousHealth > this.health) {
|
||||||
|
|
@ -75,14 +73,11 @@ export class PlayerCharacterView extends PlayerCharacterBase implements ViewObje
|
||||||
overlay.appendChild(this.nameElement);
|
overlay.appendChild(this.nameElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.timeSinceLastNameElementUpdate > 0.15) {
|
|
||||||
const screenPosition = renderer.worldToDisplayCoordinates(
|
const screenPosition = renderer.worldToDisplayCoordinates(
|
||||||
this.calculateTextPosition(),
|
this.calculateTextPosition(),
|
||||||
);
|
);
|
||||||
this.nameElement.style.left = screenPosition.x + 'px';
|
this.nameElement.style.left = screenPosition.x + 'px';
|
||||||
this.nameElement.style.top = screenPosition.y + 'px';
|
this.nameElement.style.top = screenPosition.y + 'px';
|
||||||
this.timeSinceLastNameElementUpdate = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.shape.setCircles([this.head!, this.leftFoot!, this.rightFoot!]);
|
this.shape.setCircles([this.head!, this.leftFoot!, this.rightFoot!]);
|
||||||
renderer.addDrawable(this.shape);
|
renderer.addDrawable(this.shape);
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ export abstract class SoundHandler {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.ambientSound.muted = false;
|
this.ambientSound.muted = false;
|
||||||
this.ambientSound.volume = 0.5;
|
this.ambientSound.volume = 0.5;
|
||||||
|
this.ambientSound.loop = true;
|
||||||
if (!this.isAmbientPlaying) {
|
if (!this.isAmbientPlaying) {
|
||||||
this.ambientSound.pause();
|
this.ambientSound.pause();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,6 @@ export * from './transport/serialization/serialize';
|
||||||
export * from './transport/serialization/serializes-to';
|
export * from './transport/serialization/serializes-to';
|
||||||
export * from './transport/serialization/serializable';
|
export * from './transport/serialization/serializable';
|
||||||
export * from './transport/serialization/override-deserialization';
|
export * from './transport/serialization/override-deserialization';
|
||||||
export * from './objects/types/character-base';
|
|
||||||
export * from './objects/types/character-team';
|
export * from './objects/types/character-team';
|
||||||
export * from './objects/types/player-character-base';
|
export * from './objects/types/player-character-base';
|
||||||
export * from './objects/types/lamp-base';
|
export * from './objects/types/lamp-base';
|
||||||
|
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import { Circle } from '../../helper/circle';
|
|
||||||
import { Id } from '../../transport/identity';
|
|
||||||
import { serializable } from '../../transport/serialization/serializable';
|
|
||||||
import { GameObject } from '../game-object';
|
|
||||||
import { CharacterTeam } from './character-team';
|
|
||||||
|
|
||||||
@serializable
|
|
||||||
export class CharacterBase extends GameObject {
|
|
||||||
constructor(
|
|
||||||
id: Id,
|
|
||||||
public team: CharacterTeam,
|
|
||||||
public health: number,
|
|
||||||
public head?: Circle,
|
|
||||||
public leftFoot?: Circle,
|
|
||||||
public rightFoot?: Circle,
|
|
||||||
) {
|
|
||||||
super(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onShoot(strength: number) {}
|
|
||||||
|
|
||||||
public toArray(): Array<any> {
|
|
||||||
const { id, team, health, head, leftFoot, rightFoot } = this;
|
|
||||||
return [id, team, health, head, leftFoot, rightFoot];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +1,23 @@
|
||||||
import { CharacterBase } from './character-base';
|
|
||||||
import { Circle } from '../../helper/circle';
|
import { Circle } from '../../helper/circle';
|
||||||
import { Id } from '../../transport/identity';
|
import { Id } from '../../transport/identity';
|
||||||
import { serializable } from '../../transport/serialization/serializable';
|
import { serializable } from '../../transport/serialization/serializable';
|
||||||
|
import { GameObject } from '../game-object';
|
||||||
import { CharacterTeam } from './character-team';
|
import { CharacterTeam } from './character-team';
|
||||||
|
|
||||||
@serializable
|
@serializable
|
||||||
export class PlayerCharacterBase extends CharacterBase {
|
export class PlayerCharacterBase extends GameObject {
|
||||||
constructor(
|
constructor(
|
||||||
id: Id,
|
id: Id,
|
||||||
public name: string,
|
public name: string,
|
||||||
public killCount: number,
|
public killCount: number,
|
||||||
public deathCount: number,
|
public deathCount: number,
|
||||||
team: CharacterTeam,
|
public team: CharacterTeam,
|
||||||
health: number,
|
public health: number,
|
||||||
head?: Circle,
|
public head?: Circle,
|
||||||
leftFoot?: Circle,
|
public leftFoot?: Circle,
|
||||||
rightFoot?: Circle,
|
public rightFoot?: Circle,
|
||||||
) {
|
) {
|
||||||
super(id, team, health, head, leftFoot, rightFoot);
|
super(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onShoot(strength: number) {}
|
public onShoot(strength: number) {}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,64 @@ export const settings = {
|
||||||
backgroundGradient: [rgb255(90, 38, 43), rgb255(43, 39, 73)],
|
backgroundGradient: [rgb255(90, 38, 43), rgb255(43, 39, 73)],
|
||||||
declaColor,
|
declaColor,
|
||||||
declaPlanetColor,
|
declaPlanetColor,
|
||||||
|
npcNames: [
|
||||||
|
'Adam',
|
||||||
|
'Clarence',
|
||||||
|
'Elliot',
|
||||||
|
'Elmer',
|
||||||
|
'Ernie',
|
||||||
|
'Eugene',
|
||||||
|
'Fergus',
|
||||||
|
'Ferris',
|
||||||
|
'Frank',
|
||||||
|
'Frasier',
|
||||||
|
'Fred',
|
||||||
|
'George',
|
||||||
|
'Graham',
|
||||||
|
'Harvey',
|
||||||
|
'Irwin',
|
||||||
|
'Larry',
|
||||||
|
'Lester',
|
||||||
|
'Marvin',
|
||||||
|
'Neil',
|
||||||
|
'Niles',
|
||||||
|
'Oliver',
|
||||||
|
'Blaise',
|
||||||
|
'Andrew',
|
||||||
|
'Opie',
|
||||||
|
'Ryan',
|
||||||
|
'Toby',
|
||||||
|
'Ulric',
|
||||||
|
'Ulysses',
|
||||||
|
'Uri',
|
||||||
|
'Waldo',
|
||||||
|
'Wally',
|
||||||
|
'Walt',
|
||||||
|
'Wesley',
|
||||||
|
'Yanni',
|
||||||
|
'Yogi',
|
||||||
|
'Yuri',
|
||||||
|
'Dean',
|
||||||
|
'Dustin',
|
||||||
|
'Ethan',
|
||||||
|
'Harold',
|
||||||
|
'Henry',
|
||||||
|
'Irving',
|
||||||
|
'Jason',
|
||||||
|
'Jenssen',
|
||||||
|
'Josh',
|
||||||
|
'Martin',
|
||||||
|
'Nick',
|
||||||
|
'Norm',
|
||||||
|
'Orin',
|
||||||
|
'Pat',
|
||||||
|
'Perry',
|
||||||
|
'Ron',
|
||||||
|
'Shawn',
|
||||||
|
'Tim',
|
||||||
|
'Will',
|
||||||
|
'Wyatt',
|
||||||
|
],
|
||||||
redColor,
|
redColor,
|
||||||
redPlanetColor,
|
redPlanetColor,
|
||||||
colorIndices: {
|
colorIndices: {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue