Add gmap (giant map) command.

This commit is contained in:
Condorra 2023-04-09 23:51:10 +10:00
parent b6ed5ea487
commit 284c49b4a1
3 changed files with 190 additions and 130 deletions

View File

@ -139,7 +139,11 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
"read" => look::VERB, "read" => look::VERB,
"list" => list::VERB, "list" => list::VERB,
"lm" => map::VERB,
"lmap" => map::VERB, "lmap" => map::VERB,
"gm" => map::VERB,
"gmap" => map::VERB,
"p" => page::VERB, "p" => page::VERB,
"page" => page::VERB, "page" => page::VERB,

View File

@ -8,95 +8,19 @@ use crate::{
Item, LocationActionType, Subattack, ItemFlag, ItemSpecialData Item, LocationActionType, Subattack, ItemFlag, ItemSpecialData
}}, }},
static_content::{ static_content::{
room::{self, Direction, GridCoords}, room::{self, Direction},
dynzone::self, dynzone::self,
possession_type::possession_data, possession_type::possession_data,
}, },
language, language,
services::combat::max_health, services::combat::max_health,
}; };
use super::map::{render_map, render_map_dyn};
use itertools::Itertools; use itertools::Itertools;
use std::sync::Arc; use std::sync::Arc;
use mockall_double::double; use mockall_double::double;
#[double] use crate::db::DBTrans; #[double] use crate::db::DBTrans;
pub fn render_map(room: &room::Room, width: usize, height: usize) -> String {
let mut buf = String::new();
let my_loc = &room.grid_coords;
let min_x = my_loc.x - (width as i64) / 2;
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 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!("<bgblue><red>()<reset>"))
} else {
buf.push_str(room::room_map_by_zloc()
.get(&(&room.zone, &room::GridCoords { x, y, z: my_loc.z }))
.map(|r| if room.zone == r.zone {
r.short
} else {
r.secondary_zones.iter()
.find(|sz| sz.zone == room.zone)
.map(|sz| sz.short)
.expect("Secondary zone missing")
})
.unwrap_or(" "));
}
}
buf.push('\n');
}
buf
}
pub fn render_map_dyn(dynzone: &dynzone::Dynzone,
dynroom: &dynzone::Dynroom,
width: usize, height: usize) -> String {
let mut buf = String::new();
let my_loc = &dynroom.grid_coords;
let min_x = my_loc.x - (width as i64) / 2;
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);
let main_exit: Option<GridCoords> = dynzone.dyn_rooms
.iter()
.flat_map(|(_, dr)|
dr.exits.iter()
.filter(|ex| match ex.target {
dynzone::ExitTarget::ExitZone => true,
_ => false
})
.map(|ex| dr.grid_coords.apply(&ex.direction))
).next();
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!("<bgblue><red>()<reset>"))
} else {
buf.push_str(dynzone.dyn_rooms.iter()
.find(
|(_, dr)| dr.grid_coords.x == x &&
dr.grid_coords.y == y &&
dr.grid_coords.z == my_loc.z)
.map(|(_, r)| r.short)
.or_else(|| main_exit.as_ref().and_then(
|ex_pos|
if ex_pos.x == x && ex_pos.y == y &&
ex_pos.z == my_loc.z {
Some("<<")
} else {
None
}))
.unwrap_or(" "));
}
}
buf.push('\n');
}
buf
}
pub async fn describe_normal_item(ctx: &VerbContext<'_>, item: &Item) -> UResult<()> { pub async fn describe_normal_item(ctx: &VerbContext<'_>, item: &Item) -> UResult<()> {
let mut contents_desc = String::new(); let mut contents_desc = String::new();

View File

@ -11,6 +11,84 @@ use crate::{
}; };
use std::sync::Arc; use std::sync::Arc;
pub fn render_map(room: &room::Room, width: usize, height: usize) -> String {
let mut buf = String::new();
let my_loc = &room.grid_coords;
let min_x = my_loc.x - (width as i64) / 2;
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 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!("<bgblue><red>()<reset>"))
} else {
buf.push_str(room::room_map_by_zloc()
.get(&(&room.zone, &room::GridCoords { x, y, z: my_loc.z }))
.map(|r| if room.zone == r.zone {
r.short
} else {
r.secondary_zones.iter()
.find(|sz| sz.zone == room.zone)
.map(|sz| sz.short)
.expect("Secondary zone missing")
})
.unwrap_or(" "));
}
}
buf.push('\n');
}
buf
}
pub fn render_map_dyn(dynzone: &dynzone::Dynzone,
dynroom: &dynzone::Dynroom,
width: usize, height: usize) -> String {
let mut buf = String::new();
let my_loc = &dynroom.grid_coords;
let min_x = my_loc.x - (width as i64) / 2;
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);
let main_exit: Option<GridCoords> = dynzone.dyn_rooms
.iter()
.flat_map(|(_, dr)|
dr.exits.iter()
.filter(|ex| match ex.target {
dynzone::ExitTarget::ExitZone => true,
_ => false
})
.map(|ex| dr.grid_coords.apply(&ex.direction))
).next();
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!("<bgblue><red>()<reset>"))
} else {
buf.push_str(dynzone.dyn_rooms.iter()
.find(
|(_, dr)| dr.grid_coords.x == x &&
dr.grid_coords.y == y &&
dr.grid_coords.z == my_loc.z)
.map(|(_, r)| r.short)
.or_else(|| main_exit.as_ref().and_then(
|ex_pos|
if ex_pos.x == x && ex_pos.y == y &&
ex_pos.z == my_loc.z {
Some("<<")
} else {
None
}))
.unwrap_or(" "));
}
}
buf.push('\n');
}
buf
}
pub fn render_lmap(room: &room::Room, width: usize, height: usize, pub fn render_lmap(room: &room::Room, width: usize, height: usize,
captions_needed: &mut Vec<(usize, &'static str, &'static str)>) -> String { captions_needed: &mut Vec<(usize, &'static str, &'static str)>) -> String {
let mut buf = String::new(); let mut buf = String::new();
@ -234,67 +312,121 @@ pub fn caption_lmap<'l>(captions: &Vec<(usize, &'l str, &'l str)>, width: usize,
buf buf
} }
pub async fn lmap_room(ctx: &VerbContext<'_>, #[async_trait]
room: &room::Room) -> UResult<()> { trait MapType {
let mut captions: Vec<(usize, &'static str, &'static str)> = Vec::new(); async fn map_room(&self, ctx: &VerbContext<'_>,
ctx.trans.queue_for_session( room: &room::Room) -> UResult<()>;
ctx.session, async fn map_room_dyn<'a>(
Some(&flow_around(&render_lmap(room, 9, 7, &mut captions), 45, ansi!("<reset> "), &self,
&caption_lmap(&captions, 14, 27), 31 ctx: &VerbContext<'_>,
)) zone: &'a dynzone::Dynzone,
).await?; room: &'a dynzone::Dynroom,
Ok(()) zoneref: &str
) -> UResult<()>;
} }
pub async fn lmap_room_dyn<'a>( pub struct LmapType;
ctx: &VerbContext<'_>,
zone: &'a dynzone::Dynzone, #[async_trait]
room: &'a dynzone::Dynroom, impl MapType for LmapType {
zoneref: &str async fn map_room(&self, ctx: &VerbContext<'_>,
) -> UResult<()> { room: &room::Room) -> UResult<()> {
let mut captions: Vec<(usize, &str, &str)> = Vec::new(); let mut captions: Vec<(usize, &'static str, &'static str)> = Vec::new();
let connectwhere_name_opt: Option<String> = match zoneref.split_once("/") { ctx.trans.queue_for_session(
None => None, ctx.session,
Some((zone_t, zone_c)) => { Some(&flow_around(&render_lmap(room, 9, 7, &mut captions), 45, ansi!("<reset> "),
let zone_item: Option<Arc<Item>> = ctx.trans.find_item_by_type_code(zone_t, zone_c).await?; &caption_lmap(&captions, 14, 27), 31
match zone_item.as_ref().map(|v| v.as_ref()) { ))
Some(Item { special_data: Some(ItemSpecialData::DynzoneData { zone_exit: Some(zone_exit), ..}), ).await?;
..}) => Ok(())
match zone_exit.split_once("/") { }
None => None,
Some((ex_t, ex_c)) => async fn map_room_dyn<'a>(
match ctx.trans.find_item_by_type_code(ex_t, ex_c).await?.as_ref() { &self,
Some(dest_item) => Some( ctx: &VerbContext<'_>,
dest_item.display_for_sentence( zone: &'a dynzone::Dynzone,
!ctx.session_dat.less_explicit_mode, 1, true room: &'a dynzone::Dynroom,
)), zoneref: &str
None => None ) -> UResult<()> {
} let mut captions: Vec<(usize, &str, &str)> = Vec::new();
}, let connectwhere_name_opt: Option<String> = match zoneref.split_once("/") {
_ => None, None => None,
Some((zone_t, zone_c)) => {
let zone_item: Option<Arc<Item>> = ctx.trans.find_item_by_type_code(zone_t, zone_c).await?;
match zone_item.as_ref().map(|v| v.as_ref()) {
Some(Item { special_data: Some(ItemSpecialData::DynzoneData { zone_exit: Some(zone_exit), ..}),
..}) =>
match zone_exit.split_once("/") {
None => None,
Some((ex_t, ex_c)) =>
match ctx.trans.find_item_by_type_code(ex_t, ex_c).await?.as_ref() {
Some(dest_item) => Some(
dest_item.display_for_sentence(
!ctx.session_dat.less_explicit_mode, 1, true
)),
None => None
}
},
_ => None,
}
} }
} };
}; let lmap_str =
let lmap_str = render_lmap_dynroom(zone, room, 9, 7, &mut captions,
render_lmap_dynroom(zone, room, 9, 7, &mut captions, connectwhere_name_opt.as_ref().map(|v| v.as_str()));
connectwhere_name_opt.as_ref().map(|v| v.as_str())); ctx.trans.queue_for_session(
ctx.trans.queue_for_session( ctx.session,
ctx.session, Some(&flow_around(&lmap_str,
Some(&flow_around(&lmap_str, 45, ansi!("<reset> "),
45, ansi!("<reset> "), &caption_lmap(&captions, 14, 27), 31
&caption_lmap(&captions, 14, 27), 31 ))
)) ).await?;
).await?; Ok(())
Ok(()) }
}
pub struct GmapType;
#[async_trait]
impl MapType for GmapType {
async fn map_room(&self, ctx: &VerbContext<'_>,
room: &room::Room) -> UResult<()> {
ctx.trans.queue_for_session(
ctx.session,
Some(&render_map(room, 16, 9))
).await?;
Ok(())
}
async fn map_room_dyn<'a>(
&self,
ctx: &VerbContext<'_>,
zone: &'a dynzone::Dynzone,
room: &'a dynzone::Dynroom,
_zoneref: &str
) -> UResult<()> {
ctx.trans.queue_for_session(
ctx.session,
Some(&render_map_dyn(zone, room, 16, 9))
).await?;
Ok(())
}
} }
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<()> {
if remaining.trim() != "" { if remaining.trim() != "" {
user_error("map commands don't take anything after them".to_owned())?; user_error("map commands don't take anything after them".to_owned())?;
} }
let map_type: Box<dyn MapType + Sync + Send> = match verb {
"lmap" | "lm" => Box::new(LmapType),
"gmap" | "gm" => Box::new(GmapType),
_ => user_error("I don't know how to show that map type.".to_owned())?
};
let player_item = get_player_item_or_fail(ctx).await?; let player_item = get_player_item_or_fail(ctx).await?;
let (heretype, herecode) = player_item.location.split_once("/").unwrap_or(("room", "repro_xv_chargen")); let (heretype, herecode) = player_item.location.split_once("/").unwrap_or(("room", "repro_xv_chargen"));
let room_item: Arc<Item> = ctx.trans.find_item_by_type_code(heretype, herecode).await? let room_item: Arc<Item> = ctx.trans.find_item_by_type_code(heretype, herecode).await?
@ -303,7 +435,7 @@ impl UserVerb for Verb {
let room = let room =
room::room_map_by_code().get(room_item.item_code.as_str()) room::room_map_by_code().get(room_item.item_code.as_str())
.ok_or_else(|| UserError("Sorry, that room no longer exists".to_owned()))?; .ok_or_else(|| UserError("Sorry, that room no longer exists".to_owned()))?;
lmap_room(ctx, &room).await?; map_type.map_room(ctx, &room).await?;
} else if room_item.item_type == "dynroom" { } else if room_item.item_type == "dynroom" {
let (dynzone, dynroom) = match &room_item.special_data { let (dynzone, dynroom) = match &room_item.special_data {
Some(ItemSpecialData::DynroomData { dynzone_code, dynroom_code }) => { Some(ItemSpecialData::DynroomData { dynzone_code, dynroom_code }) => {
@ -315,7 +447,7 @@ impl UserVerb for Verb {
}, },
_ => user_error("Expected dynroom to have DynroomData".to_owned())? _ => user_error("Expected dynroom to have DynroomData".to_owned())?
}; };
lmap_room_dyn(ctx, &dynzone, &dynroom, &room_item.location).await?; map_type.map_room_dyn(ctx, &dynzone, &dynroom, &room_item.location).await?;
} else { } else {
user_error("Can't map here".to_owned())?; user_error("Can't map here".to_owned())?;
} }