From 562c5c34f68e6710298a19d085448a419eb98745 Mon Sep 17 00:00:00 2001 From: Shagnor Date: Thu, 29 Dec 2022 18:44:50 +1100 Subject: [PATCH] Support looking at exits. --- .../src/message_handler/user_commands/look.rs | 79 +++++++++---- .../message_handler/user_commands/parsing.rs | 7 +- blastmud_game/src/models/item.rs | 4 + blastmud_game/src/static_content/room.rs | 109 ++++++++++++++++-- 4 files changed, 165 insertions(+), 34 deletions(-) diff --git a/blastmud_game/src/message_handler/user_commands/look.rs b/blastmud_game/src/message_handler/user_commands/look.rs index bd321e7..3bc3bbe 100644 --- a/blastmud_game/src/message_handler/user_commands/look.rs +++ b/blastmud_game/src/message_handler/user_commands/look.rs @@ -1,8 +1,8 @@ -use super::{VerbContext, UserVerb, UserVerbRef, UResult, UserError, explicit_if_allowed}; +use super::{VerbContext, UserVerb, UserVerbRef, UResult, UserError, user_error, explicit_if_allowed}; use async_trait::async_trait; use ansi::{ansi, flow_around, word_wrap}; use crate::models::{user::User, item::Item}; -use crate::static_content::room; +use crate::static_content::room::{self, Direction}; pub fn get_user_or_fail<'l>(ctx: &'l VerbContext) -> UResult<&'l User> { ctx.user_dat.as_ref() @@ -22,13 +22,13 @@ pub fn render_map(room: &room::Room, width: usize, height: usize) -> String { let max_x = min_x + (width as i64); let min_y = my_loc.y - (height as i64) / 2; let max_y = min_y + (height as i64); - for x in min_x..max_x { - for y in min_y..max_y { + for y in min_y..max_y { + for x in min_x..max_x { if my_loc.x == x && my_loc.y == y { buf.push_str(ansi!("()")) } else { - buf.push_str(room::room_map_by_loc() - .get(&room::GridCoords { x, y, z: my_loc.z }) + buf.push_str(room::room_map_by_zloc() + .get(&(&room.zone, &room::GridCoords { x, y, z: my_loc.z })) .map(|r| r.short) .unwrap_or(" ")); } @@ -41,25 +41,37 @@ pub fn render_map(room: &room::Room, width: usize, height: usize) -> String { pub async fn describe_normal_item(ctx: &VerbContext<'_>, item: &Item) -> UResult<()> { ctx.trans.queue_for_session( ctx.session, - Some(explicit_if_allowed( - ctx, - &item.display, - item.display_less_explicit.as_ref().map(|s|&**s))) + Some(&format!("{}\n{}", + explicit_if_allowed( + ctx, + &item.display, + item.display_less_explicit.as_ref().map(|s|&**s)), + explicit_if_allowed( + ctx, + item.details.as_ref().map(|v|&**v).unwrap_or(""), + item.details_less_explicit.as_ref().map(|s|&**s)), + )) ).await?; Ok(()) } +fn exits_for(room: &room::Room) -> String { + let exit_text: Vec = + room.exits.iter().map(|ex| format!(ansi!("{}"), + ex.direction.describe())).collect(); + format!(ansi!("[ Exits: {} ]"), exit_text.join(" ")) +} + pub async fn describe_room(ctx: &VerbContext<'_>, room: &room::Room) -> UResult<()> { let zone = room::zone_details().get(room.zone).map(|z|z.display).unwrap_or("Outside of time"); ctx.trans.queue_for_session( ctx.session, Some(&flow_around(&render_map(room, 5, 5), 10, " ", - &format!("{} ({})\n{}\n", room.name, zone, - word_wrap( - explicit_if_allowed(ctx, room.description, - room.description_less_explicit), - |row| if row >= 5 { 80 } else { 68 } - )), 68)) + &word_wrap(&format!("{} ({})\n{}\n{}\n", room.name, zone, + explicit_if_allowed(ctx, room.description, + room.description_less_explicit), + exits_for(room)), + |row| if row >= 5 { 80 } else { 68 }), 68)) ).await?; Ok(()) } @@ -67,16 +79,41 @@ pub async fn describe_room(ctx: &VerbContext<'_>, room: &room::Room) -> UResult< pub struct Verb; #[async_trait] impl UserVerb for Verb { - async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, _remaining: &str) -> UResult<()> { + async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, remaining: &str) -> UResult<()> { let player_item = get_player_item_or_fail(ctx).await?; - let (loctype, loccode) = player_item.location.split_once("/").unwrap_or(("room", "repro_xv_chargen")); - if loctype != "room" { - let item = ctx.trans.find_item_by_type_code(loctype, loccode).await? + + let (heretype, herecode) = player_item.location.split_once("/").unwrap_or(("room", "repro_xv_chargen")); + let (itype, icode) = if remaining == "" { + Ok((heretype, herecode)) + } else if let Some(dir) = Direction::parse(remaining) { + if heretype != "room" { + // Fix this when we have planes / boats / roomkits. + user_error("Navigating outside rooms not yet supported.".to_owned()) + } else { + if let Some(room) = room::room_map_by_code().get(herecode) { + match room.exits.iter().find(|ex| ex.direction == *dir) { + None => user_error("There is nothing in that direction".to_owned()), + Some(exit) => { + match room::resolve_exit(room, exit) { + None => user_error("There is nothing in that direction".to_owned()), + Some(room2) => Ok(("room", room2.code)) + } + } + } + } else { + user_error("Can't find your current location".to_owned()) + } + } + } else { + user_error("Sorry, I don't understand what you want to look at.".to_owned()) + }?; + if itype != "room" { + let item = ctx.trans.find_item_by_type_code(itype, icode).await? .ok_or_else(|| UserError("Sorry, that no longer exists".to_owned()))?; describe_normal_item(ctx, &item).await?; } else { let room = - room::room_map_by_code().get(loccode).ok_or_else(|| UserError("Sorry, that room no longer exists".to_owned()))?; + room::room_map_by_code().get(icode).ok_or_else(|| UserError("Sorry, that room no longer exists".to_owned()))?; describe_room(ctx, &room).await?; } Ok(()) diff --git a/blastmud_game/src/message_handler/user_commands/parsing.rs b/blastmud_game/src/message_handler/user_commands/parsing.rs index 05cb417..cf561e4 100644 --- a/blastmud_game/src/message_handler/user_commands/parsing.rs +++ b/blastmud_game/src/message_handler/user_commands/parsing.rs @@ -1,6 +1,6 @@ use nom::{ bytes::complete::{take_till1, take_while}, - character::{complete::{space0, space1, alpha1}}, + character::{complete::{space0, space1, alpha1, one_of}}, combinator::{recognize, fail, eof}, sequence::terminated, branch::alt, @@ -11,7 +11,10 @@ use nom::{ pub fn parse_command_name(input: &str) -> (&str, &str) { fn parse(input: &str) -> IResult<&str, &str> { let (input, _) = space0(input)?; - let (input, cmd) = take_till1(|c| c == ' ' || c == '\t')(input)?; + let (input, cmd) = alt(( + recognize(one_of("-\"':.")), + take_till1(|c| c == ' ' || c == '\t') + ))(input)?; let (input, _) = space0(input)?; Ok((input, cmd)) } diff --git a/blastmud_game/src/models/item.rs b/blastmud_game/src/models/item.rs index ea2ba71..6d28145 100644 --- a/blastmud_game/src/models/item.rs +++ b/blastmud_game/src/models/item.rs @@ -46,6 +46,8 @@ pub struct Item { pub item_type: String, pub display: String, pub display_less_explicit: Option, + pub details: Option, + pub details_less_explicit: Option, pub location: String, // Item reference as item_type/item_code. pub action_type: LocationActionType, pub presence_target: Option, // e.g. what are they sitting on. @@ -64,6 +66,8 @@ impl Default for Item { item_type: "unset".to_owned(), display: "Item".to_owned(), display_less_explicit: None, + details: None, + details_less_explicit: None, location: "room/storage".to_owned(), action_type: LocationActionType::Normal, presence_target: None, diff --git a/blastmud_game/src/static_content/room.rs b/blastmud_game/src/static_content/room.rs index 5ef36a5..c6bb11e 100644 --- a/blastmud_game/src/static_content/room.rs +++ b/blastmud_game/src/static_content/room.rs @@ -30,18 +30,38 @@ pub struct GridCoords { pub z: i64 } +impl GridCoords { + pub fn apply(self: &GridCoords, dir: &Direction) -> GridCoords { + match dir { + Direction::NORTH => GridCoords {y: self.y - 1, ..*self}, + Direction::SOUTH => GridCoords {y: self.y + 1, ..*self}, + Direction::EAST => GridCoords {x: self.x + 1, ..*self}, + Direction::WEST => GridCoords {x: self.x - 1, ..*self}, + Direction::NORTHEAST => GridCoords {x: self.x + 1, y: self.y - 1, ..*self}, + Direction::SOUTHEAST => GridCoords {x: self.x + 1, y: self.y + 1, ..*self}, + Direction::NORTHWEST => GridCoords {x: self.x - 1, y: self.y - 1, ..*self}, + Direction::SOUTHWEST => GridCoords {x: self.x - 1, y: self.y + 1, ..*self}, + Direction::UP => GridCoords {z: self.z + 1, ..*self}, + Direction::DOWN => GridCoords {z: self.z - 1, ..*self}, + Direction::IN(_) => self.clone() + } + } +} + pub enum ExitType { Free, // Anyone can just walk it. // Future ideas: Doors with locks, etc... } +#[allow(dead_code)] +#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug)] pub enum Direction { NORTH, SOUTH, EAST, WEST, NORTHEAST, - SOUTEAST, + SOUTHEAST, NORTHWEST, SOUTHWEST, UP, @@ -49,15 +69,48 @@ pub enum Direction { IN(&'static str) } +impl Direction { + pub fn describe(self: &Self) -> &'static str { + match self { + Direction::NORTH => "north", + Direction::SOUTH => "south", + Direction::EAST => "east", + Direction::WEST => "west", + Direction::NORTHEAST => "northeast", + Direction::SOUTHEAST => "southeast", + Direction::NORTHWEST => "northwest", + Direction::SOUTHWEST => "southwest", + Direction::UP => "up", + Direction::DOWN => "down", + Direction::IN(s) => s + } + } + + pub fn parse(input: &str) -> Option<&'static Direction> { + match input { + "north" | "n" => Some(&Direction::NORTH), + "south" | "s" => Some(&Direction::SOUTH), + "east" | "e" => Some(&Direction::EAST), + "west" | "w" => Some(&Direction::WEST), + "northeast" | "ne" => Some(&Direction::NORTHEAST), + "southeast" | "se" => Some(&Direction::SOUTHEAST), + "northwest" | "nw" => Some(&Direction::NORTHEAST), + "southwest" | "sw" => Some(&Direction::SOUTHWEST), + _ => None + } + } +} + pub enum ExitTarget { UseGPS, + #[allow(dead_code)] Custom(&'static str) } pub struct Exit { - direction: Direction, - target: ExitTarget, - exit_type: ExitType + pub direction: Direction, + pub target: ExitTarget, + pub exit_type: ExitType } pub struct Room { @@ -79,7 +132,7 @@ pub fn room_list() -> &'static Vec { zone: "repro_xv", code: "repro_xv_chargen", name: ansi!("Choice Room"), - short: ansi!("CR"), + short: ansi!("CR"), description: ansi!( "A room brightly lit in unnaturally white light, covered in sparkling \ white tiles from floor to ceiling. A loudspeaker plays a message on \ @@ -94,7 +147,26 @@ pub fn room_list() -> &'static Vec { [Try \"statbot hi, to send hi to statbot - the \" means \ to whisper to a particular person in the room]"), description_less_explicit: None, - grid_coords: GridCoords { x: 0, y: 0, z: 2 }, + grid_coords: GridCoords { x: 0, y: 0, z: 1 }, + exits: vec!(Exit { + direction: Direction::EAST, + target: ExitTarget::UseGPS, + exit_type: ExitType::Free, + }) + }, + Room { + zone: "repro_xv", + code: "repro_xv_respawn", + name: ansi!("Body Factory"), + short: ansi!("BF"), + description: ansi!( + "A room filled with glass vats full of clear fluids, with bodies of \ + various stages of development floating in them. It smells like bleach. \ + Being here makes you realise you aren't exactly alive right now... you \ + have no body. But you sense you could go up and attach \ + your memories to a body matching your current stats."), + description_less_explicit: None, + grid_coords: GridCoords { x: 1, y: 0, z: 1 }, exits: vec!() }, ).into_iter().collect()) @@ -106,10 +178,11 @@ pub fn room_map_by_code() -> &'static BTreeMap<&'static str, &'static Room> { || room_list().iter().map(|r| (r.code, r)).collect()) } -static STATIC_ROOM_MAP_BY_LOC: OnceCell> = OnceCell::new(); -pub fn room_map_by_loc() -> &'static BTreeMap<&'static GridCoords, &'static Room> { - STATIC_ROOM_MAP_BY_LOC.get_or_init( - || room_list().iter().map(|r| (&r.grid_coords, r)).collect()) +static STATIC_ROOM_MAP_BY_ZLOC: OnceCell> = OnceCell::new(); +pub fn room_map_by_zloc() -> &'static BTreeMap<(&'static str, &'static GridCoords), &'static Room> { + STATIC_ROOM_MAP_BY_ZLOC.get_or_init( + || room_list().iter().map(|r| ((r.zone, &r.grid_coords), r)).collect()) } pub fn room_static_items() -> Box> { @@ -118,7 +191,7 @@ pub fn room_static_items() -> Box> { initial_item: Box::new(|| Item { item_code: r.code.to_owned(), item_type: "room".to_owned(), - display: r.description.to_owned(), + display: r.name.to_owned(), location: format!("room/{}", r.code), is_static: true, ..Item::default() @@ -126,6 +199,20 @@ pub fn room_static_items() -> Box> { })) } +pub fn resolve_exit(room: &Room, exit: &Exit) -> Option<&'static Room> { + match exit.target { + ExitTarget::Custom(t) => t.split_once("/").and_then( + |(t,c)| + if t != "room" { + None + } else { + room_map_by_code().get(c).map(|r|*r) + }), + ExitTarget::UseGPS => + room_map_by_zloc().get(&(room.zone, &room.grid_coords.apply(&exit.direction))).map(|r|*r) + } +} + #[cfg(test)] mod test { use super::*;