Allow player to switch between multiple inhabitable entities

This commit is contained in:
Vivian Lim 2020-10-17 23:57:57 -07:00
parent ac3c399598
commit fcd2b643af
8 changed files with 139 additions and 57 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
/target
savegame.json

22
Cargo.lock generated
View File

@ -247,17 +247,6 @@ dependencies = [
"libc",
]
[[package]]
name = "chapter-41-camera"
version = "0.1.0"
dependencies = [
"rltk",
"serde",
"serde_json",
"specs",
"specs-derive",
]
[[package]]
name = "cloudabi"
version = "0.0.3"
@ -806,6 +795,17 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "machine-roguelike"
version = "0.1.0"
dependencies = [
"rltk",
"serde",
"serde_json",
"specs",
"specs-derive",
]
[[package]]
name = "malloc_buf"
version = "0.0.6"

View File

@ -1,7 +1,7 @@
[package]
name = "chapter-41-camera"
name = "machine-roguelike"
version = "0.1.0"
authors = ["Herbert Wolverson <herberticus@gmail.com>"]
authors = ["Vivian Lim <vivlim@pm.me>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -22,6 +22,9 @@ pub struct Renderable {
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Player {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Inhabitable {}
#[derive(Component, ConvertSaveload, Clone)]
pub struct Viewshed {
pub visible_tiles : Vec<rltk::Point>,

View File

@ -402,9 +402,9 @@ impl State {
fn main() -> rltk::BError {
use rltk::RltkBuilder;
let mut context = RltkBuilder::simple80x50()
.with_title("Roguelike Tutorial")
.with_title("My Life as a Distributed Machine Consciousness")
.build()?;
context.with_post_scanlines(true);
//context.with_post_scanlines(true);
let mut gs = State {
ecs: World::new(),
mapgen_next_state : Some(RunState::MainMenu{ menu_selection: gui::MainMenuSelection::NewGame }),
@ -450,6 +450,7 @@ fn main() -> rltk::BError {
gs.ecs.register::<SingleActivation>();
gs.ecs.register::<BlocksVisibility>();
gs.ecs.register::<Door>();
gs.ecs.register::<Inhabitable>();
gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new());
@ -459,7 +460,7 @@ fn main() -> rltk::BError {
let player_entity = spawner::player(&mut gs.ecs, 0, 0);
gs.ecs.insert(player_entity);
gs.ecs.insert(RunState::MapGeneration{} );
gs.ecs.insert(gamelog::GameLog{ entries : vec!["Welcome to Rusty Roguelike".to_string()] });
gs.ecs.insert(gamelog::GameLog{ entries : vec!["Welcome to My Life as a Distributed Machine Consciousness".to_string()] });
gs.ecs.insert(particle_system::ParticleBuilder::new());
gs.ecs.insert(rex_assets::RexAssets::new());

View File

@ -1,6 +1,8 @@
use rltk::{VirtualKeyCode, Rltk, Point};
use rltk::{Point, Rltk, VirtualKeyCode};
use specs::prelude::*;
use std::cmp::{max, min};
use crate::Inhabitable;
use super::{Position, Player, Viewshed, State, Map, RunState, CombatStats, WantsToMelee, Item,
gamelog::GameLog, WantsToPickupItem, TileType, Monster, HungerClock, HungerState,
EntityMoved, Door, BlocksTile, BlocksVisibility, Renderable};
@ -90,6 +92,50 @@ fn get_item(ecs: &mut World) {
}
}
fn inhabit_another_body(ecs: &mut World) {
let mut player_entity = ecs.write_resource::<Entity>();
let entities = ecs.entities();
let inhabitables = ecs.read_storage::<Inhabitable>();
let mut players = ecs.write_storage::<Player>();
// pick next one
let mut next_player: Option<Entity> = None;
for (inhabitable_entity, _inhabitable, _notplayer) in (&entities, &inhabitables, !&players).join(){
match next_player {
None => { // if not set, just take the first one.
next_player = Some(inhabitable_entity);
},
Some(_) => {
if inhabitable_entity.id() > player_entity.id(){
next_player = Some(inhabitable_entity);
break;
}
}
}
}
match next_player {
None => (),
Some(e) => {
// remove Player component from old entity and add it to the new one
players.remove(*player_entity);
players.insert(e, Player {}).unwrap();
// change Player resource to the new one
//let mut player_resource = ecs.write_resource::<Entity>();
*player_entity = e;
// update player position resource to the new one
let positions = ecs.read_storage::<Position>();
let new_pos = positions.get(e).unwrap();
let mut ppos = ecs.write_resource::<Point>();
ppos.x = new_pos.x;
ppos.y = new_pos.y;
}
}
}
fn skip_turn(ecs: &mut World) -> RunState {
let player_entity = ecs.fetch::<Entity>();
let viewshed_components = ecs.read_storage::<Viewshed>();
@ -174,6 +220,9 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState {
}
}
// Inhabit another body
VirtualKeyCode::Tab => inhabit_another_body(&mut gs.ecs),
// Picking up items
VirtualKeyCode::G => get_item(&mut gs.ecs),
VirtualKeyCode::I => return RunState::ShowInventory,

View File

@ -1,6 +1,6 @@
use rltk::{ RGB, RandomNumberGenerator };
use specs::prelude::*;
use super::{CombatStats, Player, Renderable, Name, Position, Viewshed, Monster, BlocksTile, Rect, Item,
use super::{CombatStats, Player, Renderable, Name, Position, Viewshed, Monster, BlocksTile, Inhabitable, Rect, Item,
Consumable, Ranged, ProvidesHealing, InflictsDamage, AreaOfEffect, Confusion, SerializeMe,
random_table::RandomTable, EquipmentSlot, Equippable, MeleePowerBonus, DefenseBonus, HungerClock,
HungerState, ProvidesFood, MagicMapper, Hidden, EntryTrigger, SingleActivation, Map, TileType,
@ -10,30 +10,35 @@ use std::collections::HashMap;
/// Spawns the player and returns his/her entity object.
pub fn player(ecs : &mut World, player_x : i32, player_y : i32) -> Entity {
base_walker(ecs, player_x, player_y)
.with(Player{})
.build()
}
fn base_walker(ecs: &mut World, x: i32, y: i32) -> EntityBuilder {
ecs
.create_entity()
.with(Position { x: player_x, y: player_y })
.with(Position { x: x, y: y })
.with(Renderable {
glyph: rltk::to_cp437('@'),
fg: RGB::named(rltk::YELLOW),
bg: RGB::named(rltk::BLACK),
render_order: 0
})
.with(Player{})
.with(Inhabitable{})
.with(Viewshed{ visible_tiles : Vec::new(), range: 8, dirty: true })
.with(Name{name: "Player".to_string() })
.with(Name{name: "Walker".to_string() })
.with(CombatStats{ max_hp: 30, hp: 30, defense: 2, power: 5 })
.with(HungerClock{ state: HungerState::WellFed, duration: 20 })
.marked::<SimpleMarker<SerializeMe>>()
.build()
}
const MAX_MONSTERS : i32 = 4;
fn room_table(map_depth: i32) -> RandomTable {
RandomTable::new()
.add("Goblin", 10)
.add("Orc", 1 + map_depth)
.add("Walker", 10)
.add("Battery", 1 + map_depth)
.add("Health Potion", 7)
.add("Fireball Scroll", 2 + map_depth)
.add("Confusion Scroll", 2 + map_depth)
@ -99,24 +104,34 @@ pub fn spawn_entity(ecs: &mut World, spawn : &(&usize, &String)) {
std::mem::drop(map);
match spawn.1.as_ref() {
"Goblin" => goblin(ecs, x, y),
"Orc" => orc(ecs, x, y),
"Health Potion" => health_potion(ecs, x, y),
"Fireball Scroll" => fireball_scroll(ecs, x, y),
"Confusion Scroll" => confusion_scroll(ecs, x, y),
"Magic Missile Scroll" => magic_missile_scroll(ecs, x, y),
"Dagger" => dagger(ecs, x, y),
"Shield" => shield(ecs, x, y),
"Longsword" => longsword(ecs, x, y),
"Tower Shield" => tower_shield(ecs, x, y),
"Walker" => walker(ecs, x, y, 32),
"Battery" => battery(ecs, x, y, 32),
"Rations" => rations(ecs, x, y),
"Magic Mapping Scroll" => magic_mapping_scroll(ecs, x, y),
"Bear Trap" => bear_trap(ecs, x, y),
"Door" => door(ecs, x, y),
_ => {}
}
}
fn walker(ecs: &mut World, x: i32, y: i32, starting_energy: i32) {
base_walker(ecs, x, y)
.build();
}
fn battery(ecs: &mut World, x: i32, y: i32, starting_energy: i32) {
ecs.create_entity()
.with(Position{ x, y })
.with(Renderable{
glyph: rltk::to_cp437('B'),
fg: RGB::named(rltk::MAGENTA),
bg: RGB::named(rltk::BLACK),
render_order: 2
})
.with(Name{ name : "Battery".to_string() })
.with(BlocksTile{})
.marked::<SimpleMarker<SerializeMe>>()
.build();
}
fn orc(ecs: &mut World, x: i32, y: i32) { monster(ecs, x, y, rltk::to_cp437('o'), "Orc"); }
fn goblin(ecs: &mut World, x: i32, y: i32) { monster(ecs, x, y, rltk::to_cp437('g'), "Goblin"); }

View File

@ -1,5 +1,5 @@
use specs::prelude::*;
use super::{Viewshed, Position, Map, Player, Hidden, BlocksVisibility, gamelog::GameLog, Name};
use super::{Viewshed, Position, Map, Player, Inhabitable, Hidden, BlocksVisibility, gamelog::GameLog, Name};
use rltk::{field_of_view, Point};
pub struct VisibilitySystem {}
@ -11,6 +11,7 @@ impl<'a> System<'a> for VisibilitySystem {
WriteStorage<'a, Viewshed>,
ReadStorage<'a, Position>,
ReadStorage<'a, Player>,
ReadStorage<'a, Inhabitable>,
WriteStorage<'a, Hidden>,
WriteExpect<'a, rltk::RandomNumberGenerator>,
WriteExpect<'a, GameLog>,
@ -18,7 +19,7 @@ impl<'a> System<'a> for VisibilitySystem {
ReadStorage<'a, BlocksVisibility>);
fn run(&mut self, data : Self::SystemData) {
let (mut map, entities, mut viewshed, pos, player,
let (mut map, entities, mut viewshed, pos, player, inhabitable,
mut hidden, mut rng, mut log, names, blocks_visibility) = data;
map.view_blocked.clear();
@ -27,33 +28,45 @@ impl<'a> System<'a> for VisibilitySystem {
map.view_blocked.insert(idx);
}
let mut player_visible_tiles_changed = false;
for (ent,viewshed,pos) in (&entities, &mut viewshed, &pos).join() {
if viewshed.dirty {
viewshed.dirty = false;
viewshed.visible_tiles = field_of_view(Point::new(pos.x, pos.y), viewshed.range, &*map);
viewshed.visible_tiles.retain(|p| p.x >= 0 && p.x < map.width && p.y >= 0 && p.y < map.height );
// If this is the player, reveal what they can see
let _p : Option<&Player> = player.get(ent);
if let Some(_p) = _p {
for t in map.visible_tiles.iter_mut() { *t = false };
for vis in viewshed.visible_tiles.iter() {
if vis.x > 0 && vis.x < map.width-1 && vis.y > 0 && vis.y < map.height-1 {
let idx = map.xy_idx(vis.x, vis.y);
map.revealed_tiles[idx] = true;
map.visible_tiles[idx] = true;
// If this is an inhabitable viewshed that was dirtied, we need to recompute which tiles are visible to the player.
if !player_visible_tiles_changed {
let _p : Option<&Inhabitable> = inhabitable.get(ent);
if let Some(_p) = _p {
player_visible_tiles_changed = true;
}
}
}
}
// Chance to reveal hidden things
for e in map.tile_content[idx].iter() {
let maybe_hidden = hidden.get(*e);
if let Some(_maybe_hidden) = maybe_hidden {
if rng.roll_dice(1,24)==1 {
let name = names.get(*e);
if let Some(name) = name {
log.entries.push(format!("You spotted a {}.", &name.name));
}
hidden.remove(*e);
if player_visible_tiles_changed {
// Clear all map visible tiles
for t in map.visible_tiles.iter_mut() { *t = false };
for (ent,viewshed,pos,_inhabitable) in (&entities, &mut viewshed, &pos, &inhabitable).join() {
for vis in viewshed.visible_tiles.iter() {
if vis.x > 0 && vis.x < map.width-1 && vis.y > 0 && vis.y < map.height-1 {
let idx = map.xy_idx(vis.x, vis.y);
map.revealed_tiles[idx] = true;
map.visible_tiles[idx] = true;
// Chance to reveal hidden things
for e in map.tile_content[idx].iter() {
let maybe_hidden = hidden.get(*e);
if let Some(_maybe_hidden) = maybe_hidden {
if rng.roll_dice(1,24)==1 {
let name = names.get(*e);
if let Some(name) = name {
log.entries.push(format!("You spotted a {}.", &name.name));
}
hidden.remove(*e);
}
}
}