Support looking at exits.
This commit is contained in:
parent
3dd6cf9bf2
commit
562c5c34f6
@ -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 async_trait::async_trait;
|
||||||
use ansi::{ansi, flow_around, word_wrap};
|
use ansi::{ansi, flow_around, word_wrap};
|
||||||
use crate::models::{user::User, item::Item};
|
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> {
|
pub fn get_user_or_fail<'l>(ctx: &'l VerbContext) -> UResult<&'l User> {
|
||||||
ctx.user_dat.as_ref()
|
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 max_x = min_x + (width as i64);
|
||||||
let min_y = my_loc.y - (height as i64) / 2;
|
let min_y = my_loc.y - (height as i64) / 2;
|
||||||
let max_y = min_y + (height as i64);
|
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 {
|
if my_loc.x == x && my_loc.y == y {
|
||||||
buf.push_str(ansi!("<bgblue><red>()<reset>"))
|
buf.push_str(ansi!("<bgblue><red>()<reset>"))
|
||||||
} else {
|
} else {
|
||||||
buf.push_str(room::room_map_by_loc()
|
buf.push_str(room::room_map_by_zloc()
|
||||||
.get(&room::GridCoords { x, y, z: my_loc.z })
|
.get(&(&room.zone, &room::GridCoords { x, y, z: my_loc.z }))
|
||||||
.map(|r| r.short)
|
.map(|r| r.short)
|
||||||
.unwrap_or(" "));
|
.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<()> {
|
pub async fn describe_normal_item(ctx: &VerbContext<'_>, item: &Item) -> UResult<()> {
|
||||||
ctx.trans.queue_for_session(
|
ctx.trans.queue_for_session(
|
||||||
ctx.session,
|
ctx.session,
|
||||||
Some(explicit_if_allowed(
|
Some(&format!("{}\n{}",
|
||||||
|
explicit_if_allowed(
|
||||||
ctx,
|
ctx,
|
||||||
&item.display,
|
&item.display,
|
||||||
item.display_less_explicit.as_ref().map(|s|&**s)))
|
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?;
|
).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn exits_for(room: &room::Room) -> String {
|
||||||
|
let exit_text: Vec<String> =
|
||||||
|
room.exits.iter().map(|ex| format!(ansi!("<yellow>{}"),
|
||||||
|
ex.direction.describe())).collect();
|
||||||
|
format!(ansi!("<cyan>[ Exits: <bold>{} <reset><cyan>]<reset>"), exit_text.join(" "))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn describe_room(ctx: &VerbContext<'_>, room: &room::Room) -> UResult<()> {
|
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");
|
let zone = room::zone_details().get(room.zone).map(|z|z.display).unwrap_or("Outside of time");
|
||||||
ctx.trans.queue_for_session(
|
ctx.trans.queue_for_session(
|
||||||
ctx.session,
|
ctx.session,
|
||||||
Some(&flow_around(&render_map(room, 5, 5), 10, " ",
|
Some(&flow_around(&render_map(room, 5, 5), 10, " ",
|
||||||
&format!("{} ({})\n{}\n", room.name, zone,
|
&word_wrap(&format!("{} ({})\n{}\n{}\n", room.name, zone,
|
||||||
word_wrap(
|
|
||||||
explicit_if_allowed(ctx, room.description,
|
explicit_if_allowed(ctx, room.description,
|
||||||
room.description_less_explicit),
|
room.description_less_explicit),
|
||||||
|row| if row >= 5 { 80 } else { 68 }
|
exits_for(room)),
|
||||||
)), 68))
|
|row| if row >= 5 { 80 } else { 68 }), 68))
|
||||||
).await?;
|
).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -67,16 +79,41 @@ pub async fn describe_room(ctx: &VerbContext<'_>, room: &room::Room) -> UResult<
|
|||||||
pub struct Verb;
|
pub struct Verb;
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl UserVerb for Verb {
|
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 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 (heretype, herecode) = player_item.location.split_once("/").unwrap_or(("room", "repro_xv_chargen"));
|
||||||
let item = ctx.trans.find_item_by_type_code(loctype, loccode).await?
|
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()))?;
|
.ok_or_else(|| UserError("Sorry, that no longer exists".to_owned()))?;
|
||||||
describe_normal_item(ctx, &item).await?;
|
describe_normal_item(ctx, &item).await?;
|
||||||
} else {
|
} else {
|
||||||
let room =
|
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?;
|
describe_room(ctx, &room).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use nom::{
|
use nom::{
|
||||||
bytes::complete::{take_till1, take_while},
|
bytes::complete::{take_till1, take_while},
|
||||||
character::{complete::{space0, space1, alpha1}},
|
character::{complete::{space0, space1, alpha1, one_of}},
|
||||||
combinator::{recognize, fail, eof},
|
combinator::{recognize, fail, eof},
|
||||||
sequence::terminated,
|
sequence::terminated,
|
||||||
branch::alt,
|
branch::alt,
|
||||||
@ -11,7 +11,10 @@ use nom::{
|
|||||||
pub fn parse_command_name(input: &str) -> (&str, &str) {
|
pub fn parse_command_name(input: &str) -> (&str, &str) {
|
||||||
fn parse(input: &str) -> IResult<&str, &str> {
|
fn parse(input: &str) -> IResult<&str, &str> {
|
||||||
let (input, _) = space0(input)?;
|
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)?;
|
let (input, _) = space0(input)?;
|
||||||
Ok((input, cmd))
|
Ok((input, cmd))
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,8 @@ pub struct Item {
|
|||||||
pub item_type: String,
|
pub item_type: String,
|
||||||
pub display: String,
|
pub display: String,
|
||||||
pub display_less_explicit: Option<String>,
|
pub display_less_explicit: Option<String>,
|
||||||
|
pub details: Option<String>,
|
||||||
|
pub details_less_explicit: Option<String>,
|
||||||
pub location: String, // Item reference as item_type/item_code.
|
pub location: String, // Item reference as item_type/item_code.
|
||||||
pub action_type: LocationActionType,
|
pub action_type: LocationActionType,
|
||||||
pub presence_target: Option<String>, // e.g. what are they sitting on.
|
pub presence_target: Option<String>, // e.g. what are they sitting on.
|
||||||
@ -64,6 +66,8 @@ impl Default for Item {
|
|||||||
item_type: "unset".to_owned(),
|
item_type: "unset".to_owned(),
|
||||||
display: "Item".to_owned(),
|
display: "Item".to_owned(),
|
||||||
display_less_explicit: None,
|
display_less_explicit: None,
|
||||||
|
details: None,
|
||||||
|
details_less_explicit: None,
|
||||||
location: "room/storage".to_owned(),
|
location: "room/storage".to_owned(),
|
||||||
action_type: LocationActionType::Normal,
|
action_type: LocationActionType::Normal,
|
||||||
presence_target: None,
|
presence_target: None,
|
||||||
|
@ -30,18 +30,38 @@ pub struct GridCoords {
|
|||||||
pub z: i64
|
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 {
|
pub enum ExitType {
|
||||||
Free, // Anyone can just walk it.
|
Free, // Anyone can just walk it.
|
||||||
// Future ideas: Doors with locks, etc...
|
// Future ideas: Doors with locks, etc...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug)]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
NORTH,
|
NORTH,
|
||||||
SOUTH,
|
SOUTH,
|
||||||
EAST,
|
EAST,
|
||||||
WEST,
|
WEST,
|
||||||
NORTHEAST,
|
NORTHEAST,
|
||||||
SOUTEAST,
|
SOUTHEAST,
|
||||||
NORTHWEST,
|
NORTHWEST,
|
||||||
SOUTHWEST,
|
SOUTHWEST,
|
||||||
UP,
|
UP,
|
||||||
@ -49,15 +69,48 @@ pub enum Direction {
|
|||||||
IN(&'static str)
|
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 {
|
pub enum ExitTarget {
|
||||||
UseGPS,
|
UseGPS,
|
||||||
|
#[allow(dead_code)]
|
||||||
Custom(&'static str)
|
Custom(&'static str)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Exit {
|
pub struct Exit {
|
||||||
direction: Direction,
|
pub direction: Direction,
|
||||||
target: ExitTarget,
|
pub target: ExitTarget,
|
||||||
exit_type: ExitType
|
pub exit_type: ExitType
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Room {
|
pub struct Room {
|
||||||
@ -79,7 +132,7 @@ pub fn room_list() -> &'static Vec<Room> {
|
|||||||
zone: "repro_xv",
|
zone: "repro_xv",
|
||||||
code: "repro_xv_chargen",
|
code: "repro_xv_chargen",
|
||||||
name: ansi!("<yellow>Choice Room<reset>"),
|
name: ansi!("<yellow>Choice Room<reset>"),
|
||||||
short: ansi!("<green>CR<reset>"),
|
short: ansi!("<bgwhite><green>CR<reset>"),
|
||||||
description: ansi!(
|
description: ansi!(
|
||||||
"A room brightly lit in unnaturally white light, covered in sparkling \
|
"A room brightly lit in unnaturally white light, covered in sparkling \
|
||||||
white tiles from floor to ceiling. A loudspeaker plays a message on \
|
white tiles from floor to ceiling. A loudspeaker plays a message on \
|
||||||
@ -94,7 +147,26 @@ pub fn room_list() -> &'static Vec<Room> {
|
|||||||
[Try <bold>\"statbot hi<reset>, to send hi to statbot - the \" means \
|
[Try <bold>\"statbot hi<reset>, to send hi to statbot - the \" means \
|
||||||
to whisper to a particular person in the room]"),
|
to whisper to a particular person in the room]"),
|
||||||
description_less_explicit: None,
|
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!("<yellow>Body Factory<reset>"),
|
||||||
|
short: ansi!("<bgmagenta><white>BF<reset>"),
|
||||||
|
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 <bold>up<reset> 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!()
|
exits: vec!()
|
||||||
},
|
},
|
||||||
).into_iter().collect())
|
).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())
|
|| room_list().iter().map(|r| (r.code, r)).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
static STATIC_ROOM_MAP_BY_LOC: OnceCell<BTreeMap<&'static GridCoords, &'static Room>> = OnceCell::new();
|
static STATIC_ROOM_MAP_BY_ZLOC: OnceCell<BTreeMap<(&'static str, &'static GridCoords),
|
||||||
pub fn room_map_by_loc() -> &'static BTreeMap<&'static GridCoords, &'static Room> {
|
&'static Room>> = OnceCell::new();
|
||||||
STATIC_ROOM_MAP_BY_LOC.get_or_init(
|
pub fn room_map_by_zloc() -> &'static BTreeMap<(&'static str, &'static GridCoords), &'static Room> {
|
||||||
|| room_list().iter().map(|r| (&r.grid_coords, r)).collect())
|
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<dyn Iterator<Item = StaticItem>> {
|
pub fn room_static_items() -> Box<dyn Iterator<Item = StaticItem>> {
|
||||||
@ -118,7 +191,7 @@ pub fn room_static_items() -> Box<dyn Iterator<Item = StaticItem>> {
|
|||||||
initial_item: Box::new(|| Item {
|
initial_item: Box::new(|| Item {
|
||||||
item_code: r.code.to_owned(),
|
item_code: r.code.to_owned(),
|
||||||
item_type: "room".to_owned(),
|
item_type: "room".to_owned(),
|
||||||
display: r.description.to_owned(),
|
display: r.name.to_owned(),
|
||||||
location: format!("room/{}", r.code),
|
location: format!("room/{}", r.code),
|
||||||
is_static: true,
|
is_static: true,
|
||||||
..Item::default()
|
..Item::default()
|
||||||
@ -126,6 +199,20 @@ pub fn room_static_items() -> Box<dyn Iterator<Item = StaticItem>> {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
Loading…
Reference in New Issue
Block a user