import { assertEquals } from "https://deno.land/std@0.221.0/assert/mod.ts"; import { chunks, enumerate, log_from, Prototyped, range, Vec2 } from "../common/utils.ts"; import { ClientInterface } from "./network.ts"; import { Session } from "./entities/player.ts"; import { ClassMap } from "../common/utils.ts"; import { maybe } from "../common/utils.ts"; const log = log_from(import.meta); export class WorldEntity { display; world; identifier; position; compontents; constructor(world: World, identifier: number, display: string, position: Vec2, ...components: Prototyped[]) { this.display = display; this.world = world; this.identifier = identifier; this.position = position; this.compontents = new ClassMap(); for (const c of components) this.compontents.insert(c); } } export class World { next_id; entities; constructor() { this.next_id = 0; this.entities = new Map(); } spawn_entity(display: string, pos: Vec2) { const entity = new WorldEntity(this, this.next_id++, display, pos); this.entities.set(entity.identifier, entity); return entity; } async spawn_structure(file_path: string, origin: Vec2) { const content = await Deno.readTextFile(file_path); let count = 0; for (const [y, line] of enumerate(content.split("\n"))) { for (const [x, chars] of enumerate(chunks(line, 2))) { const display = chars[0] + chars[1]; if (display === " ") continue; this.spawn_entity(display, origin.add(new Vec2(x, y))); count += 1; } } log("Spawned", count, "tiles from", file_path); } render(center: Vec2, size: Vec2) { const radius = size.scale(0.5); const result = Array.from(range(0, size.y())).map(() => Array.from(range(0, size.x())).map(() => " ")); const top_left = center.sub(radius); const bottom_right = center.add(radius); for (const entity of this.entities.values()) { if (!entity.position.inside(top_left, bottom_right)) continue; const local_pos = entity.position.sub(top_left); result[local_pos.y()][local_pos.x()] = entity.display; } return result.map((line) => line.join("")).toReversed().join("\n"); } move_collide(entity_id: number, displacement: Vec2) { let result = false; const entity = this.get_entity(entity_id); if (entity === undefined) return result; for (const step of displacement_steps(entity.position, displacement)) { const collision = this.get_entity_at(step); if (collision !== undefined) return result; entity.position = step; result = true; } return result; } get_entity(entity_id: number) { return this.entities.get(entity_id); } *get_entities_at(pos: Vec2) { for (const entity of this.entities.values()) if (entity.position.overlaps(pos)) yield entity; } get_entity_at(pos: Vec2) { return maybe([...this.get_entities_at(pos)][0]); } *get_entities_in_range(min: Vec2, max: Vec2) { for (const entity of this.entities.values()) if (entity.position.inside(min, max)) yield entity; } get_entity_in_range(min: Vec2, max: Vec2) { return maybe([...this.get_entities_in_range(min, max)][0]); } kill(entity_id: number) { this.entities.delete(entity_id); } } export class Engine { world; constructor(world: World) { this.world = world; } async start() { log("Started engine."); } add_session(client: ClientInterface) { const player_entity = this.world.spawn_entity("`/", new Vec2(0, 0)); const session = new Session(client, this.world, player_entity); session.spin(); } } function* displacement_steps(position: Vec2, displacement: Vec2) { let pos = position; let left = displacement; while (left.len() >= 1) { if (left.x() >= 1) { pos = pos.add(new Vec2(1, 0)); left = left.sub(new Vec2(1, 0)); } if (left.x() <= -1) { pos = pos.add(new Vec2(-1, 0)); left = left.sub(new Vec2(-1, 0)); } if (left.y() >= 1) { pos = pos.add(new Vec2(0, 1)); left = left.sub(new Vec2(0, 1)); } if (left.y() <= -1) { pos = pos.add(new Vec2(0, -1)); left = left.sub(new Vec2(0, -1)); } yield pos; } } Deno.test("test_displacement", () => { assertEquals( [...displacement_steps(new Vec2(1, 1), new Vec2(4, 6))], [new Vec2(2, 2), new Vec2(3, 3), new Vec2(4, 4), new Vec2(5, 5), new Vec2(5, 6), new Vec2(5, 7)], ); });