This commit is contained in:
Llywelwyn 2023-07-10 12:48:56 +01:00
parent 85881db62f
commit 4f899d329e
9 changed files with 121 additions and 67 deletions

View file

@ -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)

View file

@ -22,6 +22,21 @@ impl Logger {
} }
pub fn log(self) { pub fn log(self) {
append_entry(self.fragments) 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;
} }
} }

View file

@ -6,6 +6,7 @@ lazy_static! {
static ref LOG: Mutex<Vec<Vec<LogFragment>>> = Mutex::new(Vec::new()); static ref LOG: Mutex<Vec<Vec<LogFragment>>> = Mutex::new(Vec::new());
} }
#[allow(dead_code)]
pub fn append_fragment(fragment: LogFragment) { pub fn append_fragment(fragment: LogFragment) {
LOG.lock().unwrap().push(vec![fragment]); LOG.lock().unwrap().push(vec![fragment]);
} }

View file

@ -6,10 +6,6 @@ mod logstore;
use logstore::*; use logstore::*;
pub use logstore::{clear_log, log_display}; pub use logstore::{clear_log, log_display};
pub struct GameLog {
pub entries: Vec<String>,
}
pub struct LogFragment { pub struct LogFragment {
pub colour: RGB, pub colour: RGB,
pub text: String, pub text: String,

View file

@ -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();
} }
} }

View file

@ -157,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 {
@ -431,9 +430,7 @@ 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_log();
gamelog::Logger::new() gamelog::Logger::new()
.append("Welcome!") .append("Welcome!")

View file

@ -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);
} }
} }

View file

@ -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;

View file

@ -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;
} }