Implement a climbing system.
This commit is contained in:
parent
078519be95
commit
79b0ed8540
@ -132,7 +132,12 @@ pub async fn describe_normal_item(ctx: &VerbContext<'_>, item: &Item) -> UResult
|
||||
|
||||
fn exits_for(room: &room::Room) -> String {
|
||||
let exit_text: Vec<String> =
|
||||
room.exits.iter().map(|ex| format!(ansi!("<yellow>{}"),
|
||||
room.exits.iter().map(|ex| format!("{}{}",
|
||||
if ex.exit_climb.is_some() {
|
||||
ansi!("<red>^")
|
||||
} else {
|
||||
ansi!("<yellow>")
|
||||
},
|
||||
ex.direction.describe())).collect();
|
||||
format!(ansi!("<cyan>[ Exits: <bold>{} <reset><cyan>]<reset>"), exit_text.join(" "))
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ use crate::{
|
||||
queue_command
|
||||
},
|
||||
static_content::{
|
||||
room::{self, Direction, ExitType},
|
||||
room::{self, Direction, ExitType, ExitClimb, MaterialType},
|
||||
dynzone::{dynzone_by_type, ExitTarget as DynExitTarget, DynzoneType},
|
||||
},
|
||||
models::{
|
||||
@ -24,14 +24,18 @@ use crate::{
|
||||
SkillType,
|
||||
LocationActionType,
|
||||
DoorState,
|
||||
ActiveClimb,
|
||||
},
|
||||
consent::ConsentType,
|
||||
},
|
||||
services::{
|
||||
comms::broadcast_to_room,
|
||||
skills::skill_check_and_grind,
|
||||
combat::stop_attacking_mut,
|
||||
combat::handle_resurrect,
|
||||
combat::{
|
||||
stop_attacking_mut,
|
||||
handle_resurrect,
|
||||
change_health
|
||||
},
|
||||
check_consent,
|
||||
}
|
||||
};
|
||||
@ -40,6 +44,7 @@ use mockall_double::double;
|
||||
#[double] use crate::db::DBTrans;
|
||||
use std::time;
|
||||
use ansi::ansi;
|
||||
use rand_distr::{Normal, Distribution};
|
||||
|
||||
pub async fn announce_move(trans: &DBTrans, character: &Item, leaving: &Item, arriving: &Item) -> DResult<()> {
|
||||
let msg_leaving_exp = format!("{} departs towards {}\n",
|
||||
@ -69,14 +74,14 @@ async fn move_to_where(
|
||||
trans: &DBTrans,
|
||||
use_location: &str,
|
||||
direction: &Direction,
|
||||
mover: &mut Item,
|
||||
mover_for_exit_check: Option<&mut Item>,
|
||||
player_ctx: &mut Option<&mut VerbContext<'_>>
|
||||
) -> UResult<(String, Option<Item>)> {
|
||||
) -> UResult<(String, Option<Item>, Option<&'static ExitClimb>)> {
|
||||
// Firstly check dynamic exits, since they apply to rooms and dynrooms...
|
||||
if let Some(dynroom_result) = trans.find_exact_dyn_exit(use_location, direction).await? {
|
||||
return Ok((format!("{}/{}",
|
||||
&dynroom_result.item_type,
|
||||
&dynroom_result.item_code), Some(dynroom_result)));
|
||||
&dynroom_result.item_code), Some(dynroom_result), None));
|
||||
}
|
||||
|
||||
let (heretype, herecode) = use_location.split_once("/").unwrap_or(("room", "repro_xv_chargen"));
|
||||
@ -109,12 +114,12 @@ async fn move_to_where(
|
||||
Some(ItemSpecialData::DynzoneData { zone_exit: Some(zone_exit), .. }) => zone_exit,
|
||||
_ => user_error("The zone you are in has invalid data associated with it".to_owned())?,
|
||||
};
|
||||
Ok((zone_exit.to_string(), None))
|
||||
Ok((zone_exit.to_string(), None, None))
|
||||
},
|
||||
DynExitTarget::Intrazone { subcode } => {
|
||||
let to_item = trans.find_item_by_location_dynroom_code(&old_dynroom_item.location, &subcode).await?
|
||||
.ok_or_else(|| UserError("Can't find the room in that direction.".to_owned()))?;
|
||||
Ok((format!("{}/{}", &to_item.item_type, &to_item.item_code), Some(to_item)))
|
||||
Ok((format!("{}/{}", &to_item.item_type, &to_item.item_code), Some(to_item), None))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -129,9 +134,11 @@ async fn move_to_where(
|
||||
match exit.exit_type {
|
||||
ExitType::Free => {}
|
||||
ExitType::Blocked(blocker) => {
|
||||
if let Some(ctx) = player_ctx {
|
||||
if !blocker.attempt_exit(*ctx, mover, exit).await? {
|
||||
user_error("Stopping movement".to_owned())?;
|
||||
if let Some(mover) = mover_for_exit_check {
|
||||
if let Some(ctx) = player_ctx {
|
||||
if !blocker.attempt_exit(*ctx, mover, exit).await? {
|
||||
user_error("Stopping movement".to_owned())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -139,7 +146,7 @@ async fn move_to_where(
|
||||
|
||||
let new_room =
|
||||
room::resolve_exit(room, exit).ok_or_else(|| UserError("Can't find that room".to_owned()))?;
|
||||
Ok((format!("room/{}", new_room.code), None))
|
||||
Ok((format!("room/{}", new_room.code), None, exit.exit_climb.as_ref()))
|
||||
}
|
||||
|
||||
pub async fn check_room_access(trans: &DBTrans, player: &Item, room: &Item) -> UResult<()> {
|
||||
@ -173,6 +180,60 @@ pub async fn check_room_access(trans: &DBTrans, player: &Item, room: &Item) -> U
|
||||
the owner here.").to_owned())?
|
||||
}
|
||||
|
||||
pub async fn handle_fall(
|
||||
trans: &DBTrans,
|
||||
faller: &mut Item,
|
||||
fall_dist: u64
|
||||
) -> UResult<String> {
|
||||
// TODO depend on distance, armour, etc...
|
||||
// This is deliberately less damage than real life for the distance,
|
||||
// since we'll assume the wristpad provides reflexes to buffer some damage.
|
||||
|
||||
let damage_modifier = match faller.location.split_once("/") {
|
||||
Some((ltype, lcode)) if ltype == "room" => {
|
||||
match room::room_map_by_code().get(lcode) {
|
||||
None => 1.0,
|
||||
Some(room) => match room.material_type {
|
||||
MaterialType::WaterSurface | MaterialType::Underwater => {
|
||||
return Ok("lands with a splash".to_owned());
|
||||
},
|
||||
MaterialType::Soft { damage_modifier } => damage_modifier,
|
||||
MaterialType::Normal => 1.0
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => 1.0
|
||||
};
|
||||
|
||||
let modified_safe_distance = 5.0 / damage_modifier;
|
||||
if (fall_dist as f64) < modified_safe_distance {
|
||||
return Ok("lands softly".to_owned());
|
||||
}
|
||||
// The force is proportional to the square root of the fall distance.
|
||||
let damage = ((fall_dist as f64 - modified_safe_distance).sqrt() * 3.0 * damage_modifier *
|
||||
Normal::new(1.0, 0.3)?
|
||||
.sample(&mut rand::thread_rng())) as i64;
|
||||
|
||||
if damage > 0 {
|
||||
change_health(trans, -damage, faller, "You fell", "You fell").await?;
|
||||
}
|
||||
|
||||
let descriptor = if damage >= 30 {
|
||||
"smashes violently into the ground like a comet with a massive boom"
|
||||
} else if damage >= 25 {
|
||||
"smashes violently into the ground with a very loud bang"
|
||||
} else if damage >= 20 {
|
||||
"smashes violently into the ground with a loud bang"
|
||||
} else if damage >= 15 {
|
||||
"smashes into the ground with a loud thump"
|
||||
} else if damage >= 10 {
|
||||
"smashes into the ground with a thump"
|
||||
} else {
|
||||
"lands with a thump"
|
||||
};
|
||||
Ok(descriptor.to_owned())
|
||||
}
|
||||
|
||||
pub async fn attempt_move_immediate(
|
||||
trans: &DBTrans,
|
||||
orig_mover: &Item,
|
||||
@ -214,58 +275,177 @@ pub async fn attempt_move_immediate(
|
||||
}
|
||||
|
||||
let mut mover = (*orig_mover).clone();
|
||||
let (new_loc, new_loc_item) = move_to_where(trans, use_location, direction, &mut mover, &mut player_ctx).await?;
|
||||
let (new_loc, new_loc_item, climb_opt) =
|
||||
move_to_where(trans, use_location, direction, Some(&mut mover), &mut player_ctx).await?;
|
||||
|
||||
match mover.active_combat.as_ref().and_then(|ac| ac.attacking.clone()) {
|
||||
None => {}
|
||||
Some(old_victim) => {
|
||||
if let Some((vcode, vtype)) = old_victim.split_once("/") {
|
||||
if let Some(vitem) = trans.find_item_by_type_code(vcode, vtype).await? {
|
||||
let mut vitem_mut = (*vitem).clone();
|
||||
stop_attacking_mut(trans, &mut mover, &mut vitem_mut, false).await?;
|
||||
trans.save_item_model(&vitem_mut).await?
|
||||
let mut skip_escape_check: bool = false;
|
||||
let mut escape_check_only: bool = false;
|
||||
|
||||
if let Some(climb) = climb_opt {
|
||||
// We need to think about if NPCs climb at all.
|
||||
if let Some(ctx) = player_ctx {
|
||||
if let Some(active_climb) = mover.active_climb.clone() {
|
||||
skip_escape_check = true; // Already done if we get here.
|
||||
let skills = skill_check_and_grind(trans, &mut mover, &SkillType::Climb,
|
||||
climb.difficulty as f64).await?;
|
||||
let mut narrative = String::new();
|
||||
if skills <= -0.25 {
|
||||
// Crit fail - they have fallen.
|
||||
let (fall_dist, from_room, to_room) = if climb.height < 0 {
|
||||
// At least they get to where they want to go!
|
||||
mover.location = new_loc.clone();
|
||||
(climb.height.abs() as u64 - active_climb.height, new_loc.to_owned(), use_location.to_owned())
|
||||
} else {
|
||||
(active_climb.height, use_location.to_owned(), new_loc.to_owned())
|
||||
};
|
||||
mover.active_climb = None;
|
||||
let descriptor = handle_fall(&trans, &mut mover, fall_dist).await?;
|
||||
let msg_exp = format!(
|
||||
"{} loses {} grip from {} metres up and {}!\n",
|
||||
&mover.display_for_sentence(true, 1, true),
|
||||
&mover.pronouns.possessive,
|
||||
fall_dist,
|
||||
&descriptor
|
||||
);
|
||||
let msg_nonexp = format!(
|
||||
"{} loses {} grip from {} metres up and {}!\n",
|
||||
&mover.display_for_sentence(true, 1, false),
|
||||
&mover.pronouns.possessive,
|
||||
fall_dist,
|
||||
&descriptor
|
||||
);
|
||||
trans.save_item_model(&mover).await?;
|
||||
broadcast_to_room(&trans, &from_room,
|
||||
None, &msg_exp, Some(&msg_nonexp)).await?;
|
||||
broadcast_to_room(&trans, &to_room,
|
||||
None, &msg_exp, Some(&msg_nonexp)).await?;
|
||||
ctx.session_dat.queue.truncate(0);
|
||||
return Ok(());
|
||||
} else if skills <= 0.0 {
|
||||
if climb.height >= 0 {
|
||||
narrative.push_str("You lose your grip and slide a metre back down");
|
||||
} else {
|
||||
narrative.push_str("You struggle to find a foothold and reluctantly climb a metre back up");
|
||||
}
|
||||
if let Some(ac) = mover.active_climb.as_mut() {
|
||||
if ac.height > 0 {
|
||||
ac.height -= 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if climb.height < 0 {
|
||||
narrative.push_str("You climb down another metre");
|
||||
} else {
|
||||
narrative.push_str("You climb up another metre");
|
||||
}
|
||||
if let Some(ac) = mover.active_climb.as_mut() {
|
||||
ac.height += 1;
|
||||
}
|
||||
}
|
||||
if let Some(ac) = mover.active_climb.as_ref() {
|
||||
if climb.height >= 0 && ac.height >= climb.height as u64 {
|
||||
trans.queue_for_session(&ctx.session,
|
||||
Some("You brush yourself off and finish climbing - you \
|
||||
made it to the top!\n")).await?;
|
||||
mover.active_climb = None;
|
||||
} else if climb.height < 0 && ac.height >= (-climb.height) as u64 {
|
||||
trans.queue_for_session(&ctx.session,
|
||||
Some("You brush yourself off and finish climbing - you \
|
||||
made it down!\n")).await?;
|
||||
mover.active_climb = None;
|
||||
} else {
|
||||
let progress_quant = (((ac.height as f64) / (climb.height.abs() as f64)) * 10.0) as u64;
|
||||
trans.queue_for_session(
|
||||
&ctx.session,
|
||||
Some(&format!(ansi!("<bold>[<reset><cyan>{}{}<reset><bold>] [<reset>{}/{} m<bold>]<reset> {}\n"),
|
||||
"=".repeat(progress_quant as usize), " ".repeat((10 - progress_quant) as usize),
|
||||
ac.height, climb.height.abs(), &narrative
|
||||
))).await?;
|
||||
ctx.session_dat.queue.push_front(
|
||||
QueueCommand::Movement { direction: direction.clone() }
|
||||
);
|
||||
trans.save_item_model(&mover).await?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let msg_exp = format!("{} starts climbing {}\n",
|
||||
&orig_mover.display_for_sentence(true, 1, true),
|
||||
&direction.describe_climb(if climb.height > 0 { "up" } else { "down" }));
|
||||
let msg_nonexp = format!("{} starts climbing {}\n",
|
||||
&orig_mover.display_for_sentence(true, 1, false),
|
||||
&direction.describe_climb(if climb.height > 0 { "up" } else { "down" }));
|
||||
broadcast_to_room(&trans, &use_location,
|
||||
None, &msg_exp, Some(&msg_nonexp)).await?;
|
||||
|
||||
mover.active_climb = Some(ActiveClimb { ..Default::default() });
|
||||
|
||||
ctx.session_dat.queue.push_front(
|
||||
QueueCommand::Movement { direction: direction.clone() }
|
||||
);
|
||||
escape_check_only = true;
|
||||
}
|
||||
} else {
|
||||
user_error("NPC climbing not supported yet.".to_owned())?;
|
||||
}
|
||||
}
|
||||
|
||||
if !skip_escape_check {
|
||||
match mover.active_combat.as_ref().and_then(|ac| ac.attacking.clone()) {
|
||||
None => {}
|
||||
Some(old_victim) => {
|
||||
if let Some((vcode, vtype)) = old_victim.split_once("/") {
|
||||
if let Some(vitem) = trans.find_item_by_type_code(vcode, vtype).await? {
|
||||
let mut vitem_mut = (*vitem).clone();
|
||||
stop_attacking_mut(trans, &mut mover, &mut vitem_mut, false).await?;
|
||||
trans.save_item_model(&vitem_mut).await?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
match mover.active_combat.clone().as_ref().map(|ac| &ac.attacked_by[..]) {
|
||||
None | Some([]) => {}
|
||||
Some(attackers) => {
|
||||
let mut attacker_names = Vec::new();
|
||||
let mut attacker_items = Vec::new();
|
||||
if let Some(ctx) = player_ctx.as_ref() {
|
||||
for attacker in &attackers[..] {
|
||||
if let Some((acode, atype)) = attacker.split_once("/") {
|
||||
if let Some(aitem) = trans.find_item_by_type_code(acode, atype).await? {
|
||||
attacker_names.push(aitem.display_for_session(ctx.session_dat));
|
||||
attacker_items.push(aitem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let attacker_names_ref = attacker_names.iter().map(|n| n.as_str()).collect::<Vec<&str>>();
|
||||
let attacker_names_str = language::join_words(&attacker_names_ref[..]);
|
||||
if skill_check_and_grind(trans, &mut mover, &SkillType::Dodge, attackers.len() as f64 + 8.0).await? >= 0.0 {
|
||||
if let Some(ctx) = player_ctx.as_ref() {
|
||||
trans.queue_for_session(ctx.session,
|
||||
Some(&format!("You successfully get away from {}\n",
|
||||
&attacker_names_str))).await?;
|
||||
}
|
||||
for item in &attacker_items[..] {
|
||||
let mut item_mut = (**item).clone();
|
||||
stop_attacking_mut(trans, &mut item_mut, &mut mover, true).await?;
|
||||
trans.save_item_model(&item_mut).await?;
|
||||
}
|
||||
} else {
|
||||
if let Some(ctx) = player_ctx.as_ref() {
|
||||
trans.queue_for_session(ctx.session,
|
||||
Some(&format!("You try and fail to run past {}\n",
|
||||
&attacker_names_str))).await?;
|
||||
}
|
||||
trans.save_item_model(&mover).await?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
match mover.active_combat.clone().as_ref().map(|ac| &ac.attacked_by[..]) {
|
||||
None | Some([]) => {}
|
||||
Some(attackers) => {
|
||||
let mut attacker_names = Vec::new();
|
||||
let mut attacker_items = Vec::new();
|
||||
if let Some(ctx) = player_ctx.as_ref() {
|
||||
for attacker in &attackers[..] {
|
||||
if let Some((acode, atype)) = attacker.split_once("/") {
|
||||
if let Some(aitem) = trans.find_item_by_type_code(acode, atype).await? {
|
||||
attacker_names.push(aitem.display_for_session(ctx.session_dat));
|
||||
attacker_items.push(aitem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let attacker_names_ref = attacker_names.iter().map(|n| n.as_str()).collect::<Vec<&str>>();
|
||||
let attacker_names_str = language::join_words(&attacker_names_ref[..]);
|
||||
if skill_check_and_grind(trans, &mut mover, &SkillType::Dodge, attackers.len() as f64 + 8.0).await? >= 0.0 {
|
||||
if let Some(ctx) = player_ctx.as_ref() {
|
||||
trans.queue_for_session(ctx.session,
|
||||
Some(&format!("You successfully get away from {}\n",
|
||||
&attacker_names_str))).await?;
|
||||
}
|
||||
for item in &attacker_items[..] {
|
||||
let mut item_mut = (**item).clone();
|
||||
stop_attacking_mut(trans, &mut item_mut, &mut mover, true).await?;
|
||||
trans.save_item_model(&item_mut).await?;
|
||||
}
|
||||
} else {
|
||||
if let Some(ctx) = player_ctx.as_ref() {
|
||||
trans.queue_for_session(ctx.session,
|
||||
Some(&format!("You try and fail to run past {}\n",
|
||||
&attacker_names_str))).await?;
|
||||
}
|
||||
trans.save_item_model(&mover).await?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
if escape_check_only {
|
||||
trans.save_item_model(&mover).await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if mover.death_data.is_some() {
|
||||
@ -304,7 +484,7 @@ pub struct QueueHandler;
|
||||
#[async_trait]
|
||||
impl QueueCommandHandler for QueueHandler {
|
||||
async fn start_command(&self, _ctx: &mut VerbContext<'_>, _command: &QueueCommand)
|
||||
-> UResult<time::Duration> {
|
||||
-> UResult<time::Duration> {
|
||||
Ok(time::Duration::from_secs(1))
|
||||
}
|
||||
|
||||
|
@ -307,6 +307,20 @@ impl Default for ActiveCombat {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
#[serde(default)]
|
||||
pub struct ActiveClimb {
|
||||
pub height: u64
|
||||
}
|
||||
|
||||
impl Default for ActiveClimb {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
height: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)]
|
||||
pub enum ItemSpecialData {
|
||||
ItemWriting { text: String },
|
||||
@ -382,6 +396,7 @@ pub struct Item {
|
||||
pub flags: Vec<ItemFlag>,
|
||||
pub sex: Option<Sex>,
|
||||
pub active_combat: Option<ActiveCombat>,
|
||||
pub active_climb: Option<ActiveClimb>,
|
||||
pub weight: u64,
|
||||
pub charges: u8,
|
||||
pub special_data: Option<ItemSpecialData>,
|
||||
@ -466,6 +481,7 @@ impl Default for Item {
|
||||
flags: vec!(),
|
||||
sex: None,
|
||||
active_combat: Some(Default::default()),
|
||||
active_climb: None,
|
||||
weight: 0,
|
||||
charges: 0,
|
||||
special_data: None,
|
||||
|
@ -27,7 +27,7 @@ pub fn npc_list() -> Vec<NPC> {
|
||||
code: concat!("melbs_citizen_", $code),
|
||||
name: $name,
|
||||
pronouns: $pronouns,
|
||||
description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harst reality of post-apocalyptic life",
|
||||
description: "A fairly ordinary looking citizen of Melbs, clearly weary from the harsh reality of post-apocalyptic life",
|
||||
spawn_location: concat!("room/melbs_", $spawn),
|
||||
message_handler: None,
|
||||
wander_zones: vec!("melbs"),
|
||||
|
@ -17,6 +17,7 @@ mod special;
|
||||
mod repro_xv;
|
||||
mod melbs;
|
||||
mod cok_murl;
|
||||
mod chonkers;
|
||||
|
||||
pub struct Zone {
|
||||
pub code: &'static str,
|
||||
@ -36,10 +37,13 @@ pub fn zone_details() -> &'static BTreeMap<&'static str, Zone> {
|
||||
outdoors: true },
|
||||
Zone { code: "repro_xv",
|
||||
display: "Reprolabs XV",
|
||||
outdoors: true },
|
||||
outdoors: false },
|
||||
Zone { code: "cok_murl",
|
||||
display: "CoK-Murlison Complex",
|
||||
outdoors: true },
|
||||
outdoors: false },
|
||||
Zone { code: "chonkers",
|
||||
display: "Chonker's Gym",
|
||||
outdoors: false },
|
||||
).into_iter().map(|x|(x.code, x)).collect())
|
||||
}
|
||||
|
||||
@ -132,6 +136,22 @@ impl Direction {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn describe_climb(self: &Self, climb_dir: &str) -> String {
|
||||
match self {
|
||||
Direction::NORTH => format!("{} to the north", climb_dir),
|
||||
Direction::SOUTH => format!("{} to the south", climb_dir),
|
||||
Direction::EAST => format!("{} to the east", climb_dir),
|
||||
Direction::WEST => format!("{} to the west", climb_dir),
|
||||
Direction::NORTHEAST => format!("{} to the northeast", climb_dir),
|
||||
Direction::SOUTHEAST => format!("{} to the southeast", climb_dir),
|
||||
Direction::NORTHWEST => format!("{} to the northwest", climb_dir),
|
||||
Direction::SOUTHWEST => format!("{} to the southwest", climb_dir),
|
||||
Direction::UP => "upwards".to_owned(),
|
||||
Direction::DOWN => "downwards".to_owned(),
|
||||
Direction::IN { item } => format!("{} and in ", item)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(input: &str) -> Option<Direction> {
|
||||
if input.starts_with("in ") {
|
||||
return Some(Direction::IN { item: input["in ".len()..].trim().to_owned() })
|
||||
@ -174,10 +194,28 @@ pub enum ExitTarget {
|
||||
Custom(&'static str)
|
||||
}
|
||||
|
||||
pub struct ExitClimb {
|
||||
// Negative if it is down.
|
||||
pub height: i64,
|
||||
pub difficulty: i64,
|
||||
}
|
||||
|
||||
pub struct Exit {
|
||||
pub direction: Direction,
|
||||
pub target: ExitTarget,
|
||||
pub exit_type: ExitType,
|
||||
pub exit_climb: Option<ExitClimb>,
|
||||
}
|
||||
|
||||
impl Default for Exit {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
direction: Direction::NORTH,
|
||||
target: ExitTarget::UseGPS,
|
||||
exit_type: ExitType::Free,
|
||||
exit_climb: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SecondaryZoneRecord {
|
||||
@ -208,6 +246,14 @@ pub struct RentInfo {
|
||||
pub setup_fee: u64,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub enum MaterialType {
|
||||
Normal,
|
||||
WaterSurface,
|
||||
Underwater,
|
||||
Soft { damage_modifier: f64 }
|
||||
}
|
||||
|
||||
pub struct Room {
|
||||
pub zone: &'static str,
|
||||
// Other zones where it can be seen on the map and accessed.
|
||||
@ -225,7 +271,8 @@ pub struct Room {
|
||||
// Empty means not a shop.
|
||||
pub stock_list: Vec<RoomStock>,
|
||||
// What can be rented here...
|
||||
pub rentable_dynzone: Vec<RentInfo>
|
||||
pub rentable_dynzone: Vec<RentInfo>,
|
||||
pub material_type: MaterialType
|
||||
}
|
||||
|
||||
impl Default for Room {
|
||||
@ -244,7 +291,8 @@ impl Default for Room {
|
||||
repel_npc: false,
|
||||
item_flags: vec!(),
|
||||
stock_list: vec!(),
|
||||
rentable_dynzone: vec!()
|
||||
rentable_dynzone: vec!(),
|
||||
material_type: MaterialType::Normal,
|
||||
}
|
||||
}
|
||||
|
||||
@ -257,6 +305,7 @@ pub fn room_list() -> &'static Vec<Room> {
|
||||
let mut rooms = repro_xv::room_list();
|
||||
rooms.append(&mut melbs::room_list());
|
||||
rooms.append(&mut cok_murl::room_list());
|
||||
rooms.append(&mut chonkers::room_list());
|
||||
rooms.append(&mut special::room_list());
|
||||
rooms.into_iter().collect()
|
||||
})
|
||||
|
87
blastmud_game/src/static_content/room/chonkers.rs
Normal file
87
blastmud_game/src/static_content/room/chonkers.rs
Normal file
@ -0,0 +1,87 @@
|
||||
use super::{
|
||||
Room, GridCoords, Exit, Direction, ExitTarget, MaterialType,
|
||||
SecondaryZoneRecord, ExitClimb
|
||||
};
|
||||
use ansi::ansi;
|
||||
pub fn room_list() -> Vec<Room> {
|
||||
vec!(
|
||||
Room {
|
||||
zone: "chonkers",
|
||||
secondary_zones: vec!(
|
||||
SecondaryZoneRecord {
|
||||
zone: "melbs",
|
||||
short: ansi!("<bgyellow><blue>CG<reset>"),
|
||||
grid_coords: GridCoords { x: 8, y: 2, z: 0 },
|
||||
caption: Some("Chonker's Gym")
|
||||
}
|
||||
),
|
||||
code: "chonkers_strength_hall",
|
||||
name: "Strength Hall",
|
||||
short: ansi!("<bgblack><white>SH<reset>"),
|
||||
description: ansi!("The first of several adjoined rooms making up Chonker's Gym, this space seems to be focused on strength training, and it exudes energy, filled with the invigorating scent of sweat and determination. The proprietor, Chonkers, stands at the center, a fitness enthusiast with a chiseled physique. Mirrors line the walls, reflecting the efforts of gym-goers as they push their limits. The atmosphere is charged with determination and camaraderie, accompanied by the rhythmic beat of energetic music. To the north, you notice a climbing wall towering over another room"),
|
||||
description_less_explicit: None,
|
||||
grid_coords: GridCoords { x: 0, y: 0, z: 0 },
|
||||
exits: vec!(
|
||||
Exit {
|
||||
direction: Direction::SOUTH,
|
||||
target: ExitTarget::Custom("room/melbs_bourkest_160"),
|
||||
..Default::default()
|
||||
},
|
||||
Exit {
|
||||
direction: Direction::NORTH,
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
should_caption: true,
|
||||
..Default::default()
|
||||
},
|
||||
Room {
|
||||
zone: "chonkers",
|
||||
code: "chonkers_endurance_hall",
|
||||
name: "Endurance Hall",
|
||||
short: ansi!("<bgblack><white>EH<reset>"),
|
||||
description: ansi!("A room that appears to be dedicated to endurance training. This space exudes a sense of purpose, with its focus on building stamina and resilience. At the northern edge of the room stands a towering climbing wall, inviting enthusiasts to conquer its challenging heights. The walls are adorned with motivational posters, inspiring climbers to push beyond their limits and embrace the thrill of conquering obstacles. Surrounding the climbing wall, a variety of endurance-focused machines beckon, ready to test and strengthen the resolve of those who dare to challenge themselves. The air hums with the sounds of exertion and determination, creating an atmosphere charged with the energy of individuals striving to improve their endurance and surpass their previous achievements"),
|
||||
description_less_explicit: None,
|
||||
grid_coords: GridCoords { x: 0, y: -1, z: 0 },
|
||||
exits: vec!(
|
||||
Exit {
|
||||
direction: Direction::SOUTH,
|
||||
..Default::default()
|
||||
},
|
||||
Exit {
|
||||
direction: Direction::UP,
|
||||
exit_climb: Some(ExitClimb {
|
||||
height: 5,
|
||||
difficulty: 11
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
material_type: MaterialType::Soft { damage_modifier: 0.5 },
|
||||
should_caption: true,
|
||||
..Default::default()
|
||||
},
|
||||
Room {
|
||||
zone: "chonkers",
|
||||
code: "chonkers_climbing_top",
|
||||
name: "Top of the Climbing Wall",
|
||||
short: ansi!("<bgblack><white>CW<reset>"),
|
||||
description: ansi!("Congratulations, you made it to the top! It is quite snug up here in a little alove at the top of the wall, but the view from the top of the wall is amazing; you have a 180 degree view of buff men and women pumping iron and keeping themselves fit"),
|
||||
description_less_explicit: None,
|
||||
grid_coords: GridCoords { x: 0, y: -1, z: 1 },
|
||||
exits: vec!(
|
||||
Exit {
|
||||
direction: Direction::DOWN,
|
||||
exit_climb: Some(ExitClimb {
|
||||
height: -5,
|
||||
difficulty: 11
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
material_type: MaterialType::Soft { damage_modifier: 0.5 },
|
||||
should_caption: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
use super::{
|
||||
Room, GridCoords, Exit, Direction, ExitTarget, ExitType,
|
||||
Room, GridCoords, Exit, Direction, ExitTarget,
|
||||
SecondaryZoneRecord, RentInfo,
|
||||
};
|
||||
use crate::static_content::dynzone::DynzoneType;
|
||||
@ -27,12 +27,11 @@ pub fn room_list() -> Vec<Room> {
|
||||
Exit {
|
||||
direction: Direction::WEST,
|
||||
target: ExitTarget::Custom("room/melbs_kingst_80"),
|
||||
exit_type: ExitType::Free
|
||||
..Default::default()
|
||||
},
|
||||
Exit {
|
||||
direction: Direction::EAST,
|
||||
target: ExitTarget::UseGPS,
|
||||
exit_type: ExitType::Free
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
should_caption: true,
|
||||
@ -57,18 +56,15 @@ pub fn room_list() -> Vec<Room> {
|
||||
exits: vec!(
|
||||
Exit {
|
||||
direction: Direction::WEST,
|
||||
target: ExitTarget::UseGPS,
|
||||
exit_type: ExitType::Free
|
||||
..Default::default()
|
||||
},
|
||||
Exit {
|
||||
direction: Direction::NORTH,
|
||||
target: ExitTarget::UseGPS,
|
||||
exit_type: ExitType::Free
|
||||
..Default::default()
|
||||
},
|
||||
Exit {
|
||||
direction: Direction::SOUTH,
|
||||
target: ExitTarget::UseGPS,
|
||||
exit_type: ExitType::Free
|
||||
..Default::default()
|
||||
}
|
||||
),
|
||||
should_caption: true,
|
||||
@ -85,8 +81,7 @@ pub fn room_list() -> Vec<Room> {
|
||||
exits: vec!(
|
||||
Exit {
|
||||
direction: Direction::SOUTH,
|
||||
target: ExitTarget::UseGPS,
|
||||
exit_type: ExitType::Free
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
should_caption: true,
|
||||
@ -102,9 +97,8 @@ pub fn room_list() -> Vec<Room> {
|
||||
grid_coords: GridCoords { x: 1, y: 1, z: 0 },
|
||||
exits: vec!(
|
||||
Exit {
|
||||
direction: Direction::NORTH,
|
||||
target: ExitTarget::UseGPS,
|
||||
exit_type: ExitType::Free
|
||||
direction: Direction::NORTH,
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
should_caption: true,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -28,8 +28,8 @@ pub fn room_list() -> Vec<Room> {
|
||||
grid_coords: GridCoords { x: 0, y: 0, z: -1 },
|
||||
exits: vec!(Exit {
|
||||
direction: Direction::EAST,
|
||||
target: ExitTarget::UseGPS,
|
||||
exit_type: ExitType::Blocked(&npc::statbot::ChoiceRoomBlocker),
|
||||
..Default::default()
|
||||
}),
|
||||
should_caption: true,
|
||||
..Default::default()
|
||||
@ -47,8 +47,7 @@ pub fn room_list() -> Vec<Room> {
|
||||
grid_coords: GridCoords { x: 1, y: 0, z: -1 },
|
||||
exits: vec!(Exit {
|
||||
direction: Direction::EAST,
|
||||
target: ExitTarget::UseGPS,
|
||||
exit_type: ExitType::Free,
|
||||
..Default::default()
|
||||
}),
|
||||
should_caption: true,
|
||||
..Default::default()
|
||||
@ -68,8 +67,7 @@ pub fn room_list() -> Vec<Room> {
|
||||
grid_coords: GridCoords { x: 2, y: 0, z: -1 },
|
||||
exits: vec!(Exit {
|
||||
direction: Direction::UP,
|
||||
target: ExitTarget::UseGPS,
|
||||
exit_type: ExitType::Free
|
||||
..Default::default()
|
||||
}),
|
||||
should_caption: true,
|
||||
..Default::default()
|
||||
@ -98,7 +96,7 @@ pub fn room_list() -> Vec<Room> {
|
||||
Exit {
|
||||
direction: Direction::WEST,
|
||||
target: ExitTarget::Custom("room/melbs_kingst_50"),
|
||||
exit_type: ExitType::Free
|
||||
..Default::default()
|
||||
}),
|
||||
should_caption: true,
|
||||
..Default::default()
|
||||
|
Loading…
Reference in New Issue
Block a user