No more ecs

This commit is contained in:
2026-09-25 16:05:25 -03:00
parent 4539898c58
commit 213363d0be
56 changed files with 1103 additions and 1644 deletions
+100
View File
@@ -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;
}
}
+93
View File
@@ -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,
});
}
}
+11
View File
@@ -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;
}
}
+65
View File
@@ -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];
}