Compare commits
No commits in common. "5da4bcc64e41b33a823ffeb397f211baac9367b1" and "f277a71087c7cd90ce1d2b548cb9dc2152e19da5" have entirely different histories.
5da4bcc64e
...
f277a71087
7 changed files with 23 additions and 87 deletions
|
@ -224,14 +224,6 @@ export class Vec2 {
|
||||||
public clone() {
|
public clone() {
|
||||||
return new Vec2(this.x(), this.y());
|
return new Vec2(this.x(), this.y());
|
||||||
}
|
}
|
||||||
|
|
||||||
public normalize() {
|
|
||||||
return new Vec2(clamp(this.x(), -1, 1), clamp(this.y(), -1, 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function clamp(x: number, min: number, max: number) {
|
|
||||||
return Math.max(min, Math.min(x, max));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function v2(x: number, y: number) {
|
export function v2(x: number, y: number) {
|
||||||
|
|
|
@ -16,8 +16,8 @@ export function sys_render_world(center: Vec2, size: Vec2) {
|
||||||
const result = Array.from(range(0, size.y())).map(() => Array.from(range(0, size.x())).map(() => " "));
|
const result = Array.from(range(0, size.y())).map(() => Array.from(range(0, size.x())).map(() => " "));
|
||||||
const min = center.sub(radius);
|
const min = center.sub(radius);
|
||||||
const max = center.add(radius).sub(v2(1, 1));
|
const max = center.add(radius).sub(v2(1, 1));
|
||||||
for (const [pos, display] of engine.all(query_in_rect(min, max).with(CompDisplay))) {
|
for (const [pos, display] of engine.query_all(query_in_rect(min, max).with(CompDisplay))) {
|
||||||
const local_pos = pos.pos.sub(min);
|
const local_pos = pos.position.sub(min);
|
||||||
result[local_pos.y()][local_pos.x()] = display.display;
|
result[local_pos.y()][local_pos.x()] = display.display;
|
||||||
}
|
}
|
||||||
return result.map((line) => line.join("")).toReversed().join("\n");
|
return result.map((line) => line.join("")).toReversed().join("\n");
|
||||||
|
|
|
@ -5,24 +5,24 @@ import { CompDisplay } from "./display.ts";
|
||||||
const log = log_from(import.meta);
|
const log = log_from(import.meta);
|
||||||
|
|
||||||
export class CompPos {
|
export class CompPos {
|
||||||
pos;
|
position;
|
||||||
entity;
|
entity;
|
||||||
|
|
||||||
constructor(entity: Entity, position: Vec2) {
|
constructor(entity: Entity, position: Vec2) {
|
||||||
this.pos = position;
|
this.position = position;
|
||||||
this.entity = entity;
|
this.entity = entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
move(displacement: Vec2) {
|
move(displacement: Vec2) {
|
||||||
this.pos = this.pos.add(displacement);
|
this.position = this.position.add(displacement);
|
||||||
}
|
}
|
||||||
|
|
||||||
move_collide(engine: Engine, displacement: Vec2) {
|
move_collide(engine: Engine, displacement: Vec2) {
|
||||||
let result = false;
|
let result = false;
|
||||||
for (const step of displacement_steps(this.pos, displacement)) {
|
for (const step of displacement_steps(this.position, displacement)) {
|
||||||
const collider_exists = engine.one(query_at(step)) !== null;
|
const collider_exists = engine.query_one(query_at(step)) !== null;
|
||||||
if (collider_exists) return result;
|
if (collider_exists) return result;
|
||||||
this.pos = step;
|
this.position = step;
|
||||||
result = true;
|
result = true;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -61,11 +61,11 @@ Deno.test("test_displacement", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
export function query_at(pos: Vec2) {
|
export function query_at(pos: Vec2) {
|
||||||
return Query.with(CompPos).filter(([c]) => c.pos.overlaps(pos));
|
return Query.with(CompPos).filter(([c]) => c.position.overlaps(pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function query_in_rect(min: Vec2, max: Vec2) {
|
export function query_in_rect(min: Vec2, max: Vec2) {
|
||||||
return Query.with(CompPos).filter(([c]) => c.pos.inside(min, max));
|
return Query.with(CompPos).filter(([c]) => c.position.inside(min, max));
|
||||||
}
|
}
|
||||||
|
|
||||||
class CompStructure {}
|
class CompStructure {}
|
||||||
|
@ -100,7 +100,7 @@ export async function sys_spawn_structure(file_path: string, origin: Vec2) {
|
||||||
export function sys_find_free_pos(target: Vec2) {
|
export function sys_find_free_pos(target: Vec2) {
|
||||||
return (engine: Engine) => {
|
return (engine: Engine) => {
|
||||||
for (const pos of spiral(target)) {
|
for (const pos of spiral(target)) {
|
||||||
const found = engine.one(query_at(pos));
|
const found = engine.query_one(query_at(pos));
|
||||||
if (found === null) return pos;
|
if (found === null) return pos;
|
||||||
}
|
}
|
||||||
throw new Error("Unreachable.");
|
throw new Error("Unreachable.");
|
||||||
|
|
|
@ -2,10 +2,12 @@ import { Awaitable, ClassMap, Constructible, first, log_from, Prototyped, wait }
|
||||||
const log = log_from(import.meta);
|
const log = log_from(import.meta);
|
||||||
|
|
||||||
export class Entity {
|
export class Entity {
|
||||||
|
identifier;
|
||||||
components;
|
components;
|
||||||
engine;
|
engine;
|
||||||
|
|
||||||
constructor(engine: Engine) {
|
constructor(identifier: number, engine: Engine) {
|
||||||
|
this.identifier = identifier;
|
||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
this.components = new ClassMap();
|
this.components = new ClassMap();
|
||||||
}
|
}
|
||||||
|
@ -26,13 +28,6 @@ export class Entity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CompId {
|
|
||||||
public readonly id;
|
|
||||||
constructor(id: number) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Engine {
|
export class Engine {
|
||||||
next_identifier;
|
next_identifier;
|
||||||
entities;
|
entities;
|
||||||
|
@ -48,8 +43,7 @@ export class Engine {
|
||||||
|
|
||||||
spawn(op: (entity: Entity) => unknown) {
|
spawn(op: (entity: Entity) => unknown) {
|
||||||
const identifier = this.next_identifier++;
|
const identifier = this.next_identifier++;
|
||||||
const entity = new Entity(this);
|
const entity = new Entity(identifier, this);
|
||||||
entity.insert(new CompId(identifier));
|
|
||||||
op(entity);
|
op(entity);
|
||||||
this.entities.set(identifier, entity);
|
this.entities.set(identifier, entity);
|
||||||
return entity;
|
return entity;
|
||||||
|
@ -60,19 +54,18 @@ export class Engine {
|
||||||
}
|
}
|
||||||
|
|
||||||
async global_system_loop(sys: (engine: Engine) => Awaitable, interval: number) {
|
async global_system_loop(sys: (engine: Engine) => Awaitable, interval: number) {
|
||||||
// TODO : Wait for start.
|
|
||||||
while (true) {
|
while (true) {
|
||||||
await sys(this);
|
await sys(this);
|
||||||
await wait(interval);
|
await wait(interval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*all<O extends QueryRes>(query: Query<O>) {
|
*query_all<O extends QueryRes>(query: Query<O>) {
|
||||||
yield* query.run(this.entities.values());
|
yield* query.run(this.entities.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
one<O extends QueryRes>(query: Query<O>) {
|
query_one<O extends QueryRes>(query: Query<O>) {
|
||||||
return first(this.all(query));
|
return first(this.query_all(query));
|
||||||
}
|
}
|
||||||
|
|
||||||
get(identifier: number) {
|
get(identifier: number) {
|
||||||
|
|
|
@ -1,37 +1,15 @@
|
||||||
import { assert } from "https://deno.land/std@0.221.0/assert/assert.ts";
|
|
||||||
import { v2 } from "../../common/utils.ts";
|
|
||||||
import { Vec2 } from "../../common/utils.ts";
|
import { Vec2 } from "../../common/utils.ts";
|
||||||
import { CompDisplay } from "../components/display.ts";
|
import { CompDisplay } from "../components/display.ts";
|
||||||
import { query_in_rect } from "../components/world.ts";
|
|
||||||
import { CompPos } from "../components/world.ts";
|
import { CompPos } from "../components/world.ts";
|
||||||
import { CompId, Query } from "../engine.ts";
|
|
||||||
import { Engine } from "../engine.ts";
|
import { Engine } from "../engine.ts";
|
||||||
import { CompPlayer } from "./player.ts";
|
|
||||||
|
|
||||||
export class CompEnemy {
|
export class CompEnemy {
|
||||||
target;
|
target;
|
||||||
life;
|
life;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.target = null as number | null;
|
this.target = null as number | null;
|
||||||
this.life = 10;
|
this.life = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
get_target_pos(engine: Engine) {
|
|
||||||
if (this.target === null) return null;
|
|
||||||
const result = engine.one(Query.with(CompId).filter(([c]) => c.id === this.target).with(CompPos));
|
|
||||||
if (result === null) return null;
|
|
||||||
const [_, pos] = result;
|
|
||||||
return pos.pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
find_target_from(engine: Engine, pos: Vec2, range: number) {
|
|
||||||
const radius = v2(range, range);
|
|
||||||
const found = engine.one(Query.with(CompPlayer).with(CompId).and(query_in_rect(pos.sub(radius), pos.add(radius))));
|
|
||||||
if (found === null) return this.target = null;
|
|
||||||
const [_, id, __] = found;
|
|
||||||
return this.target = id.id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sys_spawn_enemy(pos: Vec2) {
|
export function sys_spawn_enemy(pos: Vec2) {
|
||||||
|
@ -45,27 +23,3 @@ export function sys_spawn_enemy(pos: Vec2) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sys_update_enemy() {
|
|
||||||
return (engine: Engine) => {
|
|
||||||
for (const [enemy, enemy_pos] of engine.all(Query.with(CompEnemy).with(CompPos))) {
|
|
||||||
if (enemy.target === null) enemy.find_target_from(engine, enemy_pos.pos, 3);
|
|
||||||
if (enemy.target === null) continue;
|
|
||||||
const pos = enemy.get_target_pos(engine);
|
|
||||||
assert(pos !== null);
|
|
||||||
const direction = pos.sub(enemy_pos.pos);
|
|
||||||
if (direction.len() <= 5) {
|
|
||||||
const displacement = direction.normalize();
|
|
||||||
const moved = enemy_pos.move_collide(engine, displacement);
|
|
||||||
if (!moved) {
|
|
||||||
enemy_pos.move_collide(engine, v2(displacement.x(), 0));
|
|
||||||
enemy_pos.move_collide(engine, v2(0, displacement.y()));
|
|
||||||
}
|
|
||||||
} else enemy.target = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function enemy_plugin(engine: Engine) {
|
|
||||||
engine.global_system_loop(sys_update_enemy(), 500);
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { mts } from "../../common/mod.ts";
|
||||||
import { log_from, v2, Vec2 } from "../../common/utils.ts";
|
import { log_from, v2, Vec2 } from "../../common/utils.ts";
|
||||||
import { CompDisplay, sys_render_world } from "../components/display.ts";
|
import { CompDisplay, sys_render_world } from "../components/display.ts";
|
||||||
import { CompPos, sys_find_free_pos } from "../components/world.ts";
|
import { CompPos, sys_find_free_pos } from "../components/world.ts";
|
||||||
import { CompId, Engine, Entity } from "../engine.ts";
|
import { Engine, Entity } from "../engine.ts";
|
||||||
import { ClientInterface } from "../network.ts";
|
import { ClientInterface } from "../network.ts";
|
||||||
const log = log_from(import.meta);
|
const log = log_from(import.meta);
|
||||||
|
|
||||||
|
@ -32,12 +32,12 @@ export class Session {
|
||||||
if (input.kind === "ping") this.client.outputs.send({ kind: "ping_response", content: input.content });
|
if (input.kind === "ping") this.client.outputs.send({ kind: "ping_response", content: input.content });
|
||||||
if (input.kind === "request_display") this.send_display(input.content.width, input.content.height);
|
if (input.kind === "request_display") this.send_display(input.content.width, input.content.height);
|
||||||
if (input.kind === "input") this.handle_input(input);
|
if (input.kind === "input") this.handle_input(input);
|
||||||
if (input.kind === "exit") break;
|
if (input.kind === "exit") this.engine.delete(this.entity.identifier);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Session loop failed, ", error);
|
console.error("Session loop failed, ", error);
|
||||||
|
this.engine.delete(this.entity.identifier);
|
||||||
}
|
}
|
||||||
this.engine.delete(this.entity.get_force(CompId).id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handle_input(input: mts.MsgInput) {
|
handle_input(input: mts.MsgInput) {
|
||||||
|
@ -48,7 +48,7 @@ export class Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
send_display(width: number, height: number) {
|
send_display(width: number, height: number) {
|
||||||
const raw = this.engine.run(sys_render_world(this.entity.get_force(CompPos).pos, v2(width, height)));
|
const raw = this.engine.run(sys_render_world(this.entity.get_force(CompPos).position, v2(width, height)));
|
||||||
this.client.outputs.send({ kind: "display", content: { raw } });
|
this.client.outputs.send({ kind: "display", content: { raw } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ export function sys_spawn_player(position: Vec2) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CompPlayer {
|
class CompPlayer {
|
||||||
life;
|
life;
|
||||||
constructor() {
|
constructor() {
|
||||||
this.life = 10;
|
this.life = 10;
|
||||||
|
|
|
@ -6,15 +6,12 @@ import { Session } from "./entities/player.ts";
|
||||||
import { sys_spawn_structure } from "./components/world.ts";
|
import { sys_spawn_structure } from "./components/world.ts";
|
||||||
import { Gateway } from "./network.ts";
|
import { Gateway } from "./network.ts";
|
||||||
import { sys_spawn_enemy } from "./entities/enemy.ts";
|
import { sys_spawn_enemy } from "./entities/enemy.ts";
|
||||||
import { enemy_plugin } from "./entities/enemy.ts";
|
|
||||||
const log = log_from(import.meta);
|
const log = log_from(import.meta);
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
log("Starting.");
|
log("Starting.");
|
||||||
const engine = new Engine();
|
const engine = new Engine();
|
||||||
enemy_plugin(engine);
|
|
||||||
engine.start();
|
engine.start();
|
||||||
|
|
||||||
engine.run(await sys_spawn_structure("../data/structures/houses.txt", v2(2, 2)));
|
engine.run(await sys_spawn_structure("../data/structures/houses.txt", v2(2, 2)));
|
||||||
engine.run(sys_spawn_enemy(v2(1, 1)));
|
engine.run(sys_spawn_enemy(v2(1, 1)));
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue