Skip to content

Commit

Permalink
Item investigation and pickup implemented.
Browse files Browse the repository at this point in the history
No inventory viewing yet, though there is an inventory storing the items, items get removed from the map, and the player can investigate tiles to see what's there.
  • Loading branch information
avinashv committed Sep 4, 2023
1 parent 925348a commit 3f472e3
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 11 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,13 @@ running from 4 July 2023 till 22 August 2023.
coordinates over to some new `VIEWPORT` coordinates.
- In fact, it was this straightforward. `camera.ts` now incorporates the new constants.

### Week 5 - 1 August 2023 - Items, inventory and ranged targeting
### [Week 5](https://old.reddit.com/r/roguelikedev/comments/15fd445/roguelikedev_does_the_complete_roguelike_tutorial/) - 1 August 2023 - Items, inventory and ranged targeting

- There's a sensible de-coupling of the input from the visuals, which allows for nice things like animations in the
future.
- The tutorial implements a mouse-pointer investigation mechanic--I don't like that, so I've opted for something more
traditional. To investigate items in a tile, a player must go to the tile and press `i`, and they will get a log of
the items in that tile to the message log. This is implemented in `investigatePosition()` in `actions.ts`.

### Week 6 - 8 August 2023 - Save/load and leveling up

Expand Down Expand Up @@ -165,7 +171,7 @@ never to return. Steeling your nerves, you set forth into the dungeon.
- [x] Player has field-of-view
- [x] Spawn monsters
- [x] Players can fight monsters
- [ ] Add items
- [ ] Add items and inventory
- [ ] Add Orb of Yezriel, let the player win when getting it
- [ ] Game over when the player dies

Expand Down
51 changes: 50 additions & 1 deletion src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,60 @@ export function tryMoveEntity(game: Game, entity: Entity, delta: Vector2, absolu
}
}

/**
* Inflict damage on an entity safely while checking for multiple sources of IncomingDamage.
* @param entity Target entity
* @param amount Amount of damage to inflict
*/
export function inflictDamage(entity: Entity, amount: number) {
if (!entity.hasComponent(Components.IncomingDamage)) {
entity.addComponent(Components.IncomingDamage, { amount });
} else {
const incomingDamage = entity.getMutableComponent(Components.IncomingDamage)!;
incomingDamage.amount += amount;
}
}
}

/**
* Investigate the items in the current position and log them to the message log.
* @param game Game object
* @param position Target tile position
*/
export function investigatePosition(game: Game, position: Vector2) {
// get the list of entities at this position that are an Item
const items = game.map
.getTileContent(position)
.filter((item) => item.hasComponent(Components.Item));

if (items.length === 0) {
// if there wasn't an item, return to log
game.log.addMessage('There\'s nothing here.');
} else {
// go through and log each item to the player
for (const item of items) {
const itemName = item.getComponent(Components.Name)?.name || 'mysterious thing';
game.log.addMessage(`There's a ${ itemName } here.`);
}
}
}

/**
* Attempts to pick up an item at the given Position by the Entity.
* @param game Game object
* @param entity Target entity
* @param position Target tile position
*/
export function attemptToPickUp(game: Game, entity: Entity, position: Vector2) {
// get the list of entities at this position that are an Item
const items = game.map
.getTileContent(position)
.filter((item) => item.hasComponent(Components.Item));

// get the first
const targetItem = items[0];

// if there wasn't an item, return to log
if (targetItem === undefined) game.log.addMessage('No item to pick up here.');
// otherwise Attempt To Pickup the Item
else entity.addComponent(Components.AttemptToPickupItem, { item: targetItem });
}
3 changes: 3 additions & 0 deletions src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,16 @@ export class Name extends Component<Name> {
/**
* Renderable component
* @param glyph `Terminal.Glyph` for rendering on the canvas
* @param zIndex rendering order, highest drawn last
*/
export class Renderable extends Component<Renderable> {
// glyph can never be undefined
glyph!: Terminal.Glyph;
zIndex = 0;

static schema: ComponentSchema = {
glyph: { type: Types.Ref },
zIndex: { type: Types.Number },
};
}

Expand Down
39 changes: 34 additions & 5 deletions src/level-gen.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Entity, World } from 'ecsy';
import { CharCode, Color, Glyph, Rand } from "malwoden";
import { CharCode, Color, Glyph, Rand, Vector2 } from "malwoden";

import * as Components from './components.ts';
import * as Constants from './constants.ts';
Expand Down Expand Up @@ -29,18 +29,22 @@ export function generateLevel(config: GenerateLevelConfig): LevelData {
.createEntity()
.addComponent(Components.Player)
.addComponent(Components.Position, startRoom.center())
.addComponent(Components.Renderable, { glyph: Glyph.fromCharCode(CharCode.at, Color.Yellow) })
.addComponent(Components.Renderable, {
glyph: Glyph.fromCharCode(CharCode.at, Color.Yellow),
zIndex: 10,
})
.addComponent(Components.Viewshed, { range: Constants.PLAYER_FOV_RANGE })
.addComponent(Components.BlocksTile)
.addComponent(Components.Name, { name: 'Player' })
.addComponent(Components.Inventory)
.addComponent(Components.Attributes, {
hp: 30,
maxHp: 30,
attack: 5,
defense: 2,
});

// create monsters
// create monsters and items
// i = 1 to skip the first room
for (let i = 1; i < map.rooms.length; i++) {
const room = map.rooms[i];
Expand All @@ -63,17 +67,42 @@ export function generateLevel(config: GenerateLevelConfig): LevelData {
const creatureType = rng.nextInt(0, 100);
if (creatureType < 50) {
entity
.addComponent(Components.Renderable, { glyph: Glyph.fromCharCode(CharCode.gLower, Color.Red) })
.addComponent(Components.Renderable, {
glyph: Glyph.fromCharCode(CharCode.gLower, Color.Red),
zIndex: 9,
})
.addComponent(Components.Name, { name: 'Goblin' });
} else {
entity
.addComponent(Components.Renderable, { glyph: Glyph.fromCharCode(CharCode.oLower, Color.Red) })
.addComponent(Components.Renderable, {
glyph: Glyph.fromCharCode(CharCode.oLower, Color.Red),
zIndex: 9,
})
.addComponent(Components.Name, { name: 'Orc' });
}

// create items
if (rng.next() < 0.2) {
// get a random spot in the room
const randomX = rng.nextInt(room.v1.x, room.v2.x + 1);
const randomY = rng.nextInt(room.v1.y, room.v2.y + 1);
spawnBandage(world, { x: randomX, y: randomY });
}
}

return {
map,
player,
};
}

function spawnBandage(world: World, position: Vector2) {
world
.createEntity()
.addComponent(Components.Item)
.addComponent(Components.Position, position)
.addComponent(Components.Name, { name: 'Bandage' })
.addComponent(Components.Renderable, {
glyph: Glyph.fromCharCode(CharCode.bLower, Color.Orange),
});
}
13 changes: 13 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export class Game {
.registerSystem(Systems.MeleeCombat, this)
.registerSystem(Systems.DamageSystem, this)
.registerSystem(Systems.DeathSystem, this)
.registerSystem(Systems.InventorySystem, this)
.registerSystem(Systems.MapIndexing, this)
.registerSystem(Systems.RenderSystem, this);
}
Expand All @@ -122,6 +123,18 @@ export class Game {
break;
}

// investigate
case Input.KeyCode.I: {
Actions.investigatePosition(this, this.player.getComponent(Components.Position)!);
break;
}

// pick up an item
case Input.KeyCode.P: {
Actions.attemptToPickUp(this, this.player, this.player.getComponent(Components.Position)!);
break;
}

// arrow keys
case Input.KeyCode.LeftArrow: {
Actions.tryMoveEntity(this, this.player, { x: -1, y: 0 });
Expand Down
3 changes: 2 additions & 1 deletion src/systems/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export { EnemyAISystem } from './enemy-ai';
export { MapIndexing } from './map-indexing';
export { MeleeCombat } from './melee-combat.ts';
export { DamageSystem } from './damage-system.ts';
export { DeathSystem } from './death-system.ts';
export { DeathSystem } from './death-system.ts';
export { InventorySystem } from './inventory-system.ts';
45 changes: 45 additions & 0 deletions src/systems/inventory-system.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { System, SystemQueries, World } from 'ecsy';

import { Game } from '../main.ts';
import * as Components from '../components.ts';

export class InventorySystem extends System {
game: Game;

constructor(world: World, game: Game) {
super(world, game);
this.game = game;
}

static queries: SystemQueries = {
wantsToPickup: {
components: [Components.AttemptToPickupItem],
},
};

execute() {
const { results } = this.queries.wantsToPickup;

for (const entity of results) {
const wantsToPickup = entity.getComponent(Components.AttemptToPickupItem)!;
const inventory = entity.getComponent(Components.Inventory);
const entityName = entity.getComponent(Components.Name)?.name || 'Someone';
const itemName = wantsToPickup.item.getComponent(Components.Name)?.name || 'something';

// we must have an inventory to pick something up
if (inventory) {
// add the item to inventory
inventory.items.push(wantsToPickup.item);

// remove it from the map
wantsToPickup.item.removeComponent(Components.Position);

// log the interaction
this.game.log.addMessage(`${ entityName } picked up ${ itemName }.`);
}

// interaction is complete, remove the Attempt
entity.removeComponent(Components.AttemptToPickupItem);
}
}
}
11 changes: 9 additions & 2 deletions src/systems/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,15 @@ export class RenderSystem extends System {
}
}

// draw each entity
for (const entity of results) {
// sort entities in the tile by zIndex
const zIndexResults = results.sort((entityA, entityB) => {
const entityARender = entityA.getComponent(Components.Renderable)!;
const entityBRender = entityB.getComponent(Components.Renderable)!;
return entityARender.zIndex - entityBRender.zIndex;
});

// draw each entity from the sorted list of results by zIndex
for (const entity of zIndexResults) {
const position = entity.getComponent(Components.Position)!;
const renderable = entity.getComponent(Components.Renderable)!;

Expand Down

0 comments on commit 3f472e3

Please sign in to comment.