Fix npcs
This commit is contained in:
parent
4dd90b9860
commit
271d4ba171
4 changed files with 144 additions and 64 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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!));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue