249 lines
11 KiB
Rust
249 lines
11 KiB
Rust
use std::collections::HashMap;
|
|
|
|
use api::game_actions::SpawnToken;
|
|
use game::{
|
|
Game, GameImpl,
|
|
chat_message::ChatMessage,
|
|
entry::{ActionDefinition, DiceRoll},
|
|
};
|
|
use serde::{Deserialize, Serialize};
|
|
use tokio::sync::{broadcast, mpsc};
|
|
|
|
pub mod api;
|
|
pub mod game;
|
|
pub mod pathfinder2r_impl;
|
|
pub mod user;
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct GameServer {
|
|
game: Game<pathfinder2r_impl::Pathfinder2rCharacterSheet, pathfinder2r_impl::entry::Entry>,
|
|
chat: Vec<(String, ChatMessage)>,
|
|
// we dont want to save the logged users as it will always be empty when the server restarts
|
|
#[serde(skip)]
|
|
/// Logged in users, bool value used to know if the user is an admin or not
|
|
users: HashMap<String, bool>,
|
|
// TODO: JEESH REPLACE THIS WITH A DATABASE AND PROPER SECURITY ONCE ITS DONE PLEASE GOD
|
|
creds: HashMap<String, String>,
|
|
}
|
|
impl GameServer {
|
|
pub fn new() -> Self {
|
|
let mut creds = HashMap::new();
|
|
creds.insert("rusty".to_string(), "".to_string());
|
|
creds.insert("test".to_string(), "test".to_string());
|
|
creds.insert("dragonfly".to_string(), "dragonfly".to_string());
|
|
Self {
|
|
game: Game::new(),
|
|
chat: vec![(
|
|
"Server".to_string(),
|
|
ChatMessage::new("a weapon description".to_string())
|
|
.id(1)
|
|
.character(Some("Sword or something".to_string()))
|
|
.with_action(
|
|
ActionDefinition::new("weapon/attack".to_string())
|
|
.display_name(Some("Attack +7".to_string()))
|
|
.with_roll(DiceRoll::new("Pierce".to_string(), 12, 1).constant(1))
|
|
.with_roll(DiceRoll::new("Fire".to_string(), 4, 2).enabled(false)),
|
|
)
|
|
.with_action(ActionDefinition::new("Attack +3".to_string()).with_roll(DiceRoll::new(
|
|
"Base".to_string(),
|
|
20,
|
|
1,
|
|
)))
|
|
.with_action(ActionDefinition::new("Attack -1".to_string())),
|
|
)],
|
|
users: HashMap::new(),
|
|
creds,
|
|
}
|
|
}
|
|
|
|
pub async fn server_loop(
|
|
mut self,
|
|
mut msgs: mpsc::Receiver<(String, api::Request)>,
|
|
broadcast: broadcast::Sender<(Option<String>, api::Response)>,
|
|
) {
|
|
while let Some(req) = msgs.recv().await {
|
|
// TODO: do stuff yo!
|
|
let (id, req) = req;
|
|
println!("Got message from {}: {:?}", &id, &req);
|
|
|
|
match req {
|
|
// ignore errors, should probably be blocked before they are sent here
|
|
api::Request::Error => {}
|
|
api::Request::Login(login) => {
|
|
println!("login req from {}: {:?}", &id, &login);
|
|
if !self.users.contains_key(&login.username) &&
|
|
self.creds
|
|
.get(&login.username)
|
|
.map(|p| p == &login.password)
|
|
.unwrap_or(false)
|
|
{
|
|
self.users.insert(login.username.clone(), login.username == "rusty"); // rusty will be admin for now :)
|
|
_ = broadcast.send((
|
|
Some(id),
|
|
api::Response::Login(api::login::LoginResult {
|
|
success: true,
|
|
username: login.username,
|
|
}),
|
|
));
|
|
} else {
|
|
_ = broadcast.send((
|
|
Some(id.clone()),
|
|
api::Response::Login(api::login::LoginResult {
|
|
success: false,
|
|
username: login.username,
|
|
}),
|
|
));
|
|
}
|
|
}
|
|
api::Request::Message(mut msg) => {
|
|
if msg.id == 0 || msg.id > self.chat.len() {
|
|
msg.id = self.chat.len() + 1; // set the message id, 0 is invalid
|
|
} else if id == self.chat[msg.id - 1].0 || *self.users.get(&id).unwrap_or(&false) {
|
|
self.chat[msg.id - 1] = (id.clone(), msg.clone());
|
|
} else {
|
|
// if its an edit message and editor is not the owner, skip
|
|
continue;
|
|
}
|
|
// Force the sender id to be the new id of the message, even if an id was provided
|
|
msg.source = id.clone();
|
|
|
|
self.chat.push((id.clone(), msg.clone()));
|
|
if msg.whisper.is_some() {
|
|
_ = broadcast.send((Some(id.clone()), api::Response::Message(msg.clone())));
|
|
}
|
|
_ = broadcast.send((msg.whisper.clone(), api::Response::Message(msg)));
|
|
}
|
|
api::Request::GetChatHistory {
|
|
mut amount,
|
|
from: last_msg,
|
|
} => {
|
|
if amount == 0 {
|
|
amount = self.chat.len();
|
|
}
|
|
let history: Vec<ChatMessage> = self
|
|
.chat
|
|
.iter()
|
|
.skip(last_msg)
|
|
.take(amount)
|
|
.map(|m| m.1.clone())
|
|
.collect();
|
|
_ = broadcast.send((Some(id), api::Response::GetChatHistory(history)));
|
|
}
|
|
api::Request::GetLastMessages { mut amount } => {
|
|
if amount == 0 {
|
|
amount = self.chat.len();
|
|
}
|
|
let start = if amount >= self.chat.len() {
|
|
0
|
|
} else {
|
|
self.chat.len() - amount
|
|
};
|
|
let history: Vec<ChatMessage> = self.chat.iter().skip(start).map(|m| m.1.clone()).collect();
|
|
_ = broadcast.send((Some(id), api::Response::GetChatHistory(history)));
|
|
}
|
|
api::Request::GetTokens { scene } => {
|
|
for token_id in self.game.available_tokens(scene) {
|
|
if let Some(ti) = self.game.token_info(0, token_id) {
|
|
_ = broadcast.send((
|
|
Some(id.clone()),
|
|
api::Response::SpawnToken(SpawnToken {
|
|
token_id: token_id,
|
|
x: ti.x,
|
|
y: ti.y,
|
|
img: ti.img_source.clone(),
|
|
}),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
api::Request::GetCurrentScene => {
|
|
let scene = self.game.current_scene();
|
|
let scene_tokens = self
|
|
.game
|
|
.available_tokens(scene)
|
|
.iter()
|
|
.map(|id| self.game.token_info(0, *id).map(|info| (id, info)))
|
|
.flatten()
|
|
.map(|(id, info)| SpawnToken {
|
|
token_id: *id,
|
|
x: info.x,
|
|
y: info.y,
|
|
img: info.img_source.clone(),
|
|
})
|
|
.collect::<Vec<_>>();
|
|
_ = broadcast.send((
|
|
Some(id.clone()),
|
|
api::Response::ShowScene {
|
|
scene: scene,
|
|
tokens: scene_tokens,
|
|
},
|
|
));
|
|
}
|
|
api::Request::SpawnToken {
|
|
map_id,
|
|
character,
|
|
x,
|
|
y,
|
|
img_path,
|
|
} => {
|
|
let token_id = self.game.create_token(map_id, character, img_path.clone(), x, y);
|
|
_ = broadcast.send((
|
|
Some(id.clone()),
|
|
api::Response::SpawnToken(SpawnToken { token_id, x, y, img: img_path.clone() }),
|
|
));
|
|
}
|
|
api::Request::MoveToken { token_id, x, y } => {
|
|
// TODO: add check to make sure the actor is authorized to move the token
|
|
if self.game.move_token(0, token_id, x, y) {
|
|
// TODO: maybe chage move_token to return optional x,y values if succeeded to make sure the token is where it was going to be
|
|
_ = broadcast.send((None, api::Response::MoveToken { token_id, x, y }));
|
|
}
|
|
}
|
|
api::Request::ActionResult(result) => {
|
|
let msg = ChatMessage::new(
|
|
result
|
|
.results
|
|
.iter()
|
|
// .map(|d| &d.result_text)
|
|
.fold(String::new(), |a, b| {
|
|
a + &format!("{}: {} = {}\n", &b.name, &b.result_text, b.result)
|
|
}),
|
|
)
|
|
.character(Some(result.name))
|
|
.source(id.clone())
|
|
.targets(Some(result.targets))
|
|
.id(self.chat.len() + 1);
|
|
self.chat.push((id, msg.clone()));
|
|
_ = broadcast.send((None, api::Response::Message(msg)));
|
|
}
|
|
api::Request::CreateCharacter => {
|
|
// check if user is admin
|
|
if self.users.get(&id).map(|a| *a).unwrap_or(false) {
|
|
let new_id = self.game.create_character();
|
|
// return the new id with the character i think
|
|
_ = broadcast.send((Some(id), api::Response::CharacterCreated(new_id)));
|
|
}
|
|
}
|
|
api::Request::Quit => {
|
|
if self.users.contains_key(&id) {
|
|
self.users.remove(&id);
|
|
}
|
|
_ = broadcast.send((None, api::Response::Quit { id }));
|
|
}
|
|
api::Request::Kick(id) => {
|
|
if self.users.contains_key(&id) {
|
|
self.users.remove(&id);
|
|
}
|
|
_ = broadcast.send((Some(id), api::Response::Shutdown));
|
|
}
|
|
api::Request::Shutdown => break,
|
|
api::Request::CharacterDisplay { id } => todo!(),
|
|
api::Request::CharacterInputs { id } => todo!(),
|
|
api::Request::CharacterGetField { id, field } => todo!(),
|
|
api::Request::CharacterSetField { id, field, val } => todo!(),
|
|
api::Request::CharacterAssign { id, user } => todo!(),
|
|
}
|
|
}
|
|
_ = broadcast.send((None, api::Response::Shutdown));
|
|
}
|
|
}
|