Improve gameplay
This commit is contained in:
parent
7412bc8af5
commit
793d9a81e8
27 changed files with 275 additions and 226 deletions
|
|
@ -6,11 +6,11 @@ import { PhysicalContainer } from './physics/containers/physical-container';
|
|||
import { evaluateSdf } from './physics/functions/evaluate-sdf';
|
||||
import { Physical } from './physics/physicals/physical';
|
||||
|
||||
export const createWorld = (objectContainer: PhysicalContainer, worldRadius: number) => {
|
||||
export const createWorld = (objectContainer: PhysicalContainer) => {
|
||||
const objects: Array<Physical> = [];
|
||||
const lights: Array<Physical> = [];
|
||||
|
||||
for (let r = 0; r < worldRadius; r += settings.radiusSteps) {
|
||||
for (let r = 0; r < settings.worldRadius; r += settings.radiusSteps) {
|
||||
const circumference = 2 * Math.PI * r;
|
||||
const stepCount = circumference * settings.objectsOnCircleLength;
|
||||
for (let rad = 0; rad < 2 * Math.PI; rad += (2 * Math.PI) / stepCount) {
|
||||
|
|
@ -67,8 +67,29 @@ export const createWorld = (objectContainer: PhysicalContainer, worldRadius: num
|
|||
}
|
||||
}
|
||||
}
|
||||
console.info('Generated planet count', objects.length);
|
||||
console.info('Generated light count', lights.length);
|
||||
console.info(`Generated ${objects.length} planets`);
|
||||
console.info(`Generated ${lights.length} light`);
|
||||
|
||||
// Associate each lamp with its NEAREST planet, so a planet can repaint "its"
|
||||
// lamps to the owning team's colour when it flips. Lamps are already placed by
|
||||
// proximity during world-gen, so the nearest planet is the one whose capture
|
||||
// they should advertise. Distances use the planet SDF (negative inside), which
|
||||
// is exactly the "closest planet" metric we want.
|
||||
const planets = objects.filter((o): o is PlanetPhysical => o instanceof PlanetPhysical);
|
||||
lights
|
||||
.filter((l): l is LampPhysical => l instanceof LampPhysical)
|
||||
.forEach((lamp) => {
|
||||
let nearest: PlanetPhysical | undefined;
|
||||
let nearestDistance = Infinity;
|
||||
planets.forEach((planet) => {
|
||||
const distance = planet.distance(lamp.center);
|
||||
if (distance < nearestDistance) {
|
||||
nearestDistance = distance;
|
||||
nearest = planet;
|
||||
}
|
||||
});
|
||||
nearest?.addLamp(lamp);
|
||||
});
|
||||
|
||||
[...objects, ...lights].forEach((o) => objectContainer.addObject(o));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,8 +4,7 @@ export const defaultOptions: Options = {
|
|||
port: 3000,
|
||||
name: 'Test server',
|
||||
playerLimit: 16,
|
||||
npcCount: 16,
|
||||
npcCount: 8,
|
||||
seed: Math.random(),
|
||||
scoreLimit: 1000,
|
||||
worldSize: 8000,
|
||||
scoreLimit: 2500,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ export class GameServer extends CommandReceiver {
|
|||
private initialize() {
|
||||
const previousPlayers = this.players;
|
||||
this.objects = new PhysicalContainer();
|
||||
createWorld(this.objects, this.options.worldSize);
|
||||
createWorld(this.objects);
|
||||
this.objects.initialize();
|
||||
this.players = new PlayerContainer(
|
||||
this.objects,
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@ export class LampPhysical extends LampBase implements StaticPhysical {
|
|||
return this;
|
||||
}
|
||||
|
||||
public queueSetLight(color: vec3, lightness: number) {
|
||||
this.remoteCall('setLight', color, lightness);
|
||||
}
|
||||
|
||||
public distance(target: vec2): number {
|
||||
return vec2.distance(this.center, target);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,13 @@ export class ProjectilePhysical extends ProjectileBase implements DynamicPhysica
|
|||
return !this.isDestroyed;
|
||||
}
|
||||
|
||||
public get direction(): vec2 {
|
||||
const direction = vec2.clone(this.velocity);
|
||||
return vec2.length(direction) > 0
|
||||
? vec2.normalize(direction, direction)
|
||||
: vec2.fromValues(0, -1);
|
||||
}
|
||||
|
||||
private moveOutsideOfObject() {
|
||||
let wasCollision = true;
|
||||
const delta = vec2.scale(
|
||||
|
|
|
|||
|
|
@ -5,5 +5,4 @@ export interface Options {
|
|||
scoreLimit: number;
|
||||
npcCount: number;
|
||||
seed: number;
|
||||
worldSize: number;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,17 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { CommandReceiver, Circle, PlayerInformation, CharacterTeam } from 'shared';
|
||||
import {
|
||||
CommandReceiver,
|
||||
Circle,
|
||||
PlayerInformation,
|
||||
CharacterTeam,
|
||||
Random,
|
||||
settings,
|
||||
} 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 { CharacterPhysical } from '../objects/character-physical';
|
||||
import { PlanetPhysical } from '../objects/planet-physical';
|
||||
import { PlayerContainer } from './player-container';
|
||||
|
||||
export abstract class PlayerBase extends CommandReceiver {
|
||||
|
|
@ -22,14 +30,14 @@ export abstract class PlayerBase extends CommandReceiver {
|
|||
super();
|
||||
}
|
||||
|
||||
protected createCharacter(preferredCenter: vec2) {
|
||||
protected createCharacter() {
|
||||
this.character = new CharacterPhysical(
|
||||
this.playerInfo.name.slice(0, 20),
|
||||
this.sumKills,
|
||||
this.sumDeaths,
|
||||
this.team,
|
||||
this.objectContainer,
|
||||
this.findEmptyPositionForPlayer(preferredCenter),
|
||||
this.findEmptyPositionForPlayer(this.findSpawnCenter()),
|
||||
);
|
||||
|
||||
this.objectContainer.addObject(this.character);
|
||||
|
|
@ -37,14 +45,37 @@ export abstract class PlayerBase extends CommandReceiver {
|
|||
|
||||
public abstract step(deltaTimeInSeconds: number): void;
|
||||
|
||||
protected findEmptyPositionForPlayer(preferredCenter: vec2): vec2 {
|
||||
if (!preferredCenter) {
|
||||
preferredCenter = vec2.create();
|
||||
private findSpawnCenter(): vec2 {
|
||||
const planets = this.objectContainer
|
||||
.findIntersecting(
|
||||
getBoundingBoxOfCircle(new Circle(vec2.create(), settings.worldRadius * 2)),
|
||||
)
|
||||
.filter((o): o is PlanetPhysical => o instanceof PlanetPhysical);
|
||||
|
||||
const friendly = planets.filter((p) => p.team === this.team);
|
||||
const neutral = planets.filter((p) => p.team === CharacterTeam.neutral);
|
||||
const candidates = friendly.length ? friendly : neutral.length ? neutral : planets;
|
||||
if (candidates.length === 0) {
|
||||
return vec2.create();
|
||||
}
|
||||
|
||||
const isContested = (planet: PlanetPhysical) =>
|
||||
this.playerContainer.players.some(
|
||||
(p) =>
|
||||
p.team !== this.team &&
|
||||
p.character?.isAlive &&
|
||||
vec2.distance(p.center, planet.center) < settings.spawnSafetyDistance,
|
||||
);
|
||||
const safe = candidates.filter((p) => !isContested(p));
|
||||
|
||||
// candidates is non-empty here, so choose() always returns a planet.
|
||||
return vec2.clone(Random.choose(safe.length ? safe : candidates)!.center);
|
||||
}
|
||||
|
||||
protected findEmptyPositionForPlayer(preferredCenter: vec2): vec2 {
|
||||
let rotation = 0;
|
||||
let radius = 0;
|
||||
for (;;) {
|
||||
for (; ;) {
|
||||
const playerPosition = vec2.fromValues(
|
||||
radius * Math.cos(rotation) + preferredCenter.x,
|
||||
radius * Math.sin(rotation) + preferredCenter.y,
|
||||
|
|
|
|||
|
|
@ -42,9 +42,8 @@ export class Player extends PlayerBase {
|
|||
(this.aspectRatio = v.aspectRatio),
|
||||
[MoveActionCommand.type]: (c: MoveActionCommand) =>
|
||||
this.character?.handleMovementAction(c),
|
||||
[PrimaryActionCommand.type]: (c: PrimaryActionCommand) => {
|
||||
this.character?.shootTowards(c.position);
|
||||
},
|
||||
[PrimaryActionCommand.type]: (c: PrimaryActionCommand) =>
|
||||
this.character?.shootTowards(c.position, c.charge),
|
||||
};
|
||||
|
||||
constructor(
|
||||
|
|
@ -60,11 +59,7 @@ export class Player extends PlayerBase {
|
|||
}
|
||||
|
||||
protected createCharacter() {
|
||||
const preferredCenter = this.playerContainer.players.find(
|
||||
(p) => p.character?.isAlive && p.team === this.team,
|
||||
)?.center;
|
||||
|
||||
super.createCharacter(preferredCenter ?? vec2.create());
|
||||
super.createCharacter();
|
||||
|
||||
this.objectsPreviouslyInViewArea.push(this.character!);
|
||||
this.queueCommandSend(new CreatePlayerCommand(this.character!));
|
||||
|
|
@ -206,8 +201,4 @@ export class Player extends PlayerBase {
|
|||
this.queueCommandSend(new ServerAnnouncement(announcement));
|
||||
}
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
import { Command, GameObject } from 'shared';
|
||||
|
||||
export class ReactToCollisionCommand extends Command {
|
||||
public constructor(public readonly other: GameObject) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue