This commit is contained in:
schmelczerandras 2020-10-26 16:09:05 +01:00
parent 4dd90b9860
commit 271d4ba171
4 changed files with 144 additions and 64 deletions

View file

@ -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: 8, playerLimit: 16,
npcCount: 4, npcCount: 8,
seed: 0, seed: 0,
scoreLimit: 500, scoreLimit: 500,
worldSize: 8000, worldSize: 8000,

View file

@ -13,12 +13,15 @@ import { PlayerBase } from './player-base';
import { getBoundingBoxOfCircle } from '../physics/functions/get-bounding-box-of-circle'; import { getBoundingBoxOfCircle } from '../physics/functions/get-bounding-box-of-circle';
import { PlayerCharacterPhysical } from '../objects/player-character-physical'; import { PlayerCharacterPhysical } from '../objects/player-character-physical';
import { PlanetPhysical } from '../objects/planet-physical'; import { PlanetPhysical } from '../objects/planet-physical';
import { Physical } from '../physics/physicals/physical';
export class NPC extends PlayerBase { export class NPC extends PlayerBase {
private moveTarget: vec2 = vec2.create(); private direction: vec2 = vec2.fromValues(Random.getRandom(), Random.getRandom());
private planet?: PlanetPhysical;
private timeSinceLastFindTarget = 10000; private timeSinceLastFindTarget = 10000;
private timeSinceLastFindShootTarget = 10000; private timeSinceLastFindShootTarget = 10000;
private isWandering = false;
private timeSinceLastWanderingConsideration = 0;
private isComingBack = false;
constructor( constructor(
playerInfo: PlayerInformation, playerInfo: PlayerInformation,
@ -32,47 +35,105 @@ export class NPC extends PlayerBase {
} }
private findTarget() { private findTarget() {
const observableArea = getBoundingBoxOfCircle(new Circle(this.center, 100)); if (
(!this.isComingBack && vec2.length(this.center) > settings.worldRadius) ||
(this.isComingBack && vec2.length(this.center) > settings.worldRadius / 2)
) {
this.isComingBack = true;
vec2.subtract(this.direction, vec2.fromValues(0, 0), this.center);
return;
}
this.isComingBack = false;
const observableArea = getBoundingBoxOfCircle(new Circle(this.center, 2000));
const nearObjects = this.objectContainer.findIntersecting(observableArea); const nearObjects = this.objectContainer.findIntersecting(observableArea);
const characters = this.findNearCharactersSorted(nearObjects);
const enemies = nearObjects.filter( if (characters.length > 0) {
(o) => const nearest = characters[0];
o.gameObject instanceof PlayerCharacterPhysical && if (nearest.distance < 200) {
o.gameObject.team !== this.team, vec2.subtract(this.direction, this.center, nearest.character.center);
); return;
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) { const enemies = characters.filter((o) => o.character.team !== this.team);
const enemiesCenter = enemies.reduce(
(sum, e) => vec2.add(sum, sum, (e.gameObject as PlayerCharacterPhysical).center), if (enemies.length > 0) {
const nearest = enemies[0];
if (nearest.distance < 500) {
vec2.subtract(this.direction, this.center, nearest.character.center);
return;
}
}
if (enemies.length > 0) {
const nearest = enemies[0];
if (nearest.distance > 1000) {
vec2.subtract(this.direction, nearest.character.center, this.center);
return;
}
}
if (this.isWandering) {
vec2.rotate(
this.direction,
this.direction,
vec2.create(), vec2.create(),
Random.getRandomInRange(-0.2, 0.2),
); );
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 { } else {
this.moveTarget = vec2.fromValues( const planets = this.findNearPlanetsSorted(nearObjects).filter(
Random.getRandomInRange(-5000, 5000), (p) => p.planet.team !== this.team,
Random.getRandomInRange(-5000, 5000),
); );
if (planets.length > 0) {
vec2.subtract(this.direction, planets[0].planet.center, this.center);
} else {
this.isWandering = true;
} }
} }
} }
private findNearCharactersSorted(
nearObjects: Array<Physical>,
): Array<{ character: PlayerCharacterPhysical; distance: number }> {
const characters = Array.from(
new Set(
nearObjects.filter(
(o) =>
o.gameObject instanceof PlayerCharacterPhysical &&
o.gameObject !== this.character,
),
),
).map((c) => ({
character: c.gameObject,
distance: vec2.distance(
this.center,
(c.gameObject as PlayerCharacterPhysical).center,
),
})) as Array<{ character: PlayerCharacterPhysical; distance: number }>;
characters.sort((a, b) => a.distance - b.distance);
return characters;
}
private findNearPlanetsSorted(
nearObjects: Array<Physical>,
): Array<{ planet: PlanetPhysical; distance: number }> {
const planets = nearObjects
.filter((o) => o.gameObject instanceof PlanetPhysical)
.map((c) => ({
planet: c.gameObject,
distance: vec2.distance(this.center, (c.gameObject as PlanetPhysical).center),
})) as Array<{ planet: PlanetPhysical; distance: number }>;
planets.sort((a, b) => a.distance - b.distance);
return planets;
}
private findShootTarget(): vec2 | undefined { private findShootTarget(): vec2 | undefined {
const observableArea = getBoundingBoxOfCircle(new Circle(this.center, 1000)); const observableArea = getBoundingBoxOfCircle(new Circle(this.center, 1000));
const nearObjects = this.objectContainer.findIntersecting(observableArea); const nearObjects = this.objectContainer.findIntersecting(observableArea);
@ -106,26 +167,45 @@ export class NPC extends PlayerBase {
} }
} }
if (this.planet && Math.abs(this.planet.ownership - 0.5) > 0.3) { if ((this.timeSinceLastWanderingConsideration += deltaTimeInSeconds) > 3) {
this.planet = undefined; this.timeSinceLastWanderingConsideration = 0;
this.findTarget(); this.isWandering = Random.getRandom() > 0.5;
} else { }
if ((this.timeSinceLastFindTarget += deltaTimeInSeconds) > 1) { if ((this.timeSinceLastFindTarget += deltaTimeInSeconds) > 1) {
this.timeSinceLastFindTarget = 0; this.timeSinceLastFindTarget = 0;
this.findTarget(); this.findTarget();
} }
}
if ((this.timeSinceLastFindShootTarget += deltaTimeInSeconds) > 0.5) { if ((this.timeSinceLastFindShootTarget += deltaTimeInSeconds) > 0.5) {
if (Random.getRandom() > 0.5) {
const shootTarget = this.findShootTarget(); const shootTarget = this.findShootTarget();
if (shootTarget) { if (shootTarget) {
vec2.add(
shootTarget,
shootTarget,
vec2.fromValues(
Random.getRandomInRange(-200, 200),
Random.getRandomInRange(-200, 200),
),
);
this.character?.shootTowards(shootTarget); this.character?.shootTowards(shootTarget);
} }
}
this.timeSinceLastFindShootTarget = 0; this.timeSinceLastFindShootTarget = 0;
} }
const targetDelta = vec2.subtract(vec2.create(), this.moveTarget, this.center); this.character?.handleMovementAction(new MoveActionCommand(this.direction, false));
vec2.normalize(targetDelta, targetDelta); }
this.character?.handleMovementAction(new MoveActionCommand(targetDelta, false));
protected createCharacter() {
const randomPoint = vec2.rotate(
vec2.create(),
vec2.fromValues(Random.getRandomInRange(0, settings.worldRadius), 0),
vec2.create(),
Random.getRandomInRange(0, Math.PI * 2),
);
super.createCharacter(randomPoint);
} }
} }

View file

@ -22,14 +22,14 @@ export abstract class PlayerBase extends CommandReceiver {
super(); super();
} }
protected createCharacter() { protected createCharacter(preferredCenter: vec2) {
this.character = new PlayerCharacterPhysical( this.character = new PlayerCharacterPhysical(
this.playerInfo.name.slice(0, 20), this.playerInfo.name.slice(0, 20),
this.sumKills, this.sumKills,
this.sumDeaths, this.sumDeaths,
this.team, this.team,
this.objectContainer, this.objectContainer,
this.findEmptyPositionForPlayer(), this.findEmptyPositionForPlayer(preferredCenter),
); );
this.objectContainer.addObject(this.character); this.objectContainer.addObject(this.character);
@ -37,21 +37,17 @@ export abstract class PlayerBase extends CommandReceiver {
public abstract step(deltaTimeInSeconds: number): void; public abstract step(deltaTimeInSeconds: number): void;
protected findEmptyPositionForPlayer(): vec2 { protected findEmptyPositionForPlayer(preferredCenter: vec2): vec2 {
let possibleCenter = this.playerContainer.players.find( if (!preferredCenter) {
(p) => p.character?.isAlive && p.team === this.team, preferredCenter = vec2.create();
)?.center;
if (!possibleCenter) {
possibleCenter = vec2.create();
} }
let rotation = 0; let rotation = 0;
let radius = 0; let radius = 0;
for (;;) { for (;;) {
const playerPosition = vec2.fromValues( const playerPosition = vec2.fromValues(
radius * Math.cos(rotation) + possibleCenter.x, radius * Math.cos(rotation) + preferredCenter.x,
radius * Math.sin(rotation) + possibleCenter.y, radius * Math.sin(rotation) + preferredCenter.y,
); );
const playerBoundingCircle = new Circle( const playerBoundingCircle = new Circle(

View file

@ -83,7 +83,11 @@ export class Player extends PlayerBase {
} }
protected createCharacter() { protected createCharacter() {
super.createCharacter(); const preferredCenter = this.playerContainer.players.find(
(p) => p.character?.isAlive && p.team === this.team,
)?.center;
super.createCharacter(preferredCenter ?? vec2.create());
this.objectsPreviouslyInViewArea.push(this.character!); this.objectsPreviouslyInViewArea.push(this.character!);
this.queueCommandSend(new CreatePlayerCommand(this.character!)); this.queueCommandSend(new CreatePlayerCommand(this.character!));