Move the logic to mainloop
This commit is contained in:
parent
1c22edaac1
commit
90bac08e0b
|
|
@ -10,13 +10,7 @@ the attacker/source that CombatSystem folds into the final damage value.
|
||||||
RNG layer on top of damage calculation (crit chance, crit multiplier, random range).
|
RNG layer on top of damage calculation (crit chance, crit multiplier, random range).
|
||||||
|
|
||||||
### Rendering
|
### Rendering
|
||||||
- Sprite rendering system.
|
|
||||||
- Generic, use `resourceId` to link image.
|
|
||||||
- `framesCount`
|
|
||||||
- `currentFrame`
|
|
||||||
- `animationSpeed`
|
|
||||||
- Animation support for sprites.
|
|
||||||
- Different display systems
|
- Different display systems
|
||||||
- TextDisplay
|
- [x] TextDisplay
|
||||||
- BrickDisplay
|
- [ ] BrickDisplay
|
||||||
- Canvas
|
- [ ] Canvas
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,4 @@ export class Position extends Component<{ x: number, y: number, z: number }> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getPosition = (entity: Entity) => entity.get(Position)?.state;
|
export const getPosition = (entity?: Entity, key?: string) => entity?.get(Position, key)?.state;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import Input from '@common/input';
|
||||||
import { Inventory } from '@common/rpg/components/inventory';
|
import { Inventory } from '@common/rpg/components/inventory';
|
||||||
import { Item } from '@common/rpg/components/item';
|
import { Item } from '@common/rpg/components/item';
|
||||||
import { Position } from '@common/rpg/components/position';
|
import { Position } from '@common/rpg/components/position';
|
||||||
import { Entity, System, World } from '@common/rpg/core/world';
|
import { World } from '@common/rpg/core/world';
|
||||||
import { TextDisplaySystem } from '@common/rpg/systems/render/text';
|
import { TextDisplaySystem } from '@common/rpg/systems/render/text';
|
||||||
import { nextFrame } from '@common/utils';
|
import { nextFrame } from '@common/utils';
|
||||||
import './assets/style.css';
|
import './assets/style.css';
|
||||||
|
|
@ -11,47 +11,37 @@ import { Direction, getOppositeDirection, MAP_HEIGHT, MAP_WIDTH, MAP_X, MAP_Y, R
|
||||||
import { getPossibleRoomsCount, getRoom, getRoomsCount, getRoomsForLayer, getMapRoomChar, Room, Door } from './room';
|
import { getPossibleRoomsCount, getRoom, getRoomsCount, getRoomsForLayer, getMapRoomChar, Room, Door } from './room';
|
||||||
import { createPlayer, Player } from './player';
|
import { createPlayer, Player } from './player';
|
||||||
|
|
||||||
let lastMove = Date.now();
|
|
||||||
|
|
||||||
class GameSystem extends System {
|
export default async function main() {
|
||||||
private readonly display = new TextDisplay();
|
const world = new World();
|
||||||
private room: Entity | undefined;
|
const display = new TextDisplay();
|
||||||
private player: Entity | undefined;
|
world.addSystem(new TextDisplaySystem(display));
|
||||||
|
|
||||||
override onAdd(world: World) {
|
display.drawTextInBox(8, 11, 'Use arrow keys to move\nSHIFT to run\nSPACE to activate\nAWOO!', { fg: Color.CYAN });
|
||||||
world.addSystem(new TextDisplaySystem(this.display));
|
|
||||||
|
|
||||||
this.display.drawTextInBox(8, 11, 'Use arrow keys to move\nSHIFT to run\nSPACE to activate\nAWOO!', { fg: Color.CYAN });
|
let roomEntity = getRoom(world, 0, 0, 0);
|
||||||
|
let room = roomEntity?.get(Room);
|
||||||
this.room = getRoom(world, 0, 0, 0);
|
if (!room) {
|
||||||
const room = this.room?.get(Room);
|
throw new Error('No room found');
|
||||||
if (room) {
|
|
||||||
room.onEnter();
|
|
||||||
this.player = createPlayer(world, (room.x + room.width / 2) | 0, (room.y + room.height / 2) | 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
room.onEnter();
|
||||||
|
|
||||||
override update(world: World) {
|
const playerEntity = createPlayer(world, (room.x + room.width / 2) | 0, (room.y + room.height / 2) | 0);
|
||||||
this.handleInput(world);
|
const player = playerEntity.get(Player);
|
||||||
|
if (!player) {
|
||||||
this.drawRoom();
|
throw new Error('No player found');
|
||||||
this.drawMap(world);
|
|
||||||
this.drawInfo(world);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private drawRoom() {
|
function drawRoom() {
|
||||||
const room = this.room?.get(Room);
|
|
||||||
if (!room) return;
|
if (!room) return;
|
||||||
|
|
||||||
this.display.drawBox(ROOM_AREA_X, ROOM_AREA_Y, ROOM_AREA_WIDTH - 2, ROOM_AREA_HEIGHT - 2, { fill: [' '] });
|
display.drawBox(ROOM_AREA_X, ROOM_AREA_Y, ROOM_AREA_WIDTH - 2, ROOM_AREA_HEIGHT - 2, { fill: ' ' });
|
||||||
this.display.drawBox(room.x, room.y, room.width, room.height, { fill: ['.', Color.GRAY] });
|
display.drawBox(room.x, room.y, room.width, room.height, { fill: ['.', Color.GRAY] });
|
||||||
}
|
}
|
||||||
|
|
||||||
private drawMap(world: World) {
|
function drawMap() {
|
||||||
if (!this.room) return;
|
display.drawBox(MAP_X, MAP_Y, MAP_WIDTH - 2, MAP_HEIGHT - 2, { fill: [' '], title: 'Map' });
|
||||||
|
const worldPos = roomEntity.get(Position, 'world');
|
||||||
this.display.drawBox(MAP_X, MAP_Y, MAP_WIDTH - 2, MAP_HEIGHT - 2, { fill: [' '], title: 'Map' });
|
|
||||||
const worldPos = this.room.get(Position, 'world');
|
|
||||||
const centerX = worldPos ? worldPos.state.x - MAP_X - MAP_WIDTH / 2 : 0;
|
const centerX = worldPos ? worldPos.state.x - MAP_X - MAP_WIDTH / 2 : 0;
|
||||||
const centerY = worldPos ? worldPos.state.y - MAP_Y - MAP_HEIGHT / 2 : 0;
|
const centerY = worldPos ? worldPos.state.y - MAP_Y - MAP_HEIGHT / 2 : 0;
|
||||||
const worldZ = worldPos ? worldPos.state.z : 0;
|
const worldZ = worldPos ? worldPos.state.z : 0;
|
||||||
|
|
@ -65,31 +55,27 @@ class GameSystem extends System {
|
||||||
|
|
||||||
if (x > MAP_X && x < MAP_X + MAP_WIDTH - 1 && y > MAP_Y && y < MAP_Y + MAP_HEIGHT - 1) {
|
if (x > MAP_X && x < MAP_X + MAP_WIDTH - 1 && y > MAP_Y && y < MAP_Y + MAP_HEIGHT - 1) {
|
||||||
const char = getMapRoomChar(room);
|
const char = getMapRoomChar(room);
|
||||||
this.display.setChar(x, y, [char, room === this.room ? Color.YELLOW : Color.WHITE]);
|
display.setChar(x, y, [char, room === roomEntity ? Color.YELLOW : Color.WHITE]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private drawInfo(world: World) {
|
function drawInfo() {
|
||||||
if (!this.room || !this.player) return;
|
const worldPos = roomEntity.get(Position, 'world');
|
||||||
|
|
||||||
const worldPos = this.room.get(Position, 'world');
|
|
||||||
const coords = worldPos ? `${worldPos.state.x},${worldPos.state.y},${worldPos.state.z}`.padStart(9) : '0,0,0';
|
const coords = worldPos ? `${worldPos.state.x},${worldPos.state.y},${worldPos.state.z}`.padStart(9) : '0,0,0';
|
||||||
const foundRooms = getRoomsCount(world);
|
const foundRooms = getRoomsCount(world);
|
||||||
const totalRooms = getPossibleRoomsCount(world);
|
const totalRooms = getPossibleRoomsCount(world);
|
||||||
const rooms = `${foundRooms}/${totalRooms}${foundRooms === totalRooms ? '' : '+'}`.padStart(coords.length - 2);
|
const rooms = `${foundRooms}/${totalRooms}${foundRooms === totalRooms ? '' : '+'}`.padStart(coords.length - 2);
|
||||||
const inv = this.player.get(Inventory);
|
const inv = playerEntity.get(Inventory);
|
||||||
const foundItems = inv ? Object.values(inv.state.slots).length : 0;
|
const foundItems = inv ? Object.values(inv.state.slots).length : 0;
|
||||||
const totalItems = Array.from(world.query(Item)).length;
|
const totalItems = Array.from(world.query(Item)).length;
|
||||||
const items = `${foundItems}/${totalItems}`.padStart(coords.length - 2);
|
const items = `${foundItems}/${totalItems}`.padStart(coords.length - 2);
|
||||||
this.display.drawTextInBox(0, 0, `Pos: ${coords}\nRooms: ${rooms}\nItems: ${items}`, { fg: Color.YELLOW, title: 'Info' });
|
display.drawTextInBox(0, 0, `Pos: ${coords}\nRooms: ${rooms}\nItems: ${items}`, { fg: Color.YELLOW, title: 'Info' });
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleInput(world: World) {
|
let lastMove = Date.now();
|
||||||
const player = this.player?.get(Player);
|
function handleInput() {
|
||||||
let room = this.room?.get(Room);
|
if (!room || !player) return;
|
||||||
if (!player || !room) return;
|
|
||||||
|
|
||||||
Input.updateKeys();
|
Input.updateKeys();
|
||||||
|
|
||||||
const isSpacePressed = Input.isPressed(Input.KeyCode.SPACE);
|
const isSpacePressed = Input.isPressed(Input.KeyCode.SPACE);
|
||||||
|
|
@ -122,17 +108,17 @@ class GameSystem extends System {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (moved) {
|
if (moved) {
|
||||||
const activatedDoor = room.getActivatedDoor(newX, newY)?.get(Door);
|
const activatedDoor = room.getActivatedDoor(newX, newY);
|
||||||
if (activatedDoor) {
|
if (activatedDoor) {
|
||||||
const shouldTravel = ![Direction.UP, Direction.DOWN].includes(activatedDoor.direction) || isSpacePressed;
|
const shouldTravel = ![Direction.UP, Direction.DOWN].includes(activatedDoor.direction) || isSpacePressed;
|
||||||
|
|
||||||
if (shouldTravel) {
|
if (shouldTravel) {
|
||||||
room.onLeave();
|
room.onLeave();
|
||||||
this.room = getRoom(world, activatedDoor.worldX, activatedDoor.worldY, activatedDoor.worldZ);
|
roomEntity = getRoom(world, activatedDoor.worldX, activatedDoor.worldY, activatedDoor.worldZ);
|
||||||
room = this.room.get(Room);
|
room = roomEntity.get(Room);
|
||||||
if (!room) return;
|
if (!room) return;
|
||||||
room.onEnter();
|
room.onEnter();
|
||||||
const oppositeDoor = room.doors[getOppositeDirection(activatedDoor.direction)]?.get(Door);
|
const oppositeDoor = room.getDoor(getOppositeDirection(activatedDoor.direction));
|
||||||
|
|
||||||
if (oppositeDoor) {
|
if (oppositeDoor) {
|
||||||
switch (activatedDoor.direction) {
|
switch (activatedDoor.direction) {
|
||||||
|
|
@ -181,15 +167,15 @@ class GameSystem extends System {
|
||||||
player.y = newY;
|
player.y = newY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export default async function main() {
|
|
||||||
const world = new World();
|
|
||||||
world.addSystem(new GameSystem());
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const dt = await nextFrame();
|
const dt = await nextFrame();
|
||||||
|
|
||||||
|
handleInput();
|
||||||
|
|
||||||
|
drawRoom();
|
||||||
|
drawMap();
|
||||||
|
drawInfo();
|
||||||
world.update(dt);
|
world.update(dt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { spawnItem } from "./item";
|
||||||
import { Resources } from "@common/rpg/utils/resources";
|
import { Resources } from "@common/rpg/utils/resources";
|
||||||
import { TextRegion } from "@common/display/text";
|
import { TextRegion } from "@common/display/text";
|
||||||
|
|
||||||
type IDoors = [Entity?, Entity?, Entity?, Entity?, Entity?, Entity?];
|
type IDoors = [string?, string?, string?, string?, string?, string?];
|
||||||
|
|
||||||
@component
|
@component
|
||||||
export class Room extends Component<{ doors: IDoors }> {
|
export class Room extends Component<{ doors: IDoors }> {
|
||||||
|
|
@ -45,8 +45,11 @@ export class Room extends Component<{ doors: IDoors }> {
|
||||||
get doors() {
|
get doors() {
|
||||||
return this.state.doors;
|
return this.state.doors;
|
||||||
}
|
}
|
||||||
getActivatedDoor(px: number, py: number): Entity | undefined {
|
getActivatedDoor(px: number, py: number): Door | undefined {
|
||||||
return this.state.doors.find((door) => {
|
const id = this.state.doors.find((doorId) => {
|
||||||
|
if (!doorId) return false;
|
||||||
|
|
||||||
|
const door = this.entity.world.getEntity(doorId);
|
||||||
const doorPos = door?.get(Position);
|
const doorPos = door?.get(Position);
|
||||||
if (!doorPos) return false;
|
if (!doorPos) return false;
|
||||||
|
|
||||||
|
|
@ -56,14 +59,24 @@ export class Room extends Component<{ doors: IDoors }> {
|
||||||
const { x, y } = doorPos.state;
|
const { x, y } = doorPos.state;
|
||||||
return px === x - dx && py === y - dy;
|
return px === x - dx && py === y - dy;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!id) return undefined;
|
||||||
|
|
||||||
|
return this.entity.world.getEntity(id)?.get(Door);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDoor(direction: Direction): Door | undefined {
|
||||||
|
const id = this.doors[direction];
|
||||||
|
if (!id) return undefined;
|
||||||
|
return this.entity.world.getEntity(id)?.get(Door);
|
||||||
}
|
}
|
||||||
|
|
||||||
onEnter() {
|
onEnter() {
|
||||||
this.doors.forEach(door => door?.remove(Hidden));
|
this.doors.forEach(doorId => doorId && this.entity.world.getEntity(doorId)?.remove(Hidden));
|
||||||
}
|
}
|
||||||
|
|
||||||
onLeave() {
|
onLeave() {
|
||||||
this.doors.forEach(door => door?.add(new Hidden()));
|
this.doors.forEach(doorId => doorId && this.entity.world.getEntity(doorId)?.add(new Hidden()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,7 +123,7 @@ interface IRoomBlank {
|
||||||
|
|
||||||
const roomId = (worldX: number, worldY: number, worldZ: number) => `room_${worldX}_${worldY}_${worldZ}`;
|
const roomId = (worldX: number, worldY: number, worldZ: number) => `room_${worldX}_${worldY}_${worldZ}`;
|
||||||
|
|
||||||
const createDoor = (world: World, x: number, y: number, direction: Direction, worldX: number, worldY: number, worldZ: number): Entity => {
|
const createDoor = (world: World, x: number, y: number, direction: Direction, worldX: number, worldY: number, worldZ: number): string => {
|
||||||
const door = world.createEntity('door_*');
|
const door = world.createEntity('door_*');
|
||||||
door.add(new Position(x, y));
|
door.add(new Position(x, y));
|
||||||
door.add(new Position(worldX, worldY, worldZ), 'world');
|
door.add(new Position(worldX, worldY, worldZ), 'world');
|
||||||
|
|
@ -121,7 +134,7 @@ const createDoor = (world: World, x: number, y: number, direction: Direction, wo
|
||||||
if (spr) {
|
if (spr) {
|
||||||
door.add(new Position((-spr.width / 2) | 0, (-spr.height / 2) | 0), 'offset');
|
door.add(new Position((-spr.width / 2) | 0, (-spr.height / 2) | 0), 'offset');
|
||||||
}
|
}
|
||||||
return door;
|
return door.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateDoors = (world: World, { x, y, width, height, worldX, worldY, worldZ }: IRoomBlank): IDoors => {
|
const generateDoors = (world: World, { x, y, width, height, worldX, worldY, worldZ }: IRoomBlank): IDoors => {
|
||||||
|
|
@ -195,8 +208,12 @@ const generateItems = (world: World, { x, y, width, height, doors }: IRoomBlank)
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasObjectAt = (x: number, y: number) =>
|
const hasObjectAt = (x: number, y: number) =>
|
||||||
doors.flat().filter(x => x != null).map(getPosition).some((d) => d?.x === x && d.y === y) ||
|
doors
|
||||||
items.map(getPosition).some((i) => i?.x === x && i.y === y);
|
.filter(x => x != null)
|
||||||
|
.map((id) => getPosition(world.getEntity(id)))
|
||||||
|
.some((d) => d?.x === x && d.y === y)
|
||||||
|
|| items.map((i) => getPosition(i))
|
||||||
|
.some((i) => i?.x === x && i.y === y);
|
||||||
|
|
||||||
const oneDoor = doors.filter((d) => Boolean(d)).length === 1;
|
const oneDoor = doors.filter((d) => Boolean(d)).length === 1;
|
||||||
|
|
||||||
|
|
@ -258,9 +275,10 @@ export const getPossibleRoomsCount = (world: World) => {
|
||||||
for (const room of rooms) {
|
for (const room of rooms) {
|
||||||
const roomComp = room.get(Room);
|
const roomComp = room.get(Room);
|
||||||
roomComp?.state.doors.forEach((d) => {
|
roomComp?.state.doors.forEach((d) => {
|
||||||
const worldPos = d?.get(Position, 'world');
|
if (!d) return;
|
||||||
|
const worldPos = getPosition(world.getEntity(d) ,'world');
|
||||||
if (worldPos) {
|
if (worldPos) {
|
||||||
const id = roomId(worldPos.state.x, worldPos.state.y, worldPos.state.z);
|
const id = roomId(worldPos.x, worldPos.y, worldPos.z);
|
||||||
roomsSet.add(id);
|
roomsSet.add(id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue