mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'master' into 'master'
client-server impl See merge request veloren/fresh!10 Former-commit-id: 7389a98fec540371097b183503e47dbbdbf836df
This commit is contained in:
commit
d7b87bd1a8
@ -2,6 +2,7 @@
|
||||
members = [
|
||||
"common",
|
||||
"client",
|
||||
"chat-cli",
|
||||
"server",
|
||||
"server-cli",
|
||||
"voxygen",
|
||||
|
1
assets/voxygen/.gitignore
vendored
Normal file
1
assets/voxygen/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
tmp/
|
3
chat-cli/.gitignore
vendored
Normal file
3
chat-cli/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
12
chat-cli/Cargo.toml
Normal file
12
chat-cli/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "veloren-chat-cli"
|
||||
version = "0.1.0"
|
||||
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
client = { package = "veloren-client", path = "../client" }
|
||||
common = { package = "veloren-common", path = "../common" }
|
||||
|
||||
log = "0.4"
|
||||
pretty_env_logger = "0.3"
|
37
chat-cli/src/main.rs
Normal file
37
chat-cli/src/main.rs
Normal file
@ -0,0 +1,37 @@
|
||||
use std::time::Duration;
|
||||
use log::info;
|
||||
use client::{Input, Client, Event};
|
||||
use common::clock::Clock;
|
||||
|
||||
const FPS: u64 = 60;
|
||||
|
||||
fn main() {
|
||||
// Init logging
|
||||
pretty_env_logger::init();
|
||||
|
||||
info!("Starting chat-cli...");
|
||||
|
||||
// Set up an fps clock
|
||||
let mut clock = Clock::new();
|
||||
|
||||
// Create client
|
||||
let mut client = Client::new(([127, 0, 0, 1], 59003))
|
||||
.expect("Failed to create client instance");
|
||||
|
||||
loop {
|
||||
let events = client.tick(Input::default(), clock.get_last_delta())
|
||||
.expect("Failed to tick client");
|
||||
|
||||
for event in events {
|
||||
match event {
|
||||
Event::Chat(msg) => println!("[chat] {}", msg),
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up the server after a tick
|
||||
client.cleanup();
|
||||
|
||||
// Wait for the next tick
|
||||
clock.tick(Duration::from_millis(1000 / FPS));
|
||||
}
|
||||
}
|
17
client/src/error.rs
Normal file
17
client/src/error.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use common::net::PostError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Network(PostError),
|
||||
ServerShutdown,
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl From<PostError> for Error {
|
||||
fn from(err: PostError) -> Self {
|
||||
match err {
|
||||
PostError::Disconnected => Error::ServerShutdown,
|
||||
err => Error::Network(err),
|
||||
}
|
||||
}
|
||||
}
|
14
client/src/input.rs
Normal file
14
client/src/input.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use vek::*;
|
||||
|
||||
pub struct Input {
|
||||
// TODO: Use this type to manage client input
|
||||
pub move_dir: Vec2<f32>,
|
||||
}
|
||||
|
||||
impl Default for Input {
|
||||
fn default() -> Self {
|
||||
Input {
|
||||
move_dir: Vec2::zero(),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,29 +1,39 @@
|
||||
// Standard
|
||||
use std::time::Duration;
|
||||
pub mod error;
|
||||
pub mod input;
|
||||
|
||||
// Library
|
||||
use specs::Entity as EcsEntity;
|
||||
// Reexports
|
||||
pub use specs::Entity as EcsEntity;
|
||||
pub use crate::{
|
||||
error::Error,
|
||||
input::Input,
|
||||
};
|
||||
|
||||
use std::{
|
||||
time::Duration,
|
||||
net::SocketAddr,
|
||||
};
|
||||
use vek::*;
|
||||
use threadpool;
|
||||
|
||||
// Project
|
||||
use common::{comp::phys::Vel, state::State, terrain::TerrainChunk};
|
||||
use specs::Builder;
|
||||
use common::{
|
||||
comp,
|
||||
state::State,
|
||||
terrain::TerrainChunk,
|
||||
net::PostBox,
|
||||
msg::{ClientMsg, ServerMsg},
|
||||
};
|
||||
use world::World;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
ServerShutdown,
|
||||
Other(String),
|
||||
}
|
||||
|
||||
pub struct Input {
|
||||
// TODO: Use this type to manage client input
|
||||
pub move_dir: Vec2<f32>,
|
||||
pub enum Event {
|
||||
Chat(String),
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
thread_pool: threadpool::ThreadPool,
|
||||
|
||||
last_ping: f64,
|
||||
postbox: PostBox<ClientMsg, ServerMsg>,
|
||||
|
||||
tick: u64,
|
||||
state: State,
|
||||
player: Option<EcsEntity>,
|
||||
@ -35,25 +45,35 @@ pub struct Client {
|
||||
|
||||
impl Client {
|
||||
/// Create a new `Client`.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
#[allow(dead_code)]
|
||||
pub fn new<A: Into<SocketAddr>>(addr: A) -> Result<Self, Error> {
|
||||
let state = State::new();
|
||||
|
||||
let mut postbox = PostBox::to_server(addr)?;
|
||||
postbox.send(ClientMsg::Chat(String::from("Hello, world!")));
|
||||
|
||||
Ok(Self {
|
||||
thread_pool: threadpool::Builder::new()
|
||||
.thread_name("veloren-worker".into())
|
||||
.build(),
|
||||
|
||||
last_ping: state.get_time(),
|
||||
postbox,
|
||||
|
||||
tick: 0,
|
||||
state: State::new(),
|
||||
state,
|
||||
player: None,
|
||||
|
||||
// Testing
|
||||
world: World::new(),
|
||||
chunk: None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a reference to the client's worker thread pool. This pool should be used for any
|
||||
/// computationally expensive operations that run outside of the main thread (i.e: threads that
|
||||
/// block on I/O operations are exempt).
|
||||
#[allow(dead_code)]
|
||||
pub fn thread_pool(&self) -> &threadpool::ThreadPool { &self.thread_pool }
|
||||
|
||||
// TODO: Get rid of this
|
||||
@ -70,23 +90,41 @@ impl Client {
|
||||
}
|
||||
|
||||
/// Get a reference to the client's game state.
|
||||
#[allow(dead_code)]
|
||||
pub fn state(&self) -> &State { &self.state }
|
||||
|
||||
/// Get a mutable reference to the client's game state.
|
||||
#[allow(dead_code)]
|
||||
pub fn state_mut(&mut self) -> &mut State { &mut self.state }
|
||||
|
||||
/// Get an entity from its UID, creating it if it does not exists
|
||||
pub fn get_or_create_entity(&mut self, uid: u64) -> EcsEntity {
|
||||
self.state.ecs_world_mut().create_entity()
|
||||
.with(comp::Uid(uid))
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Get the player entity
|
||||
#[allow(dead_code)]
|
||||
pub fn player(&self) -> Option<EcsEntity> {
|
||||
self.player
|
||||
}
|
||||
|
||||
/// Get the current tick number.
|
||||
#[allow(dead_code)]
|
||||
pub fn get_tick(&self) -> u64 {
|
||||
self.tick
|
||||
}
|
||||
|
||||
/// Send a chat message to the server
|
||||
#[allow(dead_code)]
|
||||
pub fn send_chat(&mut self, msg: String) -> Result<(), Error> {
|
||||
Ok(self.postbox.send(ClientMsg::Chat(msg))?)
|
||||
}
|
||||
|
||||
/// Execute a single client tick, handle input and update the game state by the given duration
|
||||
pub fn tick(&mut self, input: Input, dt: Duration) -> Result<(), Error> {
|
||||
#[allow(dead_code)]
|
||||
pub fn tick(&mut self, input: Input, dt: Duration) -> Result<Vec<Event>, Error> {
|
||||
// This tick function is the centre of the Veloren universe. Most client-side things are
|
||||
// managed from here, and as such it's important that it stays organised. Please consult
|
||||
// the core developers before making significant changes to this code. Here is the
|
||||
@ -99,13 +137,19 @@ impl Client {
|
||||
// 4) Go through the terrain update queue and apply all changes to the terrain
|
||||
// 5) Finish the tick, passing control of the main thread back to the frontend
|
||||
|
||||
// (step 1)
|
||||
// Build up a list of events for this frame, to be passed to the frontend
|
||||
let mut frontend_events = Vec::new();
|
||||
|
||||
// Handle new messages from the server
|
||||
frontend_events.append(&mut self.handle_new_messages()?);
|
||||
|
||||
// Step 3
|
||||
if let Some(p) = self.player {
|
||||
// TODO: remove this
|
||||
const PLAYER_VELOCITY: f32 = 100.0;
|
||||
|
||||
// TODO: Set acceleration instead
|
||||
self.state.write_component(p, Vel(Vec3::from(input.move_dir * PLAYER_VELOCITY)));
|
||||
self.state.write_component(p, comp::phys::Vel(Vec3::from(input.move_dir * PLAYER_VELOCITY)));
|
||||
}
|
||||
|
||||
// Tick the client's LocalState (step 3)
|
||||
@ -113,12 +157,49 @@ impl Client {
|
||||
|
||||
// Finish the tick, pass control back to the frontend (step 6)
|
||||
self.tick += 1;
|
||||
Ok(())
|
||||
Ok(frontend_events)
|
||||
}
|
||||
|
||||
/// Clean up the client after a tick
|
||||
#[allow(dead_code)]
|
||||
pub fn cleanup(&mut self) {
|
||||
// Cleanup the local state
|
||||
self.state.cleanup();
|
||||
}
|
||||
|
||||
/// Handle new server messages
|
||||
fn handle_new_messages(&mut self) -> Result<Vec<Event>, Error> {
|
||||
let mut frontend_events = Vec::new();
|
||||
|
||||
// Step 1
|
||||
let new_msgs = self.postbox.new_messages();
|
||||
|
||||
if new_msgs.len() > 0 {
|
||||
self.last_ping = self.state.get_time();
|
||||
|
||||
for msg in new_msgs {
|
||||
println!("Received message");
|
||||
match msg {
|
||||
ServerMsg::Shutdown => return Err(Error::ServerShutdown),
|
||||
ServerMsg::Chat(msg) => frontend_events.push(Event::Chat(msg)),
|
||||
ServerMsg::EntityPhysics { uid, pos, vel, dir } => {
|
||||
let ecs_entity = self.get_or_create_entity(uid);
|
||||
self.state.write_component(ecs_entity, pos);
|
||||
self.state.write_component(ecs_entity, vel);
|
||||
self.state.write_component(ecs_entity, dir);
|
||||
},
|
||||
}
|
||||
}
|
||||
} else if let Some(err) = self.postbox.status() {
|
||||
return Err(err.into());
|
||||
}
|
||||
|
||||
Ok(frontend_events)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Client {
|
||||
fn drop(&mut self) {
|
||||
self.postbox.send(ClientMsg::Disconnect).unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,9 @@ authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>", "Maciej Ćwięka <mc
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
specs = "0.14"
|
||||
specs = { version = "0.14", features = ["serde"] }
|
||||
shred = "0.7"
|
||||
vek = "0.9"
|
||||
vek = { version = "0.9", features = ["serde"] }
|
||||
dot_vox = "1.0"
|
||||
threadpool = "1.7"
|
||||
mio = "0.6"
|
||||
|
@ -1,9 +1,14 @@
|
||||
pub mod phys;
|
||||
pub mod uid;
|
||||
|
||||
// Reexports
|
||||
pub use uid::{Uid, UidAllocator};
|
||||
|
||||
// External
|
||||
use specs::World as EcsWorld;
|
||||
|
||||
pub fn register_local_components(ecs_world: &mut EcsWorld) {
|
||||
ecs_world.register::<Uid>();
|
||||
|
||||
ecs_world.register::<phys::Pos>();
|
||||
ecs_world.register::<phys::Vel>();
|
||||
ecs_world.register::<phys::Dir>();
|
||||
|
@ -4,7 +4,7 @@ use vek::*;
|
||||
|
||||
// Pos
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Pos(pub Vec3<f32>);
|
||||
|
||||
impl Component for Pos {
|
||||
@ -13,7 +13,7 @@ impl Component for Pos {
|
||||
|
||||
// Vel
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Vel(pub Vec3<f32>);
|
||||
|
||||
impl Component for Vel {
|
||||
@ -22,7 +22,7 @@ impl Component for Vel {
|
||||
|
||||
// Dir
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Dir(pub Vec3<f32>);
|
||||
|
||||
impl Component for Dir {
|
||||
|
77
common/src/comp/uid.rs
Normal file
77
common/src/comp/uid.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ops::Range,
|
||||
u64,
|
||||
};
|
||||
use specs::{
|
||||
saveload::{Marker, MarkerAllocator},
|
||||
world::EntitiesRes,
|
||||
Component,
|
||||
VecStorage,
|
||||
Entity,
|
||||
Join,
|
||||
ReadStorage,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Uid(pub u64);
|
||||
|
||||
impl Into<u64> for Uid {
|
||||
fn into(self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Uid {
|
||||
type Storage = VecStorage<Self>;
|
||||
}
|
||||
|
||||
impl Marker for Uid {
|
||||
type Identifier = u64;
|
||||
type Allocator = UidAllocator;
|
||||
|
||||
fn id(&self) -> u64 { self.0 }
|
||||
|
||||
fn update(&mut self, update: Self) {
|
||||
assert_eq!(self.0, update.0);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UidAllocator {
|
||||
pub(crate) range: Range<u64>,
|
||||
pub(crate) mapping: HashMap<u64, Entity>,
|
||||
}
|
||||
|
||||
impl UidAllocator {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
range: 0..u64::MAX,
|
||||
mapping: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MarkerAllocator<Uid> for UidAllocator {
|
||||
fn allocate(&mut self, entity: Entity, id: Option<u64>) -> Uid {
|
||||
let id = id.unwrap_or_else(|| {
|
||||
self.range.next().expect("
|
||||
Id range must be effectively endless.
|
||||
Somehow, you ran this program for longer than the lifetime of the universe.
|
||||
It's probably time to stop playing and prepare for your imminent extinction.
|
||||
")
|
||||
});
|
||||
self.mapping.insert(id, entity);
|
||||
Uid(id)
|
||||
}
|
||||
|
||||
fn retrieve_entity_internal(&self, id: u64) -> Option<Entity> {
|
||||
self.mapping.get(&id).cloned()
|
||||
}
|
||||
|
||||
fn maintain(&mut self, entities: &EntitiesRes, storage: &ReadStorage<Uid>) {
|
||||
self.mapping = (&*entities, storage)
|
||||
.join()
|
||||
.map(|(e, m)| (m.id(), e))
|
||||
.collect();
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ extern crate serde_derive;
|
||||
pub mod clock;
|
||||
pub mod comp;
|
||||
pub mod figure;
|
||||
pub mod msg;
|
||||
pub mod state;
|
||||
pub mod sys;
|
||||
pub mod terrain;
|
||||
|
5
common/src/msg/client.rs
Normal file
5
common/src/msg/client.rs
Normal file
@ -0,0 +1,5 @@
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum ClientMsg {
|
||||
Chat(String),
|
||||
Disconnect,
|
||||
}
|
6
common/src/msg/mod.rs
Normal file
6
common/src/msg/mod.rs
Normal file
@ -0,0 +1,6 @@
|
||||
pub mod server;
|
||||
pub mod client;
|
||||
|
||||
// Reexports
|
||||
pub use server::ServerMsg;
|
||||
pub use client::ClientMsg;
|
13
common/src/msg/server.rs
Normal file
13
common/src/msg/server.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use crate::comp::phys;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum ServerMsg {
|
||||
Shutdown,
|
||||
Chat(String),
|
||||
EntityPhysics {
|
||||
uid: u64,
|
||||
pos: phys::Pos,
|
||||
vel: phys::Vel,
|
||||
dir: phys::Dir,
|
||||
},
|
||||
}
|
@ -112,7 +112,7 @@ where
|
||||
/// Non-blocking receiver method returning an iterator over already received and deserialized objects
|
||||
/// # Errors
|
||||
/// If the other side disconnects PostBox won't realize that until you try to send something
|
||||
pub fn recv_iter(&mut self) -> impl Iterator<Item = R> {
|
||||
pub fn new_messages(&mut self) -> impl ExactSizeIterator<Item = R> {
|
||||
let mut events = Events::with_capacity(4096);
|
||||
let mut items = VecDeque::new();
|
||||
|
||||
|
@ -79,7 +79,7 @@ where
|
||||
/// Non-blocking method returning an iterator over new connections wrapped in [`PostBox`]es
|
||||
pub fn new_connections(
|
||||
&mut self,
|
||||
) -> impl Iterator<Item = PostBox<S, R>> {
|
||||
) -> impl ExactSizeIterator<Item = PostBox<S, R>> {
|
||||
let mut events = Events::with_capacity(256);
|
||||
let mut conns = VecDeque::new();
|
||||
|
||||
|
@ -25,8 +25,8 @@ fn basic_run() {
|
||||
scon.send(String::from("foo")).unwrap();
|
||||
client.send(String::from("bar")).unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
assert_eq!("foo", client.recv_iter().next().unwrap());
|
||||
assert_eq!("bar", scon.recv_iter().next().unwrap());
|
||||
assert_eq!("foo", client.new_messages().next().unwrap());
|
||||
assert_eq!("bar", scon.new_messages().next().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -40,7 +40,7 @@ fn huge_size_header() {
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
client.write(&[0xffu8; 64]).unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
assert_eq!(scon.recv_iter().next(), None);
|
||||
assert_eq!(scon.new_messages().next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -66,7 +66,7 @@ fn disconnect() {
|
||||
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
|
||||
match to_client.recv_iter().next() {
|
||||
match to_client.new_messages().next() {
|
||||
None => {},
|
||||
_ => panic!("Unexpected message!"),
|
||||
}
|
||||
|
@ -3,7 +3,17 @@ use std::time::Duration;
|
||||
|
||||
// Library
|
||||
use shred::{Fetch, FetchMut};
|
||||
use specs::{Builder, Component, DispatcherBuilder, Entity as EcsEntity, World as EcsWorld};
|
||||
use specs::{
|
||||
Builder,
|
||||
Component,
|
||||
DispatcherBuilder,
|
||||
Entity as EcsEntity,
|
||||
World as EcsWorld,
|
||||
storage::{
|
||||
Storage as EcsStorage,
|
||||
MaskedStorage as EcsMaskedStorage,
|
||||
},
|
||||
};
|
||||
use vek::*;
|
||||
|
||||
// Crate
|
||||
@ -72,6 +82,19 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a component with the state's ECS
|
||||
pub fn with_component<T: Component>(mut self) -> Self
|
||||
where <T as Component>::Storage: Default
|
||||
{
|
||||
self.ecs_world.register::<T>();
|
||||
self
|
||||
}
|
||||
|
||||
/// Delete an entity from the state's ECS, if it exists
|
||||
pub fn delete_entity(&mut self, entity: EcsEntity) {
|
||||
let _ = self.ecs_world.delete_entity(entity);
|
||||
}
|
||||
|
||||
// TODO: Get rid of this
|
||||
pub fn new_test_player(&mut self) -> EcsEntity {
|
||||
self.ecs_world
|
||||
@ -82,9 +105,19 @@ impl State {
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Write a component
|
||||
pub fn write_component<C: Component>(&mut self, e: EcsEntity, c: C) {
|
||||
let _ = self.ecs_world.write_storage().insert(e, c);
|
||||
/// Write a component attributed to a particular entity
|
||||
pub fn write_component<C: Component>(&mut self, entity: EcsEntity, comp: C) {
|
||||
let _ = self.ecs_world.write_storage().insert(entity, comp);
|
||||
}
|
||||
|
||||
/// Read a clone of a component attributed to a particular entity
|
||||
pub fn read_component<C: Component + Clone>(&self, entity: EcsEntity) -> Option<C> {
|
||||
self.ecs_world.read_storage::<C>().get(entity).cloned()
|
||||
}
|
||||
|
||||
/// Get a read-only reference to the storage of a particular component type
|
||||
pub fn read_storage<C: Component>(&self) -> EcsStorage<C, Fetch<EcsMaskedStorage<C>>> {
|
||||
self.ecs_world.read_storage::<C>()
|
||||
}
|
||||
|
||||
/// Get a reference to the internal ECS world
|
||||
@ -92,6 +125,11 @@ impl State {
|
||||
&self.ecs_world
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the internal ECS world
|
||||
pub fn ecs_world_mut(&mut self) -> &mut EcsWorld {
|
||||
&mut self.ecs_world
|
||||
}
|
||||
|
||||
/// Get a reference to the `Changes` structure of the state. This contains
|
||||
/// information about state that has changed since the last game tick.
|
||||
pub fn changes(&self) -> &Changes {
|
||||
|
@ -1,11 +1,6 @@
|
||||
// Standard
|
||||
use std::time::Duration;
|
||||
|
||||
// Library
|
||||
use log::info;
|
||||
|
||||
// Project
|
||||
use server::{self, Server};
|
||||
use server::{Input, Event, Server};
|
||||
use common::clock::Clock;
|
||||
|
||||
const FPS: u64 = 60;
|
||||
@ -20,12 +15,21 @@ fn main() {
|
||||
let mut clock = Clock::new();
|
||||
|
||||
// Create server
|
||||
let mut server = Server::new();
|
||||
let mut server = Server::new()
|
||||
.expect("Failed to create server instance");
|
||||
|
||||
loop {
|
||||
server.tick(server::Input {}, clock.get_last_delta())
|
||||
let events = server.tick(Input::default(), clock.get_last_delta())
|
||||
.expect("Failed to tick server");
|
||||
|
||||
for event in events {
|
||||
match event {
|
||||
Event::ClientConnected { ecs_entity } => println!("Client connected!"),
|
||||
Event::ClientDisconnected { ecs_entity } => println!("Client disconnected!"),
|
||||
Event::Chat { msg, .. } => println!("[chat] {}", msg),
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up the server after a tick
|
||||
server.cleanup();
|
||||
|
||||
|
@ -9,3 +9,4 @@ common = { package = "veloren-common", path = "../common" }
|
||||
world = { package = "veloren-world", path = "../world" }
|
||||
|
||||
specs = "0.14"
|
||||
vek = "0.9"
|
||||
|
40
server/src/client.rs
Normal file
40
server/src/client.rs
Normal file
@ -0,0 +1,40 @@
|
||||
use specs::Entity as EcsEntity;
|
||||
use common::{
|
||||
msg::{ServerMsg, ClientMsg},
|
||||
net::PostBox,
|
||||
};
|
||||
use crate::Error;
|
||||
|
||||
pub struct Client {
|
||||
pub ecs_entity: EcsEntity,
|
||||
pub postbox: PostBox<ServerMsg, ClientMsg>,
|
||||
pub last_ping: f64,
|
||||
}
|
||||
|
||||
pub struct Clients {
|
||||
clients: Vec<Client>,
|
||||
}
|
||||
|
||||
impl Clients {
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
clients: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&mut self, client: Client) {
|
||||
self.clients.push(client);
|
||||
}
|
||||
|
||||
pub fn remove_if<F: FnMut(&mut Client) -> bool>(&mut self, f: F) {
|
||||
self.clients.drain_filter(f);
|
||||
}
|
||||
|
||||
pub fn notify_all(&mut self, msg: ServerMsg) {
|
||||
for client in &mut self.clients {
|
||||
// Consume any errors, deal with them later
|
||||
let _ = client.postbox.send(msg.clone());
|
||||
println!("Sending message...");
|
||||
}
|
||||
}
|
||||
}
|
13
server/src/error.rs
Normal file
13
server/src/error.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use common::net::PostError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Network(PostError),
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl From<PostError> for Error {
|
||||
fn from(err: PostError) -> Self {
|
||||
Error::Network(err)
|
||||
}
|
||||
}
|
9
server/src/input.rs
Normal file
9
server/src/input.rs
Normal file
@ -0,0 +1,9 @@
|
||||
pub struct Input {
|
||||
// TODO: Use this type to manage server input
|
||||
}
|
||||
|
||||
impl Default for Input {
|
||||
fn default() -> Self {
|
||||
Input {}
|
||||
}
|
||||
}
|
@ -1,47 +1,110 @@
|
||||
// Standard
|
||||
use std::time::Duration;
|
||||
#![feature(drain_filter)]
|
||||
|
||||
// Internal
|
||||
use common::state::State;
|
||||
pub mod client;
|
||||
pub mod error;
|
||||
pub mod input;
|
||||
|
||||
// Reexports
|
||||
pub use crate::{
|
||||
error::Error,
|
||||
input::Input,
|
||||
};
|
||||
|
||||
use std::{
|
||||
time::Duration,
|
||||
net::SocketAddr,
|
||||
};
|
||||
use specs::{
|
||||
Entity as EcsEntity,
|
||||
world::EntityBuilder as EcsEntityBuilder,
|
||||
Builder,
|
||||
join::Join,
|
||||
saveload::MarkedBuilder,
|
||||
};
|
||||
use vek::*;
|
||||
use common::{
|
||||
comp,
|
||||
state::State,
|
||||
net::PostOffice,
|
||||
msg::{ServerMsg, ClientMsg},
|
||||
};
|
||||
use world::World;
|
||||
use crate::client::{
|
||||
Client,
|
||||
Clients,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Other(String),
|
||||
}
|
||||
const CLIENT_TIMEOUT: f64 = 5.0; // Seconds
|
||||
|
||||
pub struct Input {
|
||||
// TODO: Use this type to manage server input
|
||||
pub enum Event {
|
||||
ClientConnected {
|
||||
ecs_entity: EcsEntity,
|
||||
},
|
||||
ClientDisconnected {
|
||||
ecs_entity: EcsEntity,
|
||||
},
|
||||
Chat {
|
||||
ecs_entity: EcsEntity,
|
||||
msg: String,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct Server {
|
||||
state: State,
|
||||
world: World,
|
||||
|
||||
// TODO: Add "meta" state here
|
||||
postoffice: PostOffice<ServerMsg, ClientMsg>,
|
||||
clients: Clients,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
/// Create a new `Server`.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: State::new(),
|
||||
#[allow(dead_code)]
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
let mut state = State::new();
|
||||
|
||||
state.ecs_world_mut().add_resource(comp::UidAllocator::new());
|
||||
|
||||
Ok(Self {
|
||||
state,
|
||||
world: World::new(),
|
||||
}
|
||||
|
||||
postoffice: PostOffice::new(SocketAddr::from(([0; 4], 59003)))?,
|
||||
clients: Clients::empty(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a reference to the server's game state.
|
||||
#[allow(dead_code)]
|
||||
pub fn state(&self) -> &State { &self.state }
|
||||
/// Get a mutable reference to the server's game state.
|
||||
#[allow(dead_code)]
|
||||
pub fn state_mut(&mut self) -> &mut State { &mut self.state }
|
||||
|
||||
/// Build a new entity with a generated UID
|
||||
pub fn build_entity(&mut self) -> EcsEntityBuilder {
|
||||
self.state.ecs_world_mut().create_entity()
|
||||
.marked::<comp::Uid>()
|
||||
}
|
||||
|
||||
/// Build a new player with a generated UID
|
||||
pub fn build_player(&mut self) -> EcsEntityBuilder {
|
||||
self.build_entity()
|
||||
.with(comp::phys::Pos(Vec3::zero()))
|
||||
.with(comp::phys::Vel(Vec3::zero()))
|
||||
.with(comp::phys::Dir(Vec3::unit_y()))
|
||||
}
|
||||
|
||||
/// Get a reference to the server's world.
|
||||
#[allow(dead_code)]
|
||||
pub fn world(&self) -> &World { &self.world }
|
||||
/// Get a mutable reference to the server's world.
|
||||
#[allow(dead_code)]
|
||||
pub fn world_mut(&mut self) -> &mut World { &mut self.world }
|
||||
|
||||
/// Execute a single server tick, handle input and update the game state by the given duration
|
||||
pub fn tick(&mut self, input: Input, dt: Duration) -> Result<(), Error> {
|
||||
#[allow(dead_code)]
|
||||
pub fn tick(&mut self, input: Input, dt: Duration) -> Result<Vec<Event>, Error> {
|
||||
// This tick function is the centre of the Veloren universe. Most server-side things are
|
||||
// managed from here, and as such it's important that it stays organised. Please consult
|
||||
// the core developers before making significant changes to this code. Here is the
|
||||
@ -56,16 +119,126 @@ impl Server {
|
||||
// 6) Send relevant state updates to all clients
|
||||
// 7) Finish the tick, passing control of the main thread back to the frontend
|
||||
|
||||
// Build up a list of events for this frame, to be passed to the frontend
|
||||
let mut frontend_events = Vec::new();
|
||||
|
||||
// If networking has problems, handle them
|
||||
if let Some(err) = self.postoffice.status() {
|
||||
return Err(err.into());
|
||||
}
|
||||
|
||||
// Handle new client connections (step 2)
|
||||
frontend_events.append(&mut self.handle_new_connections()?);
|
||||
|
||||
// Handle new messages from clients
|
||||
frontend_events.append(&mut self.handle_new_messages()?);
|
||||
|
||||
// Tick the client's LocalState (step 3)
|
||||
self.state.tick(dt);
|
||||
|
||||
// Synchronise clients with the new state of the world
|
||||
self.sync_clients();
|
||||
|
||||
// Finish the tick, pass control back to the frontend (step 6)
|
||||
Ok(())
|
||||
Ok(frontend_events)
|
||||
}
|
||||
|
||||
/// Clean up the server after a tick
|
||||
#[allow(dead_code)]
|
||||
pub fn cleanup(&mut self) {
|
||||
// Cleanup the local state
|
||||
self.state.cleanup();
|
||||
}
|
||||
|
||||
/// Handle new client connections
|
||||
fn handle_new_connections(&mut self) -> Result<Vec<Event>, Error> {
|
||||
let mut frontend_events = Vec::new();
|
||||
|
||||
for postbox in self.postoffice.new_connections() {
|
||||
let ecs_entity = self.build_player()
|
||||
.build();
|
||||
|
||||
frontend_events.push(Event::ClientConnected {
|
||||
ecs_entity,
|
||||
});
|
||||
|
||||
self.clients.add(Client {
|
||||
ecs_entity,
|
||||
postbox,
|
||||
last_ping: self.state.get_time(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(frontend_events)
|
||||
}
|
||||
|
||||
/// Handle new client messages
|
||||
fn handle_new_messages(&mut self) -> Result<Vec<Event>, Error> {
|
||||
let mut frontend_events = Vec::new();
|
||||
|
||||
let state = &mut self.state;
|
||||
let mut new_chat_msgs = Vec::new();
|
||||
|
||||
self.clients.remove_if(|client| {
|
||||
let mut disconnected = false;
|
||||
let new_msgs = client.postbox.new_messages();
|
||||
|
||||
// Update client ping
|
||||
if new_msgs.len() > 0 {
|
||||
client.last_ping = state.get_time();
|
||||
|
||||
// Process incoming messages
|
||||
for msg in new_msgs {
|
||||
match msg {
|
||||
ClientMsg::Chat(msg) => new_chat_msgs.push((client.ecs_entity, msg)),
|
||||
ClientMsg::Disconnect => disconnected = true,
|
||||
}
|
||||
}
|
||||
} else if
|
||||
state.get_time() - client.last_ping > CLIENT_TIMEOUT || // Timeout
|
||||
client.postbox.status().is_some() // Postbox eror
|
||||
{
|
||||
disconnected = true;
|
||||
}
|
||||
|
||||
if disconnected {
|
||||
state.delete_entity(client.ecs_entity);
|
||||
frontend_events.push(Event::ClientDisconnected {
|
||||
ecs_entity: client.ecs_entity,
|
||||
});
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
// Handle new chat messages
|
||||
for (ecs_entity, msg) in new_chat_msgs {
|
||||
self.clients.notify_all(ServerMsg::Chat(msg.clone()));
|
||||
|
||||
frontend_events.push(Event::Chat {
|
||||
ecs_entity,
|
||||
msg,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(frontend_events)
|
||||
}
|
||||
|
||||
/// Sync client states with the most up to date information
|
||||
fn sync_clients(&mut self) {
|
||||
for (&uid, &pos, &vel, &dir) in (
|
||||
&self.state.ecs_world().read_storage::<comp::Uid>(),
|
||||
&self.state.ecs_world().read_storage::<comp::phys::Pos>(),
|
||||
&self.state.ecs_world().read_storage::<comp::phys::Vel>(),
|
||||
&self.state.ecs_world().read_storage::<comp::phys::Dir>(),
|
||||
).join() {
|
||||
self.clients.notify_all(ServerMsg::EntityPhysics {
|
||||
uid: uid.into(),
|
||||
pos,
|
||||
vel,
|
||||
dir,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,10 +15,10 @@ use super::{
|
||||
pub struct CharacterSkeleton {
|
||||
head: Bone,
|
||||
chest: Bone,
|
||||
belt: Bone,
|
||||
shorts: Bone,
|
||||
l_hand: Bone,
|
||||
bl_foot: Bone,
|
||||
br_foot: Bone,
|
||||
r_hand: Bone,
|
||||
l_hand: Bone,
|
||||
l_foot: Bone,
|
||||
r_foot: Bone,
|
||||
back: Bone,
|
||||
@ -29,10 +29,10 @@ impl CharacterSkeleton {
|
||||
Self {
|
||||
head: Bone::default(),
|
||||
chest: Bone::default(),
|
||||
belt: Bone::default(),
|
||||
shorts: Bone::default(),
|
||||
l_hand: Bone::default(),
|
||||
br_foot: Bone::default(),
|
||||
bl_foot: Bone::default(),
|
||||
r_hand: Bone::default(),
|
||||
l_hand: Bone::default(),
|
||||
l_foot: Bone::default(),
|
||||
r_foot: Bone::default(),
|
||||
back: Bone::default(),
|
||||
@ -47,10 +47,10 @@ impl Skeleton for CharacterSkeleton {
|
||||
[
|
||||
FigureBoneData::new(self.head.compute_base_matrix()),
|
||||
FigureBoneData::new(chest_mat),
|
||||
FigureBoneData::new(self.belt.compute_base_matrix()),
|
||||
FigureBoneData::new(self.shorts.compute_base_matrix()),
|
||||
FigureBoneData::new(self.l_hand.compute_base_matrix()),
|
||||
FigureBoneData::new(self.bl_foot.compute_base_matrix()),
|
||||
FigureBoneData::new(self.br_foot.compute_base_matrix()),
|
||||
FigureBoneData::new(self.r_hand.compute_base_matrix()),
|
||||
FigureBoneData::new(self.l_hand.compute_base_matrix()),
|
||||
FigureBoneData::new(self.l_foot.compute_base_matrix()),
|
||||
FigureBoneData::new(self.r_foot.compute_base_matrix()),
|
||||
FigureBoneData::new(chest_mat * self.back.compute_base_matrix()),
|
||||
|
@ -21,30 +21,34 @@ impl Animation for RunAnimation {
|
||||
time: f64,
|
||||
) {
|
||||
let wave = (time as f32 * 12.0).sin();
|
||||
let wavecos = (time as f32 * 12.0).cos();
|
||||
let wave_slow = (time as f32 * 6.0 + PI).sin();
|
||||
let wavecos_slow = (time as f32 * 6.0 + PI).cos();
|
||||
let wave_dip = (wave_slow.abs() - 0.5).abs();
|
||||
|
||||
skeleton.head.offset = Vec3::unit_z() * 13.0;
|
||||
skeleton.head.ori = Quaternion::rotation_z(wave * 0.3);
|
||||
skeleton.head.offset = Vec3::new(0.0, 0.0, 0.0);
|
||||
skeleton.head.ori = Quaternion::rotation_x(0.0);
|
||||
|
||||
skeleton.chest.offset = Vec3::unit_z() * 9.0;
|
||||
skeleton.chest.ori = Quaternion::rotation_z(wave * 0.3);
|
||||
skeleton.chest.offset = Vec3::new(0.0, 0.0, 0.0);
|
||||
skeleton.chest.ori = Quaternion::rotation_x(0.0);
|
||||
|
||||
skeleton.belt.offset = Vec3::unit_z() * 7.0;
|
||||
skeleton.belt.ori = Quaternion::rotation_z(wave * 0.2);
|
||||
//skeleton.br_foot.offset = Vec3::new(0.0, wavecos_slow * 1.0, wave_slow * 2.0 + wave_dip * 1.0);
|
||||
//skeleton.br_foot.ori = Quaternion::rotation_x(0.0 + wave_slow * 10.1);
|
||||
|
||||
skeleton.shorts.offset = Vec3::unit_z() * 4.0;
|
||||
skeleton.shorts.ori = Quaternion::rotation_z(wave * 0.1);
|
||||
skeleton.bl_foot.offset = Vec3::new(0.0, 0.0, 0.0);
|
||||
skeleton.bl_foot.ori = Quaternion::rotation_x(wave_slow * 2.0);
|
||||
//skeleton.bl_foot.offset = Vec3::new(0.0, wavecos_slow * 1.0, wave_slow * 2.0 + wave_dip * 1.0);
|
||||
//skeleton.bl_foot.ori = Quaternion::rotation_x(0.5 + wave_slow * 0.1);
|
||||
|
||||
skeleton.l_hand.offset = Vec3::new(-6.0 - wave_dip * 6.0, wave * 5.0, 11.0 - wave_dip * 6.0);
|
||||
skeleton.r_hand.offset = Vec3::new(6.0 + wave_dip * 6.0, -wave * 5.0, 11.0 - wave_dip * 6.0);
|
||||
//skeleton.r_hand.offset = Vec3::new(0.0, wavecos_slow * 1.0, wave_slow * 2.0 + wave_dip * 1.0);
|
||||
//skeleton.r_hand.ori = Quaternion::rotation_x(0.5 + wave_slow * 0.1);
|
||||
|
||||
skeleton.l_hand.offset = Vec3::new(0.0, 0.0, 0.0);
|
||||
skeleton.l_hand.ori = Quaternion::rotation_x(wave_slow * 2.0);
|
||||
|
||||
//skeleton.l_hand.offset = Vec3::new(0.0, wavecos_slow * 1.0, wave_slow * 2.0 + wave_dip * 1.0);
|
||||
//skeleton.l_hand.ori = Quaternion::rotation_x(0.5 + wave_slow * 0.1);
|
||||
|
||||
skeleton.l_foot.offset = Vec3::new(-3.5, 1.0 - wave * 8.0, 3.5 - wave_dip * 4.0);
|
||||
skeleton.l_foot.ori = Quaternion::rotation_x(-wave + 1.0);
|
||||
skeleton.r_foot.offset = Vec3::new(3.5, 1.0 + wave * 8.0, 3.5 - wave_dip * 4.0);
|
||||
skeleton.r_foot.ori = Quaternion::rotation_x(wave + 1.0);
|
||||
|
||||
skeleton.back.offset = Vec3::new(-9.0, 5.0, 18.0);
|
||||
skeleton.back.ori = Quaternion::rotation_y(2.5);
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ impl PlayState for TitleState {
|
||||
Event::Close => return PlayStateResult::Shutdown,
|
||||
// When space is pressed, start a session
|
||||
Event::Char(' ') => return PlayStateResult::Push(
|
||||
Box::new(SessionState::new(global_state.window.renderer_mut())),
|
||||
Box::new(SessionState::new(global_state.window.renderer_mut()).unwrap()), // TODO: Handle this error
|
||||
),
|
||||
// Ignore all other events
|
||||
_ => {},
|
||||
|
@ -11,15 +11,12 @@ use gfx::{
|
||||
};
|
||||
|
||||
// Local
|
||||
use super::{
|
||||
Globals,
|
||||
super::{
|
||||
Pipeline,
|
||||
TgtColorFmt,
|
||||
TgtDepthFmt,
|
||||
Mesh,
|
||||
Quad,
|
||||
},
|
||||
use super::super::{
|
||||
Pipeline,
|
||||
TgtColorFmt,
|
||||
TgtDepthFmt,
|
||||
Mesh,
|
||||
Quad,
|
||||
};
|
||||
|
||||
gfx_defines! {
|
||||
|
@ -4,7 +4,7 @@ use std::marker::PhantomData;
|
||||
// Library
|
||||
use gfx::{
|
||||
self,
|
||||
traits::{Factory, FactoryExt},
|
||||
traits::Factory,
|
||||
};
|
||||
use image::{
|
||||
DynamicImage,
|
||||
@ -14,7 +14,6 @@ use image::{
|
||||
// Local
|
||||
use super::{
|
||||
RenderError,
|
||||
mesh::Mesh,
|
||||
Pipeline,
|
||||
gfx_backend,
|
||||
};
|
||||
|
@ -7,8 +7,8 @@ use vek::*;
|
||||
use dot_vox;
|
||||
|
||||
// Project
|
||||
use common::figure::Segment;
|
||||
use client::Client;
|
||||
use common::{comp::phys::Pos as PosComp, figure::Segment};
|
||||
|
||||
// Crate
|
||||
use crate::{
|
||||
@ -82,15 +82,15 @@ impl Scene {
|
||||
test_figure: Figure::new(
|
||||
renderer,
|
||||
[
|
||||
Some(load_segment("head.vox").generate_mesh(Vec3::new(-7.0, -5.5, -1.0))),
|
||||
Some(load_segment("chest.vox").generate_mesh(Vec3::new(-6.0, -3.0, 0.0))),
|
||||
Some(load_segment("belt.vox").generate_mesh(Vec3::new(-5.0, -3.0, 0.0))),
|
||||
Some(load_segment("pants.vox").generate_mesh(Vec3::new(-5.0, -3.0, 0.0))),
|
||||
Some(load_segment("hand.vox").generate_mesh(Vec3::new(-2.0, -2.0, -1.0))),
|
||||
Some(load_segment("hand.vox").generate_mesh(Vec3::new(-2.0, -2.0, -1.0))),
|
||||
Some(load_segment("foot.vox").generate_mesh(Vec3::new(-2.5, -3.0, -2.0))),
|
||||
Some(load_segment("foot.vox").generate_mesh(Vec3::new(-2.5, -3.0, -2.0))),
|
||||
Some(load_segment("sword.vox").generate_mesh(Vec3::new(-6.5, -1.0, 0.0))),
|
||||
Some(load_segment("dragonhead.vox").generate_mesh(Vec3::new(2.0, -12.0, 2.0))),
|
||||
Some(load_segment("dragon_body.vox").generate_mesh(Vec3::new(0.0, 0.0, 0.0))),
|
||||
Some(load_segment("dragon_lfoot.vox").generate_mesh(Vec3::new(0.0, 10.0, -4.0))),
|
||||
Some(load_segment("dragon_rfoot.vox").generate_mesh(Vec3::new(0.0, 10.0, -4.0))),
|
||||
Some(load_segment("dragon_rfoot.vox").generate_mesh(Vec3::new(0.0, -10.0, -4.0))),
|
||||
Some(load_segment("dragon_lfoot.vox").generate_mesh(Vec3::new(0.0, 0.0, 0.0))),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
@ -138,22 +138,6 @@ impl Scene {
|
||||
|
||||
/// Maintain data such as GPU constant buffers, models, etc. To be called once per tick.
|
||||
pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) {
|
||||
// Get player position
|
||||
let player_pos = match client.player() {
|
||||
Some(entity) => {
|
||||
client
|
||||
.state()
|
||||
.ecs_world()
|
||||
.read_storage::<PosComp>()
|
||||
.get(entity)
|
||||
.expect("There was no position component on the player entity!")
|
||||
.0
|
||||
}
|
||||
None => Vec3::default(),
|
||||
};
|
||||
// Alter camera position to match player
|
||||
self.camera.set_focus_pos(player_pos);
|
||||
|
||||
// Compute camera matrices
|
||||
let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents();
|
||||
|
||||
@ -177,15 +161,7 @@ impl Scene {
|
||||
&mut self.test_figure.skeleton,
|
||||
client.state().get_time(),
|
||||
);
|
||||
|
||||
// Calculate entity model matrix
|
||||
let model_mat = Mat4::<f32>::translation_3d(player_pos);
|
||||
//* Mat4::rotation_z(PI - entity.look_dir().x)
|
||||
//* Mat4::rotation_x(entity.look_dir().y);
|
||||
|
||||
self.test_figure
|
||||
.update_locals(renderer, FigureLocals::new(model_mat))
|
||||
.unwrap();
|
||||
self.test_figure.update_locals(renderer, FigureLocals::default()).unwrap();
|
||||
self.test_figure.update_skeleton(renderer).unwrap();
|
||||
}
|
||||
|
||||
|
@ -34,14 +34,14 @@ pub struct SessionState {
|
||||
/// Represents an active game session (i.e: one that is being played)
|
||||
impl SessionState {
|
||||
/// Create a new `SessionState`
|
||||
pub fn new(renderer: &mut Renderer) -> Self {
|
||||
let client = Client::new().with_test_state(); // <--- TODO: Remove this
|
||||
Self {
|
||||
pub fn new(renderer: &mut Renderer) -> Result<Self, Error> {
|
||||
let client = Client::new(([127, 0, 0, 1], 59003))?.with_test_state(); // <--- TODO: Remove this
|
||||
Ok(Self {
|
||||
// Create a scene for this session. The scene handles visible elements of the game world
|
||||
scene: Scene::new(renderer, &client),
|
||||
client,
|
||||
key_state: KeyState::new(),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,5 @@
|
||||
// Standard
|
||||
use std::{
|
||||
rc::Rc,
|
||||
cell::RefCell,
|
||||
};
|
||||
use std::rc::Rc;
|
||||
|
||||
// Library
|
||||
use image::DynamicImage;
|
||||
|
BIN
voxygen/test_assets/body.vox
Normal file
BIN
voxygen/test_assets/body.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/dragon_body.vox
Normal file
BIN
voxygen/test_assets/dragon_body.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/dragon_lfoot.vox
Normal file
BIN
voxygen/test_assets/dragon_lfoot.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/dragon_rfoot.vox
Normal file
BIN
voxygen/test_assets/dragon_rfoot.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/dragon_tail.vox
Normal file
BIN
voxygen/test_assets/dragon_tail.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/dragon_wingL_in.vox
Normal file
BIN
voxygen/test_assets/dragon_wingL_in.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/dragon_wingL_out.vox
Normal file
BIN
voxygen/test_assets/dragon_wingL_out.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/dragon_wingR_in.vox
Normal file
BIN
voxygen/test_assets/dragon_wingR_in.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/dragon_wingR_out.vox
Normal file
BIN
voxygen/test_assets/dragon_wingR_out.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/dragonhead.vox
Normal file
BIN
voxygen/test_assets/dragonhead.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/foot_BL.vox
Normal file
BIN
voxygen/test_assets/foot_BL.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/foot_BR.vox
Normal file
BIN
voxygen/test_assets/foot_BR.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/foot_FL.vox
Normal file
BIN
voxygen/test_assets/foot_FL.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/foot_FR.vox
Normal file
BIN
voxygen/test_assets/foot_FR.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/lionhead.vox
Normal file
BIN
voxygen/test_assets/lionhead.vox
Normal file
Binary file not shown.
BIN
voxygen/test_assets/sabremale.vox
Normal file
BIN
voxygen/test_assets/sabremale.vox
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user