├── .gitignore ├── src ├── tests │ ├── mod.rs │ └── util.rs ├── data │ ├── map.rs │ ├── monster.rs │ ├── player.rs │ ├── management.rs │ ├── monster.rs.in │ ├── map.rs.in │ ├── mod.rs │ ├── player.rs.in │ └── management.rs.in ├── actor │ ├── mod.rs │ ├── mob.rs │ └── network.rs ├── entity │ ├── status.rs │ ├── serialize.rs │ ├── update │ │ ├── movement.rs │ │ ├── mod.rs │ │ └── attacks.rs │ ├── hitbox.rs │ ├── store.rs │ ├── double_iterator.rs │ └── mod.rs ├── utils.rs ├── lib.rs ├── instance │ ├── management.rs │ └── mod.rs ├── main.rs ├── game │ ├── authentication.rs │ ├── resource_manager.rs │ ├── mod.rs │ └── management.rs ├── scripts.rs ├── messages │ ├── conversions.rs │ └── mod.rs ├── network │ ├── stream_adapter.rs │ ├── buffered_tcp.rs │ └── mod.rs ├── id.rs └── ai │ └── mod.rs ├── scripts ├── start_server.sh ├── combat.aariba ├── zombie.bt ├── get_maps.sh ├── get_players.sh ├── shutdown.sh ├── get_instances.sh ├── get_entities.sh ├── delete_entity.sh ├── connect_character.sh └── spawn_monster.sh ├── README.md └── Cargo.toml /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | *.swp 3 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod util; 2 | -------------------------------------------------------------------------------- /src/data/map.rs: -------------------------------------------------------------------------------- 1 | include!(concat!(env!("OUT_DIR"), "/", file!())); 2 | -------------------------------------------------------------------------------- /src/data/monster.rs: -------------------------------------------------------------------------------- 1 | include!(concat!(env!("OUT_DIR"), "/", file!())); 2 | -------------------------------------------------------------------------------- /src/data/player.rs: -------------------------------------------------------------------------------- 1 | include!(concat!(env!("OUT_DIR"), "/", file!())); 2 | -------------------------------------------------------------------------------- /src/data/management.rs: -------------------------------------------------------------------------------- 1 | include!(concat!(env!("OUT_DIR"), "/", file!())); 2 | -------------------------------------------------------------------------------- /scripts/start_server.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | PORT=9000 3 | /usr/bin/env python2 -m SimpleHTTPServer $PORT 4 | -------------------------------------------------------------------------------- /scripts/combat.aariba: -------------------------------------------------------------------------------- 1 | difference = $source.strength - $target.constitution; 2 | damages = max(rand(0, 3) + difference, 1); 3 | $target.damage = damages; 4 | -------------------------------------------------------------------------------- /src/actor/mod.rs: -------------------------------------------------------------------------------- 1 | mod network; 2 | mod mob; 3 | 4 | use id::Id; 5 | 6 | pub use self::network::NetworkActor; 7 | pub use self::mob::AiActor; 8 | 9 | pub type ActorId = Id; 10 | -------------------------------------------------------------------------------- /scripts/zombie.bt: -------------------------------------------------------------------------------- 1 | tree zombie { 2 | priority { 3 | get_closest_target, 4 | walk_to_target, 5 | print_text("EAAAAAATTTTTT"), 6 | print_text("BRRRRAIIIIIINNNNNSSSSS"), 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/entity/status.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | // List of status currently affecting an entity 4 | // The status can be things like rooted, stunned, silenced ... 5 | // 6 | // NOTE: We will probably need to keep a refcount of effects 7 | // producing these status, to know when to remove it 8 | struct Status; 9 | -------------------------------------------------------------------------------- /src/data/monster.rs.in: -------------------------------------------------------------------------------- 1 | use id::{HasForgeableId,HasId,Id}; 2 | use uuid::Uuid; 3 | 4 | // Intended to be all the info needed to spawn a monster 5 | // TODO 6 | #[derive(Serialize,Deserialize,Debug,Clone)] 7 | pub struct Monster { 8 | pub monster_class: Id, 9 | } 10 | 11 | impl HasForgeableId for Monster {} 12 | 13 | impl HasId for Monster { 14 | type Type = Uuid; 15 | } 16 | -------------------------------------------------------------------------------- /src/tests/util.rs: -------------------------------------------------------------------------------- 1 | use std::net::{TcpStream,TcpListener}; 2 | use std::thread; 3 | 4 | pub fn create_connection() -> (TcpStream,TcpStream) { 5 | let server = TcpListener::bind("localhost:0").unwrap(); 6 | let port = server.local_addr().unwrap().port(); 7 | let guard = thread::scoped(|| { 8 | TcpStream::connect(("localhost",port)).unwrap() 9 | }); 10 | let (server_socket, _) = server.accept().unwrap(); 11 | let client_socket = guard.join(); 12 | (server_socket, client_socket) 13 | } 14 | -------------------------------------------------------------------------------- /src/data/map.rs.in: -------------------------------------------------------------------------------- 1 | use id::{Id, HasForgeableId, HasId}; 2 | use uuid::Uuid; 3 | 4 | #[derive(Clone, Debug, Serialize, Deserialize)] 5 | pub struct Map { 6 | pub uuid: Id, 7 | pub name: String, 8 | } 9 | 10 | impl HasId for Map { 11 | type Type = Uuid; 12 | } 13 | 14 | impl HasForgeableId for Map {} 15 | 16 | impl Map { 17 | pub fn new(id: Id, name: String) -> Map { 18 | Map { 19 | uuid: id, 20 | name: name, 21 | } 22 | } 23 | 24 | pub fn get_id(&self) -> Id { 25 | self.uuid 26 | } 27 | 28 | pub fn get_name(&self) -> &str { 29 | &self.name 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/data/mod.rs: -------------------------------------------------------------------------------- 1 | use uuid::Uuid; 2 | 3 | use id::Id; 4 | 5 | mod map; 6 | mod player; 7 | mod management; 8 | mod monster; 9 | 10 | pub use self::map::Map; 11 | pub use self::management::EntityManagement; 12 | pub use self::management::EntityType; 13 | pub use self::management::PositionInstance; 14 | pub use self::management::PlayerStruct; 15 | pub use self::management::MonsterStruct; 16 | pub use self::management::SpawnMonster; 17 | pub use self::management::ConnectCharacterParam; 18 | pub use self::management::GetInstances; 19 | pub use self::management::GetMaps; 20 | pub use self::player::Player; 21 | pub use self::player::Stats; 22 | pub use self::player::Position; 23 | pub use self::monster::Monster; 24 | 25 | // XXX: Hack to remove ... currently we consider only one map 26 | lazy_static!{ 27 | pub static ref UNIQUE_MAP: Map = { 28 | let uuid = Uuid::from_fields(42,42,42,&[42,42,42,42,42,42,42,42]).unwrap(); 29 | let name = "The unique map".to_string(); 30 | Map::new(Id::forge(uuid), name) 31 | }; 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read,Write}; 2 | use std::fs::File; 3 | use std::path::Path; 4 | 5 | use hyper::Client; 6 | use hyper::error::Error as HyperError; 7 | use serde::{Serialize}; 8 | use serde_json; 9 | 10 | 11 | pub fn get_file_from_url(url: &str) -> Result { 12 | let client = Client::new(); 13 | let mut response = try!(client.get(url).send()); 14 | if !response.status.is_success() { 15 | return Err(HyperError::Status); 16 | } 17 | let mut content = String::new(); 18 | try!(response.read_to_string(&mut content)); 19 | Ok(content) 20 | } 21 | 22 | pub fn serialize_to_file(file: P, s: &T) 23 | where T: Serialize, 24 | P: AsRef { 25 | if let Err(e) = serialize_to_file_inner(file.as_ref(), s) { 26 | error!("Error when serializing: {}", e); 27 | } 28 | } 29 | 30 | fn serialize_to_file_inner(file: &Path, s: &T) -> serde_json::Result<()> 31 | where T: Serialize { 32 | let mut file = try!(File::create(file)); 33 | serde_json::to_writer_pretty(&mut file, s) 34 | } 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Lycan 2 | ===== 3 | 4 | Lycan is the game engine of the Renaissance project 5 | 6 | ## Compiling 7 | 8 | Lycan currently requires a nightly compiler. You will also need the following packages: 9 | 10 | ```bash 11 | sudo apt-get install -y gcc libssl-dev 12 | ``` 13 | 14 | ## Start an instance 15 | 16 | To start an instance of Lycan, you first need to start a http server to deliver 17 | the combat.aariba file. 18 | 19 | ```bash 20 | cd scripts 21 | ./start_server.sh & 22 | ``` 23 | 24 | Once it is done, just return to the root of the project and use Cargo 25 | 26 | ```bash 27 | cargo run 28 | ``` 29 | 30 | ## Management API 31 | 32 | The management API is accessible on the port 9001. All the routes are prefixed 33 | with `api/v1`. The `Access-Token` header is needed to authenticate. As for now, 34 | its value is hardcoded to `abcdefgh`. Hence, a valid example of request is: 35 | 36 | ```bash 37 | curl localhost:9001/api/v1/players -H "access-token: abcdefgh" 38 | ``` 39 | 40 | Several scripts can be found in the `scripts/` directory. They can be use by a 41 | developer to query the API more easily. 42 | -------------------------------------------------------------------------------- /src/entity/serialize.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Write; 2 | use std::str; 3 | 4 | use rustc_serialize::json::{self,ToJson}; 5 | use rustc_serialize::{Encodable,Decodable}; 6 | 7 | use entity::Entity; 8 | 9 | use super::EntityType; 10 | 11 | // Serialization of entities on the disk / database 12 | 13 | pub fn serialize(_entity: Entity) -> Vec { 14 | /* 15 | match entity.data.player { 16 | EntityType::Invoked(Some(id)) => 17 | panic!("The serialisation of children entities is not supported yet, id {}", id), 18 | _ => {} 19 | } 20 | 21 | let mut res = String::new(); 22 | let json_encoder = json::as_pretty_json(&entity.data); 23 | write!(res, "{}", json_encoder).unwrap(); 24 | res.into_bytes() 25 | */ 26 | unimplemented!(); 27 | } 28 | 29 | /* 30 | pub fn deserialize(_data: &[u8]) -> Result { 31 | let result = str::from_utf8(data); 32 | match result { 33 | Ok(json_str) => { 34 | json::decode(json_str).map_err(|e| e.to_string()) 35 | .map(|data| Entity::new_internal(data)) 36 | } 37 | Err(e) => Err(e.to_string()), 38 | } 39 | unimplemented!(); 40 | } 41 | */ 42 | -------------------------------------------------------------------------------- /scripts/get_maps.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | DEFAULT_SERVER=localhost 3 | DEFAULT_PORT=9001 4 | DEFAULT_SECRET="abcdefgh" 5 | 6 | print_syntax() { 7 | cat << EOF 8 | Usage $0 [-h SERVER] [-p PORT] [-s SECRET] 9 | SERVER, PORT and SECRET can also be provided as environment variables 10 | EOF 11 | } 12 | 13 | while getopts h:p:s: opt; do 14 | case $opt in 15 | h) 16 | SERVER=$OPTARG 17 | ;; 18 | p) 19 | PORT=$OPTARG 20 | ;; 21 | s) 22 | SECRET=$OPTARG 23 | ;; 24 | \?) 25 | print_syntax 26 | exit 1 27 | ;; 28 | :) 29 | print_syntax 30 | exit 1 31 | ;; 32 | esac 33 | done 34 | shift $((OPTIND-1)) 35 | 36 | if (( $# != 0 )); then 37 | print_syntax 38 | exit 1 39 | fi 40 | BASE_URL=http://${SERVER-$DEFAULT_SERVER}:${PORT-$DEFAULT_PORT}/api/v1 41 | 42 | curl -X GET -H "Access-Token: ${SECRET-$DEFAULT_SECRET}" -H "Content-Type: application/json" $BASE_URL/maps 43 | -------------------------------------------------------------------------------- /scripts/get_players.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | DEFAULT_SERVER=localhost 3 | DEFAULT_PORT=9001 4 | DEFAULT_SECRET="abcdefgh" 5 | 6 | print_syntax() { 7 | cat << EOF 8 | Usage $0 [-h SERVER] [-p PORT] [-s SECRET] 9 | SERVER, PORT and SECRET can also be provided as environment variables 10 | EOF 11 | } 12 | 13 | while getopts h:p:s: opt; do 14 | case $opt in 15 | h) 16 | SERVER=$OPTARG 17 | ;; 18 | p) 19 | PORT=$OPTARG 20 | ;; 21 | s) 22 | SECRET=$OPTARG 23 | ;; 24 | \?) 25 | print_syntax 26 | exit 1 27 | ;; 28 | :) 29 | print_syntax 30 | exit 1 31 | ;; 32 | esac 33 | done 34 | shift $((OPTIND-1)) 35 | 36 | if (( $# != 0 )); then 37 | print_syntax 38 | exit 1 39 | fi 40 | BASE_URL=http://${SERVER-$DEFAULT_SERVER}:${PORT-$DEFAULT_PORT}/api/v1 41 | 42 | curl -X GET -H "Access-Token: ${SECRET-$DEFAULT_SECRET}" -H "Content-Type: application/json" $BASE_URL/players 43 | -------------------------------------------------------------------------------- /scripts/shutdown.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | DEFAULT_SERVER=localhost 3 | DEFAULT_PORT=9001 4 | DEFAULT_SECRET="abcdefgh" 5 | 6 | print_syntax() { 7 | cat << EOF 8 | Usage $0 [-h SERVER] [-p PORT] [-s SECRET] 9 | SERVER, PORT and SECRET can also be provided as environment variables 10 | EOF 11 | } 12 | 13 | while getopts h:p:s: opt; do 14 | case $opt in 15 | h) 16 | SERVER=$OPTARG 17 | ;; 18 | s) 19 | SECRET=$OPTARG 20 | ;; 21 | p) 22 | PORT=$OPTARG 23 | ;; 24 | \?) 25 | print_syntax 26 | exit 1 27 | ;; 28 | :) 29 | print_syntax 30 | exit 1 31 | ;; 32 | esac 33 | done 34 | shift $((OPTIND-1)) 35 | 36 | if (( $# != 0 )); then 37 | print_syntax 38 | exit 1 39 | fi 40 | BASE_URL=http://${SERVER-$DEFAULT_SERVER}:${PORT-$DEFAULT_PORT}/api/v1 41 | 42 | curl -X POST -H "Access-Token: ${SECRET-$DEFAULT_SECRET}" -H "Content-Type: application/json" $BASE_URL/shutdown 43 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(conservative_impl_trait)] 2 | #![feature(mpsc_select)] 3 | #![feature(fnbox)] 4 | 5 | #![allow(unused_imports)] 6 | #![allow(dead_code)] 7 | 8 | extern crate rustc_serialize; 9 | #[macro_use] extern crate lazy_static; 10 | #[macro_use] extern crate log; 11 | //extern crate id; 12 | extern crate time; 13 | //extern crate mio; 14 | extern crate byteorder; 15 | extern crate threadpool; 16 | extern crate lycan_serialize; 17 | extern crate nalgebra; 18 | extern crate smallvec; 19 | extern crate rand; 20 | extern crate aariba; 21 | extern crate bytes; 22 | extern crate behaviour_tree; 23 | extern crate serde; 24 | extern crate serde_json; 25 | extern crate uuid; 26 | extern crate futures; 27 | extern crate tokio_core; 28 | extern crate schedule_recv; 29 | 30 | // Iron and related crates 31 | #[macro_use] extern crate iron; 32 | #[macro_use] extern crate hyper; 33 | extern crate router; 34 | extern crate bodyparser; 35 | extern crate plugin; 36 | extern crate modifier; 37 | extern crate iron_error_router; 38 | extern crate mount; 39 | 40 | pub mod actor; 41 | pub mod entity; 42 | pub mod game; 43 | pub mod instance; 44 | pub mod id; 45 | pub mod data; 46 | pub mod ai; 47 | pub mod utils; 48 | mod scripts; 49 | mod network; 50 | 51 | pub mod messages; 52 | -------------------------------------------------------------------------------- /scripts/get_instances.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | DEFAULT_SERVER=localhost 3 | DEFAULT_PORT=9001 4 | DEFAULT_SECRET="abcdefgh" 5 | 6 | print_syntax() { 7 | cat << EOF 8 | Usage $0 [-h SERVER] [-p PORT] [-s SECRET] id_map 9 | SERVER, PORT and SECRET can also be provided as environment variables 10 | EOF 11 | } 12 | 13 | while getopts h:p:s: opt; do 14 | case $opt in 15 | h) 16 | SERVER=$OPTARG 17 | ;; 18 | p) 19 | PORT=$OPTARG 20 | ;; 21 | s) 22 | SECRET=$OPTARG 23 | ;; 24 | \?) 25 | print_syntax 26 | exit 1 27 | ;; 28 | :) 29 | print_syntax 30 | exit 1 31 | ;; 32 | esac 33 | done 34 | shift $((OPTIND-1)) 35 | 36 | if (( $# != 1 )); then 37 | print_syntax 38 | exit 1 39 | fi 40 | BASE_URL=http://${SERVER-$DEFAULT_SERVER}:${PORT-$DEFAULT_PORT}/api/v1 41 | 42 | ID_MAP=$1 43 | 44 | curl -X GET -H "Access-Token: ${SECRET-$DEFAULT_SECRET}" -H "Content-Type: application/json" $BASE_URL/maps/$ID_MAP/instances 45 | -------------------------------------------------------------------------------- /scripts/get_entities.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | DEFAULT_SERVER=localhost 3 | DEFAULT_PORT=9001 4 | DEFAULT_SECRET="abcdefgh" 5 | 6 | print_syntax() { 7 | cat << EOF 8 | Usage $0 [-h SERVER] [-p PORT] [-s SECRET] id_instance 9 | SERVER, PORT and SECRET can also be provided as environment variables 10 | EOF 11 | } 12 | 13 | while getopts h:p:s: opt; do 14 | case $opt in 15 | h) 16 | SERVER=$OPTARG 17 | ;; 18 | p) 19 | PORT=$OPTARG 20 | ;; 21 | s) 22 | SECRET=$OPTARG 23 | ;; 24 | \?) 25 | print_syntax 26 | exit 1 27 | ;; 28 | :) 29 | print_syntax 30 | exit 1 31 | ;; 32 | esac 33 | done 34 | shift $((OPTIND-1)) 35 | 36 | if (( $# != 1 )); then 37 | print_syntax 38 | exit 1 39 | fi 40 | BASE_URL=http://${SERVER-$DEFAULT_SERVER}:${PORT-$DEFAULT_PORT}/api/v1 41 | 42 | ID_INSTANCE=$1 43 | 44 | curl -X GET -H "Access-Token: ${SECRET-$DEFAULT_SECRET}" -H "Content-Type: application/json" $BASE_URL/instances/$ID_INSTANCE/entities 45 | -------------------------------------------------------------------------------- /src/entity/update/movement.rs: -------------------------------------------------------------------------------- 1 | use nalgebra::Vector2; 2 | use nalgebra::Point2; 3 | 4 | use lycan_serialize::Direction; 5 | 6 | use messages::Notification; 7 | use entity::{ 8 | Entity, 9 | Order, 10 | EntityStore, 11 | }; 12 | 13 | pub fn resolve_movements( 14 | entities: &mut EntityStore, 15 | notifications: &mut Vec, 16 | tick_duration: f32, 17 | ) { 18 | for entity in entities.iter_mut() { 19 | resolve_collisions(entity, notifications, tick_duration) 20 | } 21 | } 22 | 23 | fn resolve_collisions( 24 | entity: &mut Entity, 25 | _notifications: &mut Vec, 26 | tick_duration: f32, 27 | ) { 28 | // Assume no collisions at the moment ... 29 | let unitary_speed = if entity.walking { 30 | match entity.orientation { 31 | Direction::North => Vector2::new(0.0, 1.0), 32 | Direction::South => Vector2::new(0.0, -1.0), 33 | Direction::East => Vector2::new(1.0, 0.0), 34 | Direction::West => Vector2::new(-1.0, 0.0), 35 | } 36 | } else { 37 | Vector2::new(0.0, 0.0) 38 | }; 39 | let speed = unitary_speed * entity.stats.speed; 40 | let new_position = entity.position + speed * tick_duration; 41 | entity.position = new_position; 42 | entity.speed = speed; 43 | } 44 | -------------------------------------------------------------------------------- /scripts/delete_entity.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | DEFAULT_SERVER=localhost 3 | DEFAULT_PORT=9001 4 | DEFAULT_SECRET="abcdefgh" 5 | 6 | print_syntax() { 7 | cat << EOF 8 | Usage $0 [-h SERVER] [-p PORT] [-s SECRET] id_instance id_entity 9 | SERVER, PORT and SECRET can also be provided as environment variables 10 | EOF 11 | } 12 | 13 | while getopts h:p:s: opt; do 14 | case $opt in 15 | h) 16 | SERVER=$OPTARG 17 | ;; 18 | p) 19 | PORT=$OPTARG 20 | ;; 21 | s) 22 | SECRET=$OPTARG 23 | ;; 24 | \?) 25 | print_syntax 26 | exit 1 27 | ;; 28 | :) 29 | print_syntax 30 | exit 1 31 | ;; 32 | esac 33 | done 34 | shift $((OPTIND-1)) 35 | 36 | if (( $# != 2 )); then 37 | print_syntax 38 | exit 1 39 | fi 40 | BASE_URL=http://${SERVER-$DEFAULT_SERVER}:${PORT-$DEFAULT_PORT}/api/v1 41 | 42 | ID_INSTANCE=$1 43 | ID_ENTITY=$2 44 | 45 | curl -X DELETE -H "Access-Token: ${SECRET-$DEFAULT_SECRET}" -H "Content-Type: application/json" $BASE_URL/instances/$ID_INSTANCE/entities/$ID_ENTITY 46 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lycan" 3 | version = "0.1.0" 4 | authors = ["Vaelden "] 5 | build = "build.rs" 6 | 7 | [lib] 8 | name="lycan" 9 | path="src/lib.rs" 10 | 11 | [dependencies] 12 | lazy_static = "0.2" 13 | log = "0.3" 14 | env_logger = "0.3" 15 | rustc-serialize = "0.3" 16 | time = "0.1" 17 | #mio = "0.5" 18 | byteorder = "0.5" 19 | threadpool = "1.3.2" 20 | docopt = "0.6" 21 | nalgebra = "0.10" 22 | smallvec = "0.2" 23 | rand = "0.3" 24 | hyper = "0.9" 25 | bytes = "0.3" 26 | serde = "0.8" 27 | serde_json = "0.8" 28 | uuid = { version = "0.3", features = ["rand","serde"] } 29 | futures = "0.1" 30 | tokio-core = "0.1" 31 | schedule_recv = "0.1" 32 | 33 | # Iron and related crates 34 | iron = "0.4" 35 | mount = "0.2.0" 36 | router = "0.4" 37 | bodyparser = "0.4" 38 | plugin = "0.2" 39 | modifier = "0.1" 40 | iron-error-router = "0.2" 41 | 42 | [build-dependencies] 43 | serde_codegen = "0.8" 44 | 45 | [dependencies.lycan-serialize] 46 | git = "https://github.com/Greenpix/lycan-serialize.git" 47 | 48 | [dependencies.aariba] 49 | git = "https://github.com/GreenPix/aariba.git" 50 | 51 | [dependencies.behaviour-tree] 52 | git = "https://github.com/GreenPix/behaviour-tree.git" 53 | 54 | [[bin]] 55 | name = "lycan" 56 | doc = false 57 | 58 | [features] 59 | json = ["lycan-serialize/json"] 60 | 61 | default = ["json", "iron/ssl"] 62 | -------------------------------------------------------------------------------- /src/data/player.rs.in: -------------------------------------------------------------------------------- 1 | use uuid::Uuid; 2 | 3 | use id::{Id,HasForgeableId,HasId}; 4 | use data::Map; 5 | 6 | // Intended to be all the info needed for that player to go in game 7 | #[derive(Serialize,Deserialize,Debug,Clone)] 8 | pub struct Player { 9 | pub id: Id, 10 | pub name: String, 11 | //class 12 | pub skin: u64, 13 | pub current_pv: u64, 14 | pub position: Position, 15 | pub experience: u64, 16 | pub gold: u64, 17 | //group 18 | pub guild: String, 19 | pub stats: Stats, 20 | } 21 | 22 | #[derive(Serialize,Deserialize,Debug,Clone,Copy)] 23 | pub struct Stats { 24 | pub level: u64, 25 | pub strength: u64, 26 | pub dexterity: u64, 27 | pub constitution: u64, 28 | pub intelligence: u64, 29 | pub precision: u64, 30 | pub wisdom: u64, 31 | } 32 | 33 | #[derive(Serialize,Deserialize,Debug,Clone,Copy)] 34 | pub struct Position { 35 | pub map: Id, 36 | pub x: f32, 37 | pub y: f32, 38 | } 39 | 40 | impl Player { 41 | pub fn get_id(&self) -> Id { 42 | self.id 43 | } 44 | 45 | pub fn get_map_position(&self) -> Id { 46 | self.position.map 47 | } 48 | 49 | pub fn get_name(&self) -> &str { 50 | &self.name 51 | } 52 | } 53 | 54 | impl HasForgeableId for Player {} 55 | 56 | impl HasId for Player { 57 | type Type = Uuid; 58 | } 59 | -------------------------------------------------------------------------------- /scripts/connect_character.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | DEFAULT_SERVER=localhost 3 | DEFAULT_PORT=9001 4 | DEFAULT_SECRET="abcdefgh" 5 | 6 | print_syntax() { 7 | cat << EOF 8 | Usage $0 [-h SERVER] [-p PORT] [-s SECRET] id token 9 | SERVER, PORT and SECRET can also be provided as environment variables 10 | EOF 11 | } 12 | 13 | while getopts h:p:s: opt; do 14 | case $opt in 15 | h) 16 | SERVER=$OPTARG 17 | ;; 18 | s) 19 | SECRET=$OPTARG 20 | ;; 21 | p) 22 | PORT=$OPTARG 23 | ;; 24 | \?) 25 | print_syntax 26 | exit 1 27 | ;; 28 | :) 29 | print_syntax 30 | exit 1 31 | ;; 32 | esac 33 | done 34 | shift $((OPTIND-1)) 35 | 36 | if (( $# != 2 )); then 37 | print_syntax 38 | exit 1 39 | fi 40 | BASE_URL=http://${SERVER-$DEFAULT_SERVER}:${PORT-$DEFAULT_PORT}/api/v1 41 | 42 | ID=$1 43 | TOKEN=$2 44 | 45 | curl -d @- -X POST -H "Access-Token: ${SECRET-$DEFAULT_SECRET}" -H "Content-Type: application/json" $BASE_URL/connect_character << EOF 46 | { 47 | "id": "$ID", 48 | "token": "$TOKEN" 49 | } 50 | EOF 51 | -------------------------------------------------------------------------------- /src/entity/hitbox.rs: -------------------------------------------------------------------------------- 1 | use nalgebra::Point2; 2 | 3 | #[derive(Debug,Clone,Copy)] 4 | pub struct RectangleHitbox { 5 | half_width: f32, 6 | half_height: f32, 7 | } 8 | 9 | impl RectangleHitbox { 10 | pub fn new(h_width: f32, h_height: f32) -> RectangleHitbox { 11 | RectangleHitbox { 12 | half_width: h_width, 13 | half_height: h_height, 14 | } 15 | } 16 | 17 | // TODO: Need to check units, and put a sensible default 18 | pub fn new_default() -> RectangleHitbox { 19 | RectangleHitbox::new(0.75,1.0) 20 | } 21 | 22 | pub fn collision(&self, pos: Point2, other: &RectangleHitbox, pos_other: Point2) -> bool { 23 | let left1 = pos.x - self.half_width; 24 | let right1 = pos.x + self.half_width; 25 | let left2 = pos_other.x - other.half_width; 26 | let right2 = pos_other.x + other.half_width; 27 | 28 | let bot1 = pos.y - self.half_height; 29 | let top1 = pos.y + self.half_height; 30 | let bot2 = pos_other.y - other.half_height; 31 | let top2 = pos_other.y + other.half_height; 32 | 33 | if bot1 > top2 { return false; } 34 | if top1 < bot2 { return false; } 35 | 36 | if right1 < left2 { return false; } 37 | if left1 > right2 { return false; } 38 | 39 | true 40 | } 41 | 42 | pub fn rotated(&self) -> RectangleHitbox { 43 | RectangleHitbox { 44 | half_width: self.half_height, 45 | half_height: self.half_width, 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/instance/management.rs: -------------------------------------------------------------------------------- 1 | use super::Instance; 2 | use data::{ 3 | EntityManagement, 4 | SpawnMonster, 5 | }; 6 | use id::WeakId; 7 | use entity::Entity; 8 | use messages::Notification; 9 | 10 | impl Instance { 11 | pub fn get_entities(&self) -> Vec { 12 | self.entities 13 | .iter() 14 | .map(|e| e.into_management_representation(self.id, self.map_id)) 15 | .collect() 16 | } 17 | 18 | pub fn spawn_monster(&mut self, monster: SpawnMonster) -> EntityManagement { 19 | let id = self.add_fake_ai(monster.monster_class, monster.x, monster.y); 20 | self.entities.get(id).unwrap().into_management_representation(self.id, self.map_id) 21 | } 22 | 23 | pub fn remove_entity(&mut self, entity: WeakId) -> Result<(),RemoveEntityError> { 24 | let mut found = false; 25 | match self.entities.remove_if(entity, |e| { found = true; e.is_monster() }) { 26 | None => Err(if found { RemoveEntityError::IsPlayer } else { RemoveEntityError::NotFound }), 27 | Some(e) => { 28 | // Send back to game? 29 | 30 | let notification = Notification::entity_has_quit(entity.as_u64()); 31 | self.next_notifications.push(notification); 32 | if let Some(actor) = e.get_actor() { 33 | self.actors.unregister_ai(actor); 34 | } else { 35 | warn!("Found entity without attached actor: {:?}", e); 36 | } 37 | // TODO: Kick corresponding actor 38 | Ok(()) 39 | } 40 | } 41 | } 42 | } 43 | 44 | pub enum RemoveEntityError { 45 | NotFound, 46 | IsPlayer, 47 | } 48 | 49 | -------------------------------------------------------------------------------- /scripts/spawn_monster.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | DEFAULT_SERVER=localhost 3 | DEFAULT_PORT=9001 4 | DEFAULT_SECRET="abcdefgh" 5 | DEFAULT_MONSTER_CLASS="67e6001e-d735-461d-b32e-2e545e12b3d2" 6 | X="0.0" 7 | Y="0.0" 8 | 9 | print_syntax() { 10 | cat << EOF 11 | Usage $0 [-h SERVERNAME] [-p PORT] [-s SECRET] [-x X] [-y Y] [-c MONSTER_CLASS] instance_id 12 | SERVER, PORT and SECRET can also be provided as environment variables 13 | EOF 14 | } 15 | 16 | while getopts h:p:s:x:y:c: opt; do 17 | case $opt in 18 | h) 19 | SERVER=$OPTARG 20 | ;; 21 | p) 22 | PORT=$OPTARG 23 | ;; 24 | s) 25 | SECRET=$OPTARG 26 | ;; 27 | c) 28 | MONSTER_CLASS=$OPTARG 29 | ;; 30 | x) 31 | X=$OPTARG 32 | ;; 33 | y) 34 | Y=$OPTARG 35 | ;; 36 | \?) 37 | print_syntax 38 | exit 1 39 | ;; 40 | :) 41 | print_syntax 42 | exit 1 43 | ;; 44 | esac 45 | done 46 | shift $((OPTIND-1)) 47 | 48 | if (( $# != 1 )); then 49 | print_syntax 50 | exit 1 51 | fi 52 | BASE_URL=http://${SERVER-$DEFAULT_SERVER}:${PORT-$DEFAULT_PORT}/api/v1 53 | 54 | ID=$1 55 | 56 | curl -d @- -X POST -H "Access-Token: ${SECRET-$DEFAULT_SECRET}" -H "Content-Type: application/json" $BASE_URL/instances/$ID/spawn << EOF 57 | { 58 | "monster_class": "${MONSTER_CLASS-$DEFAULT_MONSTER_CLASS}", 59 | "x": $X, 60 | "y": $Y 61 | } 62 | EOF 63 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate lycan; 2 | extern crate env_logger; 3 | extern crate docopt; 4 | extern crate rustc_serialize; 5 | 6 | use std::io::{BufRead,Write}; 7 | 8 | use docopt::Docopt; 9 | 10 | use lycan::game::{Game,GameParameters}; 11 | 12 | static USAGE: &'static str = r#" 13 | Usage: 14 | lycan [options] 15 | 16 | Options: 17 | -c URL, --configuration URL URL of the configuration server [default: http://localhost:9000] 18 | -p PORT, --port PORT Listening port [default: 7777] 19 | -t TICK, --tick TICK Server tick duration in ms [default: 50] 20 | -h, --help Prints this message 21 | "#; 22 | 23 | #[derive(RustcDecodable,Debug)] 24 | struct Args { 25 | flag_port: u16, 26 | flag_configuration: String, 27 | flag_tick: f32, 28 | } 29 | 30 | fn main() { 31 | let args: Args = Docopt::new(USAGE) 32 | .and_then(|d| d.decode()) 33 | .unwrap_or_else(|e| e.exit()); 34 | if std::env::var_os("RUST_LOG").is_none() { 35 | std::env::set_var("RUST_LOG", "warn,lycan=debug"); 36 | } 37 | env_logger::init().unwrap(); 38 | let parameters = GameParameters { 39 | port: args.flag_port, 40 | configuration_url: args.flag_configuration.clone(), 41 | tick_duration: args.flag_tick / 1000.0, 42 | }; 43 | let _request = Game::spawn_game(parameters); 44 | println!("Started game with parameters {:#?}", args); 45 | 46 | print!("Enter q to quit: "); 47 | let stdin = std::io::stdin(); 48 | let mut stdout = std::io::stdout(); 49 | stdout.flush().unwrap(); 50 | let lock = stdin.lock(); 51 | for input in lock.lines() { 52 | match input.unwrap().as_ref() { 53 | "q" => break, 54 | _ => { 55 | print!("Enter q to quit: "); 56 | stdout.flush().unwrap(); 57 | continue; 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/game/authentication.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | use std::collections::HashMap; 3 | 4 | use byteorder::{LittleEndian, WriteBytesExt}; 5 | use lycan_serialize::{AuthenticationToken,ErrorCode}; 6 | 7 | use id::Id; 8 | use data::Player; 9 | 10 | pub struct AuthenticationManager { 11 | // TODO: Timeouts 12 | map: HashMap, AuthenticationToken>, 13 | } 14 | 15 | impl AuthenticationManager { 16 | pub fn new() -> AuthenticationManager { 17 | AuthenticationManager { 18 | map: HashMap::new(), 19 | } 20 | } 21 | 22 | pub fn add_token(&mut self, player: Id, token: AuthenticationToken) { 23 | trace!("Adding token {} for player {}", token.0, player); 24 | self.map.insert(player, token); 25 | } 26 | 27 | /// Verifies that the player possesses the correct authentication token 28 | /// 29 | /// Deletes the token if the authentication succeeds 30 | pub fn verify_token(&mut self, player: Id, token: AuthenticationToken) -> bool { 31 | match self.map.remove(&player) { 32 | Some(t) => { 33 | if t == token { 34 | trace!("Authentication success for player {}", player); 35 | true 36 | } else { 37 | trace!("Authentication failure for player {}: invalid token", player); 38 | // XXX: Is there a more efficient way than removing and re-adding? 39 | self.map.insert(player, t); 40 | false 41 | } 42 | } 43 | None => { 44 | trace!("Authentication failure for player {}: no associated token", player); 45 | false 46 | } 47 | } 48 | } 49 | 50 | /// Adds some "well-known" Id-AuthenticationToken pairs 51 | pub fn fake_authentication_tokens(&mut self) { 52 | for (uuid, token) in ::lycan_serialize::forge_authentication_tokens() { 53 | self.add_token(Id::forge(uuid), token); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/data/management.rs.in: -------------------------------------------------------------------------------- 1 | use time::Tm; 2 | 3 | use data::player::{Stats}; 4 | use id::{Id,HasForgeableId}; 5 | use data::{Map,Player,Monster}; 6 | use entity::Entity; 7 | use instance::Instance; 8 | 9 | // Representation of an entity in the management API 10 | #[derive(Serialize,Debug,Clone)] 11 | pub struct EntityManagement { 12 | pub id: Id, 13 | #[serde(rename="type")] 14 | pub entity_type: EntityType, 15 | pub skin: u64, 16 | pub current_pv: u64, 17 | pub position: PositionInstance, 18 | pub stats: Stats, 19 | } 20 | 21 | #[derive(Serialize,Debug,Clone)] 22 | pub enum EntityType { 23 | #[serde(rename="player")] 24 | Player(PlayerStruct), 25 | #[serde(rename="monster")] 26 | Monster(MonsterStruct), 27 | } 28 | 29 | #[derive(Serialize,Debug,Clone)] 30 | pub struct PlayerStruct { 31 | pub uuid: Id, 32 | pub name: String, 33 | pub gold: u64, 34 | pub experience: u64, 35 | pub guild: String, 36 | } 37 | 38 | #[derive(Serialize,Debug,Clone)] 39 | pub struct MonsterStruct { 40 | pub monster_class: Id, 41 | pub name: String, 42 | pub behaviour_tree: String, 43 | } 44 | 45 | // Same as data::Position but with instance information 46 | #[derive(Serialize,Debug,Clone,Copy)] 47 | pub struct PositionInstance { 48 | pub map: Id, 49 | pub x: f32, 50 | pub y: f32, 51 | pub instance: Id, 52 | } 53 | 54 | #[derive(Deserialize,Debug,Clone,Copy)] 55 | pub struct SpawnMonster { 56 | pub monster_class: Id, 57 | #[serde(default)] 58 | pub x: f32, 59 | #[serde(default)] 60 | pub y: f32, 61 | } 62 | 63 | #[derive(Deserialize,Debug,Clone)] 64 | pub struct ConnectCharacterParam { 65 | pub token: String, 66 | pub id: Id, 67 | } 68 | 69 | #[derive(Serialize,Debug,Clone)] 70 | pub struct GetInstances { 71 | pub id: Id, 72 | pub map: Id, 73 | pub created_at: String, 74 | } 75 | 76 | #[derive(Serialize,Debug,Clone)] 77 | pub struct GetMaps { 78 | pub uuid: Id, 79 | pub name: String, 80 | } 81 | -------------------------------------------------------------------------------- /src/entity/update/mod.rs: -------------------------------------------------------------------------------- 1 | // Intended to be the part that handles all the collision / effects and other core 2 | // features of the game engine 3 | 4 | use std::collections::HashMap; 5 | 6 | use entity::{ 7 | Entity, 8 | Order, 9 | EntityStore, 10 | OthersAccessor, 11 | }; 12 | use messages::Notification; 13 | use id::Id; 14 | 15 | use lycan_serialize::Direction; 16 | use instance::{ 17 | TickEvent, 18 | }; 19 | use scripts::AaribaScripts; 20 | 21 | mod attacks; 22 | mod movement; 23 | 24 | /// Triggers all temporal effects 25 | pub fn update( 26 | entities: &mut EntityStore, 27 | notifications: &mut Vec, 28 | scripts: &AaribaScripts, 29 | tick_duration: f32, 30 | ) -> Vec { 31 | // During a tick, every event that can affect an entity (an entity attacking, a spell cast, 32 | // a projectile hitting) will be randomly ordered, and all of them will be executed in 33 | // sequence. 34 | // In other words, if two actions happen during the same server tick, the order between those 35 | // two actions will be *random*, but there will be one: the result will be the same as if the 36 | // two actions happened during two separate ticks. 37 | // This algorithm aims to prevent bad interractions between spells, leading to weird behaviour 38 | // when happening during the same tick 39 | 40 | let mut tick_events = Vec::new(); 41 | movement::resolve_movements(entities, notifications, tick_duration); 42 | attacks::resolve_attacks(entities, notifications, scripts, &mut tick_events, tick_duration); 43 | generate_position_updates(entities, notifications); 44 | tick_events 45 | } 46 | 47 | fn generate_position_updates( 48 | entities: &EntityStore, 49 | notifications: &mut Vec, 50 | ) { 51 | for entity in entities.iter() { 52 | let notif = Notification::position( 53 | entity.get_id().as_u64(), 54 | entity.position, 55 | entity.speed, 56 | entity.pv, 57 | ); 58 | notifications.push(notif); 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /src/actor/mob.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::fmt::{self,Debug,Formatter}; 3 | 4 | use behaviour_tree::tree::BehaviourTreeNode; 5 | use id::Id; 6 | use actor::ActorId; 7 | use entity::{Entity,EntityStore}; 8 | use messages::{self,Command,Notification,EntityOrder}; 9 | use scripts::{BehaviourTree}; 10 | use ai::{BehaviourTreeData,Context}; 11 | 12 | pub struct AiActor { 13 | id: ActorId, 14 | entity: Option>, 15 | entities: HashSet>, // XXX: Do we really need this? 16 | tree: BehaviourTree, 17 | tree_data: BehaviourTreeData, 18 | } 19 | 20 | impl Debug for AiActor { 21 | fn fmt(&self, f: &mut Formatter) -> Result<(),fmt::Error> { 22 | let tree = "[behaviour tree]"; 23 | f.debug_struct("AiActor") 24 | .field("id", &self.id) 25 | .field("entities", &self.entities) 26 | .field("tree", &tree) 27 | .finish() 28 | } 29 | } 30 | 31 | impl AiActor { 32 | pub fn get_id(&self) -> ActorId { 33 | self.id 34 | } 35 | pub fn get_commands(&mut self, _commands: &mut Vec) { 36 | } 37 | pub fn execute_orders(&mut self, 38 | entities: &mut EntityStore, 39 | _notifications: &mut Vec, 40 | _previous: &[Notification]) { 41 | // Context should give access to storage / current game state 42 | let me = match self.entity { 43 | None => { 44 | warn!("Trying to execute behaviour tree on AI without main entity {}", self.id); 45 | return; 46 | } 47 | Some(me) => me, 48 | }; 49 | let mut context = Context::new(me, entities, &mut self.tree_data); 50 | self.tree.visit(&mut context); 51 | } 52 | pub fn register_entity(&mut self, entity: Id) { 53 | self.entity = Some(entity); 54 | self.entities.insert(entity); 55 | } 56 | 57 | pub fn fake(tree: BehaviourTree) -> AiActor { 58 | AiActor { 59 | id: Id::new(), 60 | entity: None, 61 | entities: Default::default(), 62 | tree: tree, 63 | tree_data: BehaviourTreeData::new(), 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/entity/store.rs: -------------------------------------------------------------------------------- 1 | use id::{Id, WeakId}; 2 | use super::Entity; 3 | use super::{OthersAccessor,DoubleIterMut}; 4 | 5 | // Abstraction so that if we change the implementation it doesn't affect the rest 6 | #[derive(Debug)] 7 | pub struct EntityStore { 8 | entities: Vec, 9 | } 10 | 11 | impl EntityStore { 12 | pub fn new() -> EntityStore { 13 | EntityStore { 14 | entities: Vec::new(), 15 | } 16 | } 17 | 18 | pub fn push(&mut self, entity: Entity) { 19 | self.entities.push(entity) 20 | } 21 | 22 | pub fn remove>>(&mut self, id: T) -> Option { 23 | let position = match self.get_position(id.into()) { 24 | Some(pos) => pos, 25 | None => return None, 26 | }; 27 | 28 | Some(self.entities.remove(position)) 29 | } 30 | 31 | pub fn remove_if(&mut self, id: T, f: F) -> Option 32 | where T: Into>, 33 | F: FnOnce(&Entity) -> bool { 34 | let position = match self.get_position(id.into()) { 35 | Some(pos) => pos, 36 | None => return None, 37 | }; 38 | 39 | if f(self.entities.get(position).unwrap()) { 40 | Some(self.entities.remove(position)) 41 | } else { 42 | None 43 | } 44 | } 45 | 46 | pub fn get>>(&self, id: T) -> Option<&Entity> { 47 | self.get_position(id.into()).map(move |position| self.entities.get(position).unwrap()) 48 | } 49 | 50 | pub fn get_mut>>(&mut self, id: T) -> Option<&mut Entity> { 51 | self.get_position(id.into()).map(move |position| self.entities.get_mut(position).unwrap()) 52 | } 53 | 54 | pub fn get_mut_wrapper<'a,T: Into>>(&'a mut self, id: T) -> Option<(&'a mut Entity, OthersAccessor<'a>)> { 55 | self.get_position(id.into()).map(move |position| { 56 | OthersAccessor::new(&mut self.entities, position).unwrap() 57 | }) 58 | } 59 | 60 | fn get_position(&self, id: WeakId) -> Option { 61 | for (position, entity) in self.entities.iter().enumerate() { 62 | if WeakId::from(entity.get_id()) == id { 63 | return Some(position); 64 | } 65 | } 66 | None 67 | } 68 | 69 | pub fn iter(&self) -> ::std::slice::Iter { 70 | self.entities.iter() 71 | } 72 | 73 | pub fn iter_mut(&mut self) -> ::std::slice::IterMut { 74 | self.entities.iter_mut() 75 | } 76 | 77 | pub fn iter_mut_wrapper(&mut self) -> DoubleIterMut { 78 | DoubleIterMut::new(&mut self.entities) 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /src/scripts.rs: -------------------------------------------------------------------------------- 1 | // Aariba scripts and behaviour trees used by Lycan 2 | use std::collections::HashMap; 3 | 4 | use aariba::rules::RulesEvaluator; 5 | use aariba; 6 | use hyper::error::Error as HyperError; 7 | 8 | use behaviour_tree::tree::factory::TreeFactory; 9 | use behaviour_tree::tree::{LeafNodeFactory,BehaviourTreeNode}; 10 | use behaviour_tree; 11 | 12 | use ai::{Context,ActionNode,ActionNodeFactory,LeavesCollection}; 13 | use utils; 14 | 15 | pub type BehaviourTreeFactory = TreeFactory; 16 | pub type BehaviourTree = behaviour_tree::BehaviourTree; 17 | 18 | #[derive(Debug,Clone)] 19 | pub struct AaribaScripts { 20 | pub combat: RulesEvaluator, 21 | } 22 | 23 | #[derive(Debug)] 24 | pub enum Error { 25 | Hyper(HyperError), 26 | AaribaParsing(String), 27 | BehaviourTreeParsing(String), 28 | } 29 | 30 | impl From for Error { 31 | fn from(e: HyperError) -> Error { 32 | Error::Hyper(e) 33 | } 34 | } 35 | 36 | impl AaribaScripts { 37 | pub fn get_from_url(base_url: &str) -> Result { 38 | let mut url = String::from(base_url); 39 | url.push_str("/combat.aariba"); 40 | debug!("Getting file {}", url); 41 | let script = try!(utils::get_file_from_url(&url)); 42 | let parsed_script = 43 | try!(aariba::parse_rule(&script) 44 | .map_err(Error::AaribaParsing)); 45 | let scripts = AaribaScripts { 46 | combat: parsed_script, 47 | }; 48 | Ok(scripts) 49 | } 50 | } 51 | 52 | #[derive(Clone)] 53 | pub struct BehaviourTrees { 54 | inner: HashMap, 55 | } 56 | 57 | impl BehaviourTrees { 58 | // TODO: An append command 59 | pub fn get_from_url(base_url: &str) -> Result { 60 | let mut url = String::from(base_url); 61 | url.push_str("/zombie.bt"); 62 | debug!("Getting file {}", url); 63 | let script = try!(utils::get_file_from_url(&url)); 64 | let mut map = HashMap::new(); 65 | let leaves = LeavesCollection::standard(); 66 | let parsed_trees = 67 | try!(behaviour_tree::parse(&script,&leaves) 68 | .map_err(Error::BehaviourTreeParsing)); 69 | for tree in parsed_trees { 70 | let name = String::from(tree.get_name()); 71 | map.insert(name,tree); 72 | } 73 | let trees = BehaviourTrees { 74 | inner: map, 75 | }; 76 | 77 | Ok(trees) 78 | } 79 | 80 | pub fn generate_factory(&self, name: &str) -> Option { 81 | self.inner.get(name).map(|f| f.clone()) 82 | } 83 | 84 | pub fn generate_tree(&self, name: &str) -> Option { 85 | self.inner.get(name).map(|f| f.optimize()) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/messages/conversions.rs: -------------------------------------------------------------------------------- 1 | use lycan_serialize::Notification as NetworkNotification; 2 | use lycan_serialize::Order as NetworkOrder; 3 | use lycan_serialize::EntityOrder as NetworkEntityOrder; 4 | use lycan_serialize::Command as NetworkCommand; 5 | use lycan_serialize::GameCommand as NetworkGameCommand; 6 | use lycan_serialize::Direction; 7 | use lycan_serialize::Vec2d; 8 | 9 | use std::fmt::{self,Formatter,Debug}; 10 | use std::boxed::FnBox; 11 | 12 | use entity::{Entity}; 13 | use game::Game; 14 | use id::Id; 15 | use instance::{Instance,ShuttingDownState}; 16 | use super::{GameCommand,Order,EntityOrder,Command,Notification}; 17 | 18 | /* 19 | impl From for Order { 20 | fn from(net: NetworkOrder) -> Order { 21 | unimplemented!(); 22 | } 23 | } 24 | 25 | impl From for EntityOrder { 26 | fn from(net: NetworkEntityOrder) -> EntityOrder { 27 | unimplemented!(); 28 | } 29 | } 30 | 31 | 32 | impl From for GameCommand { 33 | fn from(net: NetworkGameCommand) -> GameCommand { 34 | unimplemented!(); 35 | } 36 | } 37 | 38 | impl From for Command { 39 | fn from(net: NetworkCommand) -> Command { 40 | unimplemented!(); 41 | } 42 | } 43 | 44 | */ 45 | 46 | // TODO REMOVE 47 | impl Into> for Notification { 48 | fn into(self) -> Option { 49 | match self { 50 | Notification::Walk {entity,orientation} => 51 | Some(NetworkNotification::walk(entity,orientation)), 52 | Notification::Say{entity,message} => 53 | Some(NetworkNotification::say(entity,message)), 54 | Notification::Position{entity,position,speed,pv} => 55 | Some(NetworkNotification::position(entity, 56 | Vec2d{x: position.x, y: position.y}, 57 | Vec2d{x: speed.x, y: speed.y}, 58 | pv)), 59 | Notification::ThisIsYou{entity} => 60 | Some(NetworkNotification::this_is_you(entity)), 61 | Notification::NewEntity{entity,position,skin,pv} => 62 | Some(NetworkNotification::new_entity(entity, 63 | Vec2d{x: position.x, y: position.y}, 64 | skin, 65 | pv)), 66 | Notification::EntityHasQuit{entity} => 67 | Some(NetworkNotification::entity_has_quit(entity)), 68 | Notification::Damage{..} => { 69 | // XXX: Need to send that to the network 70 | None 71 | } 72 | Notification::Death{..} => { 73 | // XXX: Need to send that to the network 74 | None 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/network/stream_adapter.rs: -------------------------------------------------------------------------------- 1 | //! TODO: This module is being contributed back to futures-rs 2 | use std::mem; 3 | 4 | use futures::{Future, IntoFuture, Async, Poll}; 5 | use futures::stream::Stream; 6 | 7 | /// Converts a closure generating a future into a Stream 8 | /// 9 | /// This adapter will continuously use a provided generator to generate a future, then wait 10 | /// for its completion and output its result. 11 | /// 12 | /// The generator is given a state in input, and must output a structure implementing 13 | /// `IntoFuture`. The future must resolve to a tuple containing a new state, and a value. 14 | /// The value will be returned by the stream, and the new state will be given to the generator 15 | /// to create a new future. 16 | /// 17 | /// The initial state is provided to this method 18 | /// 19 | /// # Example 20 | /// 21 | /// ```rust 22 | /// use futures::*; 23 | /// use futures::stream::Stream; 24 | /// 25 | /// let mut stream = stream::repeat(0, |state| { 26 | /// if state <= 2 { 27 | /// let fut: Result<_,()> = Ok((state+1, state*2)); 28 | /// Some(fut) 29 | /// } else { 30 | /// None 31 | /// } 32 | /// }); 33 | /// assert_eq!(Ok(Async::Ready(Some(0))), stream.poll()); 34 | /// assert_eq!(Ok(Async::Ready(Some(2))), stream.poll()); 35 | /// assert_eq!(Ok(Async::Ready(Some(4))), stream.poll()); 36 | /// assert_eq!(Ok(Async::Ready(None)), stream.poll()); 37 | /// ``` 38 | pub fn repeat(init: T, f: F) -> Repeat 39 | where F: FnMut(T) -> Option, 40 | Fut: IntoFuture { 41 | Repeat { 42 | f: f, 43 | state: State::Ready(init), 44 | } 45 | } 46 | 47 | /// A stream which creates futures, polls them and return their result 48 | /// 49 | /// This stream is returned by the `futures::stream::repeat` method 50 | #[must_use = "streams do nothing unless polled"] 51 | pub struct Repeat where Fut: IntoFuture { 52 | f: F, 53 | state: State, 54 | } 55 | 56 | impl Stream for Repeat 57 | where F: FnMut(T) -> Option, 58 | Fut: IntoFuture { 59 | type Item = It; 60 | type Error = Fut::Error; 61 | 62 | fn poll(&mut self) -> Poll, Fut::Error> { 63 | loop { 64 | match mem::replace(&mut self.state, State::Empty) { 65 | State::Empty => panic!("cannot poll Repeat twice"), 66 | State::Ready(state) => { 67 | match (self.f)(state) { 68 | Some(fut) => { self.state = State::Processing(fut.into_future()); } 69 | None => { return Ok(Async::Ready(None)); } 70 | } 71 | } 72 | State::Processing(mut fut) => { 73 | match try!(fut.poll()) { 74 | Async:: Ready((state, item)) => { 75 | self.state = State::Ready(state); 76 | return Ok(Async::Ready(Some(item))); 77 | } 78 | Async::NotReady => { 79 | self.state = State::Processing(fut); 80 | return Ok(Async::NotReady); 81 | } 82 | } 83 | } 84 | } 85 | } 86 | } 87 | } 88 | 89 | enum State where F: Future { 90 | /// Placeholder state when doing work 91 | Empty, 92 | 93 | /// Ready to generate new future; current internal state is the `T` 94 | Ready(T), 95 | 96 | /// Working on a future generated previously 97 | Processing(F), 98 | } 99 | 100 | -------------------------------------------------------------------------------- /src/actor/network.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self,Formatter,Display}; 2 | use std::io::{self,Write,Error}; 3 | use std::collections::{hash_set,HashSet,HashMap}; 4 | 5 | use id::{self,Id,HasId}; 6 | use entity::{Entity,EntityStore}; 7 | use messages::{self,Command,Notification,EntityOrder}; 8 | use messages::{NetworkCommand}; 9 | use network::{Client,ClientError}; 10 | use actor::ActorId; 11 | 12 | #[derive(Debug)] 13 | pub struct NetworkActor { 14 | id: ActorId, 15 | entities: HashSet>, 16 | // XXX Does this belong here? 17 | client: Client, 18 | commands: CommandBuffer, 19 | } 20 | 21 | // A buffer of commands intented to apply some policies before adding a new command 22 | // A policy can for example be that only one "change direction" command can be in the queue 23 | // per Entity 24 | // 25 | // Currently a dumb structure that does no checks 26 | #[derive(Default,Debug)] 27 | struct CommandBuffer { 28 | commands: Vec, 29 | orders: Vec, 30 | } 31 | 32 | impl NetworkActor { 33 | pub fn entities_iter(&self) -> hash_set::Iter> { 34 | self.entities.iter() 35 | } 36 | 37 | pub fn get_id(&self) -> ActorId { 38 | self.id 39 | } 40 | 41 | pub fn register_entity(&mut self, entity: Id) { 42 | self.entities.insert(entity); 43 | } 44 | 45 | pub fn new(id: ActorId, client: Client) -> NetworkActor { 46 | NetworkActor { 47 | id: id, 48 | entities: HashSet::new(), 49 | client: client, 50 | commands: Default::default(), 51 | } 52 | } 53 | 54 | fn receive_commands(&mut self) { 55 | loop { 56 | match self.client.recv() { 57 | Ok(Some(NetworkCommand::EntityOrder(order))) => { 58 | self.commands.orders.push(order); 59 | } 60 | Ok(Some(NetworkCommand::GameCommand(c))) => { 61 | error!("Error: the client is not supposed to send GameCommands after authentication {:?}", c); 62 | self.commands.push(Command::UnregisterActor(self.id)); 63 | break; 64 | } 65 | Ok(None) => break, 66 | Err(()) => { 67 | self.commands.push(Command::UnregisterActor(self.id)); 68 | break; 69 | } 70 | } 71 | } 72 | } 73 | 74 | pub fn get_commands(&mut self, other: &mut Vec) { 75 | self.commands.get_commands(other); 76 | } 77 | 78 | pub fn execute_orders(&mut self, 79 | entities: &mut EntityStore, 80 | notifications: &mut Vec, 81 | _previous: &[Notification]) { 82 | self.receive_commands(); 83 | for order in self.commands.orders.drain(..) { 84 | match id::get_id_if_exists(&self.entities, order.entity) { 85 | None => { 86 | warn!("Trying to give order to non-owned entity {}", order.entity); 87 | } 88 | Some(target) => { 89 | match entities.get_mut(target) { 90 | None => error!("Inconsistency entities / owned entities"), 91 | Some(entity) => { 92 | let res = entity.apply(order.order); 93 | match res { 94 | Err(_e) => {} // TODO: Send back error to network 95 | Ok(Some(notif)) => notifications.push(notif), 96 | Ok(None) => {} 97 | } 98 | } 99 | } 100 | } 101 | } 102 | } 103 | } 104 | 105 | pub fn dump(&self, f: &mut Formatter, indent: &str) -> Result<(), fmt::Error> { 106 | try!(writeln!(f, "{}Actor {} ", indent, self.id)); 107 | // TODO 108 | Ok(()) 109 | } 110 | 111 | pub fn send_message(&mut self, message: Notification) { 112 | match message.into() { 113 | Some(network_notif) => { 114 | if let Err(e) = self.client.send(network_notif) { 115 | error!("Error when sending message to client {}: {:?}", self.client.uuid, e); 116 | self.commands.push(Command::UnregisterActor(self.id)); 117 | } 118 | } 119 | None => { 120 | // A notification that didn't translate to a network event 121 | } 122 | } 123 | } 124 | } 125 | 126 | impl Display for NetworkActor { 127 | fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { 128 | self.dump(f, "") 129 | } 130 | } 131 | 132 | impl CommandBuffer { 133 | fn push(&mut self, command: Command) { 134 | self.commands.push(command); 135 | } 136 | 137 | // Could have a better API 138 | fn get_commands(&mut self, other: &mut Vec) { 139 | other.append(&mut self.commands); 140 | } 141 | } 142 | 143 | impl HasId for NetworkActor { 144 | type Type = u64; 145 | } 146 | -------------------------------------------------------------------------------- /src/messages/mod.rs: -------------------------------------------------------------------------------- 1 | pub use lycan_serialize::Notification as NetworkNotification; 2 | pub use lycan_serialize::Order as NetworkOrder; 3 | pub use lycan_serialize::EntityOrder as NetworkEntityOrder; 4 | pub use lycan_serialize::Command as NetworkCommand; 5 | pub use lycan_serialize::GameCommand as NetworkGameCommand; 6 | pub use lycan_serialize::Direction; 7 | 8 | // TODO REMOVE 9 | pub use lycan_serialize::Order; 10 | pub use lycan_serialize::EntityOrder; 11 | 12 | use std::fmt::{self,Formatter,Debug}; 13 | use std::boxed::FnBox; 14 | use std::sync::mpsc::Sender; 15 | 16 | use nalgebra::{Point2,Vector2}; 17 | 18 | use entity::{Entity}; 19 | use game::Game; 20 | use actor::{NetworkActor,ActorId}; 21 | use id::Id; 22 | use instance::{Instance,ShuttingDownState}; 23 | use data::EntityManagement; 24 | use network::Client; 25 | 26 | mod conversions; 27 | 28 | #[derive(Debug)] 29 | pub enum Command { 30 | NewClient(NetworkActor,Vec), 31 | Shutdown, 32 | Arbitrary(Arbitrary), 33 | UnregisterActor(ActorId), 34 | AssignEntity((ActorId,Entity)), 35 | } 36 | 37 | impl Command { 38 | /// Should only be used for debugging or testing 39 | pub fn new(closure: T) -> Command 40 | where T: FnOnce(&mut Instance), 41 | T: Send + 'static { 42 | let command = Arbitrary(Box::new(closure)); 43 | Command::Arbitrary(command) 44 | } 45 | } 46 | 47 | 48 | pub struct Arbitrary(Box); 49 | 50 | impl Arbitrary { 51 | pub fn execute(self, target: &mut T) { 52 | self.0.call_box((target,)); 53 | } 54 | } 55 | 56 | impl Debug for Arbitrary { 57 | fn fmt(&self, formatter: &mut Formatter) -> Result<(),fmt::Error> { 58 | formatter.write_str("[arbitrary debug command]") 59 | } 60 | } 61 | 62 | #[derive(Debug)] 63 | pub enum Request { 64 | Arbitrary(Arbitrary), 65 | 66 | NewClient(Client), 67 | 68 | // Responses from Instances 69 | UnregisteredActor { 70 | actor: NetworkActor, 71 | entities: Vec, 72 | }, 73 | InstanceShuttingDown(ShuttingDownState), 74 | PlayerUpdate(Vec), 75 | 76 | // Callback from ResourceManager 77 | JobFinished(usize), 78 | } 79 | 80 | impl Request { 81 | /// Should only be used for debugging or testing 82 | pub fn new(closure: T) -> Request 83 | where T: FnOnce(&mut Game) + Send + 'static { 84 | let request = Arbitrary(Box::new(closure)); 85 | Request::Arbitrary(request) 86 | } 87 | } 88 | 89 | #[derive(Debug,Clone)] 90 | pub enum Notification { 91 | Walk { 92 | entity: u64, 93 | orientation: Option, 94 | }, 95 | Say { 96 | entity: u64, 97 | message: String, 98 | }, 99 | Position { 100 | entity: u64, 101 | position: Point2, 102 | speed: Vector2, 103 | pv: u64, 104 | }, 105 | ThisIsYou { 106 | entity: u64, 107 | }, 108 | NewEntity { 109 | entity: u64, 110 | position: Point2, 111 | skin: u64, 112 | pv: u64, 113 | }, 114 | EntityHasQuit { 115 | entity: u64, 116 | }, 117 | Damage { 118 | source: u64, 119 | victim: u64, 120 | amount: u64, 121 | }, 122 | Death { 123 | entity: u64, 124 | }, 125 | } 126 | 127 | pub enum GameCommand {} 128 | 129 | impl Notification { 130 | pub fn walk(id: u64, orientation: Option) -> Notification { 131 | Notification::Walk { 132 | entity: id, 133 | orientation: orientation, 134 | } 135 | } 136 | 137 | pub fn say(id: u64, message: String) -> Notification { 138 | Notification::Say { 139 | entity: id, 140 | message: message, 141 | } 142 | } 143 | 144 | pub fn position(id: u64, position: Point2, speed: Vector2, pv: u64) -> Notification { 145 | Notification::Position { 146 | entity: id, 147 | position: position, 148 | speed: speed, 149 | pv: pv, 150 | } 151 | } 152 | 153 | pub fn this_is_you(id: u64) -> Notification { 154 | Notification::ThisIsYou { 155 | entity: id, 156 | } 157 | } 158 | 159 | pub fn new_entity(id: u64, position: Point2, skin: u64, pv: u64) -> Notification { 160 | Notification::NewEntity { 161 | entity: id, 162 | position: position, 163 | skin: skin, 164 | pv: pv, 165 | } 166 | } 167 | 168 | pub fn entity_has_quit(id: u64) -> Notification { 169 | Notification::EntityHasQuit { 170 | entity: id, 171 | } 172 | } 173 | } 174 | 175 | /* 176 | #[derive(Debug)] 177 | pub struct EntityOrder { 178 | pub target: u64, 179 | pub order: Order, 180 | } 181 | 182 | #[derive(Debug)] 183 | pub enum Order { 184 | } 185 | */ 186 | 187 | // TODO: Move to lycan-serialize 188 | #[derive(Debug)] 189 | pub struct EntityState { 190 | id: u64, 191 | position: Point2, 192 | orientation: Direction, 193 | // Skin 194 | // Hitbox 195 | } 196 | 197 | impl EntityState { 198 | pub fn new(id: Id, position: Point2, orientation: Direction) -> EntityState { 199 | EntityState { 200 | id: id.as_u64(), 201 | position: position, 202 | orientation: orientation, 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/entity/double_iterator.rs: -------------------------------------------------------------------------------- 1 | use std::marker; 2 | 3 | use entity::Entity; 4 | use id::Id; 5 | 6 | pub struct OthersAccessor<'a> { 7 | inner: &'a mut [Entity], 8 | borrowed_entity_position: usize, 9 | } 10 | 11 | impl <'a> OthersAccessor<'a> { 12 | pub fn new(a: &'a mut [Entity], position: usize) -> Option<(&'a mut Entity, OthersAccessor<'a>)> { 13 | let entity: &mut Entity = unsafe { 14 | match a.get_mut(position) { 15 | None => return None, 16 | Some(entity) => ::std::mem::transmute(entity), 17 | } 18 | }; 19 | let wrapper = OthersAccessor { 20 | inner: a, 21 | borrowed_entity_position: position 22 | }; 23 | Some((entity, wrapper)) 24 | } 25 | 26 | pub fn get_by_index(&mut self, index: usize) -> Option<&mut Entity> { 27 | if index == self.borrowed_entity_position { 28 | None 29 | } else { 30 | self.inner.get_mut(index) 31 | } 32 | /* 33 | let entity = self.inner.get( 34 | let a: &mut [T] = unsafe { mem::transmute(self.inner as *mut [T]) }; 35 | a.get_mut(index) 36 | */ 37 | } 38 | 39 | pub fn get(&mut self, id: Id) -> Option<&mut Entity> { 40 | match self.get_position(id) { 41 | Some(pos) => self.get_by_index(pos), 42 | None => None, 43 | } 44 | } 45 | 46 | pub fn iter_mut(&mut self) -> OthersIterMut { 47 | let p = self.inner.as_mut_ptr(); 48 | unsafe { 49 | OthersIterMut { 50 | ptr: p, 51 | end: p.offset(self.inner.len() as isize) , 52 | borrowed_entity: p.offset(self.borrowed_entity_position as isize), 53 | _marker: marker::PhantomData, 54 | } 55 | } 56 | } 57 | 58 | pub fn iter(&self) -> OthersIter { 59 | let p = self.inner.as_ptr(); 60 | unsafe { 61 | OthersIter { 62 | ptr: p, 63 | end: p.offset(self.inner.len() as isize) , 64 | borrowed_entity: p.offset(self.borrowed_entity_position as isize), 65 | _marker: marker::PhantomData, 66 | } 67 | } 68 | } 69 | 70 | // XXX: We should probably have a &self version 71 | pub fn get_position(&self, id: Id) -> Option { 72 | let borrowed = self.borrowed_entity_position; 73 | for (position, entity) in self.iter().enumerate() { 74 | if entity.get_id() == id { 75 | let adjusted_position = if position >= borrowed { 76 | position + 1 77 | } else { 78 | position 79 | }; 80 | return Some(adjusted_position); 81 | } 82 | } 83 | None 84 | } 85 | } 86 | 87 | // TODO: Have a *const version 88 | pub struct OthersIterMut<'a> { 89 | ptr: *mut Entity, 90 | end: *mut Entity, 91 | borrowed_entity: *mut Entity, 92 | _marker: marker::PhantomData<&'a mut Entity>, 93 | } 94 | 95 | impl <'a> Iterator for OthersIterMut<'a> { 96 | type Item = &'a mut Entity; 97 | 98 | fn next(&mut self) -> Option<::Item> { 99 | if self.ptr == self.end { 100 | None 101 | } else { 102 | let old = self.ptr; 103 | self.ptr = unsafe { self.ptr.offset(1) }; 104 | if old == self.borrowed_entity { 105 | self.next() 106 | } else { 107 | unsafe { Some(::std::mem::transmute(old)) } 108 | } 109 | } 110 | } 111 | } 112 | 113 | pub struct OthersIter<'a> { 114 | ptr: *const Entity, 115 | end: *const Entity, 116 | borrowed_entity: *const Entity, 117 | _marker: marker::PhantomData<&'a Entity>, 118 | } 119 | 120 | impl <'a> Iterator for OthersIter<'a> { 121 | type Item = &'a Entity; 122 | 123 | fn next(&mut self) -> Option<::Item> { 124 | if self.ptr == self.end { 125 | None 126 | } else { 127 | let old = self.ptr; 128 | self.ptr = unsafe { self.ptr.offset(1) }; 129 | if old == self.borrowed_entity { 130 | self.next() 131 | } else { 132 | unsafe { Some(::std::mem::transmute(old)) } 133 | } 134 | } 135 | } 136 | } 137 | 138 | pub struct DoubleIterMut<'a> { 139 | inner: &'a mut [Entity], 140 | current_position: usize, 141 | } 142 | 143 | // Cannot implement Iterator because an item borrows the iterator 144 | impl <'a> DoubleIterMut<'a> { 145 | pub fn next_item<'b>(&'b mut self) -> Option<(&'b mut Entity, OthersAccessor<'b>)> { 146 | let res = OthersAccessor::new(self.inner, self.current_position); 147 | self.current_position += 1; 148 | res 149 | } 150 | 151 | pub fn new(e: &mut [Entity]) -> DoubleIterMut { 152 | DoubleIterMut { 153 | inner: e, 154 | current_position: 0, 155 | } 156 | } 157 | } 158 | 159 | #[cfg(test)] 160 | mod test { 161 | use entity::{Entity, EntityStore}; 162 | use id::Id; 163 | #[test] 164 | fn test() { 165 | let mut store = EntityStore::new(); 166 | store.push(Entity::fake_player(Id::forge(0))); 167 | store.push(Entity::fake_player(Id::forge(1))); 168 | store.push(Entity::fake_player(Id::forge(2))); 169 | { 170 | let mut double_iter = store.iter_mut_wrapper(); 171 | while let Some((entity,mut wrapper)) = double_iter.next_item() { 172 | let id = entity.get_id(); 173 | for other in wrapper.iter() { 174 | assert!(id != other.get_id()); 175 | } 176 | assert!(wrapper.get(id).is_none()); 177 | } 178 | } 179 | } 180 | } 181 | 182 | -------------------------------------------------------------------------------- /src/id.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicUsize, Ordering}; 2 | use std::fmt::{Debug,Formatter,Display,Error}; 3 | use std::hash::{Hash,Hasher}; 4 | use std::marker::{PhantomData}; 5 | use std::ops::Deref; 6 | use std::borrow::Borrow; 7 | use std::collections::HashSet; 8 | 9 | use rustc_serialize::{Encodable,Encoder,Decodable,Decoder}; 10 | use serde::de::{self,Deserialize,Deserializer,Visitor}; 11 | use serde::ser::{Serialize,Serializer}; 12 | 13 | pub trait HasId { 14 | type Type: Hash + Eq + Send + Sync + Clone + Copy + Debug; 15 | } 16 | 17 | /// A Typed-ID. 18 | pub struct Id { 19 | inner: WeakId, 20 | } 21 | 22 | /// A Typed-ID coming from unsure input 23 | pub struct WeakId{ 24 | id: T::Type, 25 | marker: PhantomData T>, 26 | } 27 | 28 | impl> Id { 29 | /// Create a new Typed-ID. 30 | /// 31 | /// By design, `Id::new()` always generate an unique ID that has never been 32 | /// seen before. 33 | pub fn new() -> Id { 34 | Id{inner: WeakId::new(NEXT_ID.fetch_add(1, Ordering::Relaxed) as u64)} 35 | } 36 | 37 | pub fn as_u64(self) -> u64 { 38 | self.inner.id 39 | } 40 | } 41 | 42 | impl Id { 43 | fn new_inner(weak: WeakId) -> Id { 44 | Id { 45 | inner: weak, 46 | } 47 | } 48 | 49 | pub fn into_inner(self) -> T::Type { 50 | self.inner.into_inner() 51 | } 52 | } 53 | 54 | impl WeakId { 55 | pub fn new(id: T::Type) -> WeakId { 56 | WeakId{id: id, marker: PhantomData} 57 | } 58 | 59 | pub fn into_inner(self) -> T::Type { 60 | self.id 61 | } 62 | } 63 | 64 | impl> WeakId { 65 | pub fn as_u64(self) -> u64 { 66 | self.id 67 | } 68 | } 69 | 70 | /* 71 | impl From for WeakId { 72 | fn from(id: T::Type) -> WeakId { 73 | WeakId::new(id) 74 | } 75 | } 76 | */ 77 | 78 | impl From> for WeakId { 79 | fn from(id: Id) -> WeakId { 80 | id.inner 81 | } 82 | } 83 | 84 | impl Borrow> for Id { 85 | fn borrow(&self) -> &WeakId { 86 | &self.inner 87 | } 88 | } 89 | 90 | // TODO: Remove that! 91 | impl> Borrow for Id { 92 | fn borrow(&self) -> &u64 { 93 | &self.inner.id 94 | } 95 | } 96 | 97 | impl Clone for WeakId 98 | where T::Type: Clone { 99 | fn clone(&self) -> WeakId { 100 | WeakId { 101 | id: self.id.clone(), 102 | marker: PhantomData, 103 | } 104 | } 105 | } 106 | 107 | impl Clone for Id 108 | where T::Type: Clone { 109 | fn clone(&self) -> Id { 110 | Id { 111 | inner: self.inner.clone(), 112 | } 113 | } 114 | } 115 | 116 | impl Copy for WeakId 117 | where T::Type: Copy {} 118 | impl Eq for WeakId 119 | where T::Type: Eq {} 120 | impl PartialEq for WeakId 121 | where T::Type: PartialEq { 122 | fn eq(&self, other: &WeakId) -> bool { 123 | self.id.eq(&other.id) 124 | } 125 | } 126 | 127 | impl Hash for WeakId 128 | where T::Type: Hash { 129 | fn hash(&self, state: &mut H) { 130 | self.id.hash(state) 131 | } 132 | } 133 | 134 | impl Debug for WeakId 135 | where T::Type: Debug { 136 | fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { 137 | ::fmt(&self.id, formatter) 138 | } 139 | } 140 | 141 | impl Display for WeakId 142 | where T::Type: Display { 143 | fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { 144 | ::fmt(&self.id, formatter) 145 | } 146 | } 147 | 148 | impl Copy for Id 149 | where T::Type: Copy {} 150 | impl Eq for Id 151 | where T::Type: Eq {} 152 | impl PartialEq for Id 153 | where T::Type: PartialEq { 154 | fn eq(&self, other: &Id) -> bool { 155 | self.inner.eq(&other.inner) 156 | } 157 | } 158 | 159 | impl Hash for Id 160 | where T::Type: Hash { 161 | fn hash(&self, state: &mut H) { 162 | self.inner.hash(state) 163 | } 164 | } 165 | 166 | impl Debug for Id 167 | where T::Type: Debug { 168 | fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { 169 | as Debug>::fmt(&self.inner, formatter) 170 | } 171 | } 172 | 173 | impl Display for Id 174 | where T::Type: Display { 175 | fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { 176 | as Display>::fmt(&self.inner, formatter) 177 | } 178 | } 179 | 180 | lazy_static! { 181 | static ref NEXT_ID: AtomicUsize = AtomicUsize::new(10); 182 | } 183 | 184 | /// Mark a type so that it becomes possible to forge a Typed-ID for it. 185 | pub trait HasForgeableId: HasId {} 186 | 187 | impl Id { 188 | /// Create a new Id with the given id value 189 | /// 190 | /// Note that it is possible to create several time the same Id. In a sense, 191 | /// Forged Id can be seen as weaker Id. 192 | pub fn forge(id: T::Type) -> Id { 193 | Id { 194 | inner: WeakId::new(id), 195 | } 196 | } 197 | } 198 | 199 | impl WeakId { 200 | pub fn upgrade(self) -> Id { 201 | Id::new_inner(self) 202 | } 203 | } 204 | 205 | // TODO: Change type if 'id' to WeakId 206 | pub fn get_id_if_exists(set: &HashSet>, id: T::Type) -> Option> { 207 | let weak = WeakId::new(id); 208 | if set.contains(&weak) { 209 | Some(Id{inner: weak}) 210 | } else { 211 | None 212 | } 213 | } 214 | 215 | // We can transfrom Id in Id 216 | pub trait ConvertTo {} 217 | 218 | // Cannot use the From trait, as we would get conflicting implementations 219 | impl Id { 220 | pub fn convert(self) -> Id 221 | where T: ConvertTo, 222 | T::Type: Into { 223 | Id::new_inner(WeakId::new(self.into_inner().into())) 224 | } 225 | } 226 | 227 | impl Encodable for Id 228 | where T::Type: Encodable { 229 | fn encode(&self, s: &mut S) -> Result<(), S::Error> { 230 | self.inner.encode(s) 231 | } 232 | } 233 | 234 | impl Decodable for Id 235 | where T::Type: Decodable { 236 | fn decode(d: &mut D) -> Result { 237 | WeakId::decode(d).map(Id::new_inner) 238 | } 239 | } 240 | 241 | impl Deserialize for Id 242 | where T::Type: Deserialize { 243 | fn deserialize(d: &mut D) -> Result { 244 | WeakId::deserialize(d).map(Id::new_inner) 245 | } 246 | } 247 | 248 | impl Serialize for Id 249 | where T::Type: Serialize { 250 | fn serialize(&self, d: &mut D) -> Result<(), D::Error> { 251 | self.inner.serialize(d) 252 | } 253 | } 254 | 255 | impl Encodable for WeakId 256 | where T::Type: Encodable { 257 | fn encode(&self, s: &mut S) -> Result<(), S::Error> { 258 | self.id.encode(s) 259 | } 260 | } 261 | 262 | impl Decodable for WeakId 263 | where T::Type: Decodable { 264 | fn decode(d: &mut D) -> Result { 265 | Ok(WeakId { 266 | id: try!(T::Type::decode(d)), 267 | marker: PhantomData, 268 | }) 269 | } 270 | } 271 | 272 | impl Deserialize for WeakId 273 | where T::Type: Deserialize { 274 | fn deserialize(d: &mut D) -> Result { 275 | Ok(WeakId { 276 | id: try!(T::Type::deserialize(d)), 277 | marker: PhantomData, 278 | }) 279 | } 280 | } 281 | 282 | impl Serialize for WeakId 283 | where T::Type: Serialize { 284 | fn serialize(&self, d: &mut D) -> Result<(), D::Error> { 285 | self.id.serialize(d) 286 | } 287 | } 288 | 289 | -------------------------------------------------------------------------------- /src/network/buffered_tcp.rs: -------------------------------------------------------------------------------- 1 | use std::io::Error as IoError; 2 | use std::io::{Read,Write,ErrorKind}; 3 | use std::ops::Deref; 4 | 5 | use tokio_core::io::Io; 6 | use bytes::{Buf,MutBuf,RingBuf}; 7 | use futures; 8 | use futures::Future; 9 | use futures::Async; 10 | use futures::Poll; 11 | use futures::task::TaskRc; 12 | 13 | pub struct BufferedReader { 14 | inner: T, 15 | buffer: RingBuf, 16 | } 17 | 18 | impl BufferedReader { 19 | pub fn new(inner: T) -> BufferedReader { 20 | BufferedReader::with_capacity(inner, 8 * 1024) 21 | } 22 | 23 | pub fn with_capacity(inner: T, capacity: usize) -> BufferedReader { 24 | BufferedReader { 25 | inner: inner, 26 | buffer: RingBuf::new(capacity), 27 | } 28 | } 29 | 30 | pub fn available(&self) -> usize { 31 | Buf::remaining(&self.buffer) 32 | } 33 | 34 | pub fn capacity(&self) -> usize { 35 | self.buffer.capacity() 36 | } 37 | 38 | /// Returns a future that will complete when at least n bytes are available to read 39 | /// 40 | /// # Panics 41 | /// Panics if n > capacity 42 | pub fn ensure(self, n: usize) -> Ensure { 43 | assert!(n <= self.buffer.capacity()); 44 | Ensure { 45 | buf_read: Some(self), 46 | objective: n, 47 | } 48 | } 49 | 50 | // Try to read at least n bytes from the socket 51 | fn read_from_socket(&mut self, n: usize) -> Result { 52 | let mut read = 0; 53 | loop { 54 | if read >= n { 55 | return Ok(true); 56 | } 57 | let nb = { 58 | // XXX: Unsafety notice (not entirely solved ...) 59 | // The two unsafe calls bellow are because it exposes potentially uninitialized memory 60 | // Everything should work correctly if the type T respects the std::io::Read contract 61 | // However, it is possible that a user of the library implements a "malicious" one 62 | // in safe code only, and can thus get access to uninitialized memory 63 | let to_read = unsafe { MutBuf::mut_bytes(&mut self.buffer) }; 64 | match self.inner.read(to_read) { 65 | Ok(nb) => nb, 66 | Err(e) => { 67 | if e.kind() == ErrorKind::WouldBlock { 68 | if read == 0 { 69 | return Err(e); 70 | } else { 71 | return Ok(false); 72 | } 73 | } else { 74 | return Err(e); 75 | } 76 | } 77 | } 78 | }; 79 | // When a peer disconnects, a read to the socket will return Ok(0) 80 | if nb == 0 { 81 | return Ok(false); 82 | } 83 | unsafe { MutBuf::advance(&mut self.buffer, nb) }; 84 | read += nb; 85 | } 86 | 87 | } 88 | } 89 | 90 | impl Read for BufferedReader { 91 | fn read(&mut self, buf: &mut [u8]) -> Result { 92 | let mut written = 0; 93 | loop { 94 | let b = &mut buf[written..]; 95 | let r = Buf::read_slice(&mut self.buffer, b); 96 | written += r; 97 | if b.len() == r { 98 | // We already filled the buffer 99 | return Ok(written); 100 | } 101 | 102 | debug_assert!(!Buf::has_remaining(&self.buffer)); 103 | 104 | match self.read_from_socket(b.len()) { 105 | Ok(finished) => { 106 | if !finished && !Buf::has_remaining(&self.buffer) { 107 | // We read 0 from the socket (possible disconnect) 108 | // Get out of the loop 109 | return Ok(written); 110 | } 111 | } 112 | Err(e) => { 113 | if e.kind() == ErrorKind::WouldBlock { 114 | if written == 0 { 115 | return Err(e); 116 | } else { 117 | return Ok(written); 118 | } 119 | } else { 120 | return Err(e); 121 | } 122 | } 123 | } 124 | } 125 | } 126 | } 127 | 128 | impl Write for BufferedReader { 129 | fn write(&mut self, buf: &[u8]) -> Result { 130 | self.inner.write(buf) 131 | } 132 | 133 | fn flush(&mut self) -> Result<(), IoError> { 134 | self.inner.flush() 135 | } 136 | } 137 | 138 | impl Io for BufferedReader { 139 | fn poll_read(&mut self) -> Async<()> { 140 | if !self.buffer.is_empty() { 141 | Async::Ready(()) 142 | } else { 143 | self.inner.poll_read() 144 | } 145 | } 146 | 147 | fn poll_write(&mut self) -> Async<()> { self.inner.poll_write() } 148 | } 149 | 150 | pub struct Ensure { 151 | buf_read: Option>, 152 | objective: usize, 153 | } 154 | 155 | impl Future for Ensure { 156 | type Item = BufferedReader; 157 | type Error = IoError; 158 | 159 | fn poll(&mut self) -> Poll { 160 | match self.buf_read.take() { 161 | Some(mut b) => { 162 | let available = b.available(); 163 | if available >= self.objective { 164 | Ok(Async::Ready(b)) 165 | } else { 166 | match b.read_from_socket(self.objective - available) { 167 | Ok(true) => { 168 | Ok(Async::Ready(b)) 169 | } 170 | Ok(false) => { 171 | self.buf_read = Some(b); 172 | Ok(Async::NotReady) 173 | } 174 | Err(e) => { 175 | if e.kind() == ErrorKind::WouldBlock { 176 | self.buf_read = Some(b); 177 | Ok(Async::NotReady) 178 | } else { 179 | return Err(e); 180 | } 181 | } 182 | } 183 | } 184 | } 185 | None => panic!("Polling a Ensure after it has resolved"), 186 | } 187 | } 188 | } 189 | 190 | pub struct IoRef { 191 | inner: TaskRc, 192 | } 193 | 194 | impl Clone for IoRef { 195 | fn clone(&self) -> IoRef { 196 | IoRef { inner: self.inner.clone() } 197 | } 198 | } 199 | 200 | impl Read for IoRef 201 | where for <'a> &'a T: Read { 202 | fn read(&mut self, buf: &mut [u8]) -> Result { 203 | self.inner.with(|mut s| s.read(buf)) 204 | } 205 | } 206 | 207 | impl Write for IoRef 208 | where for <'a> &'a T: Write { 209 | fn write(&mut self, buf: &[u8]) -> Result { 210 | self.inner.with(|mut s| s.write(buf)) 211 | } 212 | 213 | fn flush(&mut self) -> Result<(), IoError> { 214 | self.inner.with(|mut s| s.flush()) 215 | } 216 | } 217 | 218 | impl Io for IoRef 219 | where for <'a> &'a T: Io { 220 | fn poll_read(&mut self) -> Async<()> { 221 | self.inner.with(|mut s| s.poll_read()) 222 | } 223 | 224 | fn poll_write(&mut self) -> Async<()> { 225 | self.inner.with(|mut s| s.poll_write()) 226 | } 227 | } 228 | 229 | impl IoRef { 230 | pub fn new(inner: T) -> impl Future,Error=E> { 231 | futures::lazy(|| Ok(IoRef { inner: TaskRc::new(inner) })) 232 | } 233 | } 234 | 235 | impl Deref for IoRef { 236 | type Target = TaskRc; 237 | 238 | fn deref(&self) -> &TaskRc { 239 | &self.inner 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/entity/update/attacks.rs: -------------------------------------------------------------------------------- 1 | use lycan_serialize::Direction; 2 | use aariba::expressions::{Store}; 3 | 4 | use id::Id; 5 | use instance::{ 6 | TickEvent, 7 | }; 8 | use entity::{ 9 | Entity, 10 | Order, 11 | EntityStore, 12 | OthersAccessor, 13 | AttackState, 14 | }; 15 | use messages::Notification; 16 | use scripts::AaribaScripts; 17 | 18 | pub fn resolve_attacks( 19 | entities: &mut EntityStore, 20 | notifications: &mut Vec, 21 | scripts: &AaribaScripts, 22 | events: &mut Vec, 23 | tick_duration: f32, 24 | ) { 25 | // Indicates entities that die during that tick 26 | // As soon as the entity dies, it should *stop interracting with the world* 27 | let mut dead_entities_id = vec![]; 28 | 29 | { 30 | // Iterate through all entities 31 | let mut double_iterator = entities.iter_mut_wrapper(); 32 | while let Some((entity, mut wrapper)) = double_iterator.next_item() { 33 | if !dead_entities_id.contains(&entity.id) { 34 | trace!("Entity {} {:?}", entity.id, entity.attacking); 35 | match entity.attacking { 36 | AttackState::Idle => {} 37 | AttackState::Attacking => { 38 | entity.attacking = AttackState::Reloading(1.0); 39 | resolve_hit(entity, &mut wrapper, notifications, scripts, &mut dead_entities_id); 40 | } 41 | AttackState::Reloading(delay) => { 42 | let remaining = delay - entity.stats.attack_speed * tick_duration; 43 | if remaining < 0.0 { 44 | entity.attacking = AttackState::Idle; 45 | } else { 46 | entity.attacking = AttackState::Reloading(remaining); 47 | } 48 | } 49 | } 50 | } 51 | } 52 | } 53 | 54 | for dead_id in dead_entities_id { 55 | match entities.remove(dead_id) { 56 | Some(dead_entity) => { 57 | events.push(TickEvent::EntityDeath(dead_entity)); 58 | } 59 | None => { 60 | error!("Could not find dead entity {} in the store, but it was scheduled for removal", 61 | dead_id); 62 | } 63 | } 64 | } 65 | } 66 | 67 | fn resolve_hit( 68 | attacker: &mut Entity, 69 | others: &mut OthersAccessor, 70 | notifications: &mut Vec, 71 | scripts: &AaribaScripts, 72 | dead_entities_id: &mut Vec>, 73 | ) { 74 | for entity in others.iter_mut() { 75 | if !dead_entities_id.contains(&entity.id) { 76 | if attack_success(attacker, entity) { 77 | let mut integration = AaribaIntegration::new(attacker, 78 | entity, 79 | notifications, 80 | dead_entities_id, 81 | ); 82 | match scripts.combat.evaluate(&mut integration) { 83 | Ok(()) => {} 84 | Err(e) => { 85 | error!("Script error: {:#?}", e); 86 | continue; 87 | } 88 | } 89 | } 90 | } 91 | } 92 | } 93 | 94 | fn attack_success(attacker: &Entity, target: &Entity) -> bool { 95 | let target_box = target.hitbox; 96 | let target_position = target.position; 97 | let attack_box; 98 | let attack_position; 99 | match attacker.get_orientation() { 100 | Direction::North => { 101 | attack_box = attacker.attack_box.rotated(); 102 | attack_position = attacker.position + attacker.attack_offset_y; 103 | } 104 | Direction::South => { 105 | attack_box = attacker.attack_box.rotated(); 106 | attack_position = attacker.position - attacker.attack_offset_y; 107 | } 108 | Direction::East => { 109 | attack_box = attacker.attack_box; 110 | attack_position = attacker.position + attacker.attack_offset_x; 111 | } 112 | Direction::West => { 113 | attack_box = attacker.attack_box; 114 | attack_position = attacker.position - attacker.attack_offset_x; 115 | } 116 | } 117 | 118 | attack_box.collision(attack_position, &target_box, target_position) 119 | } 120 | 121 | #[derive(Debug)] 122 | struct AaribaIntegration<'a,'b, 'c, 'd> { 123 | source: &'a mut Entity, 124 | target: &'b mut Entity, 125 | notifications: &'c mut Vec, 126 | dead_entities_id: &'d mut Vec>, 127 | } 128 | 129 | impl <'a, 'b, 'c, 'd> Store for AaribaIntegration<'a, 'b, 'c, 'd> { 130 | fn get_attribute(&self, var: &str) -> Option { 131 | let mut splitn = var.splitn(2, '.'); 132 | let first = match splitn.next() { 133 | Some(first) => first, 134 | None => return None, 135 | }; 136 | let second = match splitn.next() { 137 | Some(s) => s, 138 | None => return None, 139 | }; 140 | match first { 141 | "target" => self.target.get_attribute(second), 142 | "source" => self.source.get_attribute(second), 143 | _ => None, 144 | } 145 | } 146 | fn set_attribute(&mut self, var: &str, value: f64) -> Result,()> { 147 | let mut splitn = var.splitn(2, '.'); 148 | let first = match splitn.next() { 149 | Some(first) => first, 150 | None => return Err(()), 151 | }; 152 | let second = match splitn.next() { 153 | Some(s) => s, 154 | None => return Err(()), 155 | }; 156 | match first { 157 | "target" => { 158 | set_attribute(self.target, 159 | self.source.id, 160 | second, 161 | value, 162 | self.notifications, 163 | self.dead_entities_id) 164 | } 165 | "source" => { 166 | let id = self.source.id; 167 | set_attribute(self.source, 168 | id, 169 | second, 170 | value, 171 | self.notifications, 172 | self.dead_entities_id) 173 | } 174 | _ => Err(()), 175 | } 176 | } 177 | } 178 | 179 | fn set_attribute( 180 | entity: &mut Entity, 181 | source: Id, // Can potentially be the same as entity.id 182 | var: &str, 183 | value: f64, 184 | notifications: &mut Vec, 185 | dead_entities_id: &mut Vec>, 186 | ) -> Result,()> { 187 | match var { 188 | "damage" => { 189 | if entity.pv != 0 { 190 | notifications.push(Notification::Damage { 191 | source: source.as_u64(), 192 | victim: entity.id.as_u64(), 193 | amount: value as u64, 194 | }); 195 | let new_pv = entity.pv as f64 - value; 196 | if new_pv < 0.0 { 197 | // Death of entity 198 | entity.pv = 0; 199 | dead_entities_id.push(entity.id); 200 | } else { 201 | entity.pv = new_pv as u64; 202 | } 203 | } else { 204 | warn!("Trying to damage a dead entity {}", entity.id); 205 | } 206 | Ok(None) 207 | } 208 | _ => Err(()), 209 | } 210 | } 211 | 212 | impl <'a, 'b, 'c, 'd> AaribaIntegration<'a, 'b, 'c, 'd> { 213 | fn new( 214 | source: &'a mut Entity, 215 | target: &'b mut Entity, 216 | notifications: &'c mut Vec, 217 | dead_entities_id: &'d mut Vec>, 218 | ) -> AaribaIntegration<'a, 'b, 'c, 'd> { 219 | AaribaIntegration { 220 | source: source, 221 | target: target, 222 | notifications: notifications, 223 | dead_entities_id: dead_entities_id, 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/game/resource_manager.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::sync::Arc; 3 | use std::sync::mpsc::{self,Sender,Receiver}; 4 | use std::fmt::{self,Write}; 5 | use std::hash::Hash; 6 | 7 | use threadpool::ThreadPool; 8 | use serde_json; 9 | 10 | use utils; 11 | use id::{Id,HasId}; 12 | use data::{Map,Player}; 13 | use data::UNIQUE_MAP; 14 | use entity::Entity; 15 | use game::Game; 16 | use messages::Request; 17 | 18 | pub struct ResourceManager { 19 | maps: ResourceManagerInner>, 20 | players: ResourceManagerInner, 21 | requests: Sender, 22 | pool: ThreadPool, 23 | job: usize, 24 | base_url: String, 25 | } 26 | 27 | struct ResourceManagerInner { 28 | resources: HashMap, U>, 29 | errors: HashMap, Error>, 30 | jobs: CurrentJobs, 31 | requests: Sender, 32 | 33 | // XXX: Maybe a sync receiver would be better here 34 | tx: Sender <(Id, Result)>, 35 | rx: Receiver<(Id, Result)>, 36 | } 37 | 38 | impl ResourceManagerInner 39 | where U: RetreiveFromId, 40 | U: Send + 'static, 41 | T: HasId + 'static { 42 | fn new(requests: Sender) -> ResourceManagerInner { 43 | let (tx, rx) = mpsc::channel(); 44 | ResourceManagerInner { 45 | resources: HashMap::new(), 46 | errors: HashMap::new(), 47 | jobs: CurrentJobs::new(), 48 | requests: requests, 49 | 50 | tx: tx, 51 | rx: rx, 52 | } 53 | } 54 | 55 | fn load(&mut self, id: Id, pool: &ThreadPool, job: usize, info: U::Info) { 56 | self.process_inputs(); 57 | if self.resources.get(&id).is_none() && 58 | self.errors.get(&id).is_none() && 59 | !self.jobs.contains(id) { 60 | self.jobs.push(id, job); 61 | let sender = self.requests.clone(); 62 | let tx = self.tx.clone(); 63 | pool.execute(move || { 64 | // TODO: fetch the resource on the disk 65 | let fetched = U::retrieve(id, info); 66 | 67 | tx.send((id, fetched)).unwrap(); 68 | sender.send(Request::JobFinished(job)).unwrap(); 69 | }); 70 | } 71 | } 72 | 73 | fn process_inputs(&mut self) { 74 | while let Ok((id, data_res)) = self.rx.try_recv() { 75 | match data_res { 76 | Ok(data) => { 77 | self.jobs.remove(id); 78 | if let Some(_old) = self.resources.insert(id,data) { 79 | warn!("Replacing resource {:?} in the resource manager", id); 80 | } 81 | } 82 | Err(e) => { 83 | self.errors.insert(id, e); 84 | } 85 | } 86 | } 87 | } 88 | 89 | fn retrieve(&mut self, id: Id, pool: &ThreadPool, job: usize, info: U::Info) -> Result { 90 | self.process_inputs(); 91 | 92 | // We already have it 93 | if let Some(data) = self.resources.remove(&id) { 94 | return Ok(data); 95 | } 96 | 97 | if let Some(job) = self.jobs.get(id) { 98 | return Err(Error::Processing(job)); 99 | } 100 | 101 | if let Some(error) = self.errors.get(&id) { 102 | return Err(error.clone()); 103 | } 104 | 105 | // We don't have it, not processing and no errors ... we fetch it 106 | self.load(id, pool, job, info); 107 | Err(Error::Processing(job)) 108 | } 109 | } 110 | 111 | impl ResourceManagerInner 112 | where U: RetreiveFromId, 113 | U: Send + Clone + 'static, 114 | T: HasId + 'static { 115 | fn get(&mut self, id: Id, pool: &ThreadPool, job: usize, info: U::Info) -> Result { 116 | self.process_inputs(); 117 | // We already have it 118 | if let Some(data) = self.resources.get(&id) { 119 | return Ok(data.clone()); 120 | } 121 | 122 | if let Some(job) = self.jobs.get(id) { 123 | return Err(Error::Processing(job)); 124 | } 125 | 126 | if let Some(error) = self.errors.get(&id) { 127 | return Err(error.clone()); 128 | } 129 | 130 | // We don't have it, not processing and no errors ... we fetch it 131 | self.load(id, pool, job, info); 132 | Err(Error::Processing(job)) 133 | } 134 | 135 | fn get_all(&mut self) -> Vec { 136 | self.process_inputs(); 137 | self.resources.values().cloned().collect() 138 | } 139 | } 140 | 141 | impl ResourceManager { 142 | pub fn new(threads: usize, requests: Sender, url: String) -> ResourceManager { 143 | ResourceManager { 144 | maps: ResourceManagerInner::new(requests.clone()), 145 | players: ResourceManagerInner::new(requests.clone()), 146 | pool: ThreadPool::new(threads), 147 | requests: requests, 148 | job: 0, 149 | base_url: url, 150 | } 151 | } 152 | 153 | pub fn load_map(&mut self, map: Id) { 154 | let job = self.job; 155 | self.job += 1; 156 | self.maps.load(map, &self.pool, job, ()); 157 | } 158 | 159 | pub fn get_map(&mut self, map: Id) -> Result, Error> { 160 | let job = self.job; 161 | self.job += 1; 162 | self.maps.get(map, &self.pool, job, ()) 163 | } 164 | 165 | pub fn load_player(&mut self, player: Id) { 166 | let job = self.job; 167 | self.job += 1; 168 | self.players.load(player, &self.pool, job, self.base_url.clone()); 169 | } 170 | 171 | pub fn retrieve_player(&mut self, 172 | player: Id, 173 | ) -> Result { 174 | let job = self.job; 175 | self.job += 1; 176 | self.players.retrieve(player, &self.pool, job, self.base_url.clone()) 177 | } 178 | 179 | pub fn get_all_maps(&mut self) -> Vec> { 180 | self.maps.get_all() 181 | } 182 | } 183 | 184 | #[derive(Debug)] 185 | enum Data { 186 | Map(Map), 187 | } 188 | 189 | #[derive(Debug)] 190 | struct CurrentJobs { 191 | inner: HashMap,usize>, 192 | } 193 | 194 | impl CurrentJobs { 195 | fn new() -> CurrentJobs { 196 | CurrentJobs { 197 | inner: HashMap::new(), 198 | } 199 | } 200 | 201 | fn contains(&self, id: Id) -> bool { 202 | self.inner.contains_key(&id) 203 | } 204 | 205 | fn get(&self, id: Id) -> Option { 206 | self.inner.get(&id).cloned() 207 | } 208 | 209 | fn push(&mut self, id: Id, job: usize) -> bool { 210 | if !self.inner.contains_key(&id) { 211 | self.inner.insert(id, job); 212 | true 213 | } else { 214 | false 215 | } 216 | } 217 | 218 | fn remove(&mut self, id: Id) { 219 | self.inner.remove(&id); 220 | } 221 | } 222 | 223 | impl fmt::Debug for ResourceManager { 224 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 225 | fmt.debug_struct("ResourceManager") 226 | .field("maps", &self.maps) 227 | .field("players", &self.players) 228 | .finish() 229 | } 230 | } 231 | 232 | impl fmt::Debug for ResourceManagerInner { 233 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 234 | fmt.debug_struct("ResourceManagerInner") 235 | .field("resources", &self.resources) 236 | .field("errors", &self.errors) 237 | .field("jobs", &self.jobs) 238 | .finish() 239 | } 240 | } 241 | 242 | // Fetch the resource from the disk 243 | pub trait RetreiveFromId 244 | where T: HasId { 245 | type Info: Send; 246 | fn retrieve(id: Id, info: Self::Info) -> Result where Self: Sized; 247 | } 248 | 249 | #[derive(Debug,Clone,Copy)] 250 | pub enum Error { 251 | Processing(usize), 252 | NotFound, 253 | } 254 | 255 | impl RetreiveFromId for Entity { 256 | type Info = String; 257 | fn retrieve(id: Id, mut base: String) -> Result { 258 | let _ = write!(base, "/entities/{}", id); 259 | if let Ok(serialized_entity) = utils::get_file_from_url(&base) { 260 | if let Ok(entity) = serde_json::from_str::(&serialized_entity) { 261 | return Ok(Entity::from(entity)) 262 | } 263 | } 264 | Ok(Entity::fake_player(id)) 265 | } 266 | } 267 | 268 | impl RetreiveFromId for Map { 269 | type Info = (); 270 | fn retrieve(id: Id, _: ()) -> Result { 271 | if id != UNIQUE_MAP.get_id() { 272 | Err(Error::NotFound) 273 | } else { 274 | Ok(UNIQUE_MAP.clone()) 275 | } 276 | } 277 | } 278 | 279 | impl RetreiveFromId for Arc 280 | where T: RetreiveFromId { 281 | type Info = T::Info; 282 | fn retrieve(id: Id, info: Self::Info) -> Result,Error> { 283 | T::retrieve(id, info).map(Arc::new) 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /src/ai/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use nalgebra::{Vector2,FloatPoint}; 4 | 5 | use behaviour_tree::tree::{BehaviourTreeNode}; 6 | use behaviour_tree::tree::{LeafNodeFactory,VisitResult}; 7 | use behaviour_tree::parser::Value; 8 | use behaviour_tree::FactoryProducer; 9 | 10 | use id::Id; 11 | use entity::{Entity,EntityStore,Direction}; 12 | 13 | pub type ActionNode = Box BehaviourTreeNode> + Send>; 14 | //pub type ActionNodeFactory = Box BehaviourTreeNode>>>>; 15 | pub type ActionNodeFactory = Box>; 16 | pub type ActionNodeFactoryFactory = fn(&Option) -> Result; 17 | 18 | pub trait BoxedClone: LeafNodeFactory + Send { 19 | fn boxed_clone(&self) -> ActionNodeFactory; 20 | } 21 | 22 | impl Clone for ActionNodeFactory { 23 | fn clone(&self) -> ActionNodeFactory { 24 | (**self).boxed_clone() 25 | } 26 | } 27 | impl BoxedClone for T 28 | where T: Clone, 29 | T: 'static, 30 | T: Send, 31 | T: LeafNodeFactory { 32 | fn boxed_clone(&self) -> ActionNodeFactory { 33 | Box::new(self.clone()) 34 | } 35 | } 36 | 37 | pub struct Context<'a, 'b> { 38 | pub me: Id, 39 | pub entities: &'a mut EntityStore, 40 | pub storage: &'b mut BehaviourTreeData, 41 | } 42 | 43 | impl <'a, 'b> Context<'a, 'b> { 44 | pub fn new( 45 | me: Id, 46 | entities: &'a mut EntityStore, 47 | storage: &'b mut BehaviourTreeData, 48 | ) -> Context<'a, 'b> { 49 | Context { 50 | me: me, 51 | entities: entities, 52 | storage: storage, 53 | } 54 | } 55 | } 56 | 57 | #[derive(Debug,Clone)] 58 | pub struct BehaviourTreeData { 59 | map: HashMap, 60 | target: Option>, 61 | path: Option, 62 | } 63 | 64 | impl BehaviourTreeData { 65 | pub fn new() -> BehaviourTreeData { 66 | BehaviourTreeData { 67 | map: HashMap::new(), 68 | target: None, 69 | path: None, 70 | } 71 | } 72 | 73 | fn set_target(&mut self, target: Option>) { 74 | self.target = target; 75 | } 76 | } 77 | 78 | #[derive(Clone,Debug)] 79 | pub enum StoreKind { 80 | // TODO 81 | } 82 | 83 | // TODO 84 | #[derive(Clone,Debug)] 85 | pub struct Path; 86 | 87 | #[derive(Clone)] 88 | pub struct Prototype { 89 | pub inner: T, 90 | } 91 | 92 | impl Prototype { 93 | pub fn new(inner: T) -> Prototype { 94 | Prototype { 95 | inner: inner, 96 | } 97 | } 98 | } 99 | 100 | impl LeafNodeFactory for Prototype 101 | where T: Clone, 102 | T: 'static, 103 | T: Send, 104 | T: for <'a,'b> BehaviourTreeNode> { 105 | type Output = ActionNode; 106 | fn instanciate(&self) -> Self::Output { 107 | Box::new(self.inner.clone()) 108 | } 109 | } 110 | 111 | 112 | #[derive(Debug,Clone)] 113 | pub struct PrintText { 114 | pub text: String, 115 | } 116 | 117 | impl <'a,'b> BehaviourTreeNode> for PrintText { 118 | fn visit(&mut self, _context: &mut Context) -> VisitResult { 119 | println!("Message node: {}", self.text); 120 | VisitResult::Success 121 | } 122 | } 123 | 124 | pub fn print_text(options: &Option) -> Result { 125 | let message_orig = match options { 126 | &Some(Value::String(ref message)) => message, 127 | other => return Err(format!("Expected message, found {:?}", other)), 128 | }; 129 | 130 | let message = message_orig.replace("_"," "); 131 | 132 | Ok(Box::new(Prototype::new(PrintText { text: message }))) 133 | } 134 | 135 | #[derive(Clone)] 136 | pub struct GetClosestTarget { 137 | max_sqdistance: f32, 138 | } 139 | 140 | impl <'a,'b> BehaviourTreeNode> for GetClosestTarget { 141 | fn visit(&mut self, context: &mut Context) -> VisitResult { 142 | let (me, others) = match context.entities.get_mut_wrapper(context.me) { 143 | None => { 144 | warn!("Main entity {} was not found in entities list", context.me); 145 | return VisitResult::Failure; 146 | } 147 | Some((me, others)) => (me, others), 148 | }; 149 | let my_position = me.get_position(); 150 | let mut closest_other = None; 151 | let mut closest_other_sqdistance = self.max_sqdistance; 152 | for other in others.iter() { 153 | let sqdistance = my_position.distance_squared(&other.get_position()); 154 | if sqdistance < closest_other_sqdistance { 155 | closest_other = Some(other.get_id()); 156 | closest_other_sqdistance = sqdistance; 157 | } 158 | } 159 | context.storage.target = closest_other; 160 | debug!("Get closest target: found {:?} at sqdist {}", closest_other, closest_other_sqdistance); 161 | VisitResult::Success 162 | } 163 | } 164 | 165 | pub fn get_closest_target(options: &Option) -> Result { 166 | // TODO 167 | Ok(Box::new(Prototype::new(GetClosestTarget { max_sqdistance: 10000.0 }))) 168 | } 169 | 170 | #[derive(Clone)] 171 | // TODO: Timeout? Max distance? Stop condition? 172 | pub struct WalkToTarget; 173 | 174 | impl <'a,'b> BehaviourTreeNode> for WalkToTarget { 175 | fn visit(&mut self, context: &mut Context) -> VisitResult { 176 | // TODO: Proper pathfinding 177 | 178 | let (me, mut others) = match context.entities.get_mut_wrapper(context.me) { 179 | None => { 180 | warn!("Main entity {} was not found in entities list", context.me); 181 | return VisitResult::Failure; 182 | } 183 | Some((me, others)) => (me, others), 184 | }; 185 | let target = match context.storage.target { 186 | None => return VisitResult::Failure, 187 | Some(id) => match others.get(id) { 188 | None => { 189 | warn!("Could not find target {}", id); 190 | me.walk(None); 191 | return VisitResult::Failure; 192 | } 193 | Some(o) => o, 194 | } 195 | }; 196 | let vector = target.get_position() - me.get_position(); 197 | let abs_diff_x = vector.x.abs(); 198 | let abs_diff_y = vector.y.abs(); 199 | match me.get_orientation() { 200 | Direction::East | Direction::West => { 201 | if abs_diff_x > abs_diff_y/2.0 { 202 | if vector.x.is_sign_positive() { 203 | me.walk(Some(Direction::East)); 204 | } else { 205 | me.walk(Some(Direction::West)); 206 | } 207 | } else { 208 | if vector.y.is_sign_positive() { 209 | me.walk(Some(Direction::North)); 210 | } else { 211 | me.walk(Some(Direction::South)); 212 | } 213 | } 214 | } 215 | Direction::North | Direction::South => { 216 | if abs_diff_x/2.0 > abs_diff_y { 217 | if vector.x.is_sign_positive() { 218 | me.walk(Some(Direction::East)); 219 | } else { 220 | me.walk(Some(Direction::West)); 221 | } 222 | } else { 223 | if vector.y.is_sign_positive() { 224 | me.walk(Some(Direction::North)); 225 | } else { 226 | me.walk(Some(Direction::South)); 227 | } 228 | } 229 | } 230 | } 231 | VisitResult::Running 232 | } 233 | } 234 | 235 | pub fn walk_to_target(options: &Option) -> Result { 236 | // TODO 237 | Ok(Box::new(Prototype::new(WalkToTarget))) 238 | } 239 | 240 | 241 | #[derive(Default)] 242 | pub struct LeavesCollection { 243 | inner: HashMap, 244 | } 245 | 246 | macro_rules! insert_all { 247 | ($($name:expr => $fun:expr),*) => ( 248 | { 249 | let mut collection = LeavesCollection::new(); 250 | $( 251 | collection.inner.insert( 252 | String::from($name), 253 | $fun, 254 | ); 255 | )* 256 | collection 257 | } 258 | ); 259 | ($($name:expr => $fun:expr),+,) => ( 260 | insert_all!($($name => $fun),+) 261 | ); 262 | } 263 | 264 | impl LeavesCollection { 265 | pub fn new() -> LeavesCollection { 266 | LeavesCollection { 267 | inner: HashMap::new(), 268 | } 269 | } 270 | 271 | pub fn register_function( 272 | &mut self, 273 | key: String, 274 | f: ActionNodeFactoryFactory, 275 | ) { 276 | self.inner.insert(key,f); 277 | } 278 | 279 | pub fn standard() -> LeavesCollection { 280 | let collection = insert_all!( 281 | "print_text" => print_text, 282 | "get_closest_target" => get_closest_target, 283 | "walk_to_target" => walk_to_target, 284 | //"increment" => increment, 285 | 286 | ); 287 | 288 | collection 289 | } 290 | } 291 | 292 | impl FactoryProducer for LeavesCollection { 293 | type Factory = ActionNodeFactory; 294 | fn generate_leaf(&self, name: &str, option: &Option) -> Result { 295 | match self.inner.get(name) { 296 | None => Err(format!("Could not find leaf with name {}", name)), 297 | Some(fact_fact) => { 298 | let fact = try!(fact_fact(option)); 299 | Ok(fact) 300 | } 301 | } 302 | } 303 | } 304 | 305 | -------------------------------------------------------------------------------- /src/game/mod.rs: -------------------------------------------------------------------------------- 1 | use std::net::{self,SocketAddr,Ipv4Addr}; 2 | use std::collections::HashMap; 3 | use std::thread; 4 | use std::io; 5 | use std::boxed::FnBox; 6 | use std::sync::mpsc::{self,Receiver,Sender}; 7 | 8 | use lycan_serialize::AuthenticationToken; 9 | 10 | use utils; 11 | use instance::{InstanceRef,Instance}; 12 | use actor::{NetworkActor,ActorId}; 13 | use id::{Id,HasId,WeakId}; 14 | use data::{Player,Map,EntityManagement,EntityType}; 15 | use data::UNIQUE_MAP; 16 | use entity::{Entity}; 17 | use messages::{Command,Request,Notification}; 18 | use network; 19 | use scripts::{AaribaScripts,BehaviourTrees}; 20 | 21 | use self::resource_manager::{Error,ResourceManager}; 22 | use self::authentication::AuthenticationManager; 23 | 24 | mod authentication; 25 | mod resource_manager; 26 | //mod arriving_client; 27 | mod management; 28 | 29 | const RESOURCE_MANAGER_THREADS: usize = 2; 30 | 31 | #[derive(Debug,Clone)] 32 | pub struct GameParameters { 33 | pub port: u16, 34 | pub configuration_url: String, 35 | pub tick_duration: f32, 36 | } 37 | 38 | pub struct Game { 39 | // Keep track of all _active_ (not shuting down) instances, indexed by map ID 40 | map_instances: HashMap, HashMap, InstanceRef>>, 41 | // Keep track of all instances still alive 42 | instances: HashMap, InstanceRef>, 43 | players: HashMap, EntityManagement>, 44 | resource_manager: ResourceManager, 45 | authentication_manager: AuthenticationManager, 46 | sender: Sender, 47 | tick_duration: f32, 48 | callbacks: Callbacks, 49 | shutdown: bool, 50 | 51 | // TODO: Should this be integrated with the resource manager? 52 | scripts: AaribaScripts, 53 | trees: BehaviourTrees, 54 | } 55 | 56 | impl Game { 57 | fn new( 58 | scripts: AaribaScripts, 59 | trees: BehaviourTrees, 60 | sender: Sender, 61 | base_url: String, 62 | tick_duration: f32, 63 | ) -> Game { 64 | Game { 65 | map_instances: HashMap::new(), 66 | instances: HashMap::new(), 67 | players: HashMap::new(), 68 | sender: sender.clone(), 69 | authentication_manager: AuthenticationManager::new(), 70 | resource_manager: ResourceManager::new(RESOURCE_MANAGER_THREADS, sender, base_url), 71 | tick_duration: tick_duration, 72 | callbacks: Callbacks::new(), 73 | shutdown: false, 74 | scripts: scripts, 75 | trees: trees, 76 | } 77 | } 78 | 79 | pub fn spawn_game(parameters: GameParameters) -> Result,io::Error> { 80 | let scripts = AaribaScripts::get_from_url(¶meters.configuration_url).unwrap(); 81 | let behaviour_trees = BehaviourTrees::get_from_url(¶meters.configuration_url).unwrap(); 82 | 83 | let (sender, rx) = mpsc::channel(); 84 | 85 | let ip = net::IpAddr::V4(Ipv4Addr::new(0,0,0,0)); 86 | let addr = SocketAddr::new(ip,parameters.port); 87 | network::start_server(addr, sender.clone()); 88 | 89 | management::start_management_api(sender.clone()); 90 | let mut game = Game::new( 91 | scripts, 92 | behaviour_trees, 93 | sender.clone(), 94 | parameters.configuration_url.clone(), 95 | parameters.tick_duration, 96 | ); 97 | 98 | // XXX: Hacks 99 | game.authentication_manager.fake_authentication_tokens(); 100 | let _ = game.resource_manager.load_map(UNIQUE_MAP.get_id()); 101 | game.map_instances.insert(UNIQUE_MAP.get_id(), HashMap::new()); 102 | // End hacks 103 | 104 | thread::spawn(move || { 105 | // This is the "event loop" 106 | debug!("Started game"); 107 | for request in rx { 108 | if game.apply(request) { 109 | break; 110 | } 111 | } 112 | debug!("Stopping game"); 113 | }); 114 | Ok(sender) 115 | } 116 | 117 | // Returns true to exit the loop 118 | fn apply(&mut self, request: Request) -> bool { 119 | match request { 120 | Request::Arbitrary(req) => { 121 | req.execute(self); 122 | } 123 | Request::UnregisteredActor{actor,entities} => { 124 | debug!("Unregistered {} {:?}", actor, entities); 125 | // TODO: Store it or change its map ... 126 | 127 | for entity in entities { 128 | self.entity_leaving(entity); 129 | } 130 | } 131 | Request::InstanceShuttingDown(mut state) => { 132 | debug!("Instance {} shutting down. State {:?}", state.id, state); 133 | self.instances.remove(&state.id); 134 | for (_actor, entities) in state.external_actors.drain(..) { 135 | for entity in entities { 136 | self.entity_leaving(entity); 137 | } 138 | // Drop the client without goodbye? 139 | } 140 | state.was_saved = true; 141 | 142 | if self.shutdown && self.instances.is_empty() { 143 | return true; 144 | } 145 | } 146 | Request::JobFinished(job) => { 147 | let callbacks = self.callbacks.get_callbacks(job); 148 | for cb in callbacks { 149 | cb.call_box((self,)); 150 | } 151 | } 152 | Request::PlayerUpdate(players) => { 153 | for player in players { 154 | let id = if let EntityType::Player(ref p) = player.entity_type { 155 | p.uuid 156 | } else { 157 | continue; 158 | }; 159 | self.players.insert(id, player); 160 | } 161 | } 162 | Request::NewClient(client) => { 163 | if self.shutdown { 164 | // Drop the client 165 | } else { 166 | let player_id = Id::forge(client.uuid); 167 | // TODO: Generate earlier? (during the connexion, in the network code) 168 | let client_id = Id::new(); 169 | let actor = NetworkActor::new(client_id, client); 170 | self.player_ready(actor, player_id); 171 | } 172 | } 173 | } 174 | // Default: don't exit 175 | false 176 | } 177 | 178 | fn start_shutdown(&mut self) { 179 | self.shutdown = true; 180 | for (_id, instances) in self.map_instances.drain() { 181 | for instance in instances.values() { 182 | let _ = instance.send(Command::Shutdown); 183 | } 184 | } 185 | 186 | // TODO: Shutdown the network side 187 | // At the moment, there is no clean way of doing this 188 | } 189 | 190 | // Spawn a new instance if needed 191 | fn assign_actor_to_map( 192 | &mut self, 193 | map: Id, 194 | actor: NetworkActor, 195 | entities: Vec, 196 | ) { 197 | match self.map_instances.get_mut(&map) { 198 | Some(instances) => { 199 | // TODO: Load balancing 200 | let register_new_instance = match instances.iter_mut().nth(0) { 201 | Some((_id, instance)) => { 202 | // An instance is already there, send the actor to it 203 | instance.send(Command::NewClient(actor,entities)).unwrap(); 204 | None 205 | } 206 | None => { 207 | // No instance for this map, spawn one 208 | let instance = Instance::spawn_instance( 209 | self.sender.clone(), 210 | self.scripts.clone(), 211 | self.trees.clone(), 212 | map, 213 | self.tick_duration, 214 | ); 215 | instance.send(Command::NewClient(actor,entities)).unwrap(); 216 | Some(instance) 217 | } 218 | }; 219 | 220 | // Because of the borrow checker 221 | if let Some(instance) = register_new_instance { 222 | let id = instance.get_id(); 223 | instances.insert(id, instance.clone()); 224 | self.instances.insert(id, instance); 225 | } 226 | } 227 | None => { 228 | error!("Trying to access nonexisting map {}", map); 229 | } 230 | } 231 | } 232 | 233 | fn player_ready(&mut self, mut actor: NetworkActor, id: Id) { 234 | match self.resource_manager.retrieve_player(id) { 235 | Ok(entity) => { 236 | let map = entity.get_map_position().unwrap(); 237 | actor.register_entity(entity.get_id()); 238 | let notification = Notification::this_is_you(entity.get_id().as_u64()); 239 | actor.send_message(notification); 240 | self.assign_actor_to_map(map, actor, vec![entity]); 241 | } 242 | Err(Error::Processing(job)) => { 243 | self.callbacks.add(job, move |game| { 244 | game.player_ready(actor, id); 245 | }); 246 | } 247 | Err(Error::NotFound) => { 248 | //TODO 249 | unimplemented!(); 250 | } 251 | } 252 | } 253 | 254 | fn entity_leaving(&mut self, entity: Entity) { 255 | let player: Option = entity.into(); 256 | if let Some(player) = player { 257 | self.players.remove(&player.id); 258 | 259 | let path = format!("./scripts/entities/{}", player.id); 260 | utils::serialize_to_file(path, &player); 261 | } 262 | } 263 | 264 | fn connect_character(&mut self, id: Id, token: AuthenticationToken) { 265 | self.authentication_manager.add_token(id, token); 266 | self.resource_manager.load_player(id); 267 | } 268 | 269 | pub fn verify_token(&mut self, id: Id, token: AuthenticationToken) -> bool { 270 | self.authentication_manager.verify_token(id, token) 271 | } 272 | } 273 | 274 | type Callback = Box; 275 | 276 | struct Callbacks { 277 | callbacks: HashMap>, 278 | } 279 | 280 | impl Callbacks { 281 | fn new() -> Callbacks { 282 | Callbacks { 283 | callbacks: HashMap::new(), 284 | } 285 | } 286 | 287 | fn add(&mut self, job: usize, cb: F) 288 | where F: FnOnce(&mut Game) + 'static + Send { 289 | self.add_callback_inner(job, Box::new(cb)) 290 | } 291 | 292 | fn add_callback_inner(&mut self, job: usize, cb: Callback) { 293 | self.callbacks.entry(job).or_insert(Vec::new()).push(cb); 294 | } 295 | 296 | fn get_callbacks(&mut self, job: usize) -> Vec { 297 | self.callbacks.remove(&job).unwrap_or(Vec::new()) 298 | } 299 | } 300 | 301 | impl HasId for Game { 302 | type Type = u64; 303 | } 304 | -------------------------------------------------------------------------------- /src/network/mod.rs: -------------------------------------------------------------------------------- 1 | //use std::u64; 2 | //use std::io::{Write,Error,Read,Cursor}; 3 | //use std::sync::Arc; 4 | //use std::collections::VecDeque; 5 | //use std::mem; 6 | //use std::ops::Deref; 7 | //use std::os::unix::io::AsRawFd; 8 | // 9 | //use bytes::buf::{RingBuf,Buf,MutBuf}; 10 | //use byteorder::{ReadBytesExt,ByteOrder,LittleEndian}; 11 | 12 | 13 | mod stream_adapter; 14 | mod buffered_tcp; 15 | 16 | use std; 17 | use std::net::SocketAddr; 18 | use std::io::Read; 19 | use std::io::Write; 20 | use std::io::Error as IoError; 21 | use std::io::ErrorKind; 22 | use std::thread; 23 | use std::sync::mpsc::Sender as StdSender; 24 | use std::sync::mpsc::Receiver as StdReceiver; 25 | use std::sync::mpsc::{self,TryRecvError}; 26 | 27 | use futures; 28 | use futures::{Future,IntoFuture,Poll,BoxFuture}; 29 | use futures::stream::Stream; 30 | use tokio_core; 31 | use tokio_core::net::TcpStream; 32 | use tokio_core::net::TcpListener; 33 | use tokio_core::io::{self,Io}; 34 | use tokio_core::reactor::{Core,Handle}; 35 | use tokio_core::channel::{Receiver,channel,Sender}; 36 | use bytes::RingBuf; 37 | use byteorder::{ReadBytesExt,LittleEndian}; 38 | use uuid::Uuid; 39 | 40 | use lycan_serialize::ErrorCode; 41 | use lycan_serialize::Vec2d; 42 | use lycan_serialize::Error as NetworkError; 43 | use lycan_serialize::AuthenticationToken; 44 | 45 | use id::Id; 46 | use messages::{NetworkCommand,Command,NetworkGameCommand,GameCommand}; 47 | use messages::NetworkNotification; 48 | use messages::Request; 49 | 50 | use self::buffered_tcp::BufferedReader; 51 | use self::buffered_tcp::IoRef; 52 | 53 | const DEFAULT_CAPACITY: usize = 1024; 54 | 55 | pub struct Client { 56 | pub uuid: Uuid, 57 | sender: Sender, 58 | receiver: StdReceiver, 59 | } 60 | 61 | #[derive(Debug)] 62 | enum InternalNotification { 63 | Disconnect, 64 | NetworkNotification(NetworkNotification), 65 | } 66 | 67 | impl From for InternalNotification { 68 | fn from(n: NetworkNotification) -> InternalNotification { 69 | InternalNotification::NetworkNotification(n) 70 | } 71 | } 72 | 73 | impl Client { 74 | pub fn send(&mut self, notif: NetworkNotification) -> Result<(),()> { 75 | // TODO: Error handling 76 | self.sender.send(notif.into()).map_err(|_| ()) 77 | } 78 | 79 | pub fn recv(&mut self) -> Result,()> { 80 | match self.receiver.try_recv() { 81 | Ok(c) => Ok(Some(c)), 82 | Err(TryRecvError::Empty) => Ok(None), 83 | Err(TryRecvError::Disconnected) => Err(()), 84 | } 85 | } 86 | } 87 | 88 | impl Drop for Client { 89 | fn drop(&mut self) { 90 | let _ = self.sender.send(InternalNotification::Disconnect); 91 | } 92 | } 93 | 94 | impl ::std::fmt::Debug for Client { 95 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(),::std::fmt::Error> { 96 | f.debug_struct("Client").field("uuid", &self.uuid).finish() 97 | } 98 | } 99 | 100 | pub fn start_server(addr: SocketAddr, tx: StdSender) { 101 | let builder = thread::Builder::new() 102 | .name("Network Tokio".into()); 103 | 104 | builder.spawn(move || { 105 | // Create the event loop that will drive this server 106 | let mut l = Core::new().unwrap(); 107 | let handle = l.handle(); 108 | 109 | // Create a TCP listener which will listen for incoming connections 110 | let socket = TcpListener::bind(&addr, &handle).unwrap(); 111 | 112 | // Once we've got the TCP listener, inform that we have it 113 | println!("Listening on: {}", addr); 114 | 115 | let done = socket.incoming().for_each(|(socket, _addr)| { 116 | handle_client(socket, &handle, tx.clone()); 117 | 118 | Ok(()) 119 | }); 120 | 121 | // Execute our server (modeled as a future) and wait for it to 122 | // complete. 123 | // 124 | // There are currently no clean way to stop the event loop, so this 125 | // function currently never returns 126 | l.run(done).unwrap(); 127 | }).unwrap(); 128 | } 129 | 130 | // Handles an incomming client on the network 131 | // 132 | // It will spawn a task on the even look associated with `handle`, that will drive 133 | // all the network part for this client 134 | fn handle_client(socket: TcpStream, handle: &Handle, tx: StdSender) { 135 | let handle_clone = handle.clone(); 136 | let fut = IoRef::new(socket).and_then(|socket| { 137 | // Split the socket into two parts 138 | // The write part is unbuffered, the read part is 139 | let write = socket.clone(); 140 | let read = BufferedReader::new(socket); 141 | 142 | // Converts the read part of the socket to an asynchronous stream of network commands 143 | let messages = stream_adapter::repeat(read, |read| { 144 | // XXX: This .boxed() is to avoid an ICE 145 | // See https://github.com/rust-lang/rust/issues/37096 146 | // https://github.com/rust-lang/rust/pull/37111 147 | Some(next_message(read).boxed()) 148 | }) 149 | .and_then(|command| { 150 | // Log every command we receive 151 | trace!("Received command {:?}", command); 152 | Ok(command) 153 | }); 154 | 155 | // First authenticate the client 156 | let fut = authenticate_client(messages, write, tx) 157 | .and_then(|(messages, write, uuid, tx)| { 158 | debug!("Authenticated the client {}", uuid); 159 | // The connect it to the Game 160 | client_connected(messages, write, uuid, handle_clone, tx) 161 | }).map_err(|e| error!("Error in handle_client {}", e)); 162 | 163 | fut 164 | }); 165 | 166 | // Finally, spawn the resulting future, so it can run in parallel of other futures 167 | handle.spawn(fut); 168 | } 169 | 170 | // Establishes communication between a client and the Game 171 | // 172 | // The returned future currently only resolves with an error, either when a communication 173 | // problem with the client occured, or when the client disconnects 174 | fn client_connected(messages: S, 175 | write: W, 176 | uuid: Uuid, 177 | handle: Handle, 178 | sender: StdSender) 179 | -> BoxFuture<(), String> 180 | where S: Stream + Send + 'static, 181 | W: Write + Send + 'static { 182 | let (tx1, rx1) = std::sync::mpsc::channel(); 183 | let (tx2, rx2) = tokio_core::channel::channel(&handle).unwrap(); 184 | 185 | // fut1 reads every command on the network, and sends them to the corresponding Client struct 186 | let fut1 = messages 187 | .map_err(|e| format!("Error in messages {}", e)) 188 | .for_each(move |message| { 189 | // We send every message in the channel 190 | // The Game or Instance can use Client::recv() to get those messages 191 | let res = tx1.send(message); 192 | if res.is_err() { 193 | Err("Error when sending command, client disconnected".to_string()) 194 | } else { 195 | Ok(()) 196 | } 197 | }); 198 | 199 | // fut2 gets notifications from the Instance, and writes them on the network 200 | let fut2 = rx2 201 | .map_err(|e| format!("Error reading from channel {}", e)) 202 | .fold(write, |write, buffer| { 203 | debug!("Sending notification {:?}", buffer); 204 | let res = match buffer { 205 | InternalNotification::Disconnect => { 206 | Err("The Game has disconnected the client".to_string()) 207 | } 208 | InternalNotification::NetworkNotification(n) => { 209 | Ok(serialize_future(n, write)) 210 | } 211 | }; 212 | res.into_future().flatten() 213 | }).map(|_| ()); 214 | 215 | // We run the two futures in parallel 216 | // XXX: This introduces potentially unwanted polling: 217 | // every time one of fut1 or fut2 is ready, both will be polled 218 | let fut = fut1.join(fut2).map(|_| ()) 219 | .map_err(|e| format!("Error: {}", e)); 220 | 221 | // Creates the corresponding Client structure, and send it to the Game 222 | let client = Client { 223 | uuid: uuid, 224 | sender: tx2, 225 | receiver: rx1, 226 | }; 227 | let request = Request::NewClient(client); 228 | let fut = if sender.send(request).is_err() { 229 | Err(format!("Could not send client {}", uuid)) 230 | } else { 231 | Ok(fut) 232 | }; 233 | fut.into_future().flatten().boxed() 234 | } 235 | 236 | // XXX: We shouldn't need to box the future 237 | // Deals with client authentication 238 | // 239 | // The returned future resolved to the streams given in input, plus the Uuid of the authenticated 240 | // player 241 | fn authenticate_client(messages: S, 242 | write: W, 243 | tx: StdSender) 244 | -> BoxFuture<(S,W,Uuid,StdSender), String> 245 | where S: Stream + Send + 'static, 246 | W: Write + Send + 'static { 247 | // This into_future() transforms the stream of message in a future that will resolve 248 | // to one message, and the rest of messages 249 | let fut = messages.into_future().map_err(move |(error, _messages)| { 250 | // TODO: This brutally drops the client ... 251 | error 252 | }).and_then(move |(command, messages)| { 253 | match command { 254 | Some(NetworkCommand::GameCommand(NetworkGameCommand::Authenticate(uuid, token))) => { 255 | debug!("Got authentication request {} {:?}", uuid, token); 256 | 257 | let verif = verify_token(uuid, token, tx).and_then(move |(success, tx)| { 258 | debug!("Authentication result: {}", success); 259 | let response = if success { 260 | NetworkNotification::Response { code: ErrorCode::Success } 261 | } else { 262 | NetworkNotification::Response { code: ErrorCode::Error } 263 | }; 264 | serialize_future(response, write).and_then(move |write| { 265 | if success { 266 | Ok((messages, write, uuid, tx)) 267 | } else { 268 | Err("Failed authentication".to_string()) 269 | } 270 | }) 271 | }); 272 | Ok(verif) 273 | } 274 | Some(_) => Err("Client tried to send a message before authenticating".to_string()), 275 | None => Err("Client sent no message".to_string()), 276 | } 277 | }); 278 | fut.flatten().boxed() 279 | } 280 | 281 | // TODO: unbox 282 | // Serializes a notification, and sends it on the network 283 | // 284 | // The returned future will resolve to the stream 285 | fn serialize_future(notif: NetworkNotification, writer: W) -> BoxFuture { 286 | // TODO: Improve that ... 287 | let mut buffer = Vec::with_capacity(128); 288 | notif.serialize(&mut buffer).unwrap(); 289 | io::write_all(writer, buffer) 290 | .map(|(writer, _buffer)| writer) 291 | .map_err(|e| format!("Error when writing notification {}", e)) 292 | .boxed() 293 | } 294 | 295 | // Reads the next message on the network 296 | // 297 | // The returned future will resolve to the socket given in input, plus the deserialized command 298 | fn next_message(socket: BufferedReader) 299 | -> impl Future, NetworkCommand),Error=String> { 300 | let future = socket.ensure(8) 301 | .and_then(|mut socket| { 302 | let next_msg_size = socket.read_u64::().unwrap() as usize; 303 | if next_msg_size >= socket.capacity() { 304 | return Err(IoError::new(ErrorKind::Other, 305 | format!("The socket buffer is not big enough: next_msg_size {} capacity {}", 306 | next_msg_size, 307 | socket.capacity()))); 308 | } 309 | Ok(socket.ensure(next_msg_size).map(move |s| (s, next_msg_size))) 310 | }).flatten().map_err(|e| e.to_string()) 311 | .and_then(|(mut socket, next_msg_size)| { 312 | let command = NetworkCommand::deserialize(&mut socket, next_msg_size as u64) 313 | .map(|c| (socket, c)) 314 | .map_err(|e| e.to_string()); 315 | command 316 | }); 317 | future 318 | } 319 | 320 | // Verifies an authentication token, returns true if the authentication was successful 321 | fn verify_token(uuid: Uuid, 322 | token: AuthenticationToken, 323 | tx: StdSender) 324 | -> impl Future),Error=String> { 325 | let (complete, oneshot) = futures::oneshot(); 326 | let request = Request::new(move |game| { 327 | complete.complete(game.verify_token(Id::forge(uuid), token)); 328 | }); 329 | 330 | let res = tx.send(request); 331 | if res.is_err() { 332 | Err("Game was shutdown or panicked during connection".to_string()) 333 | } else { 334 | let fut = oneshot 335 | .map(|success| (success, tx)) 336 | .map_err(|_| "Verify token cancelled".to_string()); 337 | Ok(fut) 338 | }.into_future().flatten() 339 | } 340 | 341 | #[derive(Debug)] 342 | pub enum ClientError { 343 | Disconnected, 344 | Socket(IoError), 345 | Capnp(NetworkError), 346 | } 347 | 348 | impl From for ClientError { 349 | fn from(err: NetworkError) -> ClientError { 350 | ClientError::Capnp(err) 351 | } 352 | } 353 | 354 | impl From for ClientError { 355 | fn from(err: IoError) -> ClientError { 356 | ClientError::Socket(err) 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /src/game/management.rs: -------------------------------------------------------------------------------- 1 | use std::thread; 2 | use std::sync::mpsc::{self,Sender}; 3 | use std::error::Error as StdError; 4 | 5 | use serde_json::ser::to_vec_pretty; 6 | use serde::Serialize; 7 | use uuid::Uuid; 8 | use iron::prelude::*; 9 | use iron::status::Status; 10 | use iron::headers::ContentType; 11 | use iron::{BeforeMiddleware}; 12 | use iron::error::HttpError; 13 | use bodyparser::Struct; 14 | use router::{Router}; 15 | use plugin::Extensible; 16 | use modifier::Modifier; 17 | use mount::Mount; 18 | 19 | use lycan_serialize::AuthenticationToken; 20 | 21 | use id::{Id,WeakId}; 22 | use messages::Request as LycanRequest; 23 | use messages::Command; 24 | use data::{ConnectCharacterParam,Map,GetInstances,GetMaps}; 25 | use entity::Entity; 26 | use instance::management::*; 27 | use game::Game; 28 | 29 | // XXX FIXME TODO: Remove 30 | // mio channels were sync, std lib channels are not 31 | // Iron need them to be sync 32 | // For now use a mutex 33 | use std::sync::Arc; 34 | use std::sync::Mutex; 35 | struct MutexSender(Arc>>); 36 | 37 | impl MutexSender { 38 | fn new(t: Sender) -> MutexSender { 39 | MutexSender(Arc::new(Mutex::new(t))) 40 | } 41 | fn send(&self, t: T) -> Result<(),()> { 42 | let guard = self.0.lock().unwrap(); 43 | guard.send(t).map_err(|_| ()) 44 | } 45 | } 46 | 47 | impl Clone for MutexSender { 48 | fn clone(&self) -> MutexSender { 49 | MutexSender(self.0.clone()) 50 | } 51 | } 52 | 53 | // TODO 54 | // - Set correct headers in all responses 55 | // - Check if correct heahers are set (e.g. Content-Type) 56 | 57 | pub fn start_management_api(sender: Sender) { 58 | thread::spawn(move || { 59 | let sender = MutexSender::new(sender); 60 | let router = create_router(sender); 61 | let mut mount = Mount::new(); 62 | mount.mount("/api/v1", router); 63 | let mut chain = Chain::new(mount); 64 | chain.link_before(AuthenticationMiddleware("abcdefgh".to_string())); 65 | let mut error_router = ::iron_error_router::ErrorRouter::new(); 66 | error_router.handle_status(Status::NotFound, |_: &mut Request| { 67 | Ok(Response::with((Status::NotFound, "404: Not Found"))) 68 | }); 69 | error_router.handle_status(Status::Unauthorized, |_: &mut Request| { 70 | Ok(Response::with((Status::Unauthorized, "401: Unauthorized"))) 71 | }); 72 | chain.link_after(error_router); 73 | 74 | let iron = Iron::new(chain); 75 | iron.http("127.0.0.1:9001").unwrap(); 76 | }); 77 | } 78 | 79 | macro_rules! itry_map { 80 | ($result:expr, |$err:ident| $bl:expr) => { 81 | match $result { 82 | ::std::result::Result::Ok(val) => val, 83 | ::std::result::Result::Err($err) => { 84 | return Ok(::iron::response::Response::with($bl)); 85 | } 86 | } 87 | }; 88 | } 89 | 90 | /// Macro to reduce the boilerplate of creating a channel, create a request, send it to Game and 91 | /// wait for the response 92 | macro_rules! define_request { 93 | ($sender:ident, |$game:ident| $bl:block) => {{ 94 | let (tx, rx) = mpsc::channel(); 95 | let request = LycanRequest::new(move |$game| { 96 | let result = $bl; 97 | let _ = tx.send(result); 98 | }); 99 | $sender.send(request).unwrap(); 100 | rx.recv().unwrap() 101 | }}; 102 | } 103 | 104 | /// Macro to reduce the boilerplate of creating a channel, create a request, send it to Game 105 | /// Route it to the correct Instance and wait for the response 106 | macro_rules! define_request_instance { 107 | ($sender:ident, $id:ident, |$instance:ident| $bl:block) => {{ 108 | let (tx, rx) = mpsc::channel(); 109 | let request = LycanRequest::new(move |g| { 110 | let instance = match g.instances.get(&$id) { 111 | Some(i) => i, 112 | None => { let _ = tx.send(Err(())); return; } 113 | }; 114 | let command = Command::new(move |$instance| { 115 | let result = $bl; 116 | let _ = tx.send(Ok(result)); 117 | }); 118 | let _ = instance.send(command); 119 | }); 120 | $sender.send(request).unwrap(); 121 | rx.recv().unwrap() 122 | }}; 123 | } 124 | 125 | // The Rust typechecker doesn't seem to get the types of the closures right 126 | // It infers that they implement FnOnce(...), and therefore do not implement Handler 127 | // This function forces the type of the closure 128 | fn correct_bounds(f: F) -> F 129 | where F: Send + Sync + 'static + Fn(&mut Request) -> IronResult 130 | {f} 131 | 132 | fn create_router(sender: MutexSender) -> Router { 133 | let mut server = Router::new(); 134 | // TODO: Add middleware at the beginning for authentication of requests 135 | 136 | let clone = sender.clone(); 137 | server.get( 138 | "/maps", 139 | correct_bounds(move |_request| { 140 | let maps_inner = define_request!(clone, |game| { 141 | game.resource_manager.get_all_maps() 142 | }); 143 | let maps: Vec<_> = maps_inner.into_iter() 144 | .map(|m| GetMaps { uuid: m.get_id(), name: m.name.clone() }) 145 | .collect(); 146 | Ok(Response::with((Status::Ok,JsonWriter(maps)))) 147 | }), 148 | "maps"); 149 | 150 | let clone = sender.clone(); 151 | server.get( 152 | "/maps/:id/instances", 153 | correct_bounds(move |request| { 154 | let params = request.extensions.get::().unwrap(); 155 | // id is part of the route, the unwrap should never fail 156 | let id = ¶ms["id"]; 157 | let parsed = itry_map!(id.parse::(), |e| (Status::BadRequest, format!("ERROR: invalid id {}: {}", id, e))); 158 | let instances = define_request!(clone, |game| { 159 | get_instances(game, WeakId::new(parsed)) 160 | }); 161 | Ok(Response::with((Status::Ok,JsonWriter(instances)))) 162 | }), 163 | "instances"); 164 | 165 | let clone = sender.clone(); 166 | server.get( 167 | "/instances/:id/entities", 168 | correct_bounds(move |request| { 169 | // id is part of the route, the unwrap should never fail 170 | let params = request.extensions.get::().unwrap(); 171 | let id = ¶ms["id"]; 172 | let parsed = itry_map!(id.parse::(), |e| (Status::BadRequest, format!("ERROR: invalid id {}: {}", id, e))); 173 | let entities = itry_map!(define_request_instance!(clone, parsed, |instance| { 174 | instance.get_entities() 175 | }), 176 | |_e| (Status::BadRequest, format!("ERROR: Non existent instance id {}", parsed))); 177 | Ok(Response::with((Status::Ok,JsonWriter(entities)))) 178 | }), 179 | "entities"); 180 | 181 | let clone = sender.clone(); 182 | server.get( 183 | "/players", 184 | correct_bounds(move |_request| { 185 | let entities: Vec<_> = define_request!(clone, |game| { 186 | game.players.values().cloned().collect() 187 | }); 188 | Ok(Response::with((Status::Ok, JsonWriter(entities)))) 189 | }), 190 | "players"); 191 | 192 | let clone = sender.clone(); 193 | server.post( 194 | "/instances/:id/spawn", 195 | correct_bounds(move |request| { 196 | use data::SpawnMonster; 197 | let (id_parsed, parsed_monster); 198 | 199 | { 200 | let params = request.extensions.get::().unwrap(); 201 | // id is part of the route, the unwrap should never fail 202 | let id = ¶ms["id"]; 203 | id_parsed = itry_map!(id.parse::(), |e| 204 | (Status::BadRequest, format!("ERROR: invalid id {}: {}", id, e))); 205 | } 206 | { 207 | let maybe_monster = itry_map!(request.get::>(), |e| 208 | (Status::BadRequest, format!("ERROR: JSON decoding error: {}", e))); 209 | parsed_monster = iexpect!(maybe_monster, (Status::BadRequest, "ERROR: No JSON body provided")); 210 | } 211 | let monster = itry_map!( 212 | define_request_instance!(clone, id_parsed, |instance| { 213 | instance.spawn_monster(parsed_monster) 214 | }), 215 | |_e| (Status::BadRequest, format!("ERROR: Non existent instance id {}", id_parsed))); 216 | Ok(Response::with((Status::Ok,JsonWriter(monster)))) 217 | }), 218 | "spawn"); 219 | 220 | let clone = sender.clone(); 221 | server.post( 222 | "/shutdown", 223 | correct_bounds(move |_request| { 224 | define_request!(clone, |g| { 225 | g.start_shutdown(); 226 | }); 227 | Ok(Response::with((Status::Ok, "OK"))) 228 | }), 229 | "shutdown"); 230 | 231 | let clone = sender.clone(); 232 | server.post( 233 | "/connect_character", 234 | correct_bounds(move |request| { 235 | let maybe_params = itry_map!(request.get::>(), |e| 236 | (Status::BadRequest, format!("ERROR: JSON decoding error: {}", e))); 237 | let decoded = iexpect!(maybe_params, (Status::BadRequest, "ERROR: No JSON body provided")); 238 | debug!("Received request to /connect_character: {:?}", decoded); 239 | define_request!(clone, |game| { 240 | let token = AuthenticationToken(decoded.token); 241 | game.connect_character(decoded.id, token); 242 | }); 243 | Ok(Response::with((Status::Ok, "OK"))) 244 | }), 245 | "connect_character"); 246 | 247 | // Isolated in a function for easier error handling 248 | fn entity_delete(sender: &MutexSender, request: &mut Request) -> Result<(),String> { 249 | let params = request.extensions.get::().unwrap(); 250 | // id is part of the route, the unwrap should never fail 251 | let instance_id = { 252 | let id = ¶ms["instance_id"]; 253 | try!(id.parse::().map_err(|e| format!("ERROR: invalid instance id {}: {}", id, e))) 254 | }; 255 | let entity_id: WeakId = { 256 | let id = ¶ms["entity_id"]; 257 | let id_u64 = try!(id.parse::().map_err(|e| format!("ERROR: invalid entity id {}: {}", id, e))); 258 | WeakId::new(id_u64) 259 | }; 260 | let result = try!(define_request_instance!(sender, instance_id, |instance| { 261 | instance.remove_entity(entity_id) 262 | }).map_err(|_e| format!("ERROR: Non existent instance id {}", instance_id))); 263 | result.map_err(|e| { 264 | match e { 265 | RemoveEntityError::NotFound => format!("ERROR: Entity {} not found in instance {}", entity_id, instance_id), 266 | RemoveEntityError::IsPlayer => format!("ERROR: Entity {} is a player", entity_id), 267 | } 268 | }) 269 | } 270 | let clone = sender.clone(); 271 | server.delete( 272 | "/instances/:instance_id/entities/:entity_id", 273 | correct_bounds(move |request| { 274 | match entity_delete(&clone, request) { 275 | Ok(()) => Ok(Response::with((Status::Ok,"OK"))), 276 | Err(s) => Ok(Response::with((Status::BadRequest, s))), 277 | } 278 | }), 279 | "delete_entity"); 280 | 281 | server 282 | } 283 | 284 | struct JsonWriter(T); 285 | 286 | impl Modifier for JsonWriter { 287 | fn modify(self, response: &mut Response) { 288 | match to_vec_pretty(&self.0) { 289 | Ok(v) => { 290 | response.headers.set(ContentType::json()); 291 | v.modify(response); 292 | } 293 | Err(e) => { 294 | let err = format!("ERROR: JSON serialization error {}", e); 295 | let modifier = (Status::InternalServerError, err); 296 | modifier.modify(response); 297 | } 298 | } 299 | } 300 | } 301 | 302 | struct AuthenticationMiddleware(String); 303 | 304 | impl BeforeMiddleware for AuthenticationMiddleware { 305 | fn before(&self, req: &mut Request) -> IronResult<()> { 306 | match req.headers.get::() { 307 | None => Err(IronError::new(AuthenticationError::NoToken, Status::Unauthorized)), 308 | Some(token) => { 309 | if &token.0 == &self.0 { 310 | Ok(()) 311 | } else { 312 | Err(IronError::new(AuthenticationError::InvalidToken(token.0.clone()), Status::Unauthorized)) 313 | } 314 | } 315 | } 316 | } 317 | } 318 | 319 | header! { (AccessToken, "Access-Token") => [String] } 320 | 321 | #[derive(Debug,Clone)] 322 | enum AuthenticationError { 323 | NoToken, 324 | InvalidToken(String), 325 | } 326 | 327 | impl StdError for AuthenticationError { 328 | fn description(&self) -> &str { 329 | use self::AuthenticationError::*; 330 | match *self { 331 | NoToken => "No authentication token", 332 | InvalidToken(_) => "Invalid authentication token", 333 | } 334 | } 335 | } 336 | 337 | impl ::std::fmt::Display for AuthenticationError { 338 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(),::std::fmt::Error> { 339 | use self::AuthenticationError::*; 340 | match *self { 341 | NoToken => write!(f, "No authentication token"), 342 | InvalidToken(ref t) => write!(f, "Invalid authentication token {}", t), 343 | } 344 | } 345 | } 346 | 347 | 348 | fn get_instances(game: &Game, map: WeakId) -> Option> { 349 | game.map_instances.get(&map).map(|instances| { 350 | instances.values().map(|refe| { 351 | GetInstances { 352 | id: refe.get_id(), 353 | map: refe.get_map(), 354 | created_at: refe.created_at().rfc822().to_string(), 355 | } 356 | }).collect() 357 | }) 358 | } 359 | -------------------------------------------------------------------------------- /src/entity/mod.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self,Formatter}; 2 | use std::cell::{RefCell,RefMut,Ref}; 3 | use std::sync::atomic::{AtomicUsize, Ordering}; 4 | 5 | use nalgebra::{Point2,Vector2}; 6 | use rand; 7 | 8 | use id::{Id,HasForgeableId,HasId}; 9 | use data::{ 10 | Map,Player,Stats,Position, 11 | EntityManagement, PositionInstance, 12 | PlayerStruct, MonsterStruct, 13 | EntityType as DataEntityType, 14 | Monster, 15 | }; 16 | use data::UNIQUE_MAP; 17 | use messages::{EntityState, Notification}; 18 | use instance::Instance; 19 | use actor::ActorId; 20 | 21 | use self::hitbox::RectangleHitbox; 22 | pub use self::double_iterator::{DoubleIterMut,OthersAccessor,OthersIter,OthersIterMut}; 23 | pub use self::store::EntityStore; 24 | 25 | mod status; 26 | mod update; 27 | mod hitbox; 28 | mod double_iterator; 29 | mod store; 30 | //mod serialize; 31 | 32 | pub use self::update::update; 33 | pub use lycan_serialize::Order; 34 | 35 | pub use lycan_serialize::Direction; 36 | 37 | static DEFAULT_SPEED: f32 = 10.0; 38 | static DEFAULT_AI_SPEED: f32 = 5.0; 39 | static DEFAULT_ATTACK_SPEED: f32 = 2.0; // 2 attacks per seconds 40 | 41 | #[derive(Debug)] 42 | pub struct Entity { 43 | id: Id, 44 | 45 | actor: Option, 46 | e_type: EntityType, 47 | position: Point2, 48 | // We probably won't save the speed ... 49 | speed: Vector2, 50 | orientation: Direction, 51 | skin: u64, 52 | pv: u64, 53 | hitbox: RectangleHitbox, 54 | attack_box: RectangleHitbox, 55 | attack_offset_x: Vector2, 56 | attack_offset_y: Vector2, 57 | base_stats: Stats, 58 | stats: CurrentStats, 59 | 60 | // TODO: Replace by a FSM 61 | walking: bool, 62 | attacking: AttackState, 63 | } 64 | 65 | lazy_static! { 66 | static ref NEXT_SKIN: AtomicUsize = AtomicUsize::new(0); 67 | } 68 | 69 | impl Entity { 70 | pub fn new(e_type: EntityType, 71 | position: Point2, 72 | orientation: Direction, 73 | skin: u64, 74 | base_stats: Stats, 75 | pv: u64, 76 | ) 77 | -> Entity { 78 | let mut e = Entity { 79 | id: Id::new(), 80 | 81 | actor: None, 82 | e_type: e_type, 83 | position: position, 84 | speed: Vector2::new(0.0,0.0), 85 | orientation: orientation, 86 | base_stats: base_stats, 87 | stats: Default::default(), 88 | skin: skin, 89 | pv: pv, 90 | hitbox: RectangleHitbox::new_default(), 91 | attack_box: RectangleHitbox::new(0.5, 0.5), 92 | attack_offset_x: Vector2::new(0.75, 0.0), 93 | attack_offset_y: Vector2::new(0.0, 1.0), 94 | 95 | walking: false, 96 | attacking: AttackState::Idle, 97 | }; 98 | e.recompute_current_stats(); 99 | e 100 | } 101 | 102 | pub fn is_player(&self) -> bool { 103 | if let EntityType::Player(_) = self.e_type { 104 | true 105 | } else { 106 | false 107 | } 108 | } 109 | 110 | pub fn is_monster(&self) -> bool { 111 | if let EntityType::Monster(_) = self.e_type { 112 | true 113 | } else { 114 | false 115 | } 116 | } 117 | 118 | // Takes effects into account 119 | pub fn recompute_current_stats(&mut self) { 120 | let speed = match self.e_type { 121 | EntityType::Player(_) => DEFAULT_SPEED, 122 | EntityType::Monster(_) => DEFAULT_AI_SPEED, 123 | }; 124 | self.stats.speed = speed; 125 | self.stats.strength = self.base_stats.strength; 126 | self.stats.dexterity = self.base_stats.dexterity; 127 | self.stats.constitution = self.base_stats.constitution; 128 | self.stats.intelligence = self.base_stats.intelligence; 129 | self.stats.precision = self.base_stats.precision; 130 | self.stats.wisdom = self.base_stats.wisdom; 131 | self.stats.attack_speed = DEFAULT_ATTACK_SPEED; 132 | } 133 | 134 | fn get_attribute(&self, var: &str) -> Option { 135 | match var { 136 | "pv" => Some(self.pv as f64), 137 | "strength" => Some(self.stats.strength as f64), 138 | "dexterity" => Some(self.stats.dexterity as f64), 139 | "constitution" => Some(self.stats.constitution as f64), 140 | "intelligence" => Some(self.stats.intelligence as f64), 141 | "precision" => Some(self.stats.precision as f64), 142 | "wisdom" => Some(self.stats.wisdom as f64), 143 | "speed" => Some(self.stats.speed as f64), 144 | _ => None, 145 | } 146 | } 147 | 148 | pub fn walk(&mut self, orientation: Option) { 149 | match orientation { 150 | Some(o) => { 151 | self.walking = true; 152 | self.orientation = o; 153 | } 154 | None => { 155 | self.walking = false; 156 | } 157 | } 158 | } 159 | 160 | /// Apply an order to an entity, and optionally returns a notification 161 | pub fn apply(&mut self, order: Order) -> Result,Error> { 162 | debug!("Received order {:?}", order); 163 | match order { 164 | Order::Walk(orientation) => { 165 | match orientation { 166 | None => self.walking = false, 167 | Some(o) => { 168 | self.orientation = o; 169 | self.walking = true; 170 | } 171 | } 172 | Ok(Some(Notification::walk(self.id.as_u64(), orientation))) 173 | } 174 | Order::Say(message) => { 175 | Ok(Some(Notification::say(self.id.as_u64(), message))) 176 | } 177 | Order::Attack => { 178 | match self.attacking { 179 | AttackState::Idle => { 180 | self.attacking = AttackState::Attacking; 181 | // TODO: Attacking notification 182 | Ok(None) 183 | } 184 | // If the entity was already in the middle of an attack, ignore 185 | AttackState::Attacking => { Err(Error::AlreadyAttacking) } 186 | AttackState::Reloading(_) => { Err(Error::AlreadyAttacking) } 187 | } 188 | } 189 | } 190 | } 191 | 192 | pub fn get_map_position(&self) -> Option> { 193 | match self.e_type { 194 | EntityType::Player(ref player) => Some(player.map), 195 | _ => None, 196 | } 197 | } 198 | 199 | pub fn dump(&self, f: &mut Formatter, indent: &str) -> Result<(),fmt::Error> { 200 | try!(writeln!(f, "{}Entity {}", indent, self.id)); 201 | match self.e_type { 202 | EntityType::Player(ref player) => { 203 | try!(writeln!(f, "{}Player {} {} attached to map {}", 204 | indent, 205 | player.id, 206 | &player.name, 207 | player.map)); 208 | } 209 | EntityType::Monster(ref monster) => { 210 | try!(writeln!(f, "{}Monster class {}", indent, monster.class)); 211 | } 212 | } 213 | // TODO: Presence ... 214 | try!(writeln!(f, "{}{:?} {:?} {:?}", indent, self.position, self.speed,self.orientation)); 215 | writeln!(f, "{}PV: {}", indent, self.pv) 216 | } 217 | 218 | /////////////////////////////////////////////// 219 | // Accessors 220 | // 221 | 222 | pub fn get_id(&self) -> Id { 223 | self.id 224 | } 225 | 226 | pub fn get_actor(&self) -> Option { 227 | self.actor 228 | } 229 | 230 | pub fn set_actor(&mut self, actor: Option) { 231 | self.actor = actor; 232 | } 233 | 234 | pub fn get_position(&self) -> Point2 { 235 | self.position 236 | } 237 | 238 | pub fn get_skin(&self) -> u64 { 239 | self.skin 240 | } 241 | 242 | pub fn get_pv(&self) -> u64 { 243 | self.pv 244 | } 245 | 246 | pub fn get_orientation(&self) -> Direction { 247 | self.orientation 248 | } 249 | 250 | pub fn get_type(&self) -> &EntityType { 251 | &self.e_type 252 | } 253 | 254 | } 255 | 256 | // Reason why an action has been rejected 257 | // TODO: Put in lycan-serialize 258 | pub enum Error { 259 | AlreadyAttacking, 260 | } 261 | 262 | #[derive(Debug,Copy,Clone)] 263 | enum AttackState { 264 | Idle, 265 | Attacking, 266 | // A number between 1.0 and 0.0 267 | // 1.0 means the entity just started reloading 268 | // When it reaches 0.0 it switches back to the Idle state 269 | Reloading(f32), 270 | } 271 | 272 | #[derive(Debug)] 273 | pub enum EntityType { 274 | // An entity can be a player 275 | Player(PlayerData), 276 | // A monster 277 | Monster(MonsterData), 278 | } 279 | 280 | #[derive(Debug,Clone,Default)] 281 | struct CurrentStats { 282 | level: u64, 283 | strength: u64, 284 | dexterity: u64, 285 | constitution: u64, 286 | intelligence: u64, 287 | precision: u64, 288 | wisdom: u64, 289 | speed: f32, 290 | attack_speed: f32, 291 | } 292 | 293 | #[derive(Debug,Clone)] 294 | pub struct PlayerData { 295 | name: String, 296 | id: Id, 297 | map: Id, 298 | experience: u64, 299 | gold: u64, 300 | guild: String, 301 | } 302 | 303 | #[derive(Debug,Clone)] 304 | pub struct MonsterData { 305 | class: Id, 306 | } 307 | 308 | impl PlayerData { 309 | pub fn get_id(&self) -> Id { 310 | self.id 311 | } 312 | } 313 | 314 | impl HasId for Entity { 315 | type Type = u64; 316 | } 317 | 318 | /////////////////////////////////////////////// 319 | // Conversions 320 | // 321 | 322 | impl From for Entity { 323 | fn from(player: Player) -> Entity { 324 | let mut entity = Entity::new( 325 | EntityType::Player(PlayerData { 326 | name: player.name, 327 | id: player.id, 328 | map: player.position.map, 329 | experience: player.experience, 330 | gold: player.gold, 331 | guild: player.guild, 332 | }), 333 | Point2::new(player.position.x, player.position.y), 334 | Direction::East, // TODO 335 | player.skin, 336 | player.stats, 337 | player.current_pv, 338 | ); 339 | entity.recompute_current_stats(); 340 | entity 341 | } 342 | } 343 | 344 | impl Into> for Entity { 345 | fn into(self) -> Option { 346 | let player_data = match self.e_type { 347 | EntityType::Player(player) => player, 348 | _ => { 349 | error!("Attempted to convert a non-player entity to a player"); 350 | return None; 351 | } 352 | }; 353 | let position = Position { 354 | x: self.position.x, 355 | y: self.position.y, 356 | map: player_data.map, 357 | }; 358 | let player = Player { 359 | id: player_data.id, 360 | name: player_data.name, 361 | skin: self.skin, 362 | current_pv: self.pv, 363 | position: position, 364 | experience: player_data.experience, 365 | gold: player_data.gold, 366 | guild: player_data.guild, 367 | stats: self.base_stats, 368 | }; 369 | 370 | Some(player) 371 | } 372 | } 373 | 374 | impl Entity { 375 | // XXX: Should we really pass instance_id and map? 376 | pub fn into_management_representation(&self, instance_id: Id, map: Id) 377 | -> EntityManagement { 378 | let position = PositionInstance { 379 | x: self.position.x, 380 | y: self.position.y, 381 | map: map, 382 | instance: instance_id, 383 | }; 384 | let entity_type = match self.e_type { 385 | EntityType::Player(ref player) => { 386 | let player_struct = PlayerStruct { 387 | uuid: player.id, 388 | name: player.name.clone(), 389 | gold: player.gold, 390 | guild: player.guild.clone(), 391 | experience: player.experience, 392 | }; 393 | DataEntityType::Player(player_struct) 394 | } 395 | EntityType::Monster(ref monster) => { 396 | let monster_struct = MonsterStruct { 397 | monster_class: monster.class, 398 | name: "TODO".to_string(), 399 | behaviour_tree: "TODO".to_string(), 400 | }; 401 | DataEntityType::Monster(monster_struct) 402 | } 403 | }; 404 | EntityManagement { 405 | id: self.id, 406 | entity_type: entity_type, 407 | skin: self.skin, 408 | current_pv: self.pv, 409 | position: position, 410 | stats: self.base_stats, 411 | } 412 | } 413 | 414 | pub fn to_entity_state(&self) -> EntityState { 415 | EntityState::new(self.id, self.position, self.orientation) 416 | } 417 | 418 | } 419 | 420 | /////////////////////////////////////////////// 421 | // Mock entities 422 | // 423 | // TODO: Remove when it is not needed any more 424 | // 425 | 426 | impl Entity { 427 | pub fn fake_player(id: Id) -> Entity { 428 | let stats = Stats { 429 | level: 1, 430 | strength: 2, 431 | dexterity: 3, 432 | constitution: 4, 433 | intelligence: 5, 434 | precision: 6, 435 | wisdom: 7, 436 | }; 437 | let position = Position { 438 | x: 0.0, 439 | y: 0.0, 440 | map: UNIQUE_MAP.get_id() 441 | }; 442 | let name = format!("Player {}", id); 443 | let skin = NEXT_SKIN.fetch_add(1, Ordering::Relaxed) as u64; 444 | let player = Player { 445 | id: id, 446 | name: name, 447 | skin: skin, 448 | current_pv: 100, 449 | position: position, 450 | experience: 0, 451 | gold: 0, 452 | guild: String::new(), 453 | stats: stats, 454 | }; 455 | Entity::from(player) 456 | } 457 | 458 | pub fn fake_ai(class: Id, x: f32, y: f32) -> Entity { 459 | let stats = Stats { 460 | level: 1, 461 | strength: 2, 462 | dexterity: 3, 463 | constitution: 4, 464 | intelligence: 5, 465 | precision: 6, 466 | wisdom: 7, 467 | }; 468 | let skin = NEXT_SKIN.fetch_add(1, Ordering::Relaxed) as u64; 469 | let monster = MonsterData { 470 | class: class, 471 | }; 472 | Entity::new( 473 | EntityType::Monster(monster), 474 | Point2::new(x, y), 475 | Direction::South, 476 | skin, 477 | stats, 478 | 100) 479 | } 480 | } 481 | -------------------------------------------------------------------------------- /src/instance/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::{self,HashMap,Entry}; 2 | use std::thread; 3 | use std::fmt::{Formatter,Display,Error}; 4 | use std::io; 5 | use std::mem; 6 | use std::time::Duration as StdDuration; 7 | use std::sync::mpsc::{self,Receiver,Sender}; 8 | 9 | use time::{self,Duration,SteadyTime,Tm}; 10 | use schedule_recv; 11 | 12 | use id::{Id,HasId}; 13 | use entity::{self,Entity,EntityStore}; 14 | use actor::{NetworkActor,ActorId,AiActor}; 15 | use messages::{self,Command,Notification,Request}; 16 | use scripts::{BehaviourTrees,AaribaScripts}; 17 | use data::{Map,Monster}; 18 | 19 | pub mod management; 20 | 21 | lazy_static! { 22 | static ref GAME_PLAYER_REFRESH_PERIOD: Duration = Duration::seconds(2); 23 | } 24 | 25 | 26 | #[derive(Debug,Default)] 27 | struct Actors { 28 | // Design questions: separate all actors? Put them in enum? Use trait objects? 29 | internal_actors: HashMap, 30 | external_actors: HashMap, 31 | } 32 | 33 | impl Actors { 34 | fn register_client(&mut self, actor: NetworkActor) { 35 | let id = actor.get_id(); 36 | match self.external_actors.entry(id) { 37 | Entry::Occupied(mut entry) => { 38 | error!("Erasing old actor {:?}", entry.get()); 39 | entry.insert(actor); 40 | } 41 | Entry::Vacant(entry) => { 42 | entry.insert(actor); 43 | } 44 | } 45 | } 46 | 47 | fn register_internal(&mut self, actor: AiActor) { 48 | let id = actor.get_id(); 49 | match self.internal_actors.entry(id) { 50 | Entry::Occupied(mut entry) => { 51 | error!("Erasing old actor {:?}", entry.get()); 52 | entry.insert(actor); 53 | } 54 | Entry::Vacant(entry) => { 55 | entry.insert(actor); 56 | } 57 | } 58 | 59 | } 60 | 61 | fn unregister_client(&mut self, id: ActorId) -> Option { 62 | self.external_actors.remove(&id) 63 | } 64 | 65 | fn unregister_ai(&mut self, id: ActorId) -> Option { 66 | self.internal_actors.remove(&id) 67 | } 68 | 69 | fn broadcast_notifications(&mut self, 70 | notifications: &[Notification]) { 71 | for client in self.external_actors.values_mut() { 72 | for notif in notifications { 73 | client.send_message(notif.clone()); 74 | } 75 | } 76 | } 77 | 78 | fn get_commands(&mut self) -> Vec { 79 | // XXX Is this a good idea to do it this way? 80 | let mut commands_buffer = Vec::new(); 81 | for (_, actor) in self.external_actors.iter_mut() { 82 | actor.get_commands(&mut commands_buffer); 83 | } 84 | 85 | for (_, actor) in self.internal_actors.iter_mut() { 86 | actor.get_commands(&mut commands_buffer); 87 | } 88 | commands_buffer 89 | } 90 | 91 | fn execute_orders(&mut self, 92 | entities: &mut EntityStore, 93 | notifications: &mut Vec, 94 | previous: &[Notification]) { 95 | for (_, actor) in self.external_actors.iter_mut() { 96 | actor.execute_orders(entities, notifications, previous); 97 | } 98 | for (_, actor) in self.internal_actors.iter_mut() { 99 | actor.execute_orders(entities, notifications, previous); 100 | } 101 | } 102 | 103 | fn assign_entity_to_actor(&mut self, actor: ActorId, entity: Id) -> bool { 104 | if let Some(actor) = self.external_actors.get_mut(&actor) { 105 | actor.register_entity(entity); 106 | return true; 107 | } 108 | if let Some(actor) = self.internal_actors.get_mut(&actor) { 109 | actor.register_entity(entity); 110 | return true; 111 | } 112 | false 113 | } 114 | 115 | // TODO: rewrite correctly 116 | fn dump(&self, f: &mut Formatter, entities: &EntityStore) -> Result<(),Error> { 117 | let mut indent; 118 | for actor in self.external_actors.values() { 119 | indent = " "; 120 | try!(actor.dump(f, indent)); 121 | for entity_id in actor.entities_iter() { 122 | indent = " "; 123 | try!(match entities.get(*entity_id) { 124 | None => write!(f, "{}ERROR: Inconsistency found!", indent), 125 | Some(entity) => { 126 | entity.dump(f, indent) 127 | } 128 | }); 129 | } 130 | } 131 | Ok(()) 132 | } 133 | 134 | fn drain_external(&mut self) -> hash_map::Drain { 135 | self.external_actors.drain() 136 | } 137 | } 138 | 139 | 140 | pub struct Instance { 141 | id: Id, 142 | 143 | map_id: Id, 144 | entities: EntityStore, 145 | actors: Actors, 146 | request: Sender, 147 | last_tick: SteadyTime, 148 | lag: Duration, 149 | // We will need the previous notifications for AI 150 | prev_notifications: Vec, 151 | next_notifications: Vec, 152 | scripts: AaribaScripts, 153 | trees: BehaviourTrees, 154 | shutting_down: bool, 155 | created_at: Tm, 156 | 157 | tick_duration: f32, 158 | } 159 | 160 | impl Instance { 161 | pub fn spawn_instance(request: Sender, 162 | scripts: AaribaScripts, 163 | trees: BehaviourTrees, 164 | map_id: Id, 165 | tick_duration: f32, 166 | ) -> InstanceRef { 167 | let mut instance = Instance::new(request, scripts, trees, map_id, tick_duration); 168 | let id = instance.get_id(); 169 | let created_at = instance.created_at; 170 | let (sender, rx) = mpsc::channel(); 171 | thread::spawn(move || { 172 | let tick = schedule_recv::periodic(StdDuration::from_millis((tick_duration * 1000.0) as u64)); 173 | let players_update = schedule_recv::periodic(GAME_PLAYER_REFRESH_PERIOD.to_std().unwrap()); 174 | instance.last_tick = SteadyTime::now(); 175 | 176 | debug!("Started instance {}", instance.id); 177 | loop { 178 | select! { 179 | _ = tick.recv() => { 180 | trace!("Received tick notification"); 181 | let refresh_period = Duration::microseconds((instance.tick_duration * 1_000_000.0) as i64); 182 | let current = SteadyTime::now(); 183 | let elapsed = current - instance.last_tick; 184 | instance.lag = instance.lag + elapsed; 185 | let mut loop_count = 0; 186 | while instance.lag >= refresh_period { 187 | instance.calculate_tick(); 188 | instance.lag = instance.lag - refresh_period; 189 | loop_count += 1; 190 | } 191 | if loop_count != 1 { 192 | debug!("Needed to adjust the tick rate! loop count {}", loop_count); 193 | } 194 | // TODO: Should we check if we should do a few more iterations? 195 | instance.last_tick = current; 196 | }, 197 | _ = players_update.recv() => { 198 | let vec = instance.entities 199 | .iter() 200 | .filter(|e| e.is_player()) 201 | .map(|e| e.into_management_representation(instance.id, instance.map_id)) 202 | .collect(); 203 | instance.request.send(Request::PlayerUpdate(vec)).unwrap(); 204 | }, 205 | command = rx.recv() => { 206 | let command = command.unwrap(); 207 | println!("Received command {:?}", command); 208 | if instance.apply(command) { 209 | break; 210 | } 211 | } 212 | } 213 | } 214 | debug!("Stopping instance {}", instance.id); 215 | }); 216 | InstanceRef::new(id, sender, created_at, map_id) 217 | } 218 | 219 | fn new(request: Sender, 220 | scripts: AaribaScripts, 221 | trees: BehaviourTrees, 222 | map_id: Id, 223 | tick_duration: f32, 224 | ) -> Instance { 225 | use uuid::Uuid; 226 | 227 | let mut instance = Instance { 228 | id: Id::new(), 229 | map_id: map_id, 230 | entities: EntityStore::new(), 231 | actors: Default::default(), 232 | request: request, 233 | last_tick: SteadyTime::now(), 234 | lag: Duration::zero(), 235 | tick_duration: tick_duration, 236 | prev_notifications: Default::default(), 237 | next_notifications: Default::default(), 238 | scripts: scripts, 239 | trees: trees, 240 | shutting_down: false, 241 | created_at: time::now_utc(), 242 | }; 243 | 244 | // XXX Fake an AI on the map 245 | let class_str = "67e6001e-d735-461d-b32e-2e545e12b3d2"; 246 | let uuid = Uuid::parse_str(class_str).unwrap(); 247 | instance.add_fake_ai(Id::forge(uuid), 0.0, 0.0); 248 | instance 249 | } 250 | 251 | // Apply a command to update the game state. 252 | // 253 | // returns: true if the instance has been shutdown while executing 254 | // the command, false otherwise 255 | fn apply(&mut self, command: Command) -> bool { 256 | match command { 257 | Command::NewClient(actor,entities) => { 258 | self.register_client(actor, entities); 259 | } 260 | Command::Shutdown => { 261 | self.shutdown(); 262 | } 263 | Command::UnregisterActor(id) => { 264 | self.unregister_client(id); 265 | } 266 | Command::Arbitrary(command) => { 267 | command.execute(self); 268 | } 269 | Command::AssignEntity((actor,entity)) => { 270 | self.assign_entity_to_actor(actor, entity); 271 | } 272 | } 273 | 274 | self.shutting_down 275 | } 276 | 277 | fn register_client( 278 | &mut self, 279 | mut actor: NetworkActor, 280 | entities: Vec, 281 | ) { 282 | let id = actor.get_id(); 283 | trace!("Registering actor {} in instance {}", id, self.id); 284 | for entity in self.entities.iter() { 285 | let position = entity.get_position(); 286 | let skin = entity.get_skin(); 287 | let entity_id = entity.get_id().as_u64(); 288 | let pv = entity.get_pv(); 289 | let notification = Notification::new_entity(entity_id, position, skin, pv); 290 | actor.send_message(notification); 291 | } 292 | for entity in entities { 293 | let entity_id = entity.get_id().as_u64(); 294 | let position = entity.get_position(); 295 | let skin = entity.get_skin(); 296 | let pv = entity.get_pv(); 297 | let notification = Notification::new_entity(entity_id, position, skin, pv); 298 | self.next_notifications.push(notification); 299 | self.entities.push(entity); 300 | } 301 | self.actors.register_client(actor); 302 | } 303 | 304 | fn unregister_client(&mut self, id: ActorId) { 305 | match self.actors.unregister_client(id) { 306 | Some(actor) => { 307 | // TODO: Check first if the actor needs to be sent back to the Game 308 | let mut entities = Vec::new(); 309 | for entity_id in actor.entities_iter() { 310 | match self.entities.remove(*entity_id) { 311 | Some(entity) => { 312 | entities.push(entity); 313 | let notification = Notification::entity_has_quit(entity_id.as_u64()); 314 | self.next_notifications.push(notification); 315 | }, 316 | None => error!("Instance {}: Inconsistency between actor {} and its entities: \ 317 | entity {} is not present in the map array", 318 | self.id, actor.get_id(), entity_id), 319 | } 320 | } 321 | self.request.send(Request::UnregisteredActor{actor: actor,entities: entities}) 322 | .map_err(|e| format!("Failed to send unregistered actor: {:?}", e)).unwrap(); 323 | } 324 | None => error!("Instance {}: trying to unregister absent actor {}", 325 | self.id, id), 326 | } 327 | } 328 | 329 | fn shutdown(&mut self) { 330 | let mut state = ShuttingDownState::new(self.id); 331 | for (actor_id, actor) in self.actors.drain_external() { 332 | let mut entities = Vec::new(); 333 | for entity_id in actor.entities_iter() { 334 | match self.entities.remove(*entity_id) { 335 | Some(e) => entities.push(e), 336 | None => { 337 | error!("Instance {}: Inconsistency between actor {} and its entities: \ 338 | entity {} is not present in the map array", 339 | self.id, actor_id, entity_id); 340 | } 341 | } 342 | } 343 | 344 | state.push(actor, entities); 345 | } 346 | 347 | if let Err(e) = self.request.send(Request::InstanceShuttingDown(state)) { 348 | // TODO: Something to do with the state we got back? 349 | error!("The Game instance has hung up!\n{:#?}", e); 350 | } 351 | self.shutting_down = true; 352 | } 353 | 354 | fn assign_entity_to_actor(&mut self, id: ActorId, mut entity: Entity) { 355 | let entity_id = entity.get_id(); 356 | let position = entity.get_position(); 357 | let skin = entity.get_skin(); 358 | let pv = entity.get_pv(); 359 | if self.actors.assign_entity_to_actor(id, entity_id) { 360 | entity.set_actor(Some(id)); 361 | self.entities.push(entity); 362 | let notification = Notification::new_entity(entity_id.as_u64(), position, skin, pv); 363 | self.next_notifications.push(notification); 364 | } else { 365 | // Could be normal operation if the actor has just been unregistered (race 366 | // condition) 367 | warn!("Missing actor {} when sending entity {}", id, entity.get_id()); 368 | // TODO: Should send back to the Game 369 | } 370 | debug!("{}", self); 371 | } 372 | 373 | pub fn get_id(&self) -> Id { 374 | self.id 375 | } 376 | 377 | fn calculate_tick(&mut self) { 378 | trace!("Instance {}: Calculating tick\n{}", self.id, self); 379 | self.actors.execute_orders(&mut self.entities, 380 | &mut self.next_notifications, 381 | &self.prev_notifications); 382 | 383 | let events = entity::update(&mut self.entities, &mut self.next_notifications, &self.scripts, self.tick_duration); 384 | for event in events { 385 | self.process_event(event); 386 | } 387 | 388 | let commands_buffer = self.actors.get_commands(); 389 | for command in commands_buffer { 390 | self.apply(command); 391 | } 392 | self.actors.broadcast_notifications(&self.next_notifications); 393 | debug!("Notifications: {:?}", self.next_notifications); 394 | self.prev_notifications.clear(); 395 | mem::swap(&mut self.prev_notifications, &mut self.next_notifications); 396 | } 397 | 398 | fn process_event(&mut self, event: TickEvent) { 399 | match event { 400 | TickEvent::EntityDeath(dead_entity) => { 401 | self.next_notifications.push(Notification::Death { 402 | entity: dead_entity.get_id().as_u64(), 403 | }); 404 | 405 | // Trick to make sarosa make the entity disappear 406 | // Should not be needed when the client is modified to handle death properly 407 | // (animation etc) 408 | self.next_notifications.push(Notification::EntityHasQuit { 409 | entity: dead_entity.get_id().as_u64(), 410 | }); 411 | // TODO: Send entity back to actor 412 | } 413 | } 414 | } 415 | 416 | fn add_fake_ai(&mut self, class: Id, x: f32, y: f32) -> Id { 417 | let ai = AiActor::fake(self.trees.generate_tree("zombie").unwrap()); 418 | let id = ai.get_id(); 419 | self.actors.register_internal(ai); 420 | 421 | let mut entity = Entity::fake_ai(class, x, y); 422 | entity.set_actor(Some(id)); 423 | let entity_id = entity.get_id(); 424 | self.assign_entity_to_actor(id, entity); 425 | entity_id 426 | } 427 | } 428 | 429 | #[derive(Clone)] 430 | pub struct InstanceRef { 431 | id: Id, 432 | sender: Sender, 433 | created_at: Tm, 434 | map: Id, 435 | } 436 | 437 | impl InstanceRef { 438 | pub fn new(id: Id, 439 | sender: Sender, 440 | created_at: Tm, 441 | map: Id) -> InstanceRef { 442 | InstanceRef { 443 | id: id, 444 | sender: sender, 445 | created_at: created_at, 446 | map: map, 447 | } 448 | } 449 | 450 | pub fn send(&self, command: Command) -> Result<(),()> { 451 | // TODO: handle errors? 452 | self.sender.send(command).map_err(|_| ()) 453 | } 454 | 455 | pub fn get_id(&self) -> Id { 456 | self.id 457 | } 458 | 459 | pub fn get_map(&self) -> Id { 460 | self.map 461 | } 462 | 463 | pub fn created_at(&self) -> &Tm { 464 | &self.created_at 465 | } 466 | 467 | pub fn get_sender(&self) -> &Sender { 468 | &self.sender 469 | } 470 | } 471 | 472 | impl Display for Instance { 473 | fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { 474 | let indent = ""; 475 | try!(write!(f, "{}Instance {}:\n", indent, self.id)); 476 | self.actors.dump(f, &self.entities) 477 | } 478 | } 479 | 480 | /// A list of things that can happen during tick calculation, which require work from the instance 481 | pub enum TickEvent { 482 | EntityDeath(Entity), 483 | } 484 | /// Regular or delayed operations that will execute on an Instance 485 | pub enum InstanceTick { 486 | /// The main operation will be calculating the next tick 487 | /// 488 | /// This will among other things execute all actions made by players 489 | /// since the last tick, resolve AI trees and send the update to players 490 | CalculateTick, 491 | /// This will update the Game's knowledge of all Player in this map 492 | UpdatePlayers, 493 | } 494 | 495 | #[derive(Debug)] 496 | pub struct ShuttingDownState { 497 | pub id: Id, 498 | pub was_saved: bool, 499 | pub external_actors: Vec<(NetworkActor,Vec)>, 500 | //pub internal_actors: Vec<(Actor,Vec)>, 501 | } 502 | 503 | impl ShuttingDownState { 504 | pub fn new(id: Id) -> ShuttingDownState { 505 | ShuttingDownState { 506 | id: id, 507 | was_saved: false, 508 | external_actors: Vec::new(), 509 | } 510 | } 511 | 512 | pub fn push(&mut self, actor: NetworkActor, entities: Vec) { 513 | self.external_actors.push((actor, entities)); 514 | } 515 | } 516 | 517 | impl Drop for ShuttingDownState { 518 | fn drop(&mut self) { 519 | if !self.was_saved { 520 | // The state has not been processed and saved 521 | // This is our last chance to save all the modifications somewhere 522 | error!("Failed to save the state of instance {}\n{:#?}", self.id, self.external_actors); 523 | } 524 | } 525 | } 526 | 527 | impl Drop for Instance { 528 | fn drop(&mut self) { 529 | if !self.shutting_down { 530 | error!("Instance {} has not been shutdown properly", self.id); 531 | self.shutdown(); 532 | } 533 | } 534 | } 535 | 536 | impl HasId for Instance { 537 | type Type = u64; 538 | } 539 | --------------------------------------------------------------------------------