master
rasul 5 years ago
parent 2e12903c23
commit 8165be78f1

@ -0,0 +1,127 @@
use std::collections::HashMap;
use std::str::FromStr;
use mio::Token;
use crate::actions::look;
use crate::command::Command;
use crate::database::Db;
use crate::queue::SendQueue;
use crate::world::{Direction, Exit, Room};
use crate::{try_option_send_error, try_send_error};
/// Look at the room. Provide room name, description, and exits.
pub fn dig(command: &Command, args: String, token: Token, db: &mut Db) -> SendQueue {
let mut send_queue = SendQueue::new();
// find the player
let mut player = try_option_send_error!(token, db.get_connected_player(token));
// get the direction to dig
let direction = match Direction::from_str(&args) {
Ok(d) => d,
Err(e) => {
send_queue.push(token, format!("{}", e), true);
return send_queue;
}
};
// get starting room
let mut start_room = try_option_send_error!(token, db.load_room(player.location));
// make sure exit doesn't already exist
if start_room.exits.contains_key(&direction) {
send_queue.push(token, "Exit already exists", true);
return send_queue;
}
// get starting zone
let mut zone = try_option_send_error!(token, db.load_zone(start_room.zone));
let new_room_id = try_send_error!(token, db.new_area_id());
// create a new, empty room
let mut new_room = Room {
id: new_room_id,
zone: start_room.zone,
name: format!("New Room {}", new_room_id),
description: Vec::new(),
users_visible: true,
exits: HashMap::new(),
};
// add exit from start room to new room
let _ = start_room.exits.insert(
direction,
Exit {
target: new_room.id,
direction,
},
);
// add exit from new room to start room
let _ = new_room.exits.insert(
direction.opposite(),
Exit {
target: start_room.id,
direction: direction.opposite(),
},
);
// add new room to zone
let _ = zone.areas.insert(new_room.id);
// save the new room
if db.save_room(&new_room).is_ok() {
send_queue.push(token, "New room saved\n", false);
} else {
send_queue.push(token, "Unable to save new room", true);
return send_queue;
}
// save the start room
if db.save_room(&start_room).is_ok() {
send_queue.push(token, "Start room saved\n", false);
} else {
send_queue.push(token, "Unable to save start room", true);
return send_queue;
}
// save the zone
if db.save_zone(&zone).is_ok() {
send_queue.push(token, "Zone saved\n", false);
} else {
send_queue.push(token, "Unable to save zone", true);
return send_queue;
}
// move and save the player
player.location = new_room.id;
if db.save_player(&player).is_ok() {
if db.save_connected_player(token, &player).is_ok() {
send_queue.push(token, format!("You dig {}.\n\n", direction.long()), false);
} else {
send_queue.push(token, "Unable to save connected player", true);
}
} else {
send_queue.push(token, "Unable to save player", true);
return send_queue;
}
// inform people about what just took place
for (neighbor_token, _) in
try_send_error!(token, db.find_connected_players_by_location(start_room.id))
{
if neighbor_token != token {
send_queue.push(
neighbor_token,
format!("{} digs {}.", player.name, direction.long()),
true,
);
}
}
send_queue.append(&mut look(&command, args, token, db));
send_queue
}

@ -0,0 +1,11 @@
use mio::Token;
use crate::command::Command;
use crate::database::Db;
use crate::queue::SendQueue;
pub fn help(command: &Command, args: String, token: Token, db: &mut Db) -> SendQueue {
let mut send_queue = SendQueue::new();
send_queue
}

@ -0,0 +1,55 @@
use colored::Colorize;
use mio::Token;
use crate::command::Command;
use crate::database::Db;
use crate::queue::SendQueue;
use crate::{try_option_send_error, try_send_error};
/// Look at the room. Provide room name, description, and exits.
pub fn look(_: &Command, _: String, token: Token, db: &mut Db) -> SendQueue {
let mut send_queue = SendQueue::new();
// get the player
let player = try_option_send_error!(token, db.get_connected_player(token));
// get the room
let room = try_option_send_error!(token, db.load_room(player.location));
// room name
send_queue.push(token, format!("{}\n", room.name.cyan().to_string()), false);
// room description
send_queue.push(
token,
format!("{}\n", {
let mut s = room.description.join("\n");
s.push_str("\n");
s
}),
false,
);
// exits
send_queue.push(
token,
format!("[ obvious exits: {} ]\n", room.exit_string())
.cyan()
.to_string(),
false,
);
// other people in room
for (neighbor_token, neighbor_player) in try_send_error!(
token,
db.find_connected_players_by_location(player.location)
) {
if neighbor_token != token {
send_queue.push(token, format!("{} is here", neighbor_player.name), false);
}
}
send_queue.push(token, "", true);
send_queue
}

@ -0,0 +1,13 @@
mod dig;
mod help;
mod look;
mod move_room;
mod save;
mod say;
pub use dig::dig;
pub use help::help;
pub use look::look;
pub use move_room::move_room;
pub use save::save;
pub use say::say;

@ -0,0 +1,84 @@
use log::warn;
use mio::Token;
use crate::actions::look;
use crate::command::Command;
use crate::database::Db;
use crate::queue::SendQueue;
use crate::world::*;
use crate::{try_option_send_error, try_send_error};
pub fn move_room(command: &Command, _args: String, token: Token, db: &mut Db) -> SendQueue {
let mut send_queue = SendQueue::new();
let direction: Direction = match command {
Command::N | Command::North => Direction::North,
Command::S | Command::South => Direction::South,
Command::E | Command::East => Direction::East,
Command::W | Command::West => Direction::West,
Command::U | Command::Up => Direction::Up,
Command::D | Command::Down => Direction::Down,
_ => {
warn!("Can't figure out direction: {:?}", command);
return send_queue;
}
};
// find the player
let mut player = try_option_send_error!(token, db.get_connected_player(token));
// get starting room
let start_room = try_option_send_error!(token, db.load_room(player.location));
// get the exit
let exit = if let Some(exit) = start_room.exits.get(&direction) {
exit
} else {
send_queue.push(token, "You can't go that way.", true);
return send_queue;
};
// get the target room
let target_room = try_option_send_error!(token, db.load_room(exit.target));
// move and save the player
player.location = target_room.id;
let _ = try_send_error!(token, db.save_player(&player));
let _ = try_send_error!(token, db.save_connected_player(token, &player));
send_queue.push(token, format!("You leave {}.\n\n", direction.long()), false);
// tell people about leaving
for (neighbor_token, _) in
try_send_error!(token, db.find_connected_players_by_location(start_room.id))
{
if neighbor_token != token {
send_queue.push(
neighbor_token,
format!("{} leaves {}.", player.name, direction.long()),
true,
);
}
}
// tell people about entering
for (neighbor_token, _) in
try_send_error!(token, db.find_connected_players_by_location(target_room.id))
{
if neighbor_token != token {
send_queue.push(
neighbor_token,
format!(
"{} arrives from the {}.",
player.name,
direction.opposite().long()
),
true,
);
}
}
// look around
send_queue.append(&mut look(&command, String::new(), token, db));
send_queue
}

@ -0,0 +1,18 @@
use mio::Token;
use crate::command::Command;
use crate::database::Db;
use crate::queue::SendQueue;
use crate::{try_option_send_error, try_send_error};
/// Save the player information to disk.
pub fn save(_: &Command, _: String, token: Token, db: &mut Db) -> SendQueue {
let mut send_queue = SendQueue::new();
let player = try_option_send_error!(token, db.get_connected_player(token));
let _ = try_send_error!(token, db.save_player(&player));
let _ = try_send_error!(token, db.save_connected_player(token, &player));
send_queue.push(token, "Ok", true);
send_queue
}

@ -0,0 +1,30 @@
use mio::Token;
use crate::command::Command;
use crate::database::Db;
use crate::queue::SendQueue;
use crate::{try_option_send_error, try_send_error};
/// Say something to anyone in the room.
pub fn say(_: &Command, args: String, token: Token, db: &mut Db) -> SendQueue {
let mut send_queue = SendQueue::new();
let player = try_option_send_error!(token, db.get_connected_player(token));
for (neighbor_token, _) in try_send_error!(
token,
db.find_connected_players_by_location(player.location)
) {
send_queue.push(
neighbor_token,
if neighbor_token == token {
format!("You say, \"{}\"\n", args)
} else {
format!("{} says, \"{}\"\n", player.name, args)
},
true,
);
}
send_queue
}

@ -0,0 +1,84 @@
use std::fmt;
use std::io::{BufRead, BufReader, BufWriter, ErrorKind, Write};
use std::net::SocketAddr;
use log::error;
use mio::net::TcpStream;
use mio::Token;
use crate::result::RudeResult;
use crate::state::*;
/// `Client` struct for storing information about a connected client, also
/// a few helper functions for communicating with the client.
#[derive(Debug)]
pub struct Client {
/// TCP socket
pub socket: TcpStream,
/// Identifier token
pub token: Token,
/// IP information
pub addr: SocketAddr,
/// Client's play state
pub state: State,
}
impl fmt::Display for Client {
/// Only need the ip and port to be printed.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.addr.ip(), self.addr.port())
}
}
impl Into<String> for Client {
/// Convert the ip and port into a string.
fn into(self) -> String {
format!("{}:{}", self.addr.ip(), self.addr.port())
}
}
impl Client {
/// Read a message from the client
pub fn read(&self) -> RudeResult<String> {
let reader = BufReader::new(&self.socket);
let mut buf = String::new();
for line in reader.lines() {
match line {
Ok(line) => buf += &line,
Err(e) => match e.kind() {
ErrorKind::WouldBlock => break,
_ => return Err(e.into()),
},
}
}
Ok(buf)
}
/// Send a string to the client.
pub fn send_without_prompt<T: Into<String>>(&mut self, message: T) {
let message = message.into();
let mut writer = BufWriter::new(&self.socket);
if let Err(e) = writer.write_all(message.as_bytes()) {
error!("Unable to send message to client ({:?}): {}", self, e);
}
}
/// Send a string to the client, followed by a prompt.
pub fn send_with_prompt<T: Into<String>>(&mut self, message: T) {
let message = message.into() + self.prompt();
let mut writer = BufWriter::new(&self.socket);
if let Err(e) = writer.write_all(message.as_bytes()) {
error!("Unable to send message to client ({:?}): {}", self, e);
}
}
fn prompt(&self) -> &str {
"\n> "
}
}

@ -0,0 +1,107 @@
use std::default::Default;
use mio::Token;
use strum_macros::{Display, EnumIter};
use crate::actions;
use crate::command::{CommandSet, Parse, ParserError};
use crate::database::Db;
use crate::queue::SendQueue;
#[derive(Clone, Debug, Display, EnumIter, Eq, Ord, PartialEq, PartialOrd)]
pub enum Command {
N,
S,
E,
W,
U,
D,
North,
South,
East,
West,
Up,
Down,
Dig,
Help,
Look,
Save,
Say,
Set(CommandSet),
Default,
}
impl Default for Command {
fn default() -> Self {
Self::Default
}
}
impl Parse for Command {
fn help(&self) -> &str {
match self {
Self::N => "n :: Move one room to the north.",
Self::S => "s :: Move one room to the south.",
Self::E => "e :: Move one room to the east.",
Self::W => "w :: Move one room to the west.",
Self::U => "u :: Move one room up.",
Self::D => "d :: Move one room down.",
Self::North => "north :: Move one room to the north.",
Self::South => "south :: Move one room to the south.",
Self::East => "east :: Move one room to the east.",
Self::West => "west :: Move one room to the west.",
Self::Up => "up :: Move one room up.",
Self::Down => "down :: Move one room down.",
Self::Dig => "dig DIRECTION:: Dig a new path to a new room in the provided direction.",
Self::Help => "help [COMMAND]:: Provide help on a specific command, or an overview.",
Self::Look => "look :: Take a look around the current room.",
Self::Save => "save :: Write player information to disk.",
Self::Say => "say MESSAGE:: Say something to the room.",
Self::Set(_) => "set OPTIONS :: Set various parameters.",
Self::Default => "",
}
}
fn parse_subcommand(&self, s: String) -> Result<(Self, String), ParserError> {
match self {
Self::Set(_) => {
let (command, args) = CommandSet::parse(s)?;
Ok((Self::Set(command), args))
}
Self::Default => Err(ParserError::Default),
_ => Ok((self.clone(), s)),
}
}
fn subcommand_help(&self, _: String) -> Option<String> {
match self {
Self::Set(command_set) => Some(command_set.help_all()),
_ => None,
}
}
fn dispatch_map_subcommand(&self, args: String, token: Token, db: &mut Db) -> SendQueue {
match self {
Self::Set(command_set) => command_set.dispatch(command_set, args, token, db),
_ => SendQueue::new(),
}
}
fn dispatch_map(&self) -> fn(&Self, String, Token, &mut Db) -> SendQueue {
match self {
Self::N | Self::North => actions::move_room,
Self::S | Self::South => actions::move_room,
Self::E | Self::East => actions::move_room,
Self::W | Self::West => actions::move_room,
Self::U | Self::Up => actions::move_room,
Self::D | Self::Down => actions::move_room,
Self::Dig => actions::dig,
Self::Help => actions::help,
Self::Look => actions::look,
Self::Save => actions::save,
Self::Say => actions::say,
Self::Set(_) => Self::dispatch_map_subcommand,
Self::Default => Self::dispatch_default,
}
}
}

@ -0,0 +1,9 @@
mod command;
mod parse;
mod parser_error;
mod set;
pub use command::Command;
pub use parse::Parse;
pub use parser_error::ParserError;
pub use set::*;

@ -0,0 +1,131 @@
use std::string::ToString;
use mio::Token;
use strum::IntoEnumIterator;
use crate::command::ParserError;
use crate::database::Db;
use crate::queue::SendQueue;
/// Command parser. Commands consist of the text from the beginning of a string, up to but not
/// including the first space character or newline. If a space, that space is removed and the
/// rest of the string is put into args. If a newline, args will be empty. The command part of the
/// string is then parsed for a match with the variants of the enum. After that, control is passed
/// to `parse_subcommand()` to parse a subcommand if necessary. Also integrated into this is the
/// help system.
pub trait Parse: Clone + Default + IntoEnumIterator + PartialEq + ToString {
/// Help text for a single command.
fn help(&self) -> &str;
/// Check if there's a subcommand. The default implementation does not support subcommands.
/// If subcommands are necessary, you must implement the `subcommand()` function.
fn parse_subcommand(&self, s: String) -> Result<(Self, String), ParserError> {
Ok((self.clone(), s))
}
/// Gets the `help_all()` for a subcommand. The default implementation does not support
/// subcommands. You must implement the `subcommand_help()` function if you want sumcommands.
fn subcommand_help(&self, _: String) -> Option<String> {
None
}
/// Map of variants to functions to be called with 'dispatch()'.
fn dispatch_map(&self) -> fn(&Self, String, Token, &mut Db) -> SendQueue;
/// Must be implemented if there are subcommands.
fn dispatch_map_subcommand(&self, _: String, _: Token, _: &mut Db) -> SendQueue {
SendQueue::new()
}
/// This is to call the function associated with a command.
fn dispatch(
&self,
command: &Self,
command_text: String,
token: Token,
db: &mut Db,
) -> SendQueue {
let function = self.dispatch_map();
function(&command, command_text, token, db)
}
/// Boilerplate for default variant. Shouldn't ever be called. Probably should log if it is.
fn dispatch_default(&self, _: String, _: Token, _: &mut Db) -> SendQueue {
SendQueue::new()
}
/// Help text for all commands
fn help_all(&self) -> String {
Self::iter()
.filter_map(|a| {
if a == Self::default() {
None
} else {
Some(a.help().into())
}
})
.collect::<Vec<String>>()
.join("\n")
}
/// List of all commands
fn commands(&self) -> Vec<String> {
Self::iter()
.filter_map(|a| {
if a == Self::default() {
None
} else {
Some(a.to_string().to_lowercase())
}
})
.collect()
}
/// Parse a `Into<String>` into a command.
fn parse<S: Into<String>>(s: S) -> Result<(Self, String), ParserError> {
let mut s = s.into();
// get the text command and the args
let mut args: String = if let Some(pos) = s.find(' ') {
s.split_off(pos)
} else {
String::new()
};
if args.starts_with(' ') {
args = args.split_off(1);
}
// no command
if s.is_empty() {
return Err(ParserError::Empty);
}
let mut matches: Vec<Self> = Vec::new();
// look for matches
for action in Self::iter() {
if s == action.to_string() {
matches = vec![action];
break;
} else if action.to_string().to_lowercase().starts_with(s.as_str()) {
matches.push(action);
}
}
// check if there was a match
if matches.is_empty() {
return Err(ParserError::Unknown);
}
// sort so the first match is the best
matches.sort_by(|a, b| a.to_string().cmp(&b.to_string()));
// default is an error
if matches[0] == Self::default() {
Err(ParserError::Default)
} else {
Ok(matches[0].parse_subcommand(args)?)
}
}
}

@ -0,0 +1,21 @@
use std::error::Error;
use std::fmt;
#[derive(Debug)]
pub enum ParserError {
Empty,
Unknown,
Default,
}
impl fmt::Display for ParserError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => fmt::Display::fmt("No command provided", f),
Self::Unknown => fmt::Display::fmt("Unknown command", f),
Self::Default => fmt::Display::fmt("Internal error", f),
}
}
}
impl Error for ParserError {}

@ -0,0 +1,9 @@
mod player;
mod room;
mod set;
mod zone;
pub use player::*;
pub use room::*;
pub use set::*;
pub use zone::*;

@ -0,0 +1,5 @@
mod name;
mod player;
pub use name::*;
pub use player::*;

@ -0,0 +1,25 @@
use mio::Token;
use crate::command::CommandSetPlayer;
use crate::database::Db;
use crate::queue::SendQueue;
use crate::{try_option_send_error, try_send_error};
impl CommandSetPlayer {
pub fn dispatch_name(&self, args: String, token: Token, db: &mut Db) -> SendQueue {
let mut player = try_option_send_error!(token, db.get_connected_player(token));
let new_name = args.trim();
if new_name.is_empty() {
//return SendQueue::from_string(token, "Name can't be empty")
return SendQueue(vec![(token, "Name can't be empty".into(), true)].into());
}
player.name = new_name.to_string();
let _ = try_send_error!(token, db.save_player(&player));
let _ = try_send_error!(token, db.save_connected_player(token, &player));
SendQueue::ok(token)
}
}

@ -0,0 +1,36 @@
use std::default::Default;
use mio::Token;
use strum_macros::{Display, EnumIter};
use crate::command::Parse;
use crate::database::Db;
use crate::queue::SendQueue;
#[derive(Clone, Debug, Display, EnumIter, Eq, Ord, PartialEq, PartialOrd)]
pub enum CommandSetPlayer {
Name,
Default,
}
impl Default for CommandSetPlayer {
fn default() -> Self {
Self::Default
}
}
impl Parse for CommandSetPlayer {
fn help(&self) -> &str {
match self {
Self::Name => "set player name NEW_NAME :: Set player name to NEW_NAME.",
Self::Default => "",
}
}
fn dispatch_map(&self) -> fn(&Self, String, Token, &mut Db) -> SendQueue {
match self {
Self::Name => Self::dispatch_name,
Self::Default => Self::dispatch_default,
}
}
}

@ -0,0 +1,5 @@
mod name;
mod room;
pub use name::*;
pub use room::*;

@ -0,0 +1,19 @@
use mio::Token;
use crate::command::CommandSetRoom;
use crate::database::Db;
use crate::queue::SendQueue;
use crate::{try_option_send_error, try_send_error};
impl CommandSetRoom {
pub fn dispatch_name(&self, args: String, token: Token, db: &mut Db) -> SendQueue {
let player = try_option_send_error!(token, db.get_connected_player(token));
let mut room = try_option_send_error!(token, db.load_room(player.location));
room.name = args;
let _ = try_send_error!(token, db.save_room(&room));
SendQueue::ok(token)
}
}

@ -0,0 +1,36 @@
use std::default::Default;
use mio::Token;
use strum_macros::{Display, EnumIter};
use crate::command::Parse;
use crate::database::Db;
use crate::queue::SendQueue;
#[derive(Clone, Debug, Display, EnumIter, Eq, Ord, PartialEq, PartialOrd)]
pub enum CommandSetRoom {
Name,
Default,
}
impl Default for CommandSetRoom {
fn default() -> Self {
Self::Default
}
}
impl Parse for CommandSetRoom {
fn help(&self) -> &str {
match self {
Self::Name => "set room name NEW_NAME :: Set room name to NEW_NAME.",
Self::Default => "",
}
}
fn dispatch_map(&self) -> fn(&Self, String, Token, &mut Db) -> SendQueue {
match self {
Self::Name => Self::dispatch_name,
Self::Default => Self::dispatch_default,
}
}
}

@ -0,0 +1,75 @@
use std::default::Default;
use mio::Token;
use strum_macros::{Display, EnumIter};
use crate::command::{CommandSetPlayer, CommandSetRoom, CommandSetZone, Parse, ParserError};
use crate::database::Db;
use crate::queue::SendQueue;
#[derive(Clone, Debug, Display, EnumIter, Eq, Ord, PartialEq, PartialOrd)]
pub enum CommandSet {
Player(CommandSetPlayer),
Room(CommandSetRoom),
Zone(CommandSetZone),
Default,
}
impl Default for CommandSet {
fn default() -> Self {
Self::Default
}
}
impl Parse for CommandSet {
fn help(&self) -> &str {
match self {
Self::Player(_) => "set player :: Set player options.",
Self::Room(_) => "set room :: Set room options.",
Self::Zone(_) => "set zone :: Set zone options.",
Self::Default => "",
}
}
fn parse_subcommand(&self, s: String) -> Result<(Self, String), ParserError> {
match self {
Self::Player(_) => {
let (command, args) = CommandSetPlayer::parse(s)?;
Ok((Self::Player(command), args))
}
Self::Room(_) => {
let (command, args) = CommandSetRoom::parse(s)?;
Ok((Self::Room(command), args))
}
Self::Zone(_) => {
let (command, args) = CommandSetZone::parse(s)?;
Ok((Self::Zone(command), args))
}
Self::Default => Err(ParserError::Default),
}
}
fn dispatch_map_subcommand(&self, args: String, token: Token, db: &mut Db) -> SendQueue {
match self {
Self::Player(command_set_player) => {
command_set_player.dispatch(command_set_player, args, token, db)
}
Self::Room(command_set_room) => {
command_set_room.dispatch(command_set_room, args, token, db)
}
Self::Zone(command_set_zone) => {
command_set_zone.dispatch(command_set_zone, args, token, db)
}
_ => SendQueue::new(),
}
}
fn dispatch_map(&self) -> fn(&Self, String, Token, &mut Db) -> SendQueue {
match self {
Self::Player(_) => Self::dispatch_map_subcommand,
Self::Room(_) => Self::dispatch_map_subcommand,
Self::Zone(_) => Self::dispatch_map_subcommand,
Self::Default => Self::dispatch_default,
}
}
}

@ -0,0 +1,5 @@
mod name;
mod zone;
pub use name::*;
pub use zone::*;

@ -0,0 +1,19 @@
use mio::Token;
use crate::command::CommandSetZone;
use crate::database::Db;
use crate::queue::SendQueue;
use crate::{try_option_send_error, try_send_error};
impl CommandSetZone {
pub fn dispatch_name(&self, args: String, token: Token, db: &mut Db) -> SendQueue {
let player = try_option_send_error!(token, db.get_connected_player(token));
let mut zone = try_option_send_error!(token, db.load_zone(player.location));
zone.name = args;
let _ = try_send_error!(token, db.save_zone(&zone));
SendQueue::ok(token)
}
}

@ -0,0 +1,36 @@
use std::default::Default;
use mio::Token;
use strum_macros::{Display, EnumIter};
use crate::command::Parse;
use crate::database::Db;
use crate::queue::SendQueue;
#[derive(Clone, Debug, Display, EnumIter, Eq, Ord, PartialEq, PartialOrd)]
pub enum CommandSetZone {
Name,
Default,
}
impl Default for CommandSetZone {
fn default() -> Self {
Self::Default
}
}
impl Parse for CommandSetZone {
fn help(&self) -> &str {
match self {
Self::Name => "set zone name NEW_NAME :: Set zone name to NEW_NAME.",
Self::Default => "",
}
}
fn dispatch_map(&self) -> fn(&Self, String, Token, &mut Db) -> SendQueue {
match self {
Self::Name => Self::dispatch_name,
Self::Default => Self::dispatch_default,
}
}
}

@ -0,0 +1,31 @@
use std::path::PathBuf;
use serde_derive::Deserialize;
use crate::config::*;
use crate::file;
use crate::id::Id;
use crate::result::RudeResult;
/// Game configuration
#[derive(Clone, Debug, Deserialize)]
pub struct Config {
/// Directory to hold world and players
pub database: PathBuf,
/// Server configuration
pub server: Server,
/// Logging configuration
pub logging: Logging,
/// Default starting location
pub starting_location: Id,
}
impl Config {
/// Load and deserialize the toml configuration from the given file.
pub fn load<P: Into<PathBuf>>(path: P) -> RudeResult<Self> {
Ok(file::read_print(&path.into())?)
}
}

@ -0,0 +1,10 @@
use serde_derive::Deserialize;
use crate::config::LogLevel;
/// Logging configuration
#[derive(Clone, Debug, Deserialize)]
pub struct Logging {
/// Level of log verbosity.
pub level: LogLevel,
}

@ -0,0 +1,45 @@
use log::LevelFilter;
use serde_derive::Deserialize;
/// Helper enum to handle deserialization from toml because `LevelFilter`
/// from the log crate does not.
#[derive(Clone, Debug, Deserialize)]
pub enum LogLevel {
/// log::LevelFilter::Off
#[allow(non_camel_case_types)]
off,
/// log::LevelFilter::Error
#[allow(non_camel_case_types)]
error,
/// log::LevelFilter::Warn
#[allow(non_camel_case_types)]
warn,
/// log::LevelFilter::Info
#[allow(non_camel_case_types)]
info,
/// log::LevelFilter::Debug
#[allow(non_camel_case_types)]
debug,
/// log::LevelFilter::Trace
#[allow(non_camel_case_types)]
trace,
}
impl LogLevel {
/// Return the associated `LevelFilter`.
pub fn level(&self) -> LevelFilter {
match &self {
Self::off => LevelFilter::Off,
Self::error => LevelFilter::Error,
Self::warn => LevelFilter::Warn,
Self::info => LevelFilter::Info,
Self::debug => LevelFilter::Debug,
Self::trace => LevelFilter::Trace,
}
}
}

@ -0,0 +1,12 @@
//! Game configuration
#[allow(clippy::module_inception)]
mod config;
mod logging;
mod loglevel;
mod server;
pub use config::Config;
pub use logging::Logging;
pub use loglevel::LogLevel;
pub use server::Server;

@ -0,0 +1,17 @@
use serde_derive::Deserialize;
/// Server configuration
#[derive(Clone, Debug, Deserialize)]
pub struct Server {
/// IP address to listen on.
pub ip: String,
/// Port to listen on.
pub port: i64,
/// Max number of connected players.
pub connection_limit: usize,
/// Socket event capacity.
pub event_capacity: usize,
}

@ -0,0 +1,101 @@
use std::convert::TryFrom;
use mio::Token;
use rusqlite::params;
use crate::database::Db;
use crate::id::Id;
use crate::player::Player;
use crate::result::RudeResult;
use crate::try_log;
impl Db {
/// Get all connected players in a room
pub fn find_connected_players_by_location(
&self,
location: Id,
) -> RudeResult<Vec<(Token, Player)>> {
let mut statement = try_log!(
self.0.prepare(
"select connected_players.token, players.id, players.name, players.password, players.created, players.location from connected_players, players where players.location = ? and connected_players.player = players.id;"
),
"Unable to prepare sql statement"
);
let mut rows = try_log!(
statement.query(params![location]),
"Unable to perform query"
);
let mut v = Vec::<(Token, Player)>::new();
while let Some(row) = try_log!(rows.next(), "Unable to retrieve row") {
let row_token: i64 = try_log!(row.get("token"), "Unable to get token");
let token: Token = Token::from(usize::from_le_bytes(row_token.to_le_bytes()));
let player = try_log!(Player::try_from(row), "Unable to get Player from Row");
v.push((token, player));
}
Ok(v)
}
/// Load a player from the database.
pub fn get_connected_player(&self, token: Token) -> RudeResult<Option<Player>> {
let mut statement = try_log!(
self.0.prepare(
"select connected_players.token, players.id, players.name, players.password, players.created, players.location from connected_players, players where connected_players.token = ? and connected_players.player = players.id;"
),
"Unable to prepare sql statement"
);
let mut rows = try_log!(
statement.query(params![i64::from_le_bytes(token.0.to_le_bytes())]),
"Unable to perform query"
);
if let Some(row) = try_log!(rows.next(), "Unable to retrieve row") {
Ok(Some(try_log!(
Player::try_from(row),
"Unable to get Player from Row"
)))
} else {
Ok(None)
}
}
/// Save a connected player to the database.
pub fn save_connected_player(&self, token: Token, player: &Player) -> RudeResult<()> {
let mut statement = try_log!(
self.0.prepare(
"insert into connected_players (token, player) values (?, ?) on conflict(token) do update set player=?;"
),
"Unable to prepare sql statement"
);
let _ = try_log!(
statement.execute(params![
i64::from_le_bytes(token.0.to_le_bytes()),
player.id,
player.id,
]),
"Unable to perform query"
);
Ok(())
}
/// Remove player from connected_players table
pub fn remove_connected_player(&self, token: Token) -> RudeResult<()> {
let mut statement = try_log!(
self.0
.prepare("delete from connected_players where token = ?;"),
"Unable to prepare sql statement"
);
let _ = try_log!(
statement.execute(params![i64::from_le_bytes(token.0.to_le_bytes())]),
"Unable to execute sql statement"
);
Ok(())
}
}

@ -0,0 +1,84 @@
use std::path::Path;
use mio::Token;
use rusqlite::{params, Connection};
use crate::id::Id;
use crate::player::Player;
use crate::result::RudeResult;
use crate::try_log;
pub struct Db(pub Connection);
impl Db {
/// Open the database
pub fn open<P: AsRef<Path>>(path: P) -> RudeResult<Self> {
let connection = try_log!(Connection::open(path), "Unable to open database");
{
let mut statement = try_log!(
connection.prepare("delete from connected_players;"),
"Unable to prepare sql statement"
);
let _ = try_log!(
statement.execute(params![]),
"Unable to execute sql statement"
);
}
Ok(Self(connection))
}
pub fn single_save_player(&self, token: Token, player: &Player) -> RudeResult<()> {
let _ = self.save_player(&player)?;
self.save_connected_player(token, &player)
}
/// Get a new player id, checked to ensure uniqueness.
pub fn new_player_id(&self) -> RudeResult<Id> {
let mut statement = try_log!(
self.0.prepare("select * from players where id=?;"),
"Unable to prepare sql statement"
);
loop {
let id = Id::new();
let mut rows = try_log!(statement.query(params![id]), "Unable to perform query");
if try_log!(rows.next(), "Unable to retrieve row").is_none() {
return Ok(id);
}
}
}
/// Get a new area id, checked to ensure uniqueness.
pub fn new_area_id(&self) -> RudeResult<Id> {
let mut zones_statement = try_log!(
self.0.prepare("select * from zones where id = ?;"),
"Unable to prepare sql statement"
);
let mut rooms_statement = try_log!(
self.0.prepare("select * from rooms where id = ?;"),
"Unable to prepare sql statement"
);
loop {
let id = Id::new();
let mut rows = try_log!(
zones_statement.query(params![id]),
"Unable to perform query"
);
if try_log!(rows.next(), "Unable to retrieve row").is_none() {
let mut rows = try_log!(
rooms_statement.query(params![id]),
"Unable to perform sql query"
);
if try_log!(rows.next(), "Unable to retrieve row").is_none() {
return Ok(id);
}
}
}
}
}

@ -0,0 +1,7 @@
mod connected_players;
mod db;
mod players;
mod rooms;
mod zones;
pub use db::Db;

@ -0,0 +1,87 @@
use std::convert::TryFrom;
use rusqlite::params;
use crate::database::Db;
use crate::id::Id;
use crate::player::Player;
use crate::result::RudeResult;
use crate::try_log;
impl Db {
/// Load a player from the database.
pub fn load_player(&self, id: Id) -> RudeResult<Option<Player>> {
let mut statement = try_log!(
self.0
.prepare("select id, name, password, created, location from players where id = ?"),
"Unable to prepare sql statement"
);
let mut rows = try_log!(statement.query(params![id]), "Unable to perform query");
Ok(
if let Some(row) = try_log!(rows.next(), "Unable to retrieve row") {
Some(try_log!(
Player::try_from(row),
"Unable to get Player from Row"
))
} else {
None
},
)
}
/// Find a player by the name
pub fn find_player_by_name<S: Into<String>>(&self, name: S) -> RudeResult<Option<Player>> {
let mut statement = try_log!(
self.0.prepare(
"select id, name, password, created, location from players where name = ?"
),
"Unable to prepare sql statement"
);
let mut rows = try_log!(
statement.query(params![name.into()]),
"Unable to perform query"
);
Ok(
if let Some(row) = try_log!(rows.next(), "Unable to retrieve row") {
Some(try_log!(
Player::try_from(row),
"Unable to get Player from Row"
))
} else {
None
},
)
}
/// Save a player to the database.
pub fn save_player(&self, player: &Player) -> RudeResult<()> {
let mut statement = try_log!(
self.0.prepare(
"insert into players (id, name, password, created, location) values (?, ?, ?, ?, ?) on conflict(id) do update set id=?, name=?, password=?, created=?, location=?;"
),
"Unable to prepare statement"
);
let _ = try_log!(
statement.execute(params![
player.id,
player.name,
player.password,
player.created,
player.location,
player.id,
player.name,
player.password,
player.created,
player.location,
]),
"Unable to perform query"
);
Ok(())
}
}

@ -0,0 +1,92 @@
use std::collections::HashMap;
use std::convert::TryFrom;
use rusqlite::params;
use crate::database::Db;
use crate::id::Id;
use crate::result::RudeResult;
use crate::try_log;
use crate::world::{Exit, Room};
impl Db {
/// Save a room to the database.
pub fn save_room(&self, room: &Room) -> RudeResult<()> {
let mut statement = try_log!(
self.0.prepare(
"insert into rooms (id, zone, name, description, users_visible) values (?, ?, ?, ?, ?) on conflict(id) do update set id=?, zone=?, name=?, description=?, users_visible=?;"
),
"Unable to prepare sql statement"
);
let _ = try_log!(
statement.execute(params![
room.id,
room.zone,
room.name,
room.description.join("\n"),
room.users_visible,
room.id,
room.zone,
room.name,
room.description.join("\n"),
room.users_visible,
]),
"Unable to perform query"
);
for (direction, exit) in room.exits.iter() {
let mut statement = try_log!(
self.0
.prepare("insert into exits (room, target, direction) values (?, ?, ?);"),
"Unable to prepare sql statement"
);
let _ = try_log!(
statement.execute(params![room.id, exit.target, exit.direction,]),
"Unable to perform query"
);
}
Ok(())
}
/// Load a room from the database.
pub fn load_room(&self, id: Id) -> RudeResult<Option<Room>> {
let mut statement = try_log!(
self.0.prepare(
"select id, zone, name, description, users_visible from rooms where id = ?"
),
"Unable to prepare sql statement"
);
let mut rows = try_log!(statement.query(params![id]), "Unable to perform query");
let mut room = if let Some(row) = try_log!(rows.next(), "Unable to retrieve row") {
try_log!(Room::try_from(row), "Unable to get Room from Row")
} else {
return Ok(None);
};
room.exits = {
let mut e = HashMap::new();
let mut statement = try_log!(
self.0
.prepare("select room, target, direction from exits where room = ?"),
"Unable to prepare sql statement"
);
let mut rows = try_log!(statement.query(params![id]), "Unable to perform query");
while let Some(row) = try_log!(rows.next(), "Unable to retrieve row") {
let exit = try_log!(Exit::try_from(row), "Unable to get Exit from Row");
e.insert(exit.direction, exit);
}
e
};
Ok(Some(room))
}
}

@ -0,0 +1,88 @@
use std::collections::HashSet;
use std::convert::TryFrom;
use rusqlite::params;
use crate::database::Db;
use crate::id::Id;
use crate::result::RudeResult;
use crate::try_log;
use crate::world::Zone;
impl Db {
/// Save a zone to the database.
pub fn save_zone(&self, zone: &Zone) -> RudeResult<()> {
let mut statement = try_log!(
self.0.prepare(
"insert into zones (id, parent, name, users_visible) values (?, ?, ?, ?) on conflict(id) do update set id=?, parent=?, name=?, users_visible=?;"
),
"Unable to prepare sql statement"
);
let _ = try_log!(
statement.execute(params![
zone.id,
zone.parent,
zone.name,
zone.users_visible,
zone.id,
zone.parent,
zone.name,
zone.users_visible,
]),
"Unable to perform query"
);
Ok(())
}
/// Load a zone from the database.
pub fn load_zone(&self, id: Id) -> RudeResult<Option<Zone>> {
let mut statement = try_log!(
self.0
.prepare("select id, parent, name, users_visible from zones where id = ?"),
"Unable to prepare sql statement"
);
let mut rows = try_log!(statement.query(params![id]), "Unable to perform query");
let mut zone = if let Some(row) = try_log!(rows.next(), "Unable to retrieve row") {
try_log!(Zone::try_from(row), "Unable to get Zone from Row")
} else {
return Ok(None);
};
zone.areas = {
let mut a = HashSet::new();
let mut statement = try_log!(
self.0.prepare("select id from zones where parent = ?;"),
"Unable to prepare sql statement"
);
let mut rows = try_log!(statement.query(params![id]), "Unable to perform query");
while let Some(row) = try_log!(rows.next(), "Unable to retrieve row") {
let new_id = try_log!(row.get(0), "Unable to retrieve field");
if new_id != id {
a.insert(new_id);
}
}
let mut statement = try_log!(
self.0.prepare("select id from rooms where zone = ?;"),
"Unable to prepare sql statement"
);
let mut rows = try_log!(statement.query(params![id]), "Unable to perform query");
while let Some(row) = try_log!(rows.next(), "Unable to retrieve row") {
a.insert(try_log!(row.get(0), "Unable to retrieve field"));
}
a
};
Ok(Some(zone))
}
}

@ -0,0 +1,43 @@
use std::fs::File;
use std::io::{BufReader, Read};
use std::path::PathBuf;
use log::debug;
use serde::de::Deserialize;
use crate::result::*;
/// Open a file from the filesystem, read the contents, parse toml and return
/// the corresponding toml object. Errors will be printed as well as returned.
pub fn read_print<'de, P: Into<PathBuf>, T: Deserialize<'de>>(path: P) -> RudeResult<T> {
read_file(path, false)
}
/// The actual function to read a file.
fn read_file<'de, P: Into<PathBuf>, T: Deserialize<'de>>(path: P, log: bool) -> RudeResult<T> {
let path = path.into();
debug!("Reading file {}", path.display());
let file: File = try_error(
File::open(path.clone()),
format!("Unable to open file: {}", path.display()),
log,
)?;
let mut buffer = BufReader::new(file);
let mut file_contents = String::new();
try_error(
buffer.read_to_string(&mut file_contents),
format!("Unable to read file: {}", path.display()),
log,
)?;
// this Box::leak() may not be great? something about leaking memory?
// i don't know what i'm doing here?
try_error(
toml::from_str(Box::leak(file_contents.into_boxed_str())),
format!("Unable to parse toml: {}", path.display()),
log,
)
}

@ -0,0 +1,32 @@
use std::collections::{HashMap, VecDeque};
use mio::{Events, Poll, Token};
use crate::client::Client;
use crate::config::Config;
use crate::database::Db;
use crate::server::Server;
/// The `Game` struct is everything.
pub struct Game {
/// Game configuration
pub config: Config,
/// Server socket
pub server: Server,
/// Poll object
pub poll: Poll,
/// Socket events
pub events: Events,
/// Available tokens
pub tokens: VecDeque<Token>,
/// Connected clients
pub clients: HashMap<Token, Client>,
/// Database connection pool
pub db: Db,
}

@ -0,0 +1,87 @@
use std::net::Shutdown;
use log::{debug, info, warn};
use mio::unix::UnixReady;
use mio::{PollOpt, Ready};
use crate::game::Game;
use crate::queue::RecvQueue;
impl Game {
pub fn handle_events(&mut self) -> RecvQueue {
let mut recv_queue = RecvQueue::new();
let events = self.events.iter().clone();
for event in events {
if UnixReady::from(event.readiness()).is_hup() {
// remote host has disconnected
let address: String = match self.clients.remove(&event.token()) {
Some(client) => client.into(),
None => "None".into(),
};
info!("Disconnect from {}", address);
let _ = self.db.remove_connected_player(event.token());
self.tokens.push_back(event.token());
} else if event.token() == self.server.token {
// new connection
if let Some(token) = self.tokens.pop_front() {
// got a token so haven't reached connection limit
match self.server.accept(token) {
Ok(client) => {
match self.poll.register(
&client.socket,
client.token,
Ready::readable() | UnixReady::hup(),
PollOpt::edge(),
) {
Ok(_) => {
// new connection
info!("Connect from {}", &client);
recv_queue.push(token, "");
self.clients.insert(token, client);
}
Err(e) => {
warn!("Register failed: {}: {}", client, e);
self.tokens.push_back(token);
}
};
}
Err(e) => {
warn!("Accept failed: {}", e);
self.tokens.push_back(token);
}
};
} else {
// connection limit reached
warn!("Maximum connections reached");
let token = event.token();
// send message and close connection
if let Ok(mut client) = self.server.accept(token) {
client.send_without_prompt("Maximum connections reached.\n");
let _ = client.socket.shutdown(Shutdown::Both);
info!("Rejected connection from {}", &client);
}
// put the token back
self.tokens.push_back(token);
}
} else {
// there is something to read from a client
if let Some(client) = self.clients.get_mut(&event.token()) {
let r = client.read();
match r {
Ok(message) => {
recv_queue.push(event.token(), message);
}
Err(e) => debug!("Read from client failed: {}: {}", client, e),
};
}
}
}
recv_queue
}
}

@ -0,0 +1,41 @@
use log::debug;
use crate::game::Game;
use crate::queue::SendQueue;
use crate::result::RudeResult;
use crate::try_log;
impl Game {
/// One iteration of the game loop
pub fn iter_once(&mut self) -> RudeResult<bool> {
// poll for events
try_log!(self.poll.poll(&mut self.events, None), "Poll failed");
// handle socket events and build receive queue
let mut recv_queue = self.handle_events();
// get the send queue ready
let mut send_queue = SendQueue::new();
// process the receive queue and fill the send queue
while let Some((token, message)) = recv_queue.pop() {
let mut queue = self.process_recv_message(token, message);
send_queue.append(&mut queue);
}
// send everything in the send queue
while let Some((token, message, prompt)) = send_queue.pop() {
if let Some(client) = self.clients.get_mut(&token) {
if prompt {
client.send_with_prompt(message);
} else {
client.send_without_prompt(message);
}
} else {
debug!("no client?");
}
}
Ok(true)
}
}

@ -0,0 +1,9 @@
#[allow(clippy::module_inception)]
mod game;
mod handle_events;
mod iter_once;
mod new;
mod process_recv_message;
mod state;
pub use game::Game;

@ -0,0 +1,56 @@
use std::collections::{HashMap, VecDeque};
use log::{debug, info};
use mio::{Events, Poll, PollOpt, Ready, Token};
use crate::client::Client;
use crate::config::Config;
use crate::database::Db;
use crate::game::Game;
use crate::logger;
use crate::result::*;
use crate::server::Server;
use crate::try_log;
impl Game {
pub fn new() -> RudeResult<Self> {
let config = Config::load("config.toml")?;
println!("Loading configuration from config.toml");
let log_level = config.logging.level.clone();
try_print(logger::init(log_level), "Unable to initialize logger")?;
debug!("Initialized logging facility");
debug!("Opening database");
let db = Db::open(&config.database)?;
let events = Events::with_capacity(config.server.event_capacity);
let clients: HashMap<Token, Client> = HashMap::new();
let mut tokens: VecDeque<Token> = VecDeque::with_capacity(config.server.connection_limit);
for i in 1..=config.server.connection_limit {
tokens.push_back(Token(i));
}
let server_address = format!("{}:{}", config.server.ip, config.server.port);
let server = Server::listen(server_address.clone(), Token(0))?;
info!("Listening on {}", &server_address);
let poll = try_log!(Poll::new(), "Unable to create Poll");
try_log!(
poll.register(&server.socket, Token(0), Ready::readable(), PollOpt::edge()),
"Unable to register poll"
);
debug!("Accepting connections");
Ok(Self {
config,
server,
poll,
events,
tokens,
clients,
db,
})
}
}

@ -0,0 +1,37 @@
use log::warn;
use mio::Token;
use crate::game::Game;
use crate::queue::SendQueue;
use crate::state::*;
impl Game {
/// Process the received message from the client.
pub fn process_recv_message(&mut self, token: Token, message: String) -> SendQueue {
let mut send_queue = SendQueue::new();
let client_state = {
if let Some(client) = self.clients.get(&token) {
client.state.clone()
} else {
// should probably do something here like give the client a
// state or close the connection or something?
warn!("Client has no state: {:?}", token);
return send_queue;
}
};
match &client_state {
State::Login(login_state) => {
let mut queue = self.login(token, message, login_state);
send_queue.append(&mut queue);
}
State::Action => {
let mut queue = self.action(token, message);
send_queue.append(&mut queue);
}
};
send_queue
}
}

@ -0,0 +1,48 @@
use log::{debug, warn};
use mio::Token;
use crate::command::{Command, Parse};
use crate::game::Game;
use crate::queue::SendQueue;
impl Game {
/// Figure out the action to be taken and take it.
pub fn action(&mut self, token: Token, message: String) -> SendQueue {
let mut send_queue = SendQueue::new();
// get the player information
let player = if let Ok(Some(player)) = self.db.get_connected_player(token) {
player.clone()
} else {
warn!("No connected player found: {:?}", &token);
return SendQueue::error(token);
};
// break up the message into lines
let lines: Vec<String> = message.lines().map(|s| s.into()).collect();
// no need to do anything else if there's nothing to process
if lines.is_empty() {
send_queue.push(token, "", true);
return send_queue;
}
// process each line
for line in lines {
// parse the command
let (command, args) = match Command::parse(&line) {
Ok(c) => c,
Err(e) => {
send_queue.push(token, format!("{}", e), true);
continue;
}
};
debug!("{} : {:?}", player.name, command);
send_queue.append(&mut command.dispatch(&command, args, token, &mut self.db));
}
send_queue
}
}

@ -0,0 +1,155 @@
use chrono::Utc;
use log::warn;
use mio::Token;
use crate::actions;
use crate::command::Command;
use crate::game::Game;
use crate::player::Player;
use crate::queue::SendQueue;
use crate::state::*;
impl Game {
pub fn login(&mut self, token: Token, message: String, state: &Login) -> SendQueue {
let mut send_queue = SendQueue::new();
let mut client = {
if let Some(client) = self.clients.remove(&token) {
client
} else {
warn!("Can't find a client with token: {:?}", &token);
return send_queue;
}
};
match state {
// get the username
Login::Username => {
if message.is_empty() {
send_queue.push(token, "Username: ", false);
} else {
match self.db.find_player_by_name(&message) {
Ok(Some(_)) => {
send_queue.push(token, "\nPassword: ", false);
client.state = State::Login(Login::Password(message));
}
Ok(None) => {
send_queue.push(
token,
format!("\nCreate {}? [y/N]: ", message.clone()),
false,
);
client.state = State::Login(Login::CreateUser(message));
}
Err(_) => {
send_queue.push(token, "\nError\n\nUsername: ", false);
}
}
}
}
// username not found
Login::CreateUser(username) => {
if !message.clone().is_empty() && message != "n" {
send_queue.push(token, "\nNew password: ", false);
client.state = State::Login(Login::CreatePassword(username.to_owned()));
} else {
send_queue.push(token, "\n\nUsername: ", false);
client.state = State::Login(Login::Username);
}
}
// first new user password
Login::CreatePassword(username) => {
if message.is_empty() {
send_queue.push(token, "\n\nUsername: ", false);
client.state = State::Login(Login::Username);
} else {
send_queue.push(token, "\nNew password again: ", false);
client.state =
State::Login(Login::CreatePassword2((username.to_owned(), message)));
}
}
Login::CreatePassword2((username, pass)) => {
let pass = pass.to_owned();
if message.is_empty() || message != pass {
send_queue.push(token, "\n\nUsername: ", false);
client.state = State::Login(Login::Username);
} else {
if let Ok(id) = self.db.new_player_id() {
let player = Player {
id,
name: username.clone(),
password: pass,
created: Utc::now(),
location: self.config.starting_location.clone(),
};
if self.db.single_save_player(token, &player).is_ok() {
send_queue.push(token, format!("Welcome, {}\n", username), false);
send_queue.push(token, "", true);
send_queue.append(&mut actions::look(
&Command::default(),
String::new(),
token,
&mut self.db,
));
client.state = State::Action;
} else {
send_queue.push(token, "Error", true);
}
} else {
send_queue.push(token, "Error", true);
}
}
}
Login::Password(username) => {
if message.is_empty() {
send_queue.push(token, "\n\nUsername: ", false);
client.state = State::Login(Login::Username);
} else {
match self.db.find_player_by_name(username) {
Ok(Some(player)) => {
if message == player.password {
if self.db.save_connected_player(token, &player).is_ok() {
send_queue.push(
token,
format!("Welcome back, {}\n\n", username),
false,
);
client.state = State::Action;
send_queue.append(&mut actions::look(
&Command::default(),
String::new(),
token,
&mut self.db,
));
} else {
send_queue.push(token, "Unable to login\n", false);
send_queue.push(token, "\n\nUsername: ", false);
client.state = State::Login(Login::Username);
}
} else {
send_queue.push(token, "Incorrect password\n", false);
send_queue.push(token, "\n\nUsername: ", false);
client.state = State::Login(Login::Username);
}
}
Ok(None) | Err(_) => {
send_queue.push(token, "Error\n", false);
send_queue.push(token, "\n\nUsername: ", false);
client.state = State::Login(Login::Username);
}
}
}
}
};
self.clients.insert(token, client);
send_queue
}
}

@ -0,0 +1,2 @@
mod action;
mod login;

@ -0,0 +1,74 @@
use std::cmp::{Eq, PartialEq};
use std::fmt;
use std::hash::{Hash, Hasher};
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct Id(Uuid);
impl Id {
pub fn new() -> Self {
Self(Uuid::new_v4())
}
}
impl fmt::Display for Id {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl Hash for Id {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl Into<Uuid> for Id {
fn into(self) -> Uuid {
self.0
}
}
impl From<Uuid> for Id {
fn from(u: Uuid) -> Self {
Self(u)
}
}
impl Eq for Id {}
impl PartialEq for Id {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl FromSql for Id {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
let s = match value.as_str() {
Ok(s) => s,
Err(e) => {
log::error!("{}({}) :: {} :: {}", file!(), line!(), "value.as_str()", e);
return Err(e);
}
};
match Uuid::parse_str(s) {
Ok(id) => return Ok(Self(id)),
Err(e) => Err(FromSqlError::Other(Box::from(e))),
}
}
}
impl ToSql for Id {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
let h = self.0.to_hyphenated();
let mut buf = Uuid::encode_buffer();
let s = h.encode_lower(&mut buf);
Ok(ToSqlOutput::from(s.to_string()))
}
}

@ -0,0 +1,19 @@
//! The Rude Mud
pub mod actions;
pub mod client;
pub mod command;
pub mod config;
pub mod database;
pub mod file;
pub mod game;
pub mod id;
pub mod logger;
#[macro_use]
pub mod macros;
pub mod player;
pub mod queue;
pub mod result;
pub mod server;
pub mod state;
pub mod world;

@ -0,0 +1,38 @@
use chrono::Local;
use colored::Colorize;
use fern::colors::{Color, ColoredLevelConfig};
use fern::Dispatch;
use crate::config::LogLevel;
use crate::result::RudeResult;
/// Initialize the logging facilities. `LevelFilter` must be specified and will
/// determine what level of logs will be shown.`
pub fn init(level: LogLevel) -> RudeResult<()> {
Dispatch::new()
.format(|out, message, record| {
let colors = ColoredLevelConfig::new()
.error(Color::Red)
.warn(Color::Magenta)
.info(Color::Cyan)
.debug(Color::Yellow)
.trace(Color::Green);
out.finish(format_args!(
"{} {} {}",
Local::now()
.format("%Y-%m-%dT%H:%M:%S%.3f%z")
.to_string()
.white()
.bold(),
colors.color(record.level()),
message
))
})
.level(level.level())
.chain(std::io::stdout())
.apply()?;
Ok(())
}

@ -0,0 +1,82 @@
// #[macro_export]
// macro_rules! try_log {
// ($e:expr, $l:literal) => {
// match $e {
// Ok(r) => r,
// Err(e) => {
// log::error!("{}({}) :: {} :: {}", file!(), line!(), $l, e);
// return Err(Box::from(e));
// }
// }
// };
// }
#[macro_export]
macro_rules! try_log {
($e:expr, $($arg:tt)*) => {
match $e {
Ok(r) => r,
Err(e) => {
let s = std::fmt::format(format_args!($($arg)*));
log::error!("{}({}) :: {} :: {}", file!(), line!(), s, e);
return Err(Box::from(e));
}
}
};
}
/// Unwrap a Result<T, E>, if it's an error then let client know and return.
#[macro_export]
macro_rules! try_send_error {
($i:ident, $e:expr) => {
match $e {
Ok(r) => r,
Err(e) => {
log::error!(
"{}({}) :: returning SendQueue::error() :: {}",
file!(),
line!(),
e
);
return SendQueue::error($i);
}
}
};
}
#[macro_export]
macro_rules! try_option_send_error {
($i:ident, $e:expr) => {
match $e {
Ok(Some(r)) => r,
Ok(None) => {
log::error!(
"{}({}) :: returning SendQueue::error() :: None value",
file!(),
line!()
);
return SendQueue::error($i);
}
Err(e) => {
log::error!(
"{}({}) :: returning SendQueue::error() :: {}",
file!(),
line!(),
e
);
return SendQueue::error($i);
}
}
};
}
// #[macro_export]
// macro_rules! try_option_send_error {
// ($i:ident, $e:expr) => {
// if let Ok(Some(r)) = $e {
// r
// } else {
// return SendQueue::error($i);
// }
// };
// }

@ -0,0 +1,36 @@
mod actions;
mod client;
mod command;
mod config;
mod database;
mod file;
mod game;
mod id;
mod logger;
#[macro_use]
mod macros;
mod player;
mod queue;
mod result;
mod server;
mod state;
mod world;
use log::*;
use crate::game::Game;
use crate::result::RudeResult;
fn main() -> RudeResult<()> {
let mut game = Game::new()?;
loop {
match game.iter_once() {
Ok(true) => continue,
Ok(false) => break,
Err(e) => return Err(e),
}
}
Ok(())
}

@ -0,0 +1,43 @@
//! Player information
use std::convert::TryFrom;
use std::error::Error;
use chrono::{DateTime, Utc};
use rusqlite::{types::FromSql, Row};
use serde_derive::{Deserialize, Serialize};
use crate::id::Id;
/// Player information
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Player {
/// Unique identifier
pub id: Id,
/// Player name
pub name: String,
/// Player's password (this needs to be properly salted and hashed but isn't)
pub password: String,
/// Creation DateTime
pub created: DateTime<Utc>,
/// Player's location
pub location: Id,
}
impl<'a> TryFrom<&Row<'a>> for Player {
type Error = Box<dyn Error>;
fn try_from(row: &Row) -> Result<Self, Self::Error> {
Ok(Self {
id: try_log!(row.get("id"), "id"),
name: try_log!(row.get("name"), "name"),
password: try_log!(row.get("password"), "password"),
created: try_log!(row.get("created"), "created"),
location: try_log!(row.get("location"), "location"),
})
}
}

@ -0,0 +1,26 @@
//! All the players of the game
use std::collections::HashMap;
use uuid::Uuid;
use crate::player::Player;
/// Map of each player id to [`Player`](../Player/struct.Player.html) object.
pub type Players = HashMap<Uuid, Player>;
/// Methods for the [`Players`](type.Players.html) type
pub trait PlayersMethods {
fn find_by_name<S: Into<String>>(&self, name: S) -> Option<Player>;
}
impl PlayersMethods for Players {
/// Find a [`Player`](../Player/struct.Player.html) by the player name.
fn find_by_name<S: Into<String>>(&self, name: S) -> Option<Player> {
let name = name.into();
match self.iter().find(|(_id, player)| player.name == name) {
Some((_id, player)) => Some(player.clone()),
None => None,
}
}
}

@ -0,0 +1,65 @@
//! Queues for recv and send
use std::collections::VecDeque;
use mio::Token;
/// Queue of clients with a message to receive.
///
/// * The client is designated with the [`Token`](../../mio/struct.Token.html).
/// * [`String`](https://doc.rust-lang.org/nightly/alloc/string/struct.String.html)
/// is used to store the message.
#[derive(Debug)]
pub struct RecvQueue(VecDeque<(Token, String)>);
impl RecvQueue {
/// Create a new, empty `RecvQueue`.
pub fn new() -> Self {
RecvQueue(VecDeque::new())
}
/// Remove and return the first message in the `RecvQueue`.
pub fn pop(&mut self) -> Option<(Token, String)> {
self.0.pop_front()
}
/// Add a message to the end of the `RecvQueue`.
pub fn push<S: Into<String>>(&mut self, token: Token, s: S) {
self.0.push_back((token, s.into()));
}
}
/// Queue of messages to send to clients.
///
/// * The client is designated with the [`Token`](../../mio/struct.Token.html).
/// * [`String`](https://doc.rust-lang.org/nightly/alloc/string/struct.String.html)
/// is used to store the message.
/// * [`bool`](https://doc.rust-lang.org/nightly/std/primitive.bool.html) is set to `true` if a
/// prompt is to be displayed following the message.
#[derive(Debug)]
pub struct SendQueue(pub VecDeque<(Token, String, bool)>);
impl SendQueue {
pub fn new() -> Self {
SendQueue(VecDeque::new())
}
pub fn append(&mut self, queue: &mut Self) {
self.0.append(&mut queue.0);
}
pub fn pop(&mut self) -> Option<(Token, String, bool)> {
self.0.pop_front()
}
pub fn push<S: Into<String>>(&mut self, token: Token, s: S, prompt: bool) {
self.0.push_back((token, s.into(), prompt));
}
pub fn error(token: Token) -> Self {
Self(vec![(token, "Error".to_string(), true)].into())
}
pub fn ok(token: Token) -> Self {
Self(vec![(token, "Ok".to_string(), true)].into())
}
}

@ -0,0 +1,56 @@
use std::error::Error;
use log::error;
/// Wrapper for `Result<T, Box<dyn Error>>`.
///
/// # Example
/// ```
/// use rude::result::RudeResult;
///
/// fn example_result() -> RudeResult<()> {
/// Ok(())
/// }
/// ```
pub type RudeResult<T> = Result<T, Box<dyn Error>>;
/// Wrap `?` with an error message into the log.
/*pub fn try_log<T, E: Into<Box<dyn Error>>, S: Into<String>>(
result: Result<T, E>,
message: S,
) -> RudeResult<T> {
try_error(result, message, true)
}*/
/// Wrap `?` with an error message to stdout.
pub fn try_print<T, E: Into<Box<dyn Error>>, S: Into<String>>(
result: Result<T, E>,
message: S,
) -> RudeResult<T> {
try_error(result, message, false)
}
/// Wrap `?` with an error message to either stdout or the log.
///
/// * `result` - `Result` type to check for `Error`.
/// * `message` - Message to send to either log or stdout if `result` is `Error`.
/// * `log` - `true` to send `message` to log, otherwise send to stdout.
//pub fn try_error<T, S: Into<String>>(result: Result<T>, message: S, log: bool) -> Result<T> {
pub fn try_error<T, E: Into<Box<dyn Error>>, S: Into<String>>(
result: Result<T, E>,
message: S,
log: bool,
) -> RudeResult<T> {
match result {
Ok(r) => Ok(r),
Err(e) => {
let e = e.into();
if log {
error!("{} :: {}", message.into(), e);
} else {
println!("{} :: {}", message.into(), e);
}
Err(e.into())
}
}
}

@ -0,0 +1,82 @@
//! Server connection information.
use std::io;
use std::net::SocketAddr;
use mio::event::Evented;
use mio::net::TcpListener;
use mio::{Poll, PollOpt, Ready, Token};
use crate::client::Client;
use crate::result::*;
use crate::state::*;
/// Connection information for the server.
#[derive(Debug)]
pub struct Server {
/// listen socket
pub socket: TcpListener,
/// token identifier (0)
pub token: Token,
/// ip address/port
pub addr: SocketAddr,
}
impl Server {
/// Bind to the provided address
pub fn listen<'a>(addr: String, token: Token) -> RudeResult<Server> {
let addr: SocketAddr = try_log!(addr.parse(), "Unable to parse server address: {}", &addr);
let socket: TcpListener = try_log!(
TcpListener::bind(&addr),
"Unable to bind to address: {}",
&addr,
);
Ok(Server {
socket,
token,
addr,
})
}
/// Accept a new client connection
pub fn accept(&self, token: Token) -> RudeResult<Client> {
let (socket, addr) = self.socket.accept()?;
Ok(Client {
socket,
token,
addr,
state: State::Login(Login::Username),
})
}
}
impl Evented for Server {
fn register(
&self,
poll: &Poll,
token: Token,
interest: Ready,
opts: PollOpt,
) -> io::Result<()> {
self.socket.register(poll, token, interest, opts)
}
fn reregister(
&self,
poll: &Poll,
token: Token,
interest: Ready,
opts: PollOpt,
) -> io::Result<()> {
self.socket.reregister(poll, token, interest, opts)
}
fn deregister(&self, poll: &Poll) -> io::Result<()> {
self.socket.deregister(poll)
}
}

@ -0,0 +1,30 @@
//! State information for a connected `Client`.
/// Play state for a conected `Client`.
#[derive(Clone, Debug, PartialEq)]
pub enum State {
/// Logging in
Login(Login),
/// Performing an action
Action,
}
/// Login state
#[derive(Clone, Debug, PartialEq)]
pub enum Login {
/// Username
Username,
/// Unknown user
CreateUser(String),
/// New user password
CreatePassword(String),
/// New user password again
CreatePassword2((String, String)),
/// Password for existing user
Password(String),
}

@ -0,0 +1,36 @@
use crate::id::Id;
/// The type of the `Area`.
#[derive(Debug)]
pub enum AreaType {
Room,
Zone,
}
/// An 'Area' is an identifiable location, either a specific `Room` or maybe a
/// grouping of `Area`s.
pub trait Area: std::fmt::Debug {
/// Returns the unique identifier of the `Area`.
fn id(&self) -> Id;
/// Return the unique identifier of the parent `Area`.
fn parent(&self) -> Id;
/// Text name of the `Area`.
fn name(&self) -> String;
/// Specifies whether users in this `Area` will be visibile (appearing in
/// the area `self.name()`) to the parent `Area` and its children. This
/// allows for granularity over the where command.
fn visible(&self) -> bool;
/// The type of area, either it's a `Room` or a `Zone`.
fn area_type(&self) -> AreaType;
/// Returns true if this `Area` is the root, or the world. This is
/// determined to be the case if `self.id()` and `self.parent()` return
/// the same `Uuid`.
fn is_world(&self) -> bool {
self.id() == self.parent()
}
}

@ -0,0 +1,193 @@
use std::default::Default;
use std::error::Error;
use std::str::FromStr;
use lazy_static::lazy_static;
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use serde_derive::{Deserialize, Serialize};
use crate::command::ParserError;
use crate::result::RudeResult;
lazy_static! {
/// List of text directions and the associated Direction
pub static ref DIRECTION_LIST: Vec<(&'static str, Direction)> = vec![
("north", Direction::North),
("south", Direction::South),
("east", Direction::East),
("west", Direction::West),
("up", Direction::Up),
("down", Direction::Down),
];
}
/// Directions for an exit from a room.
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub enum Direction {
/// Cardinal north
North = 0,
/// Cardinal east
East,
/// Cardinal south
South,
/// Cardinal west
West,
/// Up (towards the sky)
Up,
/// Down (towards the ground)
Down,
/// Default (internal usage only)
Default,
}
impl Default for Direction {
fn default() -> Self {
Self::Default
}
}
impl FromSql for Direction {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
let s = match value.as_str() {
Ok(s) => s,
Err(e) => {
log::error!("{}({}) :: {} :: {}", file!(), line!(), "value.as_str()", e);
return Err(e);
}
};
match Self::from_str(s) {
Ok(direction) => return Ok(direction),
Err(e) => Err(FromSqlError::Other(e)),
}
}
}
impl ToSql for Direction {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self.long()))
}
}
impl FromStr for Direction {
type Err = Box<dyn Error + Send + Sync + 'static>;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let mut s: String = s.into();
// get rid of extra characters
if let Some(pos) = s.find(' ') {
s.split_off(pos);
}
// no command
if s.is_empty() {
return Err(Box::from(ParserError::Empty));
}
// match directions with input
let mut matches: Vec<(&str, Self)> = DIRECTION_LIST
.iter()
.filter_map(|(text, dir)| {
if text.starts_with(&s) {
Some((text.clone(), dir.clone()))
} else {
None
}
})
.collect();
// No matches here means unknown direction
if matches.is_empty() {
return Err(Box::from(ParserError::Unknown));
}
// exact match, do no more
if matches.len() == 1 {
return Ok(matches[0].1);
}
// look for directions that match
for (text, direction) in DIRECTION_LIST.iter() {
let direction: Self = direction.clone();
if *text == s {
// exact match
matches = vec![(text, direction)];
break;
} else if text.starts_with(s.as_str()) {
// starts the same, add to the list
matches.push((text, direction));
}
}
// check if there was a match
if matches.is_empty() {
return Err(Box::from(ParserError::Unknown));
}
// sort and take the first match
// this allows possibly more directions, the original set shouldn't ever have multiple
matches.sort();
Ok(matches[0].1)
}
}
impl Direction {
/// Provide the opposite direction
pub fn opposite(self) -> Direction {
match self {
Self::North => Self::South,
Self::East => Self::West,
Self::South => Self::North,
Self::West => Self::East,
Self::Up => Self::Down,
Self::Down => Self::Up,
Self::Default => Self::Default,
}
}
/// Single character identifier for the direction.
pub fn short(&self) -> &str {
match self {
Self::North => "N",
Self::East => "E",
Self::South => "S",
Self::West => "W",
Self::Up => "U",
Self::Down => "D",
Self::Default => "",
}
}
/// String identifier for the direction.
pub fn long(&self) -> &str {
match self {
Self::North => "north",
Self::East => "east",
Self::South => "south",
Self::West => "west",
Self::Up => "up",
Self::Down => "down",
Self::Default => "",
}
}
pub fn try_from_long<S: AsRef<str>>(s: S) -> RudeResult<Self> {
let s = s.as_ref();
match s {
"north" => Ok(Self::North),
"east" => Ok(Self::East),
"south" => Ok(Self::South),
"west" => Ok(Self::West),
"up" => Ok(Self::Up),
"down" => Ok(Self::Down),
_ => Err(format!("Invalid direction: {}", s).into()),
}
}
}

@ -0,0 +1,29 @@
use std::convert::TryFrom;
use std::error::Error;
use rusqlite::Row;
use serde_derive::{Deserialize, Serialize};
use crate::id::Id;
use crate::world::Direction;
/// A one way exit, from one room to another, with a direction.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Exit {
/// Target room ID
pub target: Id,
/// Exit direction
pub direction: Direction,
}
impl<'a> TryFrom<&Row<'a>> for Exit {
type Error = Box<dyn Error>;
fn try_from(row: &Row) -> Result<Self, Self::Error> {
Ok(Self {
target: row.get("target")?,
direction: Direction::try_from_long(row.get::<&str, String>("direction")?)?,
})
}
}

@ -0,0 +1,13 @@
//! The game world. Regions, zones, and rooms.
mod area;
mod direction;
mod exit;
mod room;
mod zone;
pub use area::*;
pub use direction::*;
pub use exit::Exit;
pub use room::Room;
pub use zone::Zone;

@ -0,0 +1,83 @@
use std::collections::HashMap;
use std::convert::TryFrom;
use std::error::Error;
use rusqlite::Row;
use serde_derive::{Deserialize, Serialize};
use crate::id::Id;
use crate::try_log;
use crate::world::{Area, AreaType, Direction, Exit, DIRECTION_LIST};
#[derive(Debug, Deserialize, Serialize)]
pub struct Room {
pub id: Id,
pub zone: Id,
pub name: String,
pub description: Vec<String>,
pub users_visible: bool,
pub exits: HashMap<Direction, Exit>,
}
impl Room {
pub fn exit_string(&self) -> String {
if self.exits.is_empty() {
return String::new();
}
DIRECTION_LIST
.iter()
.filter_map(|(_, direction)| {
if self.exits.contains_key(direction) {
Some(direction.short())
} else {
None
}
})
.collect::<Vec<&str>>()
.join(" ")
}
}
impl Area for Room {
fn id(&self) -> Id {
self.id
}
fn parent(&self) -> Id {
self.zone
}
fn name(&self) -> String {
self.name.to_owned()
}
fn visible(&self) -> bool {
self.users_visible
}
fn area_type(&self) -> AreaType {
AreaType::Room
}
}
impl<'a> TryFrom<&Row<'a>> for Room {
type Error = Box<dyn Error>;
fn try_from(row: &Row) -> Result<Self, Self::Error> {
//let orig_id: String = try_log!(row.get("id"), "id");
//let new_id: Uuid = try_log!(Uuid::parse_str(&orig_id), "parse");
Ok(Self {
id: try_log!(row.get("id"), "id"),
zone: try_log!(row.get("zone"), "zone"),
name: try_log!(row.get("name"), "name"),
description: try_log!(row.get::<&str, String>("description"), "description")
.lines()
.map(|s| String::from(s))
.collect(),
users_visible: try_log!(row.get("users_visible"), "users_visible"),
exits: HashMap::new(),
})
}
}

@ -0,0 +1,54 @@
use std::collections::HashSet;
use std::convert::TryFrom;
use std::error::Error;
use rusqlite::Row;
use serde_derive::{Deserialize, Serialize};
use crate::id::Id;
use crate::world::{Area, AreaType};
#[derive(Debug, Deserialize, Serialize)]
pub struct Zone {
pub id: Id,
pub parent: Id,
pub name: String,
pub users_visible: bool,
pub areas: HashSet<Id>,
}
impl Area for Zone {
fn id(&self) -> Id {
self.id
}
fn parent(&self) -> Id {
self.parent
}
fn name(&self) -> String {
self.name.to_owned()
}
fn visible(&self) -> bool {
self.users_visible
}
fn area_type(&self) -> AreaType {
AreaType::Zone
}
}
impl<'a> TryFrom<&Row<'a>> for Zone {
type Error = Box<dyn Error>;
fn try_from(row: &Row) -> Result<Self, Self::Error> {
Ok(Self {
id: row.get("id")?,
parent: row.get("parent")?,
name: row.get("name")?,
users_visible: row.get("users_visible")?,
areas: HashSet::new(),
})
}
}
Loading…
Cancel
Save