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 = {
|
||||
port: 3000,
|
||||
name: 'Test server',
|
||||
playerLimit: 16,
|
||||
seed: Math.random(),
|
||||
playerLimit: 8,
|
||||
seed: 0,
|
||||
scoreLimit: 500,
|
||||
worldSize: 15000,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import {
|
|||
settings,
|
||||
ServerInformation,
|
||||
PlayerInformation,
|
||||
serializable,
|
||||
UpdateGameState,
|
||||
serialize,
|
||||
CharacterTeam,
|
||||
|
|
@ -18,7 +17,7 @@ import { DeltaTimeCalculator } from './helper/delta-time-calculator';
|
|||
import { Options } from './options';
|
||||
import { PlayerContainer } from './players/player-container';
|
||||
|
||||
const playerCountSubscribedRoom = 'playerCountUpdates';
|
||||
const gameStateSubscribedRoom = 'gameStateSubscribedRoom';
|
||||
|
||||
export class GameServer {
|
||||
private objects!: PhysicalContainer;
|
||||
|
|
@ -38,15 +37,15 @@ export class GameServer {
|
|||
private initialize() {
|
||||
const previousPlayers = this.players;
|
||||
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.deltaTimes = [];
|
||||
this.declaPoints = 0;
|
||||
this.redPoints = 0;
|
||||
this.isInEndGame = false;
|
||||
this.timeScaling = 1;
|
||||
createWorld(this.objects, this.options.worldSize);
|
||||
this.objects.initialize();
|
||||
previousPlayers?.sendOnSocket(serialize(new GameStart()));
|
||||
}
|
||||
|
||||
|
|
@ -58,23 +57,27 @@ export class GameServer {
|
|||
|
||||
io.on('connection', (socket: SocketIO.Socket) => {
|
||||
socket.on(TransportEvents.PlayerJoining, (playerInfo: PlayerInformation) => {
|
||||
const player = this.players.createPlayer(playerInfo, socket);
|
||||
socket.on(TransportEvents.PlayerToServer, (json: string) => {
|
||||
const command = deserialize(json);
|
||||
player.sendCommand(command);
|
||||
});
|
||||
try {
|
||||
const player = this.players.createPlayer(playerInfo, socket);
|
||||
socket.on(TransportEvents.PlayerToServer, (json: string) => {
|
||||
const command = deserialize(json);
|
||||
player.sendCommand(command);
|
||||
});
|
||||
|
||||
this.sendServerStateUpdate();
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
player.destroy();
|
||||
this.players.deletePlayer(player);
|
||||
this.sendServerStateUpdate();
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
player.destroy();
|
||||
this.players.deletePlayer(player);
|
||||
this.sendServerStateUpdate();
|
||||
});
|
||||
} catch {
|
||||
socket.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
socket.on(TransportEvents.SubscribeForServerInfoUpdates, () => {
|
||||
socket.join(playerCountSubscribedRoom);
|
||||
socket.join(gameStateSubscribedRoom);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -82,7 +85,7 @@ export class GameServer {
|
|||
private timeSinceLastServerStateUpdate = 0;
|
||||
public sendServerStateUpdate() {
|
||||
this.io
|
||||
.to(playerCountSubscribedRoom)
|
||||
.to(gameStateSubscribedRoom)
|
||||
.emit(TransportEvents.ServerInfoUpdate, [this.players.count, this.gameProgress]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ export class PlanetPhysical
|
|||
}
|
||||
|
||||
public get team(): CharacterTeam {
|
||||
return this.ownership === 0.5
|
||||
return Math.abs(this.ownership - 0.5) < 0.1
|
||||
? CharacterTeam.neutral
|
||||
: this.ownership < 0.5
|
||||
? CharacterTeam.decla
|
||||
|
|
|
|||
|
|
@ -190,7 +190,9 @@ export class PlayerCharacterPhysical
|
|||
}
|
||||
|
||||
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 {
|
||||
|
|
@ -199,7 +201,7 @@ export class PlayerCharacterPhysical
|
|||
this.head.distance(target),
|
||||
this.leftFoot.distance(target),
|
||||
this.rightFoot.distance(target),
|
||||
) - 20
|
||||
) - 5
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -222,7 +224,7 @@ export class PlayerCharacterPhysical
|
|||
this.movementActions = [];
|
||||
}
|
||||
|
||||
return direction;
|
||||
return vec2.normalize(direction, direction);
|
||||
}
|
||||
|
||||
public step(deltaTime: number) {
|
||||
|
|
@ -251,16 +253,28 @@ export class PlayerCharacterPhysical
|
|||
this.rightFoot.center,
|
||||
);
|
||||
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 movementForce = vec2.scale(direction, direction, settings.maxAcceleration);
|
||||
|
||||
if (!this.currentPlanet) {
|
||||
this.applyForce(this.leftFoot, actualGravity, deltaTime);
|
||||
this.applyForce(this.rightFoot, actualGravity, deltaTime);
|
||||
this.applyForce(this.leftFoot, leftFootGravity, 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(
|
||||
vec2.length(sumForce) === 0 ? vec2.fromValues(0, -1) : sumForce,
|
||||
|
|
@ -274,7 +288,7 @@ export class PlayerCharacterPhysical
|
|||
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(rightFootGravity, rightFootGravity, 0.35);
|
||||
}
|
||||
|
|
@ -283,7 +297,7 @@ export class PlayerCharacterPhysical
|
|||
|
||||
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.setDirection(headGravity, deltaTime);
|
||||
|
|
@ -294,7 +308,9 @@ export class PlayerCharacterPhysical
|
|||
|
||||
this.stepBodyPart(this.leftFoot, deltaTime);
|
||||
this.stepBodyPart(this.rightFoot, deltaTime);
|
||||
this.keepPosture();
|
||||
|
||||
this.keepPosture(deltaTime);
|
||||
|
||||
this.remoteCall('updateCircles', this.head, this.leftFoot, this.rightFoot);
|
||||
}
|
||||
|
||||
|
|
@ -302,27 +318,68 @@ export class PlayerCharacterPhysical
|
|||
this.direction = interpolateAngles(
|
||||
this.direction,
|
||||
Math.atan2(direction.y, direction.x) + Math.PI / 2,
|
||||
Math.pow(4, deltaTime),
|
||||
Math.pow(2, deltaTime),
|
||||
);
|
||||
}
|
||||
|
||||
private keepPosture() {
|
||||
const bodyCenter = vec2.add(vec2.create(), this.head.center, this.leftFoot.center);
|
||||
vec2.add(bodyCenter, bodyCenter, this.rightFoot.center);
|
||||
vec2.scale(bodyCenter, bodyCenter, 1 / 3);
|
||||
this.springMove(this.leftFoot, bodyCenter, PlayerCharacterPhysical.leftFootOffset);
|
||||
this.springMove(this.rightFoot, bodyCenter, PlayerCharacterPhysical.rightFootOffset);
|
||||
this.springMove(this.head, bodyCenter, PlayerCharacterPhysical.headOffset);
|
||||
private keepPosture(deltaTime: number) {
|
||||
const center = this.center;
|
||||
this.springMove(
|
||||
this.leftFoot,
|
||||
center,
|
||||
PlayerCharacterPhysical.leftFootOffset,
|
||||
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) {
|
||||
// todo: make time-independent
|
||||
const springConstant = 0.55;
|
||||
|
||||
private springMove(
|
||||
object: CirclePhysical,
|
||||
center: vec2,
|
||||
offset: vec2,
|
||||
deltaTime: number,
|
||||
) {
|
||||
const desiredPosition = vec2.add(vec2.create(), center, offset);
|
||||
vec2.rotate(desiredPosition, desiredPosition, center, this.direction);
|
||||
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);
|
||||
|
||||
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 { NPC } from './npc';
|
||||
import { Player } from './player';
|
||||
import { PlayerBase } from './player-base';
|
||||
|
||||
export class PlayerContainer {
|
||||
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 {
|
||||
const player = new Player(
|
||||
playerInfo,
|
||||
this,
|
||||
this.objects,
|
||||
socket,
|
||||
this.getTeamOfNextPlayer(),
|
||||
);
|
||||
if (this._players.length === this.playerMaxCount) {
|
||||
throw new Error('Too many players');
|
||||
}
|
||||
|
||||
const team = 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);
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
public get players(): Array<Player> {
|
||||
return this._players;
|
||||
public get players(): Array<PlayerBase> {
|
||||
return [...this._players, ...this._npcs];
|
||||
}
|
||||
|
||||
public get count(): number {
|
||||
return this.players.length;
|
||||
return this._players.length;
|
||||
}
|
||||
|
||||
public step(deltaTimeInSeconds: number) {
|
||||
|
|
@ -33,12 +63,13 @@ export class PlayerContainer {
|
|||
}
|
||||
|
||||
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 {
|
||||
const declaCount = this._players.filter((p) => p.team === CharacterTeam.decla).length;
|
||||
const redCount = this._players.filter((p) => p.team === CharacterTeam.red).length;
|
||||
private getTeamOfNextPlayer(isNpc = false): CharacterTeam {
|
||||
const players = isNpc ? this.players : this._players;
|
||||
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) {
|
||||
return CharacterTeam.decla;
|
||||
|
|
@ -49,5 +80,6 @@ export class PlayerContainer {
|
|||
|
||||
public deletePlayer(player: 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 { BoundingBox } from '../physics/bounding-boxes/bounding-box';
|
||||
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 { PlanetPhysical } from '../objects/planet-physical';
|
||||
import { PlayerContainer } from './player-container';
|
||||
import { PlayerBase } from './player-base';
|
||||
|
||||
export class Player extends CommandReceiver {
|
||||
private character?: PlayerCharacterPhysical | null;
|
||||
export class Player extends PlayerBase {
|
||||
private aspectRatio: number = 16 / 9;
|
||||
private isActive = true;
|
||||
|
||||
private sumKills = 0;
|
||||
private sumDeaths = 0;
|
||||
|
||||
private objectsPreviouslyInViewArea: Array<GameObject> = [];
|
||||
|
||||
private pingTime?: 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 = {
|
||||
[SetAspectRatioActionCommand.type]: (v: SetAspectRatioActionCommand) =>
|
||||
|
|
@ -68,14 +51,13 @@ export class Player extends CommandReceiver {
|
|||
};
|
||||
|
||||
constructor(
|
||||
private readonly playerInfo: PlayerInformation,
|
||||
private readonly playerContainer: PlayerContainer,
|
||||
private readonly objectContainer: PhysicalContainer,
|
||||
playerInfo: PlayerInformation,
|
||||
playerContainer: PlayerContainer,
|
||||
objectContainer: PhysicalContainer,
|
||||
team: CharacterTeam,
|
||||
public readonly socket: SocketIO.Socket,
|
||||
public readonly team: CharacterTeam,
|
||||
) {
|
||||
super();
|
||||
this.team = team;
|
||||
super(playerInfo, playerContainer, objectContainer, team);
|
||||
|
||||
this.createCharacter();
|
||||
|
||||
|
|
@ -88,26 +70,25 @@ export class Player extends CommandReceiver {
|
|||
this.step(0);
|
||||
}
|
||||
|
||||
private 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);
|
||||
this.objectsPreviouslyInViewArea.push(this.character);
|
||||
|
||||
this.socket.emit(
|
||||
TransportEvents.ServerToPlayer,
|
||||
serialize(new CreatePlayerCommand(this.character)),
|
||||
);
|
||||
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 createCharacter() {
|
||||
super.createCharacter();
|
||||
|
||||
this.objectsPreviouslyInViewArea.push(this.character!);
|
||||
this.sendToPlayer(new CreatePlayerCommand(this.character!));
|
||||
}
|
||||
|
||||
private center: vec2 = vec2.create();
|
||||
private timeUntilRespawn = 0;
|
||||
public step(deltaTimeInSeconds: number) {
|
||||
if (this.character) {
|
||||
|
|
@ -173,41 +154,6 @@ export class Player extends CommandReceiver {
|
|||
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> {
|
||||
if (!this.character) {
|
||||
return [];
|
||||
|
|
@ -244,7 +190,7 @@ export class Player extends CommandReceiver {
|
|||
}
|
||||
|
||||
public destroy() {
|
||||
super.destroy();
|
||||
this.isActive = false;
|
||||
this.character?.destroy();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue