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 = {
port: 3000,
name: 'Test server',
playerLimit: 8,
npcCount: 4,
playerLimit: 16,
npcCount: 8,
seed: 0,
scoreLimit: 500,
worldSize: 8000,

View file

@ -13,12 +13,15 @@ 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';
import { Physical } from '../physics/physicals/physical';
export class NPC extends PlayerBase {
private moveTarget: vec2 = vec2.create();
private planet?: PlanetPhysical;
private direction: vec2 = vec2.fromValues(Random.getRandom(), Random.getRandom());
private timeSinceLastFindTarget = 10000;
private timeSinceLastFindShootTarget = 10000;
private isWandering = false;
private timeSinceLastWanderingConsideration = 0;
private isComingBack = false;
constructor(
playerInfo: PlayerInformation,
@ -32,45 +35,103 @@ export class NPC extends PlayerBase {
}
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 characters = this.findNearCharactersSorted(nearObjects);
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) {
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),
);
if (characters.length > 0) {
const nearest = characters[0];
if (nearest.distance < 200) {
vec2.subtract(this.direction, this.center, nearest.character.center);
return;
}
}
const enemies = characters.filter((o) => o.character.team !== this.team);
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(),
Random.getRandomInRange(-0.2, 0.2),
);
} else {
const planets = this.findNearPlanetsSorted(nearObjects).filter(
(p) => p.planet.team !== this.team,
);
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 {
@ -106,26 +167,45 @@ export class NPC extends PlayerBase {
}
}
if (this.planet && Math.abs(this.planet.ownership - 0.5) > 0.3) {
this.planet = undefined;
if ((this.timeSinceLastWanderingConsideration += deltaTimeInSeconds) > 3) {
this.timeSinceLastWanderingConsideration = 0;
this.isWandering = Random.getRandom() > 0.5;
}
if ((this.timeSinceLastFindTarget += deltaTimeInSeconds) > 1) {
this.timeSinceLastFindTarget = 0;
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);
if (Random.getRandom() > 0.5) {
const shootTarget = this.findShootTarget();
if (shootTarget) {
vec2.add(
shootTarget,
shootTarget,
vec2.fromValues(
Random.getRandomInRange(-200, 200),
Random.getRandomInRange(-200, 200),
),
);
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));
this.character?.handleMovementAction(new MoveActionCommand(this.direction, 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();
}
protected createCharacter() {
protected createCharacter(preferredCenter: vec2) {
this.character = new PlayerCharacterPhysical(
this.playerInfo.name.slice(0, 20),
this.sumKills,
this.sumDeaths,
this.team,
this.objectContainer,
this.findEmptyPositionForPlayer(),
this.findEmptyPositionForPlayer(preferredCenter),
);
this.objectContainer.addObject(this.character);
@ -37,21 +37,17 @@ export abstract class PlayerBase extends CommandReceiver {
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();
protected findEmptyPositionForPlayer(preferredCenter: vec2): vec2 {
if (!preferredCenter) {
preferredCenter = 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,
radius * Math.cos(rotation) + preferredCenter.x,
radius * Math.sin(rotation) + preferredCenter.y,
);
const playerBoundingCircle = new Circle(

View file

@ -83,7 +83,11 @@ export class Player extends PlayerBase {
}
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.queueCommandSend(new CreatePlayerCommand(this.character!));