diff --git a/src/camera.rs b/src/camera.rs index 76923ae..1897b39 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -35,7 +35,8 @@ pub fn render_camera(ecs: &World, ctx: &mut Rltk) { let (glyph, fg, bg) = crate::map::themes::get_tile_renderables_for_id( idx, &*map, - Some(*ecs.fetch::()) + Some(*ecs.fetch::()), + None ); ctx.set(x + x_offset, y + y_offset, fg, bg, glyph); } @@ -66,7 +67,12 @@ pub fn render_camera(ecs: &World, ctx: &mut Rltk) { if pos.x < max_x && pos.y < max_y && pos.x >= min_x && pos.y >= min_y { let mut draw = false; let mut fg = render.fg; - let mut bg = crate::map::themes::get_tile_renderables_for_id(idx, &*map, Some(*ecs.fetch::())).2; + let mut bg = crate::map::themes::get_tile_renderables_for_id( + idx, + &*map, + Some(*ecs.fetch::()), + None + ).2; // Draw entities on visible tiles if map.visible_tiles[idx] { draw = true; @@ -124,7 +130,7 @@ pub fn render_debug_map(map: &Map, ctx: &mut Rltk) { if tx >= 0 && tx < map_width && ty >= 0 && ty < map_height { let idx = map.xy_idx(tx, ty); if map.revealed_tiles[idx] { - let (glyph, fg, bg) = crate::map::themes::get_tile_renderables_for_id(idx, &*map, None); + let (glyph, fg, bg) = crate::map::themes::get_tile_renderables_for_id(idx, &*map, None, None); ctx.set(x, y, fg, bg, glyph); } } else if SHOW_BOUNDARIES { diff --git a/src/data/messages.rs b/src/data/messages.rs index e0fadb0..ddd31e4 100644 --- a/src/data/messages.rs +++ b/src/data/messages.rs @@ -29,3 +29,17 @@ pub const YOU_DROP_ITEM: &str = "You drop the"; pub const YOU_EQUIP_ITEM: &str = "You equip the"; pub const YOU_REMOVE_ITEM: &str = "You unequip your"; pub const YOU_REMOVE_ITEM_CURSED: &str = "You can't remove the"; + +/// Prefixes death message. +pub const PLAYER_DIED: &str = "You died!"; +/// Death message specifiers. Appended after PLAYER_DIED. +pub const PLAYER_DIED_SUICIDE: &str = "You killed yourself"; +pub const PLAYER_DIED_NAMED_ATTACKER: &str = "You were killed by"; +pub const PLAYER_DIED_UNKNOWN: &str = "You were killed"; // Ultimately, this should never be used. Slowly include specific messages for any death. +/// Death message addendums. Appended at end of death message. +pub const PLAYER_DIED_ADDENDUM_FIRST: &str = " "; +pub const PLAYER_DIED_ADDENDUM_MID: &str = ", "; +pub const PLAYER_DIED_ADDENDUM_LAST: &str = ", and "; +pub const STATUS_CONFUSED_STRING: &str = "confused"; +pub const STATUS_BLIND_STRING: &str = "blinded"; +// Results in something like: "You died! You were killed by a kobold captain, whilst confused." diff --git a/src/effects/damage.rs b/src/effects/damage.rs index 1c2c913..3376f28 100644 --- a/src/effects/damage.rs +++ b/src/effects/damage.rs @@ -10,10 +10,13 @@ use crate::{ Player, Pools, Name, + Blind, }; +use crate::gui::with_article; use crate::data::visuals::{ DEFAULT_PARTICLE_LIFETIME, LONG_PARTICLE_LIFETIME }; use crate::data::messages::LEVELUP_PLAYER; use crate::data::events::*; +use crate::data::messages::*; use rltk::prelude::*; use specs::prelude::*; @@ -135,6 +138,42 @@ fn get_next_level_requirement(level: i32) -> i32 { return -1; } +fn get_death_message(ecs: &World, source: Entity) -> String { + let player = ecs.fetch::(); + let mut result: String = format!("{} ", PLAYER_DIED); + // If we killed ourselves, + if source == *player { + result.push_str(format!("{}", PLAYER_DIED_SUICIDE).as_str()); + } else if let Some(name) = ecs.read_storage::().get(source) { + result.push_str(format!("{} {}", PLAYER_DIED_NAMED_ATTACKER, with_article(name.name.clone())).as_str()); + } else { + result.push_str(format!("{}", PLAYER_DIED_UNKNOWN).as_str()); + } + // Status effects + { + let mut addendums: Vec<&str> = Vec::new(); + if let Some(_confused) = ecs.read_storage::().get(*player) { + addendums.push(STATUS_CONFUSED_STRING); + } + if let Some(_blind) = ecs.read_storage::().get(*player) { + addendums.push(STATUS_BLIND_STRING); + } + if !addendums.is_empty() { + result.push_str(" whilst"); + for (i, addendum) in addendums.iter().enumerate() { + if i == 0 { + result.push_str(format!("{}{}", PLAYER_DIED_ADDENDUM_FIRST, addendum).as_str()); + } else if i == addendums.len() { + result.push_str(format!("{}{}", PLAYER_DIED_ADDENDUM_LAST, addendum).as_str()); + } else { + result.push_str(format!("{}{}", PLAYER_DIED_ADDENDUM_MID, addendum).as_str()); + } + } + } + } + return result; +} + /// Handles EntityDeath effects. pub fn entity_death(ecs: &mut World, effect: &EffectSpawner, target: Entity) { let mut xp_gain = 0; @@ -150,9 +189,7 @@ pub fn entity_death(ecs: &mut World, effect: &EffectSpawner, target: Entity) { if let Some(source) = effect.source { // If the target was the player, game over, and record source of death. if target == *player { - if let Some(src_name) = names.get(source) { - gamelog::record_event(EVENT::PLAYER_DIED(src_name.name.clone())); - } + gamelog::record_event(EVENT::PLAYER_DIED(get_death_message(ecs, source))); return; } else { // If the player was the source, record the kill. diff --git a/src/gamelog/events.rs b/src/gamelog/events.rs index 6cf615e..cb436c6 100644 --- a/src/gamelog/events.rs +++ b/src/gamelog/events.rs @@ -126,13 +126,9 @@ pub fn record_event(event: EVENT) { EVENT::IDENTIFIED(name) => { new_event = format!("Identified {}", name); } - EVENT::PLAYER_DIED(name) => { - if name == "you" { - new_event = format!("You died! Killed by... yourself."); - } else { - // TODO: Use correct article here - or don't include article at all. - new_event = format!("You died, killed by {}", name); - } + EVENT::PLAYER_DIED(str) => { + // Generating the String is handled in the death effect, to avoid passing the ecs here. + new_event = format!("{}", str); } } diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 8270085..deb9d40 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -1195,3 +1195,16 @@ pub fn game_over(ctx: &mut Rltk) -> YesNoResult { } } } + +pub fn with_article(name: String) -> String { + // If first letter is a capital + if name.chars().nth(0).unwrap().is_uppercase() { + return format!("{}", name); + } + // a/an + let vowels = ['a', 'e', 'i', 'o', 'u']; + if vowels.contains(&name.chars().nth(0).unwrap()) { + return format!("an {}", name); + } + format!("a {}", name) +} diff --git a/src/map/themes.rs b/src/map/themes.rs index 7a7dd4e..5cc5966 100644 --- a/src/map/themes.rs +++ b/src/map/themes.rs @@ -4,10 +4,16 @@ use crate::config::CONFIG; use rltk::prelude::*; use std::ops::{ Add, Mul }; -pub fn get_tile_renderables_for_id(idx: usize, map: &Map, other_pos: Option) -> (rltk::FontCharType, RGB, RGB) { +/// Gets the renderables for a tile, with darkening/offset/post-processing/etc. Passing a val for "debug" will ignore viewshed. +pub fn get_tile_renderables_for_id( + idx: usize, + map: &Map, + other_pos: Option, + debug: Option +) -> (rltk::FontCharType, RGB, RGB) { let (glyph, mut fg, mut bg) = match map.id { - 2 => get_forest_theme_renderables(idx, map), - _ => get_default_theme_renderables(idx, map), + 2 => get_forest_theme_renderables(idx, map, debug), + _ => get_default_theme_renderables(idx, map, debug), }; // If one of the colours was left blank, make them the same. @@ -54,7 +60,7 @@ pub fn get_tile_renderables_for_id(idx: usize, map: &Map, other_pos: Option (rltk::FontCharType, RGB, RGB) { +pub fn get_default_theme_renderables(idx: usize, map: &Map, debug: Option) -> (rltk::FontCharType, RGB, RGB) { let glyph: rltk::FontCharType; #[allow(unused_assignments)] let mut fg: RGB = RGB::new(); @@ -65,7 +71,7 @@ pub fn get_default_theme_renderables(idx: usize, map: &Map) -> (rltk::FontCharTy TileType::Floor => { glyph = rltk::to_cp437(FLOOR_GLYPH); fg = RGB::named(FLOOR_COLOUR); bg = RGB::named(DEFAULT_BG_COLOUR); } TileType::WoodFloor => { glyph = rltk::to_cp437(WOOD_FLOOR_GLYPH); bg = RGB::named(WOOD_FLOOR_COLOUR); } TileType::Fence => { glyph = rltk::to_cp437(FENCE_GLYPH); fg = RGB::named(FENCE_FG_COLOUR); bg = RGB::named(FENCE_COLOUR); } - TileType::Wall => { let x = idx as i32 % map.width; let y = idx as i32 / map.width; glyph = wall_glyph(&*map, x, y); fg = RGB::named(WALL_COLOUR); bg = RGB::named(DEFAULT_BG_COLOUR); } + TileType::Wall => { let x = idx as i32 % map.width; let y = idx as i32 / map.width; glyph = wall_glyph(&*map, x, y, debug); fg = RGB::named(WALL_COLOUR); bg = RGB::named(DEFAULT_BG_COLOUR); } TileType::DownStair => { glyph = rltk::to_cp437(DOWN_STAIR_GLYPH); fg = RGB::named(STAIR_COLOUR); bg = RGB::named(DEFAULT_BG_COLOUR); } TileType::UpStair => { glyph = rltk::to_cp437(UP_STAIR_GLYPH); fg = RGB::named(STAIR_COLOUR); bg = RGB::named(DEFAULT_BG_COLOUR); } TileType::Bridge => { glyph = rltk::to_cp437(BRIDGE_GLYPH); bg = RGB::named(BRIDGE_COLOUR); } @@ -83,7 +89,7 @@ pub fn get_default_theme_renderables(idx: usize, map: &Map) -> (rltk::FontCharTy } #[rustfmt::skip] -fn get_forest_theme_renderables(idx:usize, map: &Map) -> (rltk::FontCharType, RGB, RGB) { +fn get_forest_theme_renderables(idx:usize, map: &Map, debug: Option) -> (rltk::FontCharType, RGB, RGB) { let glyph; #[allow(unused_assignments)] let mut fg = RGB::new(); @@ -94,55 +100,55 @@ fn get_forest_theme_renderables(idx:usize, map: &Map) -> (rltk::FontCharType, RG TileType::Wall => { glyph = rltk::to_cp437(FOREST_WALL_GLYPH); fg = RGB::named(FOREST_WALL_COLOUR); bg = RGB::named(GRASS_COLOUR) } TileType::Road => { glyph = rltk::to_cp437(ROAD_GLYPH); bg = RGB::named(ROAD_COLOUR); } TileType::ShallowWater => { glyph = rltk::to_cp437(SHALLOW_WATER_GLYPH); bg = RGB::named(SHALLOW_WATER_COLOUR); } - _ => { (glyph, fg, _) = get_default_theme_renderables(idx, map); bg = RGB::named(GRASS_COLOUR) } + _ => { (glyph, fg, _) = get_default_theme_renderables(idx, map, debug); bg = RGB::named(GRASS_COLOUR) } } (glyph, fg, bg) } -fn is_revealed_and_wall(map: &Map, x: i32, y: i32) -> bool { +fn is_revealed_and_wall(map: &Map, x: i32, y: i32, debug: Option) -> bool { let idx = map.xy_idx(x, y); - map.tiles[idx] == TileType::Wall && map.revealed_tiles[idx] + map.tiles[idx] == TileType::Wall && (if debug.is_none() { map.revealed_tiles[idx] } else { true }) } -fn wall_glyph(map: &Map, x: i32, y: i32) -> rltk::FontCharType { +fn wall_glyph(map: &Map, x: i32, y: i32, debug: Option) -> rltk::FontCharType { if x < 1 || x > map.width - 2 || y < 1 || y > map.height - (2 as i32) { return 35; } let mut mask: u8 = 0; let diagonals_matter: Vec = vec![7, 11, 13, 14, 15]; - if is_revealed_and_wall(map, x, y - 1) { + if is_revealed_and_wall(map, x, y - 1, debug) { // N mask += 1; } - if is_revealed_and_wall(map, x, y + 1) { + if is_revealed_and_wall(map, x, y + 1, debug) { // S mask += 2; } - if is_revealed_and_wall(map, x - 1, y) { + if is_revealed_and_wall(map, x - 1, y, debug) { // W mask += 4; } - if is_revealed_and_wall(map, x + 1, y) { + if is_revealed_and_wall(map, x + 1, y, debug) { // E mask += 8; } if diagonals_matter.contains(&mask) { - if is_revealed_and_wall(map, x + 1, y - 1) { + if is_revealed_and_wall(map, x + 1, y - 1, debug) { // Top right mask += 16; } - if is_revealed_and_wall(map, x - 1, y - 1) { + if is_revealed_and_wall(map, x - 1, y - 1, debug) { // Top left mask += 32; } - if is_revealed_and_wall(map, x + 1, y + 1) { + if is_revealed_and_wall(map, x + 1, y + 1, debug) { // Bottom right mask += 64; } - if is_revealed_and_wall(map, x - 1, y + 1) { + if is_revealed_and_wall(map, x - 1, y + 1, debug) { // Bottom left mask += 128; } diff --git a/src/morgue.rs b/src/morgue.rs index a097311..1a8283f 100644 --- a/src/morgue.rs +++ b/src/morgue.rs @@ -165,7 +165,7 @@ fn draw_map(ecs: &World) -> String { } }); } else { - glyph_u16 = crate::map::themes::get_tile_renderables_for_id(idx, &*map, None).0; + glyph_u16 = crate::map::themes::get_tile_renderables_for_id(idx, &*map, None, Some(true)).0; } let char = to_char((glyph_u16 & 0xff) as u8); result.push_str(&char.to_string());