Merge branch 'gamelog_module'
This commit is contained in:
commit
a780d13733
16 changed files with 295 additions and 80 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2277,6 +2277,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bracket-lib 0.8.7 (git+https://github.com/amethyst/bracket-lib.git?rev=851f6f08675444fb6fa088b9e67bee9fd75554c6)",
|
"bracket-lib 0.8.7 (git+https://github.com/amethyst/bracket-lib.git?rev=851f6f08675444fb6fa088b9e67bee9fd75554c6)",
|
||||||
"criterion",
|
"criterion",
|
||||||
|
"lazy_static",
|
||||||
"rltk",
|
"rltk",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ specs = { version = "0.16.1", features = ["serde"] }
|
||||||
specs-derive = "0.4.1"
|
specs-derive = "0.4.1"
|
||||||
serde = { version = "1.0.93", features = ["derive"]}
|
serde = { version = "1.0.93", features = ["derive"]}
|
||||||
serde_json = "1.0.39"
|
serde_json = "1.0.39"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { version = "^0.5" }
|
criterion = { version = "^0.5" }
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use specs::error::NoError;
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
use specs::saveload::{ConvertSaveload, Marker};
|
use specs::saveload::{ConvertSaveload, Marker};
|
||||||
use specs_derive::*;
|
use specs_derive::*;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
// Serialization helper code. We need to implement ConvertSaveload for each type that contains an
|
// Serialization helper code. We need to implement ConvertSaveload for each type that contains an
|
||||||
// Entity.
|
// Entity.
|
||||||
|
|
@ -12,6 +13,8 @@ pub struct SerializeMe;
|
||||||
#[derive(Component, Serialize, Deserialize, Clone)]
|
#[derive(Component, Serialize, Deserialize, Clone)]
|
||||||
pub struct SerializationHelper {
|
pub struct SerializationHelper {
|
||||||
pub map: super::map::Map,
|
pub map: super::map::Map,
|
||||||
|
pub log: Vec<Vec<crate::gamelog::LogFragment>>,
|
||||||
|
pub events: HashMap<String, i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, ConvertSaveload, Clone)]
|
#[derive(Component, ConvertSaveload, Clone)]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use super::{gamelog::GameLog, CombatStats, Entities, Item, Map, Name, Player, Position, SufferDamage};
|
use super::{gamelog, CombatStats, Entities, Item, Map, Name, Player, Position, SufferDamage};
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
||||||
pub struct DamageSystem {}
|
pub struct DamageSystem {}
|
||||||
|
|
@ -37,7 +37,6 @@ pub fn delete_the_dead(ecs: &mut World) {
|
||||||
let names = ecs.read_storage::<Name>();
|
let names = ecs.read_storage::<Name>();
|
||||||
let items = ecs.read_storage::<Item>();
|
let items = ecs.read_storage::<Item>();
|
||||||
let entities = ecs.entities();
|
let entities = ecs.entities();
|
||||||
let mut log = ecs.write_resource::<GameLog>();
|
|
||||||
for (entity, stats) in (&entities, &combat_stats).join() {
|
for (entity, stats) in (&entities, &combat_stats).join() {
|
||||||
if stats.hp < 1 {
|
if stats.hp < 1 {
|
||||||
let player = players.get(entity);
|
let player = players.get(entity);
|
||||||
|
|
@ -47,9 +46,19 @@ pub fn delete_the_dead(ecs: &mut World) {
|
||||||
if let Some(victim_name) = victim_name {
|
if let Some(victim_name) = victim_name {
|
||||||
let item = items.get(entity);
|
let item = items.get(entity);
|
||||||
if let Some(_item) = item {
|
if let Some(_item) = item {
|
||||||
log.entries.push(format!("{} was destroyed!", &victim_name.name));
|
gamelog::Logger::new()
|
||||||
|
.append("The")
|
||||||
|
.npc_name(&victim_name.name)
|
||||||
|
.colour(rltk::WHITE)
|
||||||
|
.append("was destroyed.")
|
||||||
|
.log();
|
||||||
} else {
|
} else {
|
||||||
log.entries.push(format!("The {} died!", &victim_name.name));
|
gamelog::Logger::new()
|
||||||
|
.append("The")
|
||||||
|
.npc_name(&victim_name.name)
|
||||||
|
.colour(rltk::WHITE)
|
||||||
|
.append("died.")
|
||||||
|
.log();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dead.push(entity)
|
dead.push(entity)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
pub struct GameLog {
|
|
||||||
pub entries: Vec<String>,
|
|
||||||
}
|
|
||||||
42
src/gamelog/builder.rs
Normal file
42
src/gamelog/builder.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
use super::{append_entry, LogFragment};
|
||||||
|
use rltk::prelude::*;
|
||||||
|
|
||||||
|
pub struct Logger {
|
||||||
|
current_colour: RGB,
|
||||||
|
fragments: Vec<LogFragment>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Logger {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Logger { current_colour: RGB::named(rltk::WHITE), fragments: Vec::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn colour(mut self, colour: (u8, u8, u8)) -> Self {
|
||||||
|
self.current_colour = RGB::named(colour);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append<T: ToString>(mut self, text: T) -> Self {
|
||||||
|
self.fragments.push(LogFragment { colour: self.current_colour, text: text.to_string() });
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log(self) {
|
||||||
|
return append_entry(self.fragments);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn npc_name<T: ToString>(mut self, text: T) -> Self {
|
||||||
|
self.fragments.push(LogFragment { colour: RGB::named(rltk::YELLOW), text: text.to_string() });
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn item_name<T: ToString>(mut self, text: T) -> Self {
|
||||||
|
self.fragments.push(LogFragment { colour: RGB::named(rltk::CYAN), text: text.to_string() });
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn damage(mut self, damage: i32) -> Self {
|
||||||
|
self.fragments.push(LogFragment { colour: RGB::named(rltk::RED), text: format!("{}", damage).to_string() });
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/gamelog/events.rs
Normal file
43
src/gamelog/events.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref EVENTS: Mutex<HashMap<String, i32>> = Mutex::new(HashMap::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_events() {
|
||||||
|
EVENTS.lock().unwrap().clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn record_event<T: ToString>(event: T, n: i32) {
|
||||||
|
let event_name = event.to_string();
|
||||||
|
let mut events_lock = EVENTS.lock();
|
||||||
|
let mut events = events_lock.as_mut().unwrap();
|
||||||
|
if let Some(e) = events.get_mut(&event_name) {
|
||||||
|
*e += n;
|
||||||
|
} else {
|
||||||
|
events.insert(event_name, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_event_count<T: ToString>(event: T) -> i32 {
|
||||||
|
let event_name = event.to_string();
|
||||||
|
let events_lock = EVENTS.lock();
|
||||||
|
let events = events_lock.unwrap();
|
||||||
|
if let Some(e) = events.get(&event_name) {
|
||||||
|
*e
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clone_events() -> HashMap<String, i32> {
|
||||||
|
EVENTS.lock().unwrap().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_events(events: HashMap<String, i32>) {
|
||||||
|
EVENTS.lock().unwrap().clear();
|
||||||
|
events.iter().for_each(|(k, v)| {
|
||||||
|
EVENTS.lock().unwrap().insert(k.to_string(), *v);
|
||||||
|
});
|
||||||
|
}
|
||||||
43
src/gamelog/logstore.rs
Normal file
43
src/gamelog/logstore.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
use super::LogFragment;
|
||||||
|
use rltk::prelude::*;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref LOG: Mutex<Vec<Vec<LogFragment>>> = Mutex::new(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn append_fragment(fragment: LogFragment) {
|
||||||
|
LOG.lock().unwrap().push(vec![fragment]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append_entry(fragments: Vec<LogFragment>) {
|
||||||
|
LOG.lock().unwrap().push(fragments);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_log() {
|
||||||
|
LOG.lock().unwrap().clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_display() -> TextBuilder {
|
||||||
|
let mut buf = TextBuilder::empty();
|
||||||
|
|
||||||
|
LOG.lock().unwrap().iter().rev().take(12).for_each(|log| {
|
||||||
|
log.iter().for_each(|frag| {
|
||||||
|
buf.fg(frag.colour);
|
||||||
|
buf.line_wrap(&frag.text);
|
||||||
|
});
|
||||||
|
buf.ln();
|
||||||
|
});
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clone_log() -> Vec<Vec<crate::gamelog::LogFragment>> {
|
||||||
|
return LOG.lock().unwrap().clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn restore_log(log: &mut Vec<Vec<crate::gamelog::LogFragment>>) {
|
||||||
|
LOG.lock().unwrap().clear();
|
||||||
|
LOG.lock().unwrap().append(log);
|
||||||
|
}
|
||||||
16
src/gamelog/mod.rs
Normal file
16
src/gamelog/mod.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
use rltk::prelude::*;
|
||||||
|
|
||||||
|
mod builder;
|
||||||
|
pub use builder::*;
|
||||||
|
mod logstore;
|
||||||
|
use logstore::*;
|
||||||
|
pub use logstore::{clear_log, clone_log, log_display, restore_log};
|
||||||
|
mod events;
|
||||||
|
pub use events::*;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
pub struct LogFragment {
|
||||||
|
pub colour: RGB,
|
||||||
|
pub text: String,
|
||||||
|
}
|
||||||
28
src/gui.rs
28
src/gui.rs
|
|
@ -1,8 +1,8 @@
|
||||||
use super::{
|
use super::{
|
||||||
gamelog::GameLog, rex_assets::RexAssets, CombatStats, InBackpack, Map, Name, Player, Point, Position, RunState,
|
gamelog, rex_assets::RexAssets, CombatStats, InBackpack, Map, Name, Player, Point, Position, RunState, State,
|
||||||
State, Viewshed,
|
Viewshed,
|
||||||
};
|
};
|
||||||
use rltk::{Rltk, VirtualKeyCode, RGB};
|
use rltk::{Rltk, TextBlock, VirtualKeyCode, RGB};
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
||||||
pub fn draw_ui(ecs: &World, ctx: &mut Rltk) {
|
pub fn draw_ui(ecs: &World, ctx: &mut Rltk) {
|
||||||
|
|
@ -18,19 +18,23 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render message log
|
// Render message log
|
||||||
let log = ecs.fetch::<GameLog>();
|
let mut block = TextBlock::new(1, 44, 78, 5);
|
||||||
let mut y = 44;
|
let _ = block.print(&gamelog::log_display());
|
||||||
for s in log.entries.iter().rev() {
|
block.render(&mut rltk::BACKEND_INTERNAL.lock().consoles[0].console);
|
||||||
if y < 49 {
|
|
||||||
ctx.print(2, y, s);
|
|
||||||
}
|
|
||||||
y += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render depth
|
// Render depth
|
||||||
let map = ecs.fetch::<Map>();
|
let map = ecs.fetch::<Map>();
|
||||||
let depth = format!(" D{} ", map.depth);
|
let depth = format!(" D{} ", map.depth);
|
||||||
ctx.print_color(74, 43, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), &depth);
|
ctx.print_color_right(78, 43, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), &depth);
|
||||||
|
|
||||||
|
// Render turn
|
||||||
|
ctx.print_color_right(
|
||||||
|
78,
|
||||||
|
49,
|
||||||
|
RGB::named(rltk::YELLOW),
|
||||||
|
RGB::named(rltk::BLACK),
|
||||||
|
&format!(" T{} ", crate::gamelog::get_event_count("Turn")),
|
||||||
|
);
|
||||||
|
|
||||||
// Render mouse cursor
|
// Render mouse cursor
|
||||||
let mouse_pos = ctx.mouse_pos();
|
let mouse_pos = ctx.mouse_pos();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use super::{
|
use super::{
|
||||||
gamelog::GameLog, CombatStats, Confusion, Consumable, Cursed, Destructible, InBackpack, InflictsDamage,
|
gamelog, CombatStats, Confusion, Consumable, Cursed, Destructible, InBackpack, InflictsDamage, MagicMapper, Map,
|
||||||
MagicMapper, Map, Name, ParticleBuilder, Point, Position, ProvidesHealing, RunState, SufferDamage, WantsToDropItem,
|
Name, ParticleBuilder, Point, Position, ProvidesHealing, RunState, SufferDamage, WantsToDropItem,
|
||||||
WantsToPickupItem, WantsToUseItem, AOE, DEFAULT_PARTICLE_LIFETIME, LONG_PARTICLE_LIFETIME,
|
WantsToPickupItem, WantsToUseItem, AOE, DEFAULT_PARTICLE_LIFETIME, LONG_PARTICLE_LIFETIME,
|
||||||
};
|
};
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
@ -11,7 +11,6 @@ impl<'a> System<'a> for ItemCollectionSystem {
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
type SystemData = (
|
type SystemData = (
|
||||||
ReadExpect<'a, Entity>,
|
ReadExpect<'a, Entity>,
|
||||||
WriteExpect<'a, GameLog>,
|
|
||||||
WriteStorage<'a, WantsToPickupItem>,
|
WriteStorage<'a, WantsToPickupItem>,
|
||||||
WriteStorage<'a, Position>,
|
WriteStorage<'a, Position>,
|
||||||
ReadStorage<'a, Name>,
|
ReadStorage<'a, Name>,
|
||||||
|
|
@ -19,14 +18,17 @@ impl<'a> System<'a> for ItemCollectionSystem {
|
||||||
);
|
);
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
fn run(&mut self, data: Self::SystemData) {
|
||||||
let (player_entity, mut gamelog, mut wants_pickup, mut positions, names, mut backpack) = data;
|
let (player_entity, mut wants_pickup, mut positions, names, mut backpack) = data;
|
||||||
|
|
||||||
for pickup in wants_pickup.join() {
|
for pickup in wants_pickup.join() {
|
||||||
positions.remove(pickup.item);
|
positions.remove(pickup.item);
|
||||||
backpack.insert(pickup.item, InBackpack { owner: pickup.collected_by }).expect("Unable to pickup item.");
|
backpack.insert(pickup.item, InBackpack { owner: pickup.collected_by }).expect("Unable to pickup item.");
|
||||||
|
|
||||||
if pickup.collected_by == *player_entity {
|
if pickup.collected_by == *player_entity {
|
||||||
gamelog.entries.push(format!("You pick up the {}.", names.get(pickup.item).unwrap().name));
|
gamelog::Logger::new()
|
||||||
|
.append("You pick up the")
|
||||||
|
.item_name(format!("{}.", &names.get(pickup.item).unwrap().name))
|
||||||
|
.log();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,7 +41,6 @@ impl<'a> System<'a> for ItemUseSystem {
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
type SystemData = (
|
type SystemData = (
|
||||||
ReadExpect<'a, Entity>,
|
ReadExpect<'a, Entity>,
|
||||||
WriteExpect<'a, GameLog>,
|
|
||||||
ReadExpect<'a, Map>,
|
ReadExpect<'a, Map>,
|
||||||
Entities<'a>,
|
Entities<'a>,
|
||||||
WriteStorage<'a, WantsToUseItem>,
|
WriteStorage<'a, WantsToUseItem>,
|
||||||
|
|
@ -62,7 +63,6 @@ impl<'a> System<'a> for ItemUseSystem {
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
fn run(&mut self, data: Self::SystemData) {
|
||||||
let (
|
let (
|
||||||
player_entity,
|
player_entity,
|
||||||
mut gamelog,
|
|
||||||
map,
|
map,
|
||||||
entities,
|
entities,
|
||||||
mut wants_to_use,
|
mut wants_to_use,
|
||||||
|
|
@ -89,7 +89,7 @@ impl<'a> System<'a> for ItemUseSystem {
|
||||||
|
|
||||||
let is_cursed = cursed_items.get(wants_to_use.item);
|
let is_cursed = cursed_items.get(wants_to_use.item);
|
||||||
|
|
||||||
gamelog.entries.push(format!("You use the {}.", item_being_used.name));
|
gamelog::Logger::new().append("You use the").item_name(format!("{}.", &item_being_used.name)).log();
|
||||||
|
|
||||||
// TARGETING
|
// TARGETING
|
||||||
let mut targets: Vec<Entity> = Vec::new();
|
let mut targets: Vec<Entity> = Vec::new();
|
||||||
|
|
@ -117,7 +117,12 @@ impl<'a> System<'a> for ItemUseSystem {
|
||||||
if let Some(pos) = pos {
|
if let Some(pos) = pos {
|
||||||
target = Point::new(pos.x, pos.y);
|
target = Point::new(pos.x, pos.y);
|
||||||
}
|
}
|
||||||
gamelog.entries.push(format!("The {} disobeys!", item_being_used.name));
|
gamelog::Logger::new()
|
||||||
|
.append("The")
|
||||||
|
.item_name(&item_being_used.name)
|
||||||
|
.colour(rltk::WHITE)
|
||||||
|
.append("disobeys!")
|
||||||
|
.log();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// AOE
|
// AOE
|
||||||
|
|
@ -153,7 +158,13 @@ impl<'a> System<'a> for ItemUseSystem {
|
||||||
if let Some(stats) = stats {
|
if let Some(stats) = stats {
|
||||||
stats.hp = i32::min(stats.max_hp, stats.hp + heal.amount);
|
stats.hp = i32::min(stats.max_hp, stats.hp + heal.amount);
|
||||||
if entity == *player_entity {
|
if entity == *player_entity {
|
||||||
gamelog.entries.push(format!("Quaffing, you heal {} hp.", heal.amount));
|
gamelog::Logger::new()
|
||||||
|
.append("Quaffing, you heal")
|
||||||
|
.colour(rltk::GREEN)
|
||||||
|
.append(heal.amount)
|
||||||
|
.colour(rltk::WHITE)
|
||||||
|
.append("hit points.")
|
||||||
|
.log();
|
||||||
}
|
}
|
||||||
let pos = positions.get(entity);
|
let pos = positions.get(entity);
|
||||||
if let Some(pos) = pos {
|
if let Some(pos) = pos {
|
||||||
|
|
@ -194,14 +205,25 @@ impl<'a> System<'a> for ItemUseSystem {
|
||||||
None => {
|
None => {
|
||||||
SufferDamage::new_damage(&mut suffer_damage, *mob, damage.amount);
|
SufferDamage::new_damage(&mut suffer_damage, *mob, damage.amount);
|
||||||
if entity == *player_entity {
|
if entity == *player_entity {
|
||||||
gamelog.entries.push(format!(
|
gamelog::Logger::new()
|
||||||
"{} takes {} damage from the {}!",
|
.append("The")
|
||||||
entity_name.name, damage.amount, item_being_used.name
|
.npc_name(&entity_name.name)
|
||||||
));
|
.colour(rltk::WHITE)
|
||||||
|
.append("takes")
|
||||||
|
.damage(damage.amount)
|
||||||
|
.colour(rltk::WHITE)
|
||||||
|
.append("damage from the")
|
||||||
|
.item_name(format!("{}.", &item_being_used.name))
|
||||||
|
.log();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(_destructible) => {
|
Some(_destructible) => {
|
||||||
gamelog.entries.push(format!("{} is destroyed!", entity_name.name));
|
gamelog::Logger::new()
|
||||||
|
.append("The")
|
||||||
|
.item_name(&entity_name.name)
|
||||||
|
.colour(rltk::WHITE)
|
||||||
|
.append("is destroyed!")
|
||||||
|
.log();
|
||||||
entities.delete(*mob).expect("Delete failed");
|
entities.delete(*mob).expect("Delete failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -237,11 +259,19 @@ impl<'a> System<'a> for ItemUseSystem {
|
||||||
used_item = true;
|
used_item = true;
|
||||||
match is_cursed {
|
match is_cursed {
|
||||||
None => {
|
None => {
|
||||||
gamelog.entries.push("You feel a sense of acuity to your surroundings.".to_string());
|
gamelog::Logger::new()
|
||||||
|
.append("You feel")
|
||||||
|
.colour(rltk::GREEN)
|
||||||
|
.append("a sense of acuity towards your surroundings.")
|
||||||
|
.log();
|
||||||
*runstate = RunState::MagicMapReveal { row: 0, cursed: false };
|
*runstate = RunState::MagicMapReveal { row: 0, cursed: false };
|
||||||
}
|
}
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
gamelog.entries.push("You forget where you just were!".to_string());
|
gamelog::Logger::new()
|
||||||
|
.append("You")
|
||||||
|
.colour(rltk::RED)
|
||||||
|
.append("forget where you last were.")
|
||||||
|
.log();
|
||||||
*runstate = RunState::MagicMapReveal { row: 0, cursed: true };
|
*runstate = RunState::MagicMapReveal { row: 0, cursed: true };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -269,7 +299,6 @@ impl<'a> System<'a> for ItemDropSystem {
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
type SystemData = (
|
type SystemData = (
|
||||||
ReadExpect<'a, Entity>,
|
ReadExpect<'a, Entity>,
|
||||||
WriteExpect<'a, GameLog>,
|
|
||||||
Entities<'a>,
|
Entities<'a>,
|
||||||
WriteStorage<'a, WantsToDropItem>,
|
WriteStorage<'a, WantsToDropItem>,
|
||||||
ReadStorage<'a, Name>,
|
ReadStorage<'a, Name>,
|
||||||
|
|
@ -278,7 +307,7 @@ impl<'a> System<'a> for ItemDropSystem {
|
||||||
);
|
);
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
fn run(&mut self, data: Self::SystemData) {
|
||||||
let (player_entity, mut gamelog, entities, mut wants_drop, names, mut positions, mut backpack) = data;
|
let (player_entity, entities, mut wants_drop, names, mut positions, mut backpack) = data;
|
||||||
|
|
||||||
for (entity, to_drop) in (&entities, &wants_drop).join() {
|
for (entity, to_drop) in (&entities, &wants_drop).join() {
|
||||||
let mut dropper_pos: Position = Position { x: 0, y: 0 };
|
let mut dropper_pos: Position = Position { x: 0, y: 0 };
|
||||||
|
|
@ -293,7 +322,10 @@ impl<'a> System<'a> for ItemDropSystem {
|
||||||
backpack.remove(to_drop.item);
|
backpack.remove(to_drop.item);
|
||||||
|
|
||||||
if entity == *player_entity {
|
if entity == *player_entity {
|
||||||
gamelog.entries.push(format!("You drop the {}.", names.get(to_drop.item).unwrap().name));
|
gamelog::Logger::new()
|
||||||
|
.append("You drop the")
|
||||||
|
.item_name(format!("{}.", &names.get(to_drop.item).unwrap().name))
|
||||||
|
.log();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
23
src/main.rs
23
src/main.rs
|
|
@ -32,6 +32,8 @@ mod particle_system;
|
||||||
use particle_system::{ParticleBuilder, DEFAULT_PARTICLE_LIFETIME, LONG_PARTICLE_LIFETIME};
|
use particle_system::{ParticleBuilder, DEFAULT_PARTICLE_LIFETIME, LONG_PARTICLE_LIFETIME};
|
||||||
mod random_table;
|
mod random_table;
|
||||||
mod rex_assets;
|
mod rex_assets;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate lazy_static;
|
||||||
|
|
||||||
// Embedded resources for use in wasm build
|
// Embedded resources for use in wasm build
|
||||||
rltk::embedded_resource!(TERMINAL8X8, "../resources/terminal8x8.jpg");
|
rltk::embedded_resource!(TERMINAL8X8, "../resources/terminal8x8.jpg");
|
||||||
|
|
@ -155,8 +157,7 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify player, restore health up to a point.
|
// Notify player, restore health up to a point.
|
||||||
let mut gamelog = self.ecs.fetch_mut::<gamelog::GameLog>();
|
gamelog::Logger::new().append("You descend the stairwell, and take a moment to gather your strength.").log();
|
||||||
gamelog.entries.push("You descend the stairwell, and take a moment to recover your strength.".to_string());
|
|
||||||
let mut player_health_store = self.ecs.write_storage::<CombatStats>();
|
let mut player_health_store = self.ecs.write_storage::<CombatStats>();
|
||||||
let player_health = player_health_store.get_mut(*player_entity);
|
let player_health = player_health_store.get_mut(*player_entity);
|
||||||
if let Some(player_health) = player_health {
|
if let Some(player_health) = player_health {
|
||||||
|
|
@ -213,6 +214,9 @@ impl GameState for State {
|
||||||
}
|
}
|
||||||
RunState::AwaitingInput => {
|
RunState::AwaitingInput => {
|
||||||
new_runstate = player_input(self, ctx);
|
new_runstate = player_input(self, ctx);
|
||||||
|
if new_runstate != RunState::AwaitingInput {
|
||||||
|
gamelog::record_event("Turn", 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
RunState::PlayerTurn => {
|
RunState::PlayerTurn => {
|
||||||
self.run_systems();
|
self.run_systems();
|
||||||
|
|
@ -375,7 +379,7 @@ fn main() -> rltk::BError {
|
||||||
.with_resource_path("resources/")
|
.with_resource_path("resources/")
|
||||||
.with_font("terminal8x8.jpg", 8, 8)
|
.with_font("terminal8x8.jpg", 8, 8)
|
||||||
.with_simple_console(DISPLAYWIDTH, DISPLAYHEIGHT, "terminal8x8.jpg")
|
.with_simple_console(DISPLAYWIDTH, DISPLAYHEIGHT, "terminal8x8.jpg")
|
||||||
.with_simple_console_no_bg(DISPLAYWIDTH, DISPLAYHEIGHT, "terminal8x8.jpg")
|
//.with_simple_console_no_bg(DISPLAYWIDTH, DISPLAYHEIGHT, "terminal8x8.jpg")
|
||||||
.build()?;
|
.build()?;
|
||||||
context.with_post_scanlines(false);
|
context.with_post_scanlines(false);
|
||||||
//context.screen_burn_color(RGB::named((150, 255, 255)));
|
//context.screen_burn_color(RGB::named((150, 255, 255)));
|
||||||
|
|
@ -429,9 +433,16 @@ fn main() -> rltk::BError {
|
||||||
gs.ecs.insert(map);
|
gs.ecs.insert(map);
|
||||||
gs.ecs.insert(Point::new(player_x, player_y));
|
gs.ecs.insert(Point::new(player_x, player_y));
|
||||||
gs.ecs.insert(player_entity);
|
gs.ecs.insert(player_entity);
|
||||||
gs.ecs.insert(gamelog::GameLog {
|
|
||||||
entries: vec!["<pretend i wrote a paragraph explaining why you're here>".to_string()],
|
gamelog::clear_log();
|
||||||
});
|
gamelog::clear_events();
|
||||||
|
gamelog::Logger::new()
|
||||||
|
.append("Welcome!")
|
||||||
|
.colour(rltk::CYAN)
|
||||||
|
.append("(")
|
||||||
|
.append("pretend i wrote a paragraph explaining why you're here")
|
||||||
|
.append(")")
|
||||||
|
.log();
|
||||||
gs.ecs.insert(RunState::MainMenu { menu_selection: gui::MainMenuSelection::NewGame });
|
gs.ecs.insert(RunState::MainMenu { menu_selection: gui::MainMenuSelection::NewGame });
|
||||||
gs.ecs.insert(particle_system::ParticleBuilder::new());
|
gs.ecs.insert(particle_system::ParticleBuilder::new());
|
||||||
gs.ecs.insert(rex_assets::RexAssets::new());
|
gs.ecs.insert(rex_assets::RexAssets::new());
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use super::{gamelog::GameLog, CombatStats, Name, ParticleBuilder, Position, SufferDamage, WantsToMelee};
|
use super::{gamelog, CombatStats, Name, ParticleBuilder, Position, SufferDamage, WantsToMelee};
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
||||||
pub struct MeleeCombatSystem {}
|
pub struct MeleeCombatSystem {}
|
||||||
|
|
@ -6,7 +6,6 @@ pub struct MeleeCombatSystem {}
|
||||||
impl<'a> System<'a> for MeleeCombatSystem {
|
impl<'a> System<'a> for MeleeCombatSystem {
|
||||||
type SystemData = (
|
type SystemData = (
|
||||||
Entities<'a>,
|
Entities<'a>,
|
||||||
WriteExpect<'a, GameLog>,
|
|
||||||
WriteStorage<'a, WantsToMelee>,
|
WriteStorage<'a, WantsToMelee>,
|
||||||
ReadStorage<'a, Name>,
|
ReadStorage<'a, Name>,
|
||||||
ReadStorage<'a, CombatStats>,
|
ReadStorage<'a, CombatStats>,
|
||||||
|
|
@ -16,16 +15,8 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
||||||
);
|
);
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
fn run(&mut self, data: Self::SystemData) {
|
||||||
let (
|
let (entities, mut wants_melee, names, combat_stats, mut inflict_damage, mut particle_builder, positions) =
|
||||||
entities,
|
data;
|
||||||
mut log,
|
|
||||||
mut wants_melee,
|
|
||||||
names,
|
|
||||||
combat_stats,
|
|
||||||
mut inflict_damage,
|
|
||||||
mut particle_builder,
|
|
||||||
positions,
|
|
||||||
) = data;
|
|
||||||
|
|
||||||
for (_entity, wants_melee, name, stats) in (&entities, &wants_melee, &names, &combat_stats).join() {
|
for (_entity, wants_melee, name, stats) in (&entities, &wants_melee, &names, &combat_stats).join() {
|
||||||
if stats.hp <= 0 {
|
if stats.hp <= 0 {
|
||||||
|
|
@ -51,9 +42,23 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
||||||
let damage = i32::max(0, stats.power - target_stats.defence);
|
let damage = i32::max(0, stats.power - target_stats.defence);
|
||||||
|
|
||||||
if damage == 0 {
|
if damage == 0 {
|
||||||
log.entries.push(format!("{} is unable to hurt {}.", &name.name, &target_name.name));
|
gamelog::Logger::new()
|
||||||
|
.append("The")
|
||||||
|
.npc_name(&name.name)
|
||||||
|
.colour(rltk::WHITE)
|
||||||
|
.append("attempts to strike")
|
||||||
|
.npc_name(&target_name.name)
|
||||||
|
.colour(rltk::WHITE)
|
||||||
|
.append("- but fails.")
|
||||||
|
.log();
|
||||||
} else {
|
} else {
|
||||||
log.entries.push(format!("{} hits {} for {} damage.", &name.name, &target_name.name, damage));
|
gamelog::Logger::new() // <name> hits the <name>!
|
||||||
|
.append("The")
|
||||||
|
.npc_name(&name.name)
|
||||||
|
.colour(rltk::WHITE)
|
||||||
|
.append("hits the")
|
||||||
|
.npc_name(format!("{}.", &target_name.name))
|
||||||
|
.log();
|
||||||
SufferDamage::new_damage(&mut inflict_damage, wants_melee.target, damage);
|
SufferDamage::new_damage(&mut inflict_damage, wants_melee.target, damage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
use super::{
|
use super::{gamelog, Confusion, Map, Monster, Name, ParticleBuilder, Position, RunState, Viewshed, WantsToMelee};
|
||||||
gamelog::GameLog, Confusion, Map, Monster, Name, ParticleBuilder, Position, RunState, Viewshed, WantsToMelee,
|
|
||||||
};
|
|
||||||
use rltk::Point;
|
use rltk::Point;
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
||||||
|
|
@ -10,7 +8,6 @@ impl<'a> System<'a> for MonsterAI {
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
type SystemData = (
|
type SystemData = (
|
||||||
WriteExpect<'a, Map>,
|
WriteExpect<'a, Map>,
|
||||||
WriteExpect<'a, GameLog>,
|
|
||||||
ReadExpect<'a, Point>,
|
ReadExpect<'a, Point>,
|
||||||
ReadExpect<'a, Entity>,
|
ReadExpect<'a, Entity>,
|
||||||
ReadExpect<'a, RunState>,
|
ReadExpect<'a, RunState>,
|
||||||
|
|
@ -27,7 +24,6 @@ impl<'a> System<'a> for MonsterAI {
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
fn run(&mut self, data: Self::SystemData) {
|
||||||
let (
|
let (
|
||||||
mut map,
|
mut map,
|
||||||
mut gamelog,
|
|
||||||
player_pos,
|
player_pos,
|
||||||
player_entity,
|
player_entity,
|
||||||
runstate,
|
runstate,
|
||||||
|
|
@ -57,11 +53,15 @@ impl<'a> System<'a> for MonsterAI {
|
||||||
let mut glyph = rltk::to_cp437('?');
|
let mut glyph = rltk::to_cp437('?');
|
||||||
if i_am_confused.turns < 1 {
|
if i_am_confused.turns < 1 {
|
||||||
confused.remove(entity);
|
confused.remove(entity);
|
||||||
gamelog.entries.push(format!("{} snaps out of its confusion!", entity_name.name));
|
gamelog::Logger::new()
|
||||||
|
.npc_name(&entity_name.name)
|
||||||
|
.colour(rltk::WHITE)
|
||||||
|
.append("snaps out of it.")
|
||||||
|
.log();
|
||||||
fg = rltk::RGB::named(rltk::MEDIUMSLATEBLUE);
|
fg = rltk::RGB::named(rltk::MEDIUMSLATEBLUE);
|
||||||
glyph = rltk::to_cp437('!');
|
glyph = rltk::to_cp437('!');
|
||||||
} else {
|
} else {
|
||||||
gamelog.entries.push(format!("{} is confused.", entity_name.name));
|
gamelog::Logger::new().npc_name(&entity_name.name).colour(rltk::WHITE).append("is confused.").log();
|
||||||
}
|
}
|
||||||
particle_builder.request(pos.x, pos.y, fg, rltk::RGB::named(rltk::BLACK), glyph, 200.0);
|
particle_builder.request(pos.x, pos.y, fg, rltk::RGB::named(rltk::BLACK), glyph, 200.0);
|
||||||
can_act = false;
|
can_act = false;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use super::{
|
use super::{
|
||||||
gamelog::GameLog, CombatStats, Item, Map, Monster, Name, Player, Position, RunState, State, TileType, Viewshed,
|
gamelog, CombatStats, Item, Map, Monster, Name, Player, Position, RunState, State, TileType, Viewshed,
|
||||||
WantsToMelee, WantsToPickupItem, MAPHEIGHT, MAPWIDTH,
|
WantsToMelee, WantsToPickupItem, MAPHEIGHT, MAPWIDTH,
|
||||||
};
|
};
|
||||||
use rltk::{Point, RandomNumberGenerator, Rltk, VirtualKeyCode};
|
use rltk::{Point, RandomNumberGenerator, Rltk, VirtualKeyCode};
|
||||||
|
|
@ -48,8 +48,7 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) {
|
||||||
}
|
}
|
||||||
if tile_content != "You see " {
|
if tile_content != "You see " {
|
||||||
tile_content.push_str(".");
|
tile_content.push_str(".");
|
||||||
let mut gamelog = ecs.write_resource::<GameLog>();
|
gamelog::Logger::new().append(tile_content).log()
|
||||||
gamelog.entries.push(tile_content);
|
|
||||||
}
|
}
|
||||||
pos.x = min((MAPWIDTH as i32) - 1, max(0, pos.x + delta_x));
|
pos.x = min((MAPWIDTH as i32) - 1, max(0, pos.x + delta_x));
|
||||||
pos.y = min((MAPHEIGHT as i32) - 1, max(0, pos.y + delta_y));
|
pos.y = min((MAPHEIGHT as i32) - 1, max(0, pos.y + delta_y));
|
||||||
|
|
@ -67,7 +66,6 @@ fn get_item(ecs: &mut World) {
|
||||||
let entities = ecs.entities();
|
let entities = ecs.entities();
|
||||||
let items = ecs.read_storage::<Item>();
|
let items = ecs.read_storage::<Item>();
|
||||||
let positions = ecs.read_storage::<Position>();
|
let positions = ecs.read_storage::<Position>();
|
||||||
let mut gamelog = ecs.fetch_mut::<GameLog>();
|
|
||||||
|
|
||||||
let mut target_item: Option<Entity> = None;
|
let mut target_item: Option<Entity> = None;
|
||||||
for (item_entity, _item, position) in (&entities, &items, &positions).join() {
|
for (item_entity, _item, position) in (&entities, &items, &positions).join() {
|
||||||
|
|
@ -77,7 +75,7 @@ fn get_item(ecs: &mut World) {
|
||||||
}
|
}
|
||||||
|
|
||||||
match target_item {
|
match target_item {
|
||||||
None => gamelog.entries.push("There is nothing to pick up.".to_string()),
|
None => gamelog::Logger::new().append("There is nothing to pick up.").log(),
|
||||||
Some(item) => {
|
Some(item) => {
|
||||||
let mut pickup = ecs.write_storage::<WantsToPickupItem>();
|
let mut pickup = ecs.write_storage::<WantsToPickupItem>();
|
||||||
pickup
|
pickup
|
||||||
|
|
@ -142,8 +140,7 @@ pub fn try_next_level(ecs: &mut World) -> bool {
|
||||||
if map.tiles[player_idx] == TileType::DownStair {
|
if map.tiles[player_idx] == TileType::DownStair {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
let mut gamelog = ecs.fetch_mut::<GameLog>();
|
gamelog::Logger::new().append("You don't see a way down.").log();
|
||||||
gamelog.entries.push("You don't see a way down.".to_string());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -152,8 +149,6 @@ fn skip_turn(ecs: &mut World) -> RunState {
|
||||||
let player_entity = ecs.fetch::<Entity>();
|
let player_entity = ecs.fetch::<Entity>();
|
||||||
let viewshed_components = ecs.read_storage::<Viewshed>();
|
let viewshed_components = ecs.read_storage::<Viewshed>();
|
||||||
let monsters = ecs.read_storage::<Monster>();
|
let monsters = ecs.read_storage::<Monster>();
|
||||||
let mut wait_message = "You wait a turn.";
|
|
||||||
|
|
||||||
let worldmap_resource = ecs.fetch::<Map>();
|
let worldmap_resource = ecs.fetch::<Map>();
|
||||||
|
|
||||||
let mut can_heal = true;
|
let mut can_heal = true;
|
||||||
|
|
@ -171,6 +166,7 @@ fn skip_turn(ecs: &mut World) -> RunState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut did_heal = false;
|
||||||
if can_heal {
|
if can_heal {
|
||||||
let mut health_components = ecs.write_storage::<CombatStats>();
|
let mut health_components = ecs.write_storage::<CombatStats>();
|
||||||
let player_hp = health_components.get_mut(*player_entity).unwrap();
|
let player_hp = health_components.get_mut(*player_entity).unwrap();
|
||||||
|
|
@ -178,12 +174,15 @@ fn skip_turn(ecs: &mut World) -> RunState {
|
||||||
let roll = rng.roll_dice(1, 6);
|
let roll = rng.roll_dice(1, 6);
|
||||||
if (roll == 6) && player_hp.hp < player_hp.max_hp {
|
if (roll == 6) && player_hp.hp < player_hp.max_hp {
|
||||||
player_hp.hp += 1;
|
player_hp.hp += 1;
|
||||||
wait_message = "You wait a turn, and recover a hit point.";
|
did_heal = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut gamelog = ecs.fetch_mut::<GameLog>();
|
if did_heal {
|
||||||
gamelog.entries.push(wait_message.to_string());
|
gamelog::Logger::new().append("You wait a turn, and").colour(rltk::GREEN).append("recover a hit point.").log();
|
||||||
|
} else {
|
||||||
|
gamelog::Logger::new().append("You wait a turn.").log();
|
||||||
|
}
|
||||||
return RunState::PlayerTurn;
|
return RunState::PlayerTurn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,15 @@ pub fn save_game(_ecs: &mut World) {}
|
||||||
pub fn save_game(ecs: &mut World) {
|
pub fn save_game(ecs: &mut World) {
|
||||||
// Create helper
|
// Create helper
|
||||||
let mapcopy = ecs.get_mut::<super::map::Map>().unwrap().clone();
|
let mapcopy = ecs.get_mut::<super::map::Map>().unwrap().clone();
|
||||||
let savehelper =
|
let savehelper = ecs
|
||||||
ecs.create_entity().with(SerializationHelper { map: mapcopy }).marked::<SimpleMarker<SerializeMe>>().build();
|
.create_entity()
|
||||||
|
.with(SerializationHelper {
|
||||||
|
map: mapcopy,
|
||||||
|
log: crate::gamelog::clone_log(),
|
||||||
|
events: crate::gamelog::clone_events(),
|
||||||
|
})
|
||||||
|
.marked::<SimpleMarker<SerializeMe>>()
|
||||||
|
.build();
|
||||||
|
|
||||||
// Actually serialize
|
// Actually serialize
|
||||||
{
|
{
|
||||||
|
|
@ -156,6 +163,8 @@ pub fn load_game(ecs: &mut World) {
|
||||||
*worldmap = h.map.clone();
|
*worldmap = h.map.clone();
|
||||||
worldmap.tile_content = vec![Vec::new(); super::map::MAPCOUNT];
|
worldmap.tile_content = vec![Vec::new(); super::map::MAPCOUNT];
|
||||||
deleteme = Some(e);
|
deleteme = Some(e);
|
||||||
|
crate::gamelog::restore_log(&mut h.log.clone());
|
||||||
|
crate::gamelog::load_events(h.events.clone());
|
||||||
}
|
}
|
||||||
for (e, _p, pos) in (&entities, &player, &position).join() {
|
for (e, _p, pos) in (&entities, &player, &position).join() {
|
||||||
let mut ppos = ecs.write_resource::<rltk::Point>();
|
let mut ppos = ecs.write_resource::<rltk::Point>();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue