Move help to separate YAML file.

This commit is contained in:
Condorra 2023-10-11 22:51:00 +11:00
parent e53949abf7
commit b06f045d10
8 changed files with 316 additions and 301 deletions

View File

@ -1,293 +1,42 @@
use std::collections::BTreeMap;
use super::{CommandHandlingError::UserError, UResult, UserVerb, UserVerbRef, VerbContext};
use ansi::ansi;
use ansi_markup::parse_ansi_markup;
use async_trait::async_trait;
use phf::phf_map;
use once_cell::sync::OnceCell;
use serde_yaml::from_str as from_yaml_str;
static ALWAYS_HELP_PAGES: phf::Map<&'static str, &'static str> = phf_map! {
"<topicname>" =>
ansi!("You are supposed to replace <lt>topicname> with the topic you want \
to learn about. Example:\n\
\t<bold>help register<reset> will tell you about the register command.")
};
fn load_help_yaml(input: &str) -> BTreeMap<String, String> {
let mut map: BTreeMap<String, String> = from_yaml_str(input).unwrap();
for val in map.values_mut() {
*val = parse_ansi_markup(val).unwrap();
}
map
}
static UNREGISTERED_HELP_PAGES: phf::Map<&'static str, &'static str> = phf_map! {
"" =>
ansi!("Type <bold>help <lt>topicname><reset> to learn about a topic. Most \
commands can be used as a topicname.\n\
Topics of interest to unregistered users:\n\
\t<bold>register<reset>\tLearn about the <bold>register<reset> command.\n\
\t<bold>login<reset>\tLearn how to log in as an existing user.\n"),
"register" =>
ansi!("Registers a new user. You are allowed at most 5 at once.\n\
\t<bold>register <lt>username> <lt>password> <lt>email><reset>\n\
Email will be used to check you don't have too many accounts and \
in case you need to reset your password."),
"login" =>
ansi!("Logs in as an existing user.\n\
\t<bold>login <lt>username> <lt>password<reset>")
};
fn always_help_pages() -> &'static BTreeMap<String, String> {
static CELL: OnceCell<BTreeMap<String, String>> = OnceCell::new();
static REGISTERED_HELP_PAGES: phf::Map<&'static str, &'static str> = phf_map! {
"" =>
ansi!("Type <bold>help <lt>topicname><reset> to learn about a topic. Most \
commands can be used as a topicname.\n\
Topics of interest:\n\
\t<bold>newbie<reset>\tLearn the absolute basics.\n\
\t<bold>movement<reset>\tCommands for moving around.\n\
\t<bold>possessions<reset>\tCommands for dealing with possessions / weapons.\n\
\t<bold>talk<reset>\tFind out how to talk in the game.\n\
\t<bold>combat<reset>\tLearn how to fight.\n\
\t<bold>information<reset>\tLearn how to find out about the world and your character."),
"newbie" =>
ansi!("So you've just landed in BlastMud, and want to know how to get started?\n\
You control your character, and can tell your character to move around the\n\
world, and see things through their eyes.\n\
The world (yes, even outside!) is divided up into rooms, and each room has\n\
exits that you are allowed to take, normally called north, south, east, west,\n\
northeast, northwest, southeast, southwest, up and down (sometimes you can also go in).\n\
\n\
Try <bold>look<reset> (or <bold>l<reset>) to look at the current room. It will\n\
also show you all the exits you can take, and a little ASCII-art map showing \n\
the local layout. <bold>lmap<reset> shows you a more detailed map showing the\n\
directions you can move in. You can also look at objects or players with \n\
<bold>l<reset> followed by the name of the object (shortening is okay).\n\
Once you know what direction to move, you can type the direction, using either\n\
the full name of the direction (e.g. <bold>northeast<reset> or <bold>south<reset>,\n\
or a short form (<bold>n<reset>, <bold>e<reset>, <bold>s<reset>, <bold>w<reset>, <bold>ne<reset>, <bold>nw<reset>, <bold>se<reset>, <bold>sw<reset>, <bold>up<reset>, <bold>down<reset>).\n\n\
Also try the map commands - <bold>lmap<reset> for a local map including exits, and <bold>gmap<reset> for a giant \
map to let you see the broader context."),
"movement" =>
ansi!("Once you know what direction to move, you can type the direction, using either\n\
the full name of the direction (e.g. <bold>northeast<reset> or <bold>south<reset>,\n\
or a short form (<bold>n<reset>, <bold>e<reset>, <bold>s<reset>, <bold>w<reset>, <bold>ne<reset>, <bold>nw<reset>, <bold>se<reset>, <bold>sw<reset>, <bold>up<reset>, <bold>down<reset>)."),
"look" =>
ansi!("Try <bold>look<reset> or <bold>l<reset> to look at the current room. Follow it with the \
name of an exit to look at an adjacent room. Or name an object or player in the room to look \
at that."),
"combat" =>
ansi!("Type <bold>kill<reset> character or <bold>k<reset> character to attack a character. \
If their health points hits zero, they die. If your health points hit zero, you die. \
Note that characters can reclone so death isn't fully permanent (see <bold>help death<reset>). \
Combat depends on your ability to dodge the enemy's attacks (based on your dodge skill), \
your armour's ability to soak their attacks, and the skill in the weapon you are wielding \
- and of course the same factors for your opponents. \
You can only attack one character at a time, but more than one can gang up on you! \
When you fight, you might notice you improve at your skills (see <bold>help skills<reset>), \
and also gain more experience. You gain more skills using a weapon matched to your current \
experience level, and more experience fighting harder opponents - but if you take on someone \
too tough, you might die and lose experience recloning! \
Remember, you can always (at least while you are not dead) try to run away from combat by just \
by using movement commands (see <bold>help movement<reset> - but if your dodge skill isn't high \
enough, it might fail."),
"death" =>
ansi!("In Blastmud, you can die if your health points reach zero. While you are dead, you can't \
move or talk! Unless someone quickly comes and uses a defibrilator on you, your current \
body is gone. Luckily, thanks to residual Gazos-Murlison technology that survived the \
fall of the empire, you can still re-clone into a new body. However, this has some \
downsides: you lose a bit of your experience in the process, and anything you had on your \
person stays with the corpse of your old body! So it is certainly worth trying not to die!"),
"skills" =>
ansi!(
"Blastmud has lots of different skills that you can learn. There are two different \
measures of a skill. Total skill is how good you are in absolute terms - you get some skill from your stats, \
and some from your raws. Raw skill is how much you have improved by learning the skill as you \
play the game. Your raw skill caps out at 15 per skill, and adds on to the contribution from \
your stats, and any temporary buffs or debuffs caused by your character's state."),
"information" =>
ansi!(
"Commands to examine the world:\n\
\t<bold>look<reset> (or <bold>l<reset>) to look around - follow it with an exit or\n\
\t\ta character / item name for detail on that.\n\
\t<bold>lmap<reset> - get a map showing exits and places."),
"talk" => TALK_HELP,
"say" => TALK_HELP,
"whisper" => TALK_HELP,
"page" => TALK_HELP,
"reply" => TALK_HELP,
"possessions" =>
ansi!("Use:\n\
\t<bold>get<reset> item to pick something up from the ground.\n\
\t<bold>drop<reset> item to drop something from your inventory.\n\
\t<bold>inv<reset> to see what you are holding.\n\
\t<bold>buy<reset> item to buy something from the shop you are in.\n\
\t<bold>list<reset> to see the price list for the stop.\n\
\t<bold>wield<reset> to hold a weapon in your inventory for use when you attack.\n\
\t<bold>gear<reset> to see what you are wearing, and how much protection it offers.\n\
Hint: get and drop support an item name, but you can also prefix it \
with a number - e.g. <bold>get 2 whip<reset>. Instead of a number, you can \
use <bold>all<reset>. You can also omit the item name to match any \
possession, e.g. <bold>drop all<reset> will drop everything you have."),
"allow" =>
ansi!("<bold>allow<reset> is the corner-stone of Blastmud's consent system. Consents in \
Blastmud let you choose how you want to play with other players (it only affects players, \
not NPCs). There are 4 types of consent: <bold>fight<reset> (for hostile actions like \
attack or pick), <bold>medicine<reset> (for medical actions, including those that might \
crit fail and do harm), <bold>gifts<reset> (lets them give you things), and \
<bold>visit<reset> (lets them on to a tile owned by you legally).\n\
\n\
To allow, as an individual, use the syntax <bold>allow <reset>type <bold>from <reset>player options\n\
As a corp, use the syntax <bold>allow <reset>type <bold>against <reset>corpname <bold>by<reset> corpname options\n\
Options can be blank to use defaults, or can be one or more of the following, separated by spaces:\n\
\t<bold>for<reset> n <bold>minutes<reset> - replace n with a number. You can use hours, days, or weeks instead of minutes. This makes the consent expire. Fight expires after a week if you don't give a shorter period, and all other consent types have no expiry unless you specify one.\n\
\t<bold>until death<reset> - makes the consent valid only until you next die.\n\
\t<bold>allow private<reset> - makes the consent valid even in privately owned places. This is the default for anything except fight.\n\
\t<bold>disallow private<reset> - the opposite of allow private.\n\
\t<bold>in<reset> place - limits where fighting can happen to selected public places. You can use \
<bold>here<reset>, or if you know the code, a place name. You can use this option more than once to allow \
any place, and if you don't use the option, it means anywhere (subject to allow private).\n\
\t<bold>allow pick<reset> - fight only - include picking in your consent.\n\
\t<bold>allow revoke<reset> - fight only - allows the player to revoke any time with disallow.\n\
\n\
Consents for anything except fight take effect immediately to let the other player do the action.\n\
Consents for fight take effect when the other player executes a reciprocal allow command.\n\
Consents for anything except than fight can be revoked instantly with:\n\
\t<bold>disallow<reset> action <bold>from<reset> player\n\
Consent for fight can be revoked similarly if the consent used the <bold>allow revoke<reset> option. \
Otherwise, attempting to revoke informs the other player, and it is revoked when they also issue a \
disallow command."),
"corp" =>
ansi!("<bold>corp<reset> is the entry point to commands related to corps. It supports the following subcommands:\n\
\t<bold>corp hire<reset> username <bold>into<reset> corpname\n\t\tHires a player into your corp.\n\
\t<bold>corp join<reset> corpname\n\t\tAccepts an offer to hire you.\n\
\t<bold>corp list<reset>\n\t\tList the corps you belong to.\n\
\t<bold>corp leave<reset> corpname\n\t\tResign from a corp.\n\
\t<bold>corp fire<reset> username <bold>from<reset> corpname\n\t\tFires a player from your corp.\n\
\t<bold>corp promote<reset> username <bold>in<reset> corpname <bold>to<reset> title <bold>privileges<reset> privileges\n\
\t\tPromotes/demotes a player within your corp.\n\
\t<bold>corp info<reset> corpname\n\t\tGet information about a corp.\n\
\t<bold>corp allow combat<reset>\n\t\tAllow the corp to consent to combat on your behalf.\n\
\t<bold>corp disallow combat<reset>\n\t\tDon't allow the corp to consent to combat on your behalf.\n\
\t<bold>corp config<reset> corpname setting\n\t\tConfigure corp settings.\n\
\t<bold>corp sub<reset> type <bold>from<reset> corpname\n\
\t\t\tSubscribe to corp broadcasts.\n\
\t<bold>corp unsub<reset> type <bold>from<reset> corpname\n\
\t\tUnsubscribe from corp broadcasts.\n\
\t<bold>corp order<reset> corpname <bold>as<reset> number\n\
\t\tSet the position of the corp in your list.\n\
Help is available for many individual corp commands, e.g. try <bold>help corp hire<reset>"),
"corp hire" =>
ansi!("<bold>corp hire<reset> username <bold>into<reset> corpname\tHires a player into your corp."),
"corp join" =>
ansi!("<bold>corp join<reset> corpname\tAccepts an offer to hire you."),
"corp list" =>
ansi!("<bold>corp list<reset>\tList the corps you belong to."),
"corp leave" =>
ansi!("<bold>corp leave<reset> corpname\tResign from a corp."),
"corp fire" =>
ansi!("<bold>corp fire<reset> username <bold>from<reset> corpname\tFires a player from your corp."),
"corp promote" =>
ansi!("<bold>corp promote<reset> username <bold>in<reset> corpname <bold>to<reset> title <bold>privileges<reset> privileges\n\
Promotes or demotes a player to a specific title within a corp, and modifies their privileges.\n\
<bold>username<reset> - the player you want to promote.\n\
<bold>corpname<reset> - the name of the corporation you want to promote the player within.\n\
<bold>title<reset> - the title you want to assign to the player.\n\
<bold>privileges<reset> - the privileges you want to grant or remove from the player, prefixed with + or - \
respectively, separated by spaces.\n\
The following privileges exist:\n\
\t<bold>holder<reset> - The owner of the corp (implies all other privileges).\n\
\t<bold>hire<reset> - Can hire new people into the corp.\n\
\t<bold>fire<reset> - Can fire people from the corp.\n\
\t<bold>promote<reset> - Can promote/demote people.\n\
\t<bold>war<reset> - Can declare war against other corps on behalf of the corp.\n\
\t<bold>configure<reset> - Can change the settings of the corp.\n\
\t<bold>finance<reset> - Can access (or embezzle!) the corps funds."),
"corp info" =>
ansi!("<bold>corp info<reset> corpname\tGet information about a corp."),
"corp allow combat" =>
ansi!("<bold>corp allow combat<reset>\tAllow the corp to consent to combat on your behalf."),
"corp disallow combat" =>
ansi!("<bold>corp disallow combat<reset>\tDon't allow the corp to consent to combat on your behalf."),
"corp config" =>
ansi!("<bold>corp config<reset> corpname setting\n\
\tConfigure various settings for your corp, such as member privileges and combat consent requirements.\n\
\t<bold>corpname<reset> - the name of your corp.\n\
\t<bold>setting<reset> - one of the following options:\n\
\t\t<bold>allow combat required<reset> - Requires that all new members have the allow combat setting.\n\
\t\t<bold>allow combat not required<reset> - Stops requiring that new members have allow combat on.\n\
\t\t<bold>base privileges privilege_name privilege_name<reset> - Sets the privileges that new members \
receive when they join the corporation. See <bold>help corp promote<reset> for privileges."),
"corp sub" =>
ansi!("<bold>corp sub<reset> type <bold>from<reset> corpname\n\
Subscribes to corp broadcasts. Type is one of:\n\
\tchat - Messages from users.\n\
\tnotice - Important corp notices.\n\
\tconnect - Connecting players.\n\
\treward - Rewards paid to the corp.\n\
\tdeath - Deaths of players in the corp.\n\
\tconsent - Changes to corp consents (war declarations)."),
"corp unsub" =>
ansi!("<bold>corp unsub<reset> type <bold>from<reset> corpname\n\
Unsubscribe from corp broadcasts. Type is one of:\n\
\tchat - Messages from users.\n\
\tnotice - Important corp notices.\n\
\tconnect - Connecting players.\n\
\treward - Rewards paid to the corp.\n\
\tdeath - Deaths of players in the corp.\n\
\tconsent - Changes to corp consents (war declarations)."),
"corp order" =>
ansi!("<bold>corp order<reset> corpname <bold>as<reset> number\tSet the position of the corp in your list."),
"install" =>
ansi!("<bold>install<reset> item <bold>on door to<reset> direction\tInstalls hardware such as a lock on a door."),
"uninstall" =>
ansi!("<bold>uninstall<reset> item <bold>from door to<reset> direction\tRemoves installed hardware such as a lock on a door."),
"gear" => ansi!("<bold>gear<reset>\tSee equipment you have on, and what protection it offers you."),
"delete" => ansi!("Delete is the most destructive command in the game - used to reset your character, or even give it up \
forever. All commands below echo out a command including a single-use command you can use to confirm the \
command - they don't take effect without the code. This prevents accidentally pasting in dangerous commands.\n\
<bold>delete character forever<reset>\tDestroy your character permanently. The command starts a one week \
countdown at the end of which your account and character (and anything in their possession) are permanently \
destroyed (as in we irreversibly wipe your data from the server), and any places you are renting get evicted. \
The command disconnects you immediately. If you reconnect and log in again within the week, \
the countdown is stopped and the deletion aborted. After the one week, anyone can register again with the \
same username. At the end of the week, when your character is destroyed, it no longer counts towards the limit \
of 5 usernames per person.\n\
<bold>delete stats<reset>\tKills your character instantly (leaving anything you are carrying on your corpse) \
and sends you back to the choice room to pick new stats. Any XP gained, apart from XP from journals, is reset \
back to 0.")
};
CELL.get_or_init(|| load_help_yaml(include_str!("help/always.yaml")))
}
static EXPLICIT_HELP_PAGES: phf::Map<&'static str, &'static str> = phf_map! {
"fuck" =>
ansi!("Type <bold>fuck <lt>name><reset> to fuck someone. It only works if \
they have consented."),
"allow" =>
ansi!("<bold>allow<reset> is the corner-stone of Blastmud's consent system. Consents in \
Blastmud let you choose how you want to play with other players (it only affects players, \
not NPCs). There are 5 types of consent: <bold>fight<reset> (for hostile actions like \
attack or pick), <bold>medicine<reset> (for medical actions, including those that might \
crit fail and do harm), <bold>gifts<reset> (lets them give you things), \
<bold>visit<reset> (lets them on to a tile owned by you legally), and <bold>fuck<reset> \
(lets them do fun but dirty things to you).\n\
\n\
To allow, as an individual, use the syntax <bold>allow <reset>type <bold>from <reset>player options\n\
As a corp, use the syntax <bold>allow <reset>type <bold>against <reset>corpname <bold>by<reset> corpname options\n\
Options can be blank to use defaults, or can be one or more of the following, separated by spaces:\n\
\t<bold>until <reset> n <bold>minutes<reset> - replace n with a number. You can use hours, days, or weeks instead of minutes. This makes the consent expire. Fight expires after a week if you don't give a shorter period, and all other consent types have no expiry unless you specify one.\n\
\t<bold>until death<reset> - makes the consent valid only until you next die.\n\
\t<bold>allow private<reset> - makes the consent valid even in privately owned places. This is the default for anything except fight.\n\
\t<bold>disallow private<reset> - the opposite of allow private.\n\
\t<bold>in<reset> place - limits where fighting can happen to selected public places. You can use \
<bold>here<reset>, or if you know the code, a place name. You can use this option more than once to allow \
any place, and if you don't use the option, it means anywhere (subject to allow private).\n\
\t<bold>allow pick<reset> - fight only - include picking in your consent.\n\
\t<bold>allow revoke<reset> - fight only - allows the player to revoke any time with disallow.\n\
\n\
Consents for anything except fight take effect immediately to let the other player do the action.\n\
Consents for fight take effect when the other player executes a reciprocal allow command.\n\
Consents for anything except than fight can be revoked instantly with:\n\
\t<bold>disallow<reset> action <bold>from<reset> player\n\
Consent for fight can be revoked similarly if the consent used the <bold>allow revoke<reset> option. \
Otherwise, attempting to revoke informs the other player, and it is revoked when they also issue a \
disallow command."),
};
fn registered_help_pages() -> &'static BTreeMap<String, String> {
static CELL: OnceCell<BTreeMap<String, String>> = OnceCell::new();
static TALK_HELP: &'static str = ansi!(
"Use:\n\
\t<bold>'<reset>message to send message to the room.\n\
\t<bold>-<reset>user message to whisper to someone.\n\
\t<bold>p<reset> user message to page someone on your wristpad.\n\
\t<bold>reply<reset> message to page back the last person to page you."
);
CELL.get_or_init(|| load_help_yaml(include_str!("help/registered.yaml")))
}
fn unregistered_help_pages() -> &'static BTreeMap<String, String> {
static CELL: OnceCell<BTreeMap<String, String>> = OnceCell::new();
CELL.get_or_init(|| load_help_yaml(include_str!("help/unregistered.yaml")))
}
fn explicit_help_pages() -> &'static BTreeMap<String, String> {
static CELL: OnceCell<BTreeMap<String, String>> = OnceCell::new();
CELL.get_or_init(|| load_help_yaml(include_str!("help/explicit.yaml")))
}
pub struct Verb;
#[async_trait]
@ -305,20 +54,45 @@ impl UserVerb for Verb {
};
let remaining = remaining.trim();
if is_unregistered {
help = help.or_else(|| UNREGISTERED_HELP_PAGES.get(remaining));
help = help.or_else(|| unregistered_help_pages().get(remaining));
} else {
help = help.or_else(|| REGISTERED_HELP_PAGES.get(remaining));
help = help.or_else(|| registered_help_pages().get(remaining));
if !ctx.session_dat.less_explicit_mode {
help = EXPLICIT_HELP_PAGES.get(remaining).or(help);
help = explicit_help_pages().get(remaining).or(help);
}
}
help = help.or_else(|| ALWAYS_HELP_PAGES.get(remaining));
help = help.or_else(|| always_help_pages().get(remaining).map(|v| v));
let help_final = help.ok_or(UserError("No help available on that".to_string()))?;
ctx.trans
.queue_for_session(ctx.session, Some(&(help_final.to_string() + "\n")))
.queue_for_session(ctx.session, Some(&(help_final.clone() + "\n")))
.await?;
Ok(())
}
}
static VERB_INT: Verb = Verb;
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn registered_help_ok() {
registered_help_pages();
}
#[test]
fn unregistered_help_ok() {
unregistered_help_pages();
}
#[test]
fn always_help_ok() {
always_help_pages();
}
#[test]
fn explicit_help_ok() {
explicit_help_pages();
}
}

View File

@ -0,0 +1,3 @@
"<topicname>": |-
You are supposed to replace <lt>topicname> with the topic you want to learn about. Example:
<bold>help register<reset> will tell you about the register command.

View File

@ -0,0 +1,20 @@
fuck: "Type <bold>fuck <lt>name><reset> to fuck someone. It only works if they have consented."
allow: |-
<bold>allow<reset> is the corner-stone of Blastmud's consent system. Consents in Blastmud let you choose how you want to play with other players (it only affects players, not NPCs). There are 5 types of consent: <bold>fight<reset> (for hostile actions like attack or pick), <bold>medicine<reset> (for medical actions, including those that might crit fail and do harm), <bold>gifts<reset> (lets them give you things), <bold>visit<reset> (lets them on to a tile owned by you legally), and <bold>fuck<reset> (lets them do fun but dirty things to you).
To allow, as an individual, use the syntax <bold>allow <reset>type <bold>from <reset>player options
As a corp, use the syntax <bold>allow <reset>type <bold>against <reset>corpname <bold>by<reset> corpname options
Options can be blank to use defaults, or can be one or more of the following, separated by spaces:
<bold>until <reset> n <bold>minutes<reset> - replace n with a number. You can use hours, days, or weeks instead of minutes. This makes the consent expire. Fight expires after a week if you don't give a shorter period, and all other consent types have no expiry unless you specify one.
<bold>until death<reset> - makes the consent valid only until you next die.
<bold>allow private<reset> - makes the consent valid even in privately owned places. This is the default for anything except fight.
<bold>disallow private<reset> - the opposite of allow private.
<bold>in<reset> place - limits where fighting can happen to selected public places. You can use <bold>here<reset>, or if you know the code, a place name. You can use this option more than once to allow any place, and if you don't use the option, it means anywhere (subject to allow private).
<bold>allow pick<reset> - fight only - include picking in your consent.
<bold>allow revoke<reset> - fight only - allows the player to revoke any time with disallow.
Consents for anything except fight take effect immediately to let the other player do the action.
Consents for fight take effect when the other player executes a reciprocal allow command.
Consents for anything except than fight can be revoked instantly with:
<bold>disallow<reset> action <bold>from<reset> player
Consent for fight can be revoked similarly if the consent used the <bold>allow revoke<reset> option. Otherwise, attempting to revoke informs the other player, and it is revoked when they also issue a disallow command.

View File

@ -0,0 +1,202 @@
"": |-
Type <bold>help <lt>topicname><reset> to learn about a topic. Most commands can be used as a topicname.
Topics of interest:
<bold>newbie<reset> Learn the absolute basics.
<bold>movement<reset> Commands for moving around.
<bold>possessions<reset> Commands for dealing with possessions / weapons.
<bold>talk<reset> Find out how to talk in the game.
<bold>combat<reset> Learn how to fight.
<bold>information<reset> Learn how to find out about the world and your character.
newbie: |-
So you've just landed in Blastmud, and want to know how to get started?
You control your character, and can tell your character to move around the
world, and see things through their eyes.
The world (yes, even outside!) is divided up into rooms, and each room has
exits that you are allowed to take, normally called north, south, east, west,
northeast, northwest, southeast, southwest, up and down (sometimes you can also go in).
Try <bold>look<reset> (or <bold>l<reset>) to look at the current room. It will
also show you all the exits you can take, and a little ASCII-art map showing
the local layout. <bold>lmap<reset> shows you a more detailed map showing the
directions you can move in. You can also look at objects or players with
<bold>l<reset> followed by the name of the object (shortening is okay).
Once you know what direction to move, you can type the direction, using either
the full name of the direction (e.g. <bold>northeast<reset> or <bold>south<reset>,
or a short form (<bold>n<reset>, <bold>e<reset>, <bold>s<reset>, <bold>w<reset>, <bold>ne<reset>, <bold>nw<reset>, <bold>se<reset>, <bold>sw<reset>, <bold>up<reset>, <bold>down<reset>).
Also try the map commands - <bold>lmap<reset> for a local map including exits, and <bold>gmap<reset> for a giant
map to let you see the broader context.
movement: |-
Once you know what direction to move, you can type the direction, using either
the full name of the direction (e.g. <bold>northeast<reset> or <bold>south<reset>,
or a short form (<bold>n<reset>, <bold>e<reset>, <bold>s<reset>, <bold>w<reset>, <bold>ne<reset>, <bold>nw<reset>, <bold>se<reset>, <bold>sw<reset>, <bold>up<reset>, <bold>down<reset>).
look: Try <bold>look<reset> or <bold>l<reset> to look at the current room. Follow it with the name of an exit to look at an adjacent room. Or name an object or player in the room to look at that.
combat: >-
Type <bold>kill<reset> character or <bold>k<reset> character to attack a character.
If their health points hits zero, they die. If your health points hit zero, you die.
Note that characters can reclone so death isn't fully permanent (see <bold>help death<reset>).
Combat depends on your ability to dodge the enemy's attacks (based on your dodge skill),
your armour's ability to soak their attacks, and the skill in the weapon you are wielding
- and of course the same factors for your opponents.
You can only attack one character at a time, but more than one can gang up on you!
When you fight, you might notice you improve at your skills (see <bold>help skills<reset>),
and also gain more experience. You gain more skills using a weapon matched to your current
experience level, and more experience fighting harder opponents - but if you take on someone
too tough, you might die and lose experience recloning!
Remember, you can always (at least while you are not dead) try to run away from combat by just
by using movement commands (see <bold>help movement<reset> - but if your dodge skill isn't high
enough, it might fail.
death: >-
In Blastmud, you can die if your health points reach zero. While you are dead, you can't
move or talk! Unless someone quickly comes and uses a defibrilator on you, your current
body is gone. Luckily, thanks to residual Gazos-Murlison technology that survived the
fall of the empire, you can still re-clone into a new body. However, this has some
downsides: you lose a bit of your experience in the process, and anything you had on your
person stays with the corpse of your old body! So it is certainly worth trying not to die!
skills: >-
Blastmud has lots of different skills that you can learn. There are two different
measures of a skill. Total skill is how good you are in absolute terms - you get some skill from your stats,
and some from your raws. Raw skill is how much you have improved by learning the skill as you
play the game. Your raw skill caps out at 15 per skill, and adds on to the contribution from
your stats, and any temporary buffs or debuffs caused by your character's state.
information: |-
Commands to examine the world:
<bold>look<reset> (or <bold>l<reset>) to look around - follow it with an exit or
a character / item name for detail on that.
<bold>lmap<reset> - get a map showing exits and places.
talk: &talk |-
Use:
<bold>'<reset>message to send message to the room.
<bold>-<reset>user message to whisper to someone.
<bold>p<reset> user message to page someone on your wristpad.
<bold>reply<reset> message to page back the last person to page you.
say: *talk
whisper: *talk
page: *talk
reply: *talk
possessions: &possessions |-
Use:
<bold>get<reset> item to pick something up from the ground.
<bold>drop<reset> item to drop something from your inventory.
<bold>inv<reset> to see what you are holding.
<bold>buy<reset> item to buy something from the shop you are in.
<bold>list<reset> to see the price list for the stop.
<bold>wield<reset> to hold a weapon in your inventory for use when you attack.
<bold>gear<reset> to see what you are wearing, and how much protection it offers.
Hint: get and drop support an item name, but you can also prefix it with a number - e.g. <bold>get 2 whip<reset>. Instead of a number, you can use <bold>all<reset>. You can also omit the item name to match any possession, e.g. <bold>drop all<reset> will drop everything you have.
get: *possessions
drop: *possessions
inv: *possessions
inventory: *possessions
buy: *possessions
list: *possessions
wield: *possessions
gear: *possessions
allow: |-
<bold>allow<reset> is the corner-stone of Blastmud's consent system. Consents in Blastmud let you choose how you want to play with other players (it only affects players, not NPCs). There are 4 types of consent: <bold>fight<reset> (for hostile actions like attack or pick), <bold>medicine<reset> (for medical actions, including those that might crit fail and do harm), <bold>gifts<reset> (lets them give you things), and <bold>visit<reset> (lets them on to a tile owned by you legally).
To allow, as an individual, use the syntax <bold>allow <reset>type <bold>from <reset>player options
As a corp, use the syntax <bold>allow <reset>type <bold>against <reset>corpname <bold>by<reset> corpname options
Options can be blank to use defaults, or can be one or more of the following, separated by spaces:
<bold>for<reset> n <bold>minutes<reset> - replace n with a number. You can use hours, days, or weeks instead of minutes. This makes the consent expire. Fight expires after a week if you don't give a shorter period, and all other consent types have no expiry unless you specify one.
<bold>until death<reset> - makes the consent valid only until you next die.
<bold>allow private<reset> - makes the consent valid even in privately owned places. This is the default for anything except fight.
<bold>disallow private<reset> - the opposite of allow private.
<bold>in<reset> place - limits where fighting can happen to selected public places. You can use
<bold>here<reset>, or if you know the code, a place name. You can use this option more than once to allow any place, and if you don't use the option, it means anywhere (subject to allow private).
<bold>allow pick<reset> - fight only - include picking in your consent.
<bold>allow revoke<reset> - fight only - allows the player to revoke any time with disallow.
Consents for anything except fight take effect immediately to let the other player do the action.
Consents for fight take effect when the other player executes a reciprocal allow command.
Consents for anything except than fight can be revoked instantly with:
<bold>disallow<reset> action <bold>from<reset> player
Consent for fight can be revoked similarly if the consent used the <bold>allow revoke<reset> option.
Otherwise, attempting to revoke informs the other player, and it is revoked when they also issue a disallow command.
corp: |-
<bold>corp<reset> is the entry point to commands related to corps. It supports the following subcommands:
<bold>corp hire<reset> username <bold>into<reset> corpname
Hires a player into your corp.
<bold>corp join<reset> corpname
Accepts an offer to hire you.
<bold>corp list<reset>
List the corps you belong to.
<bold>corp leave<reset> corpname
Resign from a corp.
<bold>corp fire<reset> username <bold>from<reset> corpname
Fires a player from your corp.
<bold>corp promote<reset> username <bold>in<reset> corpname <bold>to<reset> title <bold>privileges<reset> privileges
Promotes/demotes a player within your corp.
<bold>corp info<reset> corpname
Get information about a corp.
<bold>corp allow combat<reset>
Allow the corp to consent to combat on your behalf.
<bold>corp disallow combat<reset>
Don't allow the corp to consent to combat on your behalf.
<bold>corp config<reset> corpname setting
Configure corp settings.
<bold>corp sub<reset> type <bold>from<reset> corpname
Subscribe to corp broadcasts.
<bold>corp unsub<reset> type <bold>from<reset> corpname
Unsubscribe from corp broadcasts.
<bold>corp order<reset> corpname <bold>as<reset> number
Set the position of the corp in your list.
Help is available for many individual corp commands, e.g. try <bold>help corp hire<reset>
"corp hire": "<bold>corp hire<reset> username <bold>into<reset> corpname Hires a player into your corp."
"corp join": "<bold>corp join<reset> corpname Accepts an offer to hire you."
"corp list": "<bold>corp list<reset> List the corps you belong to."
"corp leave": &corp-leave "<bold>corp leave<reset> corpname Resign from a corp."
"corp resign": *corp-leave
"corp fire": "<bold>corp fire<reset> username <bold>from<reset> corpname Fires a player from your corp."
"corp promote": |-
<bold>corp promote<reset> username <bold>in<reset> corpname <bold>to<reset> title <bold>privileges<reset> privileges
Promotes or demotes a player to a specific title within a corp, and modifies their privileges.
<bold>username<reset> - the player you want to promote.
<bold>corpname<reset> - the name of the corporation you want to promote the player within.
<bold>title<reset> - the title you want to assign to the player.
<bold>privileges<reset> - the privileges you want to grant or remove from the player, prefixed with + or - respectively, separated by spaces.
The following privileges exist:
<bold>holder<reset> - The owner of the corp (implies all other privileges).
<bold>hire<reset> - Can hire new people into the corp.
<bold>fire<reset> - Can fire people from the corp.
<bold>promote<reset> - Can promote/demote people.
<bold>war<reset> - Can declare war against other corps on behalf of the corp.
<bold>configure<reset> - Can change the settings of the corp.
<bold>finance<reset> - Can access (or embezzle!) the corps funds.
"corp info": "<bold>corp info<reset> corpname Get information about a corp."
"corp allow combat": "<bold>corp allow combat<reset> Allow the corp to consent to combat on your behalf."
"corp disallow combat": "<bold>corp disallow combat<reset> Don't allow the corp to consent to combat on your behalf."
"corp config": |-
<bold>corp config<reset> corpname setting
Configure various settings for your corp, such as member privileges and combat consent requirements.
<bold>corpname<reset> - the name of your corp.
<bold>setting<reset> - one of the following options:
<bold>allow combat required<reset> - Requires that all new members have the allow combat setting.
<bold>allow combat not required<reset> - Stops requiring that new members have allow combat on.
<bold>base privileges privilege_name privilege_name<reset> - Sets the privileges that new members receive when they join the corporation. See <bold>help corp promote<reset> for privileges.
"corp sub": |-
<bold>corp sub<reset> type <bold>from<reset> corpname
Subscribes to corp broadcasts. Type is one of:
chat - Messages from users.
notice - Important corp notices.
connect - Connecting players.
reward - Rewards paid to the corp.
death - Deaths of players in the corp.
consent - Changes to corp consents (war declarations).
"corp unsub": |-
<bold>corp unsub<reset> type <bold>from<reset> corpname
Unsubscribe from corp broadcasts. Type is one of:
chat - Messages from users.
notice - Important corp notices.
connect - Connecting players.
reward - Rewards paid to the corp.
death - Deaths of players in the corp.
consent - Changes to corp consents (war declarations).
"corp order": "<bold>corp order<reset> corpname <bold>as<reset> number Set the position of the corp in your list."
"install": "<bold>install<reset> item <bold>on door to<reset> direction Installs hardware such as a lock on a door."
"uninstall": "<bold>uninstall<reset> item <bold>from door to<reset> direction Removes installed hardware such as a lock on a door."
"gear": "<bold>gear<reset> See equipment you have on, and what protection it offers you."
"delete": |-
Delete is the most destructive command in the game - used to reset your character, or even give it up forever. All commands below echo out a command including a single-use command you can use to confirm the command - they don't take effect without the code. This prevents accidentally pasting in dangerous commands.
<bold>delete character forever<reset> Destroy your character permanently. The command starts a one week countdown at the end of which your account and character (and anything in their possession) are permanently destroyed (as in we irreversibly wipe your data from the server), and any places you are renting get evicted. The command disconnects you immediately. If you reconnect and log in again within the week, the countdown is stopped and the deletion aborted. After the one week, anyone can register again with the same username. At the end of the week, when your character is destroyed, it no longer counts towards the limit of 5 usernames per person.
<bold>delete stats<reset> Kills your character instantly (leaving anything you are carrying on your corpse) and sends you back to the choice room to pick new stats. Any XP gained, apart from XP from journals, is reset back to 0.

View File

@ -0,0 +1,12 @@
"": |-
Type <bold>help <lt>topicname><reset> to learn about a topic. Most commands can be used as a topicname.
Topics of interest to unregistered users:
<bold>register<reset>\tLearn about the <bold>register<reset> command.
<bold>login<reset>\tLearn how to log in as an existing user.
register: |-
Registers a new user. You are allowed at most 5 at once.
<bold>register <lt>username> <lt>password> <lt>email><reset>
Email will be used to check you don't have too many accounts and in case you need to reset your password.
login: |-
Logs in as an existing user.
<bold>login <lt>username> <lt>password<reset>

View File

@ -212,8 +212,8 @@ mod test {
for type_group in registry.iter() {
let iterator: Box<dyn Iterator<Item = StaticItem>> = (type_group.things)();
let duplicates: Vec<&'static str> = iterator
.group_by(|x| x.item_code)
let duplicates: Vec<String> = iterator
.group_by(|x| x.item_code.clone())
.into_iter()
.filter_map(|(k, v)| if v.count() <= 1 { None } else { Some(k) })
.collect();

View File

@ -182,7 +182,8 @@ pub fn npc_list() -> &'static Vec<NPC> {
code: "repro_xv_chargen_statbot".to_owned(),
name: "Statbot".to_owned(),
description:
"A silvery shiny metal mechanical being. It lets out a whirring sound as it moves.".to_owned(),
"A silvery shiny metal mechanical being. It lets out a whirring sound as it moves."
.to_owned(),
spawn_location: "room/repro_xv_chargen".to_owned(),
message_handler: Some(&statbot::StatbotMessageHandler),
says: vec![],
@ -198,7 +199,12 @@ pub fn npc_list() -> &'static Vec<NPC> {
pub fn npc_by_code() -> &'static BTreeMap<&'static str, &'static NPC> {
static NPC_CODE_MAP: OnceCell<BTreeMap<&'static str, &'static NPC>> = OnceCell::new();
NPC_CODE_MAP.get_or_init(|| npc_list().iter().map(|npc| (npc.code.as_str(), npc)).collect())
NPC_CODE_MAP.get_or_init(|| {
npc_list()
.iter()
.map(|npc| (npc.code.as_str(), npc))
.collect()
})
}
pub fn npc_say_info_by_npc_code_say_code(
@ -481,9 +487,7 @@ impl TaskHandler for NPCWanderTaskHandler {
}
let ex_iter = room.exits.iter().filter(|ex| {
resolve_exit(room, ex)
.map(|new_room| {
npc.wander_zones.contains(&new_room.zone) && !new_room.repel_npc
})
.map(|new_room| npc.wander_zones.contains(&new_room.zone) && !new_room.repel_npc)
.unwrap_or(false)
});
let dir_opt = ex_iter

View File

@ -1,26 +1,26 @@
- Geek:
- !Geek
code: "1"
adjectives: excited
spawn_loc: lobby
pronouns: Male
- Geek:
- !Geek
code: "2"
adjectives: mesmerised
spawn_loc: lobby
pronouns: Female
- Killbot:
- !Killbot
code: "3"
adjectives: abhorrent
spawn_loc: hw_1
- Killbot:
- !Killbot
code: "4"
adjectives: berserk
spawn_loc: hw_2
- Killbot:
- !Killbot
code: "5"
adjectives: vicious
spawn_loc: hw_3
- Killbot:
- !Killbot
code: "6"
adjectives: murderous
spawn_loc: club_door