No more ecs
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
import type { ClientLevel } from "../level/client_level.ts";
|
||||
|
||||
// anything that exists in the level and moves, like minecraft's Entity. position is the middle of its feet
|
||||
export abstract class Entity {
|
||||
id: string;
|
||||
level: ClientLevel;
|
||||
|
||||
x = 0;
|
||||
y = 0;
|
||||
z = 0;
|
||||
// blocks per second
|
||||
vx = 0;
|
||||
vy = 0;
|
||||
vz = 0;
|
||||
yaw = 0;
|
||||
pitch = 0;
|
||||
|
||||
// the collision box, width is used for both x and z
|
||||
width: number;
|
||||
height: number;
|
||||
eye_height: number;
|
||||
gravity = -15.8;
|
||||
|
||||
// which way it hit something on each axis in the last move: 1 or -1, 0 for nothing.
|
||||
// 1 on y means it's standing on something
|
||||
colliding_x = 0;
|
||||
colliding_y = 0;
|
||||
colliding_z = 0;
|
||||
|
||||
constructor(level: ClientLevel, id: string, width: number, height: number, eye_height: number) {
|
||||
this.level = level;
|
||||
this.id = id;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.eye_height = eye_height;
|
||||
}
|
||||
|
||||
get on_ground() {
|
||||
return this.colliding_y === 1;
|
||||
}
|
||||
|
||||
set_position(x: number, y: number, z: number) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
abstract tick(delta: number): void;
|
||||
|
||||
// falls and moves by its velocity, stopping on each axis it would run into a block on
|
||||
move(delta: number) {
|
||||
this.vy += this.gravity * delta;
|
||||
|
||||
this.colliding_x = this.vx !== 0 && this.#collides(this.x + this.vx * delta, this.y, this.z)
|
||||
? -Math.sign(this.vx)
|
||||
: 0;
|
||||
if (this.colliding_x !== 0) {
|
||||
this.vx = 0;
|
||||
}
|
||||
|
||||
this.colliding_y = this.vy !== 0 && this.#collides(this.x, this.y + this.vy * delta, this.z)
|
||||
? -Math.sign(this.vy)
|
||||
: 0;
|
||||
if (this.colliding_y !== 0) {
|
||||
this.vy = 0;
|
||||
}
|
||||
|
||||
this.colliding_z = this.vz !== 0 && this.#collides(this.x, this.y, this.z + this.vz * delta)
|
||||
? -Math.sign(this.vz)
|
||||
: 0;
|
||||
if (this.colliding_z !== 0) {
|
||||
this.vz = 0;
|
||||
}
|
||||
|
||||
this.x += this.vx * delta;
|
||||
this.y += this.vy * delta;
|
||||
this.z += this.vz * delta;
|
||||
}
|
||||
|
||||
// whether the collision box at x/y/z overlaps any block, unloaded chunks included
|
||||
#collides(x: number, y: number, z: number) {
|
||||
const min_x = Math.floor(x - this.width / 2);
|
||||
const max_x = Math.floor(x + this.width / 2);
|
||||
const min_y = Math.floor(y);
|
||||
const max_y = Math.floor(y + this.height);
|
||||
const min_z = Math.floor(z - this.width / 2);
|
||||
const max_z = Math.floor(z + this.width / 2);
|
||||
|
||||
for (let bx = min_x; bx <= max_x; bx++) {
|
||||
for (let by = min_y; by <= max_y; by++) {
|
||||
for (let bz = min_z; bz <= max_z; bz++) {
|
||||
if (this.level.get_block(bx, by, bz)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import type { Client } from "../client.ts";
|
||||
import { InputManager } from "../input_manager.ts";
|
||||
import { ClientInventories } from "../inventory.ts";
|
||||
import { Player } from "./player.ts";
|
||||
|
||||
// how often the position goes to the server
|
||||
const SEND_POSITION_INTERVAL = 1 / 10;
|
||||
|
||||
// the player this client controls, like minecraft's LocalPlayer
|
||||
export class LocalPlayer extends Player {
|
||||
client: Client;
|
||||
inventories = new ClientInventories();
|
||||
|
||||
move_speed = 4;
|
||||
jump_force = 6.7;
|
||||
|
||||
#send_timer = 0;
|
||||
|
||||
constructor(client: Client, id: string, name: string) {
|
||||
super(client.level, id, name);
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
tick(delta: number) {
|
||||
if (!this.client.screen) {
|
||||
this.#apply_input();
|
||||
}
|
||||
this.move(delta);
|
||||
this.#send_position(delta);
|
||||
}
|
||||
|
||||
// turns with the mouse, like minecraft's MouseHandler.turnPlayer
|
||||
turn(mouse_dx: number, mouse_dy: number) {
|
||||
this.yaw += -mouse_dx * 0.001;
|
||||
this.pitch += -mouse_dy * 0.001;
|
||||
|
||||
const limit = Math.PI / 2 - 0.01;
|
||||
this.pitch = Math.max(-limit, Math.min(limit, this.pitch));
|
||||
}
|
||||
|
||||
// walking relative to where it's looking
|
||||
#apply_input() {
|
||||
const options = this.client.options;
|
||||
let input_x = 0;
|
||||
let input_z = 0;
|
||||
|
||||
if (InputManager.is_key_down(options.key_left)) {
|
||||
input_x -= 1;
|
||||
}
|
||||
if (InputManager.is_key_down(options.key_right)) {
|
||||
input_x += 1;
|
||||
}
|
||||
if (InputManager.is_key_down(options.key_forward)) {
|
||||
input_z -= 1;
|
||||
}
|
||||
if (InputManager.is_key_down(options.key_back)) {
|
||||
input_z += 1;
|
||||
}
|
||||
|
||||
const size = Math.hypot(input_x, input_z);
|
||||
if (size > 0) {
|
||||
input_x /= size;
|
||||
input_z /= size;
|
||||
}
|
||||
|
||||
const sin = Math.sin(this.yaw);
|
||||
const cos = Math.cos(this.yaw);
|
||||
const speed = this.move_speed * (InputManager.is_key_down(options.key_sprint) ? 1.75 : 1);
|
||||
|
||||
this.vx = (sin * input_z + cos * input_x) * speed;
|
||||
this.vz = (cos * input_z - sin * input_x) * speed;
|
||||
|
||||
if (InputManager.is_key_down(options.key_jump) && this.on_ground) {
|
||||
this.vy += this.jump_force;
|
||||
}
|
||||
}
|
||||
|
||||
#send_position(delta: number) {
|
||||
this.#send_timer += delta;
|
||||
if (this.#send_timer < SEND_POSITION_INTERVAL) {
|
||||
return;
|
||||
}
|
||||
this.#send_timer = 0;
|
||||
this.client.connection.send({
|
||||
type: "move",
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
z: this.z,
|
||||
yaw: this.yaw,
|
||||
pitch: this.pitch,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { ClientLevel } from "../level/client_level.ts";
|
||||
import { Entity } from "./entity.ts";
|
||||
|
||||
export abstract class Player extends Entity {
|
||||
name: string;
|
||||
|
||||
constructor(level: ClientLevel, id: string, name: string) {
|
||||
super(level, id, 0.55, 1.79, 1.69);
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import type { PlayerInfo } from "$/common/protocol.ts";
|
||||
import type { ClientLevel } from "../level/client_level.ts";
|
||||
import { Player } from "./player.ts";
|
||||
|
||||
const SMOOTHING = 12;
|
||||
|
||||
// another player on the server, it moves where the server says instead of simulating anything
|
||||
export class RemotePlayer extends Player {
|
||||
color: [number, number, number];
|
||||
|
||||
// where the server last said it is, the drawn position eases towards it so movement isn't choppy
|
||||
target_x: number;
|
||||
target_y: number;
|
||||
target_z: number;
|
||||
|
||||
constructor(level: ClientLevel, info: PlayerInfo) {
|
||||
super(level, info.id, info.name);
|
||||
this.set_position(info.x, info.y, info.z);
|
||||
this.target_x = info.x;
|
||||
this.target_y = info.y;
|
||||
this.target_z = info.z;
|
||||
this.yaw = info.yaw;
|
||||
this.pitch = info.pitch;
|
||||
this.color = color_from_name(info.name);
|
||||
}
|
||||
|
||||
lerp_to(x: number, y: number, z: number, yaw: number, pitch: number) {
|
||||
this.target_x = x;
|
||||
this.target_y = y;
|
||||
this.target_z = z;
|
||||
this.yaw = yaw;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
|
||||
tick(delta: number) {
|
||||
const t = Math.min(1, delta * SMOOTHING);
|
||||
this.x += (this.target_x - this.x) * t;
|
||||
this.y += (this.target_y - this.y) * t;
|
||||
this.z += (this.target_z - this.z) * t;
|
||||
}
|
||||
}
|
||||
|
||||
function color_from_name(name: string): [number, number, number] {
|
||||
let hash = 0;
|
||||
for (const ch of name) {
|
||||
hash = (hash * 31 + ch.charCodeAt(0)) | 0;
|
||||
}
|
||||
const hue = ((hash % 360) + 360) % 360;
|
||||
// hsl with s=0.6 l=0.6 to rgb
|
||||
const c = 0.48;
|
||||
const x = c * (1 - Math.abs(((hue / 60) % 2) - 1));
|
||||
const m = 0.36;
|
||||
const [r, g, b] = hue < 60
|
||||
? [c, x, 0]
|
||||
: hue < 120
|
||||
? [x, c, 0]
|
||||
: hue < 180
|
||||
? [0, c, x]
|
||||
: hue < 240
|
||||
? [0, x, c]
|
||||
: hue < 300
|
||||
? [x, 0, c]
|
||||
: [c, 0, x];
|
||||
return [r + m, g + m, b + m];
|
||||
}
|
||||
Reference in New Issue
Block a user