Add wristpad hack concept - enhancements to characters.
And add one hidden in the computer museum.
This commit is contained in:
parent
6dc4a870fc
commit
92d7b22921
@ -36,6 +36,7 @@ mod fire;
|
|||||||
pub mod follow;
|
pub mod follow;
|
||||||
mod gear;
|
mod gear;
|
||||||
pub mod get;
|
pub mod get;
|
||||||
|
mod hack;
|
||||||
mod help;
|
mod help;
|
||||||
pub mod hire;
|
pub mod hire;
|
||||||
mod ignore;
|
mod ignore;
|
||||||
@ -174,7 +175,7 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
|
|||||||
|
|
||||||
"gear" => gear::VERB,
|
"gear" => gear::VERB,
|
||||||
"get" => get::VERB,
|
"get" => get::VERB,
|
||||||
|
"hack" => hack::VERB,
|
||||||
"hire" => hire::VERB,
|
"hire" => hire::VERB,
|
||||||
|
|
||||||
"improv" => improvise::VERB,
|
"improv" => improvise::VERB,
|
||||||
|
96
blastmud_game/src/message_handler/user_commands/hack.rs
Normal file
96
blastmud_game/src/message_handler/user_commands/hack.rs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
use crate::{
|
||||||
|
models::user::{wristpad_hack_data, xp_to_hack_slots},
|
||||||
|
services::skills::calculate_total_stats_skills_for_user,
|
||||||
|
static_content::room::room_map_by_code,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
get_player_item_or_fail, user_error, UResult, UserError, UserVerb, UserVerbRef, VerbContext,
|
||||||
|
};
|
||||||
|
use ansi::ansi;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
pub struct Verb;
|
||||||
|
#[async_trait]
|
||||||
|
impl UserVerb for Verb {
|
||||||
|
async fn handle(
|
||||||
|
self: &Self,
|
||||||
|
ctx: &mut VerbContext,
|
||||||
|
_verb: &str,
|
||||||
|
remaining: &str,
|
||||||
|
) -> UResult<()> {
|
||||||
|
let player_item = get_player_item_or_fail(ctx).await?;
|
||||||
|
let (loc_type, loc_code) = player_item
|
||||||
|
.location
|
||||||
|
.split_once("/")
|
||||||
|
.ok_or_else(|| UserError("Your location is invalid".to_owned()))?;
|
||||||
|
if loc_type != "room" {
|
||||||
|
user_error("You can't find a hacking unit here.".to_owned())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let room = room_map_by_code()
|
||||||
|
.get(&loc_code)
|
||||||
|
.ok_or_else(|| UserError("Your location no longer exists!".to_owned()))?;
|
||||||
|
|
||||||
|
let allowed_hack = room
|
||||||
|
.wristpad_hack_allowed
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| UserError("You can't find a hacking unit here.".to_owned()))?;
|
||||||
|
let hack_data = wristpad_hack_data()
|
||||||
|
.get(allowed_hack)
|
||||||
|
.ok_or_else(|| UserError("The hacking unit is currently broken.".to_owned()))?;
|
||||||
|
if hack_data.name.to_lowercase() != remaining.trim() {
|
||||||
|
user_error(format!(
|
||||||
|
ansi!("The equipment here only allows you to <bold>hack {}<reset>"),
|
||||||
|
&hack_data.name
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = ctx
|
||||||
|
.user_dat
|
||||||
|
.as_mut()
|
||||||
|
.ok_or(UserError("Please log in first".to_owned()))?;
|
||||||
|
|
||||||
|
let slots_available = xp_to_hack_slots(player_item.total_xp) as usize;
|
||||||
|
let slots_used = user.wristpad_hacks.len();
|
||||||
|
if slots_used >= slots_available {
|
||||||
|
user_error(format!(
|
||||||
|
"Your wristpad crashes and reboots, flashing up an error that \
|
||||||
|
there was no space to install the hack. [You only have {} slots \
|
||||||
|
total on your wristpad to install hacks - try getting some \
|
||||||
|
more experience to earn more]",
|
||||||
|
slots_available
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.wristpad_hacks.contains(&allowed_hack) {
|
||||||
|
user_error(
|
||||||
|
"Your wristpad crashes and reboots, flashing up an error that \
|
||||||
|
the same hack was already found on the device."
|
||||||
|
.to_owned(),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
user.wristpad_hacks.push(allowed_hack.clone());
|
||||||
|
ctx.trans.save_user_model(&user).await?;
|
||||||
|
|
||||||
|
let mut player_mut = (*player_item).clone();
|
||||||
|
calculate_total_stats_skills_for_user(&mut player_mut, user);
|
||||||
|
ctx.trans.save_item_model(&player_mut).await?;
|
||||||
|
|
||||||
|
ctx.trans
|
||||||
|
.queue_for_session(
|
||||||
|
&ctx.session,
|
||||||
|
Some(&format!(
|
||||||
|
"Your wristpad beeps and reboots. You notice new icon on \
|
||||||
|
it indicating the {} hack has been applied succesfully!\n",
|
||||||
|
hack_data.name
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static VERB_INT: Verb = Verb;
|
||||||
|
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;
|
@ -1,5 +1,11 @@
|
|||||||
use super::{get_player_item_or_fail, user_error, UResult, UserVerb, UserVerbRef, VerbContext};
|
use super::{get_player_item_or_fail, user_error, UResult, UserVerb, UserVerbRef, VerbContext};
|
||||||
use crate::models::item::{SkillType, StatType};
|
use crate::{
|
||||||
|
language,
|
||||||
|
models::{
|
||||||
|
item::{SkillType, StatType},
|
||||||
|
user::{wristpad_hack_data, xp_to_hack_slots},
|
||||||
|
},
|
||||||
|
};
|
||||||
use ansi::ansi;
|
use ansi::ansi;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
||||||
@ -58,6 +64,35 @@ impl UserVerb for Verb {
|
|||||||
user.experience.spent_xp
|
user.experience.spent_xp
|
||||||
));
|
));
|
||||||
|
|
||||||
|
if !user.wristpad_hacks.is_empty() {
|
||||||
|
let hack_names = user
|
||||||
|
.wristpad_hacks
|
||||||
|
.iter()
|
||||||
|
.map(|h| {
|
||||||
|
wristpad_hack_data()
|
||||||
|
.get(h)
|
||||||
|
.map(|hd| hd.name)
|
||||||
|
.unwrap_or("UNKNOWN")
|
||||||
|
})
|
||||||
|
.collect::<Vec<&'static str>>();
|
||||||
|
msg.push_str(&format!(
|
||||||
|
"You have hacks installed on your wristpad: {}\n",
|
||||||
|
&language::join_words(&hack_names)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let hack_slots = xp_to_hack_slots(player_item.total_xp) as usize;
|
||||||
|
if hack_slots > user.wristpad_hacks.len() {
|
||||||
|
let free_slots = hack_slots - user.wristpad_hacks.len();
|
||||||
|
msg.push_str(&format!(
|
||||||
|
"You have {} free hack slot{} on your wristpad.\n",
|
||||||
|
free_slots,
|
||||||
|
if free_slots == 1 { "" } else { "s" }
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
msg.push_str("You have no free hack slots on your wristpad.\n");
|
||||||
|
};
|
||||||
|
|
||||||
ctx.trans.queue_for_session(ctx.session, Some(&msg)).await?;
|
ctx.trans.queue_for_session(ctx.session, Some(&msg)).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
use super::{
|
use super::{
|
||||||
item::{SkillType, StatType},
|
item::{BuffImpact, Item, SkillType, StatType},
|
||||||
journal::JournalState,
|
journal::JournalState,
|
||||||
};
|
};
|
||||||
|
#[double]
|
||||||
|
use crate::db::DBTrans;
|
||||||
|
use crate::{message_handler::ListenerSession, DResult};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use mockall_double::double;
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
@ -27,6 +32,41 @@ pub enum UserFlag {
|
|||||||
Staff,
|
Staff,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub enum WristpadHack {
|
||||||
|
Superdork,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)]
|
||||||
|
pub struct WristpadHackData {
|
||||||
|
pub name: &'static str,
|
||||||
|
pub buff: Vec<BuffImpact>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wristpad_hack_data() -> &'static BTreeMap<WristpadHack, WristpadHackData> {
|
||||||
|
static D: OnceCell<BTreeMap<WristpadHack, WristpadHackData>> = OnceCell::new();
|
||||||
|
D.get_or_init(|| {
|
||||||
|
vec![(
|
||||||
|
WristpadHack::Superdork,
|
||||||
|
WristpadHackData {
|
||||||
|
name: "Superdork",
|
||||||
|
buff: vec![
|
||||||
|
BuffImpact::ChangeStat {
|
||||||
|
stat: StatType::Brains,
|
||||||
|
magnitude: 3.0,
|
||||||
|
},
|
||||||
|
BuffImpact::ChangeStat {
|
||||||
|
stat: StatType::Cool,
|
||||||
|
magnitude: -1.0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
.into_iter()
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
@ -38,11 +78,11 @@ pub struct User {
|
|||||||
pub banned_until: Option<DateTime<Utc>>,
|
pub banned_until: Option<DateTime<Utc>>,
|
||||||
pub abandoned_at: Option<DateTime<Utc>>,
|
pub abandoned_at: Option<DateTime<Utc>>,
|
||||||
pub chargen_last_completed_at: Option<DateTime<Utc>>,
|
pub chargen_last_completed_at: Option<DateTime<Utc>>,
|
||||||
|
|
||||||
pub terms: UserTermData,
|
pub terms: UserTermData,
|
||||||
pub experience: UserExperienceData,
|
pub experience: UserExperienceData,
|
||||||
pub raw_skills: BTreeMap<SkillType, f64>,
|
pub raw_skills: BTreeMap<SkillType, f64>,
|
||||||
pub raw_stats: BTreeMap<StatType, f64>,
|
pub raw_stats: BTreeMap<StatType, f64>,
|
||||||
|
pub wristpad_hacks: Vec<WristpadHack>,
|
||||||
pub last_skill_improve: BTreeMap<SkillType, DateTime<Utc>>,
|
pub last_skill_improve: BTreeMap<SkillType, DateTime<Utc>>,
|
||||||
pub last_page_from: Option<String>,
|
pub last_page_from: Option<String>,
|
||||||
pub credits: u64,
|
pub credits: u64,
|
||||||
@ -51,6 +91,93 @@ pub struct User {
|
|||||||
// Reminder: Consider backwards compatibility when updating this.
|
// Reminder: Consider backwards compatibility when updating this.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static NO_BUFFS: Vec<BuffImpact> = vec![];
|
||||||
|
|
||||||
|
pub fn xp_to_hack_slots(xp: u64) -> u64 {
|
||||||
|
if xp >= 1000000 {
|
||||||
|
14
|
||||||
|
} else if xp >= 500000 {
|
||||||
|
13
|
||||||
|
} else if xp >= 100000 {
|
||||||
|
12
|
||||||
|
} else if xp >= 70000 {
|
||||||
|
11
|
||||||
|
} else if xp >= 40000 {
|
||||||
|
10
|
||||||
|
} else if xp >= 20000 {
|
||||||
|
9
|
||||||
|
} else if xp >= 10000 {
|
||||||
|
8
|
||||||
|
} else if xp >= 8000 {
|
||||||
|
7
|
||||||
|
} else if xp >= 6000 {
|
||||||
|
6
|
||||||
|
} else if xp >= 4000 {
|
||||||
|
5
|
||||||
|
} else if xp >= 2000 {
|
||||||
|
4
|
||||||
|
} else if xp >= 1500 {
|
||||||
|
3
|
||||||
|
} else if xp >= 1000 {
|
||||||
|
2
|
||||||
|
} else if xp >= 500 {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl User {
|
||||||
|
pub fn wristpad_hack_buffs<'a>(self: &'a Self) -> impl Iterator<Item = &'a BuffImpact> + 'a {
|
||||||
|
self.wristpad_hacks.iter().flat_map(|h| {
|
||||||
|
wristpad_hack_data()
|
||||||
|
.get(h)
|
||||||
|
.map(|hd| &hd.buff)
|
||||||
|
.unwrap_or_else(|| &NO_BUFFS)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn adjust_xp_for_reroll(
|
||||||
|
self: &mut User,
|
||||||
|
item: &mut Item,
|
||||||
|
change: i64,
|
||||||
|
trans: &DBTrans,
|
||||||
|
sess: &ListenerSession,
|
||||||
|
) -> DResult<()> {
|
||||||
|
self.experience.xp_change_for_this_reroll += change;
|
||||||
|
self.xp_adjusted(item, change, trans, sess).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn xp_adjusted(
|
||||||
|
self: &mut User,
|
||||||
|
item: &mut Item,
|
||||||
|
change: i64,
|
||||||
|
trans: &DBTrans,
|
||||||
|
sess: &ListenerSession,
|
||||||
|
) -> DResult<()> {
|
||||||
|
let old_slots = xp_to_hack_slots(item.total_xp);
|
||||||
|
if change >= 0 {
|
||||||
|
item.total_xp += change as u64;
|
||||||
|
} else if (-change) as u64 <= item.total_xp {
|
||||||
|
item.total_xp -= (-change) as u64;
|
||||||
|
} else {
|
||||||
|
item.total_xp = 0;
|
||||||
|
}
|
||||||
|
let new_slots = xp_to_hack_slots(item.total_xp);
|
||||||
|
|
||||||
|
if new_slots > old_slots {
|
||||||
|
trans
|
||||||
|
.queue_for_session(
|
||||||
|
sess,
|
||||||
|
Some("You just earned a new hack slot on your wristpad!\n"),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for UserTermData {
|
impl Default for UserTermData {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
UserTermData {
|
UserTermData {
|
||||||
@ -83,11 +210,11 @@ impl Default for User {
|
|||||||
banned_until: None,
|
banned_until: None,
|
||||||
abandoned_at: None,
|
abandoned_at: None,
|
||||||
chargen_last_completed_at: None,
|
chargen_last_completed_at: None,
|
||||||
|
|
||||||
terms: UserTermData::default(),
|
terms: UserTermData::default(),
|
||||||
experience: UserExperienceData::default(),
|
experience: UserExperienceData::default(),
|
||||||
raw_skills: BTreeMap::new(),
|
raw_skills: BTreeMap::new(),
|
||||||
raw_stats: BTreeMap::new(),
|
raw_stats: BTreeMap::new(),
|
||||||
|
wristpad_hacks: vec![],
|
||||||
last_skill_improve: BTreeMap::new(),
|
last_skill_improve: BTreeMap::new(),
|
||||||
last_page_from: None,
|
last_page_from: None,
|
||||||
credits: 500,
|
credits: 500,
|
||||||
|
@ -418,11 +418,11 @@ pub async fn consider_reward_for(
|
|||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
let xp_gain = (((for_item.total_xp - by_item.total_xp) as f64 * 10.0
|
let xp_gain = (((for_item.total_xp - by_item.total_xp) as f64 * 10.0
|
||||||
/ (by_item.total_xp + 1) as f64) as u64)
|
/ (by_item.total_xp + 1) as f64) as i64)
|
||||||
.min(100);
|
.min(100);
|
||||||
|
|
||||||
by_item.total_xp += xp_gain;
|
user.adjust_xp_for_reroll(by_item, xp_gain, trans, &session)
|
||||||
user.experience.xp_change_for_this_reroll += xp_gain as i64;
|
.await?;
|
||||||
xp_gain
|
xp_gain
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -28,22 +28,29 @@ pub fn calculate_total_stats_skills_for_user(target_item: &mut Item, user: &User
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
// 2: Apply stat (de)buffs...
|
// 2: Apply stat (de)buffs...
|
||||||
for buff in &target_item.temporary_buffs {
|
for impact in target_item
|
||||||
for impact in &buff.impacts {
|
.temporary_buffs
|
||||||
match impact {
|
.iter()
|
||||||
BuffImpact::ChangeStat { stat, magnitude } => {
|
.flat_map(|buff| &buff.impacts)
|
||||||
target_item
|
.chain(user.wristpad_hack_buffs())
|
||||||
.total_stats
|
{
|
||||||
.entry(stat.clone())
|
match impact {
|
||||||
.and_modify(|old_value| {
|
BuffImpact::ChangeStat {
|
||||||
*old_value = (*old_value + magnitude.clone() as f64).max(0.0)
|
ref stat,
|
||||||
})
|
ref magnitude,
|
||||||
.or_insert((*magnitude).max(0.0));
|
} => {
|
||||||
}
|
target_item
|
||||||
_ => {}
|
.total_stats
|
||||||
|
.entry(stat.clone())
|
||||||
|
.and_modify(|old_value| {
|
||||||
|
*old_value = (*old_value + magnitude.clone() as f64).max(0.0)
|
||||||
|
})
|
||||||
|
.or_insert((*magnitude).max(0.0));
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3: Total skills = raw skills
|
// 3: Total skills = raw skills
|
||||||
target_item.total_skills = BTreeMap::new();
|
target_item.total_skills = BTreeMap::new();
|
||||||
for skill_type in SkillType::values() {
|
for skill_type in SkillType::values() {
|
||||||
@ -330,20 +337,23 @@ pub fn calculate_total_stats_skills_for_user(target_item: &mut Item, user: &User
|
|||||||
.and_modify(|sk| *sk += refl * 0.5)
|
.and_modify(|sk| *sk += refl * 0.5)
|
||||||
.or_insert(refl * 0.5);
|
.or_insert(refl * 0.5);
|
||||||
// 5: Apply skill (de)buffs...
|
// 5: Apply skill (de)buffs...
|
||||||
for buff in &target_item.temporary_buffs {
|
for impact in target_item
|
||||||
for impact in &buff.impacts {
|
.temporary_buffs
|
||||||
match impact {
|
.iter()
|
||||||
BuffImpact::ChangeSkill { skill, magnitude } => {
|
.flat_map(|buff| &buff.impacts)
|
||||||
target_item
|
.chain(user.wristpad_hack_buffs())
|
||||||
.total_skills
|
{
|
||||||
.entry(skill.clone())
|
match impact {
|
||||||
.and_modify(|old_value| {
|
BuffImpact::ChangeSkill { skill, magnitude } => {
|
||||||
*old_value = (*old_value + magnitude.clone() as f64).max(0.0)
|
target_item
|
||||||
})
|
.total_skills
|
||||||
.or_insert((*magnitude).max(0.0));
|
.entry(skill.clone())
|
||||||
}
|
.and_modify(|old_value| {
|
||||||
_ => {}
|
*old_value = (*old_value + magnitude.clone() as f64).max(0.0)
|
||||||
|
})
|
||||||
|
.or_insert((*magnitude).max(0.0));
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,9 +132,6 @@ pub async fn award_journal_if_needed(
|
|||||||
}
|
}
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
};
|
};
|
||||||
user.experience.journals.completed_journals.insert(journal);
|
|
||||||
// Note: Not counted as 'change for this reroll' since it is permanent.
|
|
||||||
player.total_xp += journal_data.xp;
|
|
||||||
if let Some((sess, _)) = trans.find_session_for_player(&player.item_code).await? {
|
if let Some((sess, _)) = trans.find_session_for_player(&player.item_code).await? {
|
||||||
trans
|
trans
|
||||||
.queue_for_session(
|
.queue_for_session(
|
||||||
@ -145,6 +142,10 @@ pub async fn award_journal_if_needed(
|
|||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
user.experience.journals.completed_journals.insert(journal);
|
||||||
|
// Note: Not counted as 'change for this reroll' since it is permanent.
|
||||||
|
user.xp_adjusted(player, journal_data.xp as i64, trans, &sess)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(true)
|
Ok(true)
|
||||||
|
@ -23,7 +23,7 @@ use mockall_double::double;
|
|||||||
use nom::{
|
use nom::{
|
||||||
bytes::complete::tag,
|
bytes::complete::tag,
|
||||||
character::complete::{multispace1, u8},
|
character::complete::{multispace1, u8},
|
||||||
combinator::eof,
|
combinator::{eof, opt},
|
||||||
sequence::{delimited, pair, preceded, terminated},
|
sequence::{delimited, pair, preceded, terminated},
|
||||||
};
|
};
|
||||||
use std::time;
|
use std::time;
|
||||||
@ -44,7 +44,13 @@ fn parse_move_message(mut input: &str) -> Result<(u8, u8), &str> {
|
|||||||
input = input.trim();
|
input = input.trim();
|
||||||
let (from, to) = match terminated(
|
let (from, to) = match terminated(
|
||||||
preceded(
|
preceded(
|
||||||
preceded(tag("move"), multispace1::<&str, ()>),
|
preceded(
|
||||||
|
tag("move"),
|
||||||
|
preceded(
|
||||||
|
multispace1::<&str, ()>,
|
||||||
|
opt(preceded(tag("from"), multispace1::<&str, ()>)),
|
||||||
|
),
|
||||||
|
),
|
||||||
pair(
|
pair(
|
||||||
u8,
|
u8,
|
||||||
preceded(delimited(multispace1, tag("to"), multispace1), u8),
|
preceded(delimited(multispace1, tag("to"), multispace1), u8),
|
||||||
@ -131,6 +137,11 @@ mod test {
|
|||||||
assert_eq!(parse_move_message(" move 1 to 2"), Ok((1, 2)));
|
assert_eq!(parse_move_message(" move 1 to 2"), Ok((1, 2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_move_message_accepts_valid_with_from() {
|
||||||
|
assert_eq!(parse_move_message(" move from 1 to 2"), Ok((1, 2)));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_move_message_rejects_badstart() {
|
fn parse_move_message_rejects_badstart() {
|
||||||
assert_eq!(parse_move_message("eat 1 to 2"), Err("Invalid command, feeble human. I only understand commands like: -doorbot move 1 to 2"));
|
assert_eq!(parse_move_message("eat 1 to 2"), Err("Invalid command, feeble human. I only understand commands like: -doorbot move 1 to 2"));
|
||||||
|
@ -34,7 +34,7 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
|
|||||||
wear_data: Some(WearData {
|
wear_data: Some(WearData {
|
||||||
covers_parts: vec!(BodyPart::Groin, BodyPart::Legs),
|
covers_parts: vec!(BodyPart::Groin, BodyPart::Legs),
|
||||||
thickness: 4.0,
|
thickness: 4.0,
|
||||||
dodge_penalty: 0.5,
|
dodge_penalty: 0.25,
|
||||||
soaks: vec!(
|
soaks: vec!(
|
||||||
(DamageType::Beat,
|
(DamageType::Beat,
|
||||||
SoakData {
|
SoakData {
|
||||||
|
@ -37,7 +37,7 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> {
|
|||||||
BodyPart::Chest,
|
BodyPart::Chest,
|
||||||
BodyPart::Back),
|
BodyPart::Back),
|
||||||
thickness: 4.0,
|
thickness: 4.0,
|
||||||
dodge_penalty: 0.5,
|
dodge_penalty: 0.3,
|
||||||
soaks: vec!(
|
soaks: vec!(
|
||||||
(DamageType::Beat,
|
(DamageType::Beat,
|
||||||
SoakData {
|
SoakData {
|
||||||
|
@ -3,7 +3,10 @@ use super::{possession_type::PossessionType, StaticItem};
|
|||||||
use crate::db::DBTrans;
|
use crate::db::DBTrans;
|
||||||
use crate::{
|
use crate::{
|
||||||
message_handler::user_commands::UResult,
|
message_handler::user_commands::UResult,
|
||||||
models::item::{DoorState, Item, ItemFlag},
|
models::{
|
||||||
|
item::{DoorState, Item, ItemFlag},
|
||||||
|
user::WristpadHack,
|
||||||
|
},
|
||||||
regular_tasks::queued_command::QueuedCommandContext,
|
regular_tasks::queued_command::QueuedCommandContext,
|
||||||
DResult,
|
DResult,
|
||||||
};
|
};
|
||||||
@ -335,6 +338,7 @@ pub struct Room {
|
|||||||
pub material_type: MaterialType,
|
pub material_type: MaterialType,
|
||||||
pub has_power: bool,
|
pub has_power: bool,
|
||||||
pub door_states: Option<BTreeMap<Direction, DoorState>>,
|
pub door_states: Option<BTreeMap<Direction, DoorState>>,
|
||||||
|
pub wristpad_hack_allowed: Option<WristpadHack>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Room {
|
impl Default for Room {
|
||||||
@ -357,6 +361,7 @@ impl Default for Room {
|
|||||||
material_type: MaterialType::Normal,
|
material_type: MaterialType::Normal,
|
||||||
has_power: false,
|
has_power: false,
|
||||||
door_states: None,
|
door_states: None,
|
||||||
|
wristpad_hack_allowed: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
models::item::{DoorState, LocationActionType},
|
models::{
|
||||||
|
item::{DoorState, LocationActionType},
|
||||||
|
user::WristpadHack,
|
||||||
|
},
|
||||||
static_content::{
|
static_content::{
|
||||||
fixed_item::FixedItem,
|
fixed_item::FixedItem,
|
||||||
possession_type::{possession_data, PossessionData, PossessionType},
|
possession_type::{possession_data, PossessionData, PossessionType},
|
||||||
@ -164,7 +167,7 @@ pub fn room_list() -> Vec<Room> {
|
|||||||
code: "computer_museum_hackers_club",
|
code: "computer_museum_hackers_club",
|
||||||
name: "Hackers' Club",
|
name: "Hackers' Club",
|
||||||
short: ansi!("<bgblack><green>HC<reset>"),
|
short: ansi!("<bgblack><green>HC<reset>"),
|
||||||
description: ansi!("A room full of beeping and whirring equipment. One shiny stainless steel piece of equipment really catches your eye. It has a large plaque on it saying: Wristpad hacking unit - intelligence upgrade program"),
|
description: ansi!("A room full of beeping and whirring equipment. One shiny stainless steel piece of equipment really catches your eye. It has a large plaque on it saying: Wristpad hacking unit - intelligence upgrade program. [You realise you can hack your wristpad here, if you have a free wristpad hack slot, to make yourself a superdork; it will increase your brains by 3, but decrease your cool by 1. To do it, type <bold>hack superdork<reset>]"),
|
||||||
description_less_explicit: None,
|
description_less_explicit: None,
|
||||||
grid_coords: GridCoords { x: 4, y: -1, z: -1 },
|
grid_coords: GridCoords { x: 4, y: -1, z: -1 },
|
||||||
exits: vec!(
|
exits: vec!(
|
||||||
@ -183,6 +186,7 @@ pub fn room_list() -> Vec<Room> {
|
|||||||
)
|
)
|
||||||
].into_iter().collect()),
|
].into_iter().collect()),
|
||||||
should_caption: true,
|
should_caption: true,
|
||||||
|
wristpad_hack_allowed: Some(WristpadHack::Superdork),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user