mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'master' into 'master'
Client States and Basis for Character Creation Closes #3 i #39 See merge request veloren/veloren!51 Former-commit-id: 895693c2e0821497312f2a6400a0ffe73b12d154
This commit is contained in:
commit
e3de381001
@ -18,9 +18,11 @@ fn main() {
|
||||
let mut clock = Clock::new();
|
||||
|
||||
// Create client
|
||||
let mut client = Client::new(([127, 0, 0, 1], 59003), comp::Player::new("test".to_string()), None, 300)
|
||||
let mut client = Client::new(([127, 0, 0, 1], 59003), 300)
|
||||
.expect("Failed to create client instance");
|
||||
|
||||
client.register(comp::Player::new("test".to_string()));
|
||||
|
||||
client.send_chat("Hello!".to_string());
|
||||
|
||||
loop {
|
||||
|
@ -4,43 +4,39 @@ pub mod error;
|
||||
pub mod input;
|
||||
|
||||
// Reexports
|
||||
pub use crate::{error::Error, input::Input};
|
||||
pub use specs::join::Join;
|
||||
pub use specs::Entity as EcsEntity;
|
||||
pub use crate::{
|
||||
error::Error,
|
||||
input::Input,
|
||||
};
|
||||
|
||||
use std::{
|
||||
time::Duration,
|
||||
net::SocketAddr,
|
||||
collections::HashSet,
|
||||
};
|
||||
use vek::*;
|
||||
use threadpool::ThreadPool;
|
||||
use specs::Builder;
|
||||
use common::{
|
||||
comp,
|
||||
msg::{ClientMsg, ClientState, ServerMsg},
|
||||
net::PostBox,
|
||||
state::State,
|
||||
terrain::TerrainChunk,
|
||||
net::PostBox,
|
||||
msg::{ClientMsg, ServerMsg},
|
||||
};
|
||||
use specs::Builder;
|
||||
use std::{collections::HashSet, net::SocketAddr, time::Duration};
|
||||
use threadpool::ThreadPool;
|
||||
use vek::*;
|
||||
|
||||
const SERVER_TIMEOUT: f64 = 20.0; // Seconds
|
||||
|
||||
pub enum Event {
|
||||
Chat(String),
|
||||
Disconnect,
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
client_state: Option<ClientState>,
|
||||
thread_pool: ThreadPool,
|
||||
|
||||
last_ping: f64,
|
||||
postbox: PostBox<ClientMsg, ServerMsg>,
|
||||
pub postbox: PostBox<ClientMsg, ServerMsg>,
|
||||
|
||||
tick: u64,
|
||||
state: State,
|
||||
player: EcsEntity,
|
||||
entity: EcsEntity,
|
||||
view_distance: u64,
|
||||
|
||||
pending_chunks: HashSet<Vec3<i32>>,
|
||||
@ -49,32 +45,28 @@ pub struct Client {
|
||||
impl Client {
|
||||
/// Create a new `Client`.
|
||||
#[allow(dead_code)]
|
||||
pub fn new<A: Into<SocketAddr>>(
|
||||
addr: A,
|
||||
player: comp::Player,
|
||||
character: Option<comp::Character>,
|
||||
view_distance: u64,
|
||||
) -> Result<Self, Error> {
|
||||
|
||||
pub fn new<A: Into<SocketAddr>>(addr: A, view_distance: u64) -> Result<Self, Error> {
|
||||
let mut client_state = Some(ClientState::Connected);
|
||||
let mut postbox = PostBox::to(addr)?;
|
||||
|
||||
// Send connection request
|
||||
postbox.send_message(ClientMsg::Connect {
|
||||
player,
|
||||
character,
|
||||
});
|
||||
|
||||
// Wait for handshake from server
|
||||
let (state, player) = match postbox.next_message() {
|
||||
Some(ServerMsg::Handshake { ecs_state, player_entity }) => {
|
||||
// Wait for initial sync
|
||||
let (state, entity) = match postbox.next_message() {
|
||||
Some(ServerMsg::InitialSync {
|
||||
ecs_state,
|
||||
entity_uid,
|
||||
}) => {
|
||||
let mut state = State::from_state_package(ecs_state);
|
||||
let player_entity = state.ecs().entity_from_uid(player_entity).ok_or(Error::ServerWentMad)?;
|
||||
(state, player_entity)
|
||||
},
|
||||
let entity = state
|
||||
.ecs()
|
||||
.entity_from_uid(entity_uid)
|
||||
.ok_or(Error::ServerWentMad)?;
|
||||
(state, entity)
|
||||
}
|
||||
_ => return Err(Error::ServerWentMad),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
client_state,
|
||||
thread_pool: threadpool::Builder::new()
|
||||
.thread_name("veloren-worker".into())
|
||||
.build(),
|
||||
@ -84,31 +76,41 @@ impl Client {
|
||||
|
||||
tick: 0,
|
||||
state,
|
||||
player,
|
||||
entity,
|
||||
view_distance,
|
||||
|
||||
pending_chunks: HashSet::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn register(&mut self, player: comp::Player) {
|
||||
self.postbox.send_message(ClientMsg::Register { player });
|
||||
}
|
||||
|
||||
/// 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 }
|
||||
pub fn thread_pool(&self) -> &threadpool::ThreadPool {
|
||||
&self.thread_pool
|
||||
}
|
||||
|
||||
/// Get a reference to the client's game state.
|
||||
#[allow(dead_code)]
|
||||
pub fn state(&self) -> &State { &self.state }
|
||||
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 }
|
||||
pub fn state_mut(&mut self) -> &mut State {
|
||||
&mut self.state
|
||||
}
|
||||
|
||||
/// Get the player entity
|
||||
/// Get the player's entity
|
||||
#[allow(dead_code)]
|
||||
pub fn player(&self) -> EcsEntity {
|
||||
self.player
|
||||
pub fn entity(&self) -> EcsEntity {
|
||||
self.entity
|
||||
}
|
||||
|
||||
/// Get the current tick number.
|
||||
@ -148,42 +150,59 @@ impl Client {
|
||||
println!("Chunk at {:?}", k);
|
||||
});
|
||||
|
||||
self.state.write_component(self.player, comp::Control {
|
||||
move_dir: input.move_dir,
|
||||
});
|
||||
self.state.write_component(
|
||||
self.entity,
|
||||
comp::Control {
|
||||
move_dir: input.move_dir,
|
||||
},
|
||||
);
|
||||
|
||||
// Tick the client's LocalState (step 3)
|
||||
self.state.tick(dt);
|
||||
|
||||
// Update the server about the player's physics attributes
|
||||
match (
|
||||
self.state.read_storage().get(self.player).cloned(),
|
||||
self.state.read_storage().get(self.player).cloned(),
|
||||
self.state.read_storage().get(self.player).cloned(),
|
||||
self.state.read_storage().get(self.entity).cloned(),
|
||||
self.state.read_storage().get(self.entity).cloned(),
|
||||
self.state.read_storage().get(self.entity).cloned(),
|
||||
) {
|
||||
(Some(pos), Some(vel), Some(dir)) => {
|
||||
self.postbox.send_message(ClientMsg::PlayerPhysics { pos, vel, dir });
|
||||
},
|
||||
_ => {},
|
||||
self.postbox
|
||||
.send_message(ClientMsg::PlayerPhysics { pos, vel, dir });
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Update the server about the player's currently playing animation and the previous one
|
||||
if let Some(animation_history) = self.state.read_storage::<comp::AnimationHistory>().get(self.player).cloned() {
|
||||
if let Some(animation_history) = self
|
||||
.state
|
||||
.read_storage::<comp::AnimationHistory>()
|
||||
.get(self.entity)
|
||||
.cloned()
|
||||
{
|
||||
if Some(animation_history.current) != animation_history.last {
|
||||
self.postbox.send_message(ClientMsg::PlayerAnimation(animation_history));
|
||||
self.postbox
|
||||
.send_message(ClientMsg::PlayerAnimation(animation_history));
|
||||
}
|
||||
}
|
||||
|
||||
// Request chunks from the server
|
||||
if let Some(pos) = self.state.read_storage::<comp::phys::Pos>().get(self.player) {
|
||||
if let Some(pos) = self
|
||||
.state
|
||||
.read_storage::<comp::phys::Pos>()
|
||||
.get(self.entity)
|
||||
{
|
||||
let chunk_pos = self.state.terrain().pos_key(pos.0.map(|e| e as i32));
|
||||
|
||||
for i in chunk_pos.x - 1..chunk_pos.x + 1 {
|
||||
for j in chunk_pos.y - 1..chunk_pos.y + 1 {
|
||||
for k in -1..3 {
|
||||
let key = chunk_pos + Vec3::new(i, j, k);
|
||||
if self.state.terrain().get_key(key).is_none() && !self.pending_chunks.contains(&key) {
|
||||
self.postbox.send_message(ClientMsg::TerrainChunkRequest { key });
|
||||
if self.state.terrain().get_key(key).is_none()
|
||||
&& !self.pending_chunks.contains(&key)
|
||||
{
|
||||
self.postbox
|
||||
.send_message(ClientMsg::TerrainChunkRequest { key });
|
||||
self.pending_chunks.insert(key);
|
||||
}
|
||||
}
|
||||
@ -215,31 +234,44 @@ impl Client {
|
||||
|
||||
for msg in new_msgs {
|
||||
match msg {
|
||||
ServerMsg::Handshake { .. } => return Err(Error::ServerWentMad),
|
||||
ServerMsg::InitialSync { .. } => return Err(Error::ServerWentMad),
|
||||
ServerMsg::Shutdown => return Err(Error::ServerShutdown),
|
||||
ServerMsg::Ping => self.postbox.send_message(ClientMsg::Pong),
|
||||
ServerMsg::Pong => {},
|
||||
ServerMsg::Pong => {}
|
||||
ServerMsg::Chat(msg) => frontend_events.push(Event::Chat(msg)),
|
||||
ServerMsg::SetPlayerEntity(uid) => self.player = self.state.ecs().entity_from_uid(uid).unwrap(), // TODO: Don't unwrap here!
|
||||
ServerMsg::SetPlayerEntity(uid) => self.entity = self.state.ecs().entity_from_uid(uid).unwrap(), // TODO: Don't unwrap here!
|
||||
ServerMsg::EcsSync(sync_package) => self.state.ecs_mut().sync_with_package(sync_package),
|
||||
ServerMsg::EntityPhysics { entity, pos, vel, dir } => match self.state.ecs().entity_from_uid(entity) {
|
||||
Some(entity) => {
|
||||
self.state.write_component(entity, pos);
|
||||
self.state.write_component(entity, vel);
|
||||
self.state.write_component(entity, dir);
|
||||
},
|
||||
None => {},
|
||||
}
|
||||
None => {}
|
||||
},
|
||||
ServerMsg::EntityAnimation { entity, animation_history } => match self.state.ecs().entity_from_uid(entity) {
|
||||
Some(entity) => {
|
||||
self.state.write_component(entity, animation_history);
|
||||
},
|
||||
None => {},
|
||||
}
|
||||
None => {}
|
||||
},
|
||||
ServerMsg::TerrainChunkUpdate { key, chunk } => {
|
||||
self.state.insert_chunk(key, *chunk);
|
||||
self.pending_chunks.remove(&key);
|
||||
},
|
||||
}
|
||||
ServerMsg::StateAnswer(Ok(state)) => {
|
||||
self.client_state = Some(state);
|
||||
}
|
||||
ServerMsg::StateAnswer(Err((error, state))) => {
|
||||
self.client_state = Some(state);
|
||||
}
|
||||
ServerMsg::ForceState(state) => {
|
||||
self.client_state = Some(state);
|
||||
}
|
||||
ServerMsg::Disconnect => {
|
||||
self.client_state = None;
|
||||
frontend_events.push(Event::Disconnect);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(err) = self.postbox.error() {
|
||||
|
@ -1,7 +1,8 @@
|
||||
use specs::{Component, VecStorage, FlaggedStorage};
|
||||
use vek::*;
|
||||
use rand::prelude::*;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum Race {
|
||||
Danari,
|
||||
Dwarf,
|
||||
@ -11,13 +12,103 @@ pub enum Race {
|
||||
Undead,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum Gender {
|
||||
Female,
|
||||
Male,
|
||||
Unspecified,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum Head {
|
||||
DefaultHead,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum Chest {
|
||||
DefaultChest,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum Belt {
|
||||
DefaultBelt,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum Pants {
|
||||
DefaultPants,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum Hand {
|
||||
DefaultHand,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum Foot {
|
||||
DefaultFoot,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum Weapon {
|
||||
Daggers,
|
||||
SwordShield,
|
||||
Sword,
|
||||
Axe,
|
||||
Hammer,
|
||||
Bow,
|
||||
Staff,
|
||||
}
|
||||
|
||||
use Race::*;
|
||||
use Gender::*;
|
||||
use Head::*;
|
||||
use Chest::*;
|
||||
use Belt::*;
|
||||
use Pants::*;
|
||||
use Hand::*;
|
||||
use Foot::*;
|
||||
use Weapon::*;
|
||||
|
||||
const ALL_RACES: [Race; 6] = [Danari, Dwarf, Elf, Human, Orc, Undead];
|
||||
const ALL_GENDERS: [Gender; 3] = [Female, Male, Unspecified];
|
||||
const ALL_HEADS: [Head; 1] = [DefaultHead];
|
||||
const ALL_CHESTS: [Chest; 1] = [DefaultChest];
|
||||
const ALL_BELTS: [Belt; 1] = [DefaultBelt];
|
||||
const ALL_PANTS: [Pants; 1] = [DefaultPants];
|
||||
const ALL_HANDS: [Hand; 1] = [DefaultHand];
|
||||
const ALL_FEET: [Foot; 1] = [DefaultFoot];
|
||||
const ALL_WEAPONS: [Weapon; 7] = [Daggers, SwordShield, Sword, Axe, Hammer, Bow, Staff];
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct Character {
|
||||
pub race: Race,
|
||||
pub gender: Gender,
|
||||
pub head: Head,
|
||||
pub chest: Chest,
|
||||
pub belt: Belt,
|
||||
pub pants: Pants,
|
||||
pub hand: Hand,
|
||||
pub foot: Foot,
|
||||
pub weapon: Weapon,
|
||||
}
|
||||
|
||||
impl Character {
|
||||
pub fn random() -> Self {
|
||||
Self {
|
||||
race: *thread_rng().choose(&ALL_RACES).unwrap(),
|
||||
gender: *thread_rng().choose(&ALL_GENDERS).unwrap(),
|
||||
head: *thread_rng().choose(&ALL_HEADS).unwrap(),
|
||||
chest: *thread_rng().choose(&ALL_CHESTS).unwrap(),
|
||||
belt: *thread_rng().choose(&ALL_BELTS).unwrap(),
|
||||
pants: *thread_rng().choose(&ALL_PANTS).unwrap(),
|
||||
hand: *thread_rng().choose(&ALL_HANDS).unwrap(),
|
||||
foot: *thread_rng().choose(&ALL_FEET).unwrap(),
|
||||
weapon: *thread_rng().choose(&ALL_WEAPONS).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AnimationHistory {
|
||||
pub last: Option<Animation>,
|
||||
@ -30,33 +121,6 @@ pub enum Animation {
|
||||
Run,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Character {
|
||||
race: Race,
|
||||
gender: Gender,
|
||||
head: (),
|
||||
chest: (),
|
||||
belt: (),
|
||||
arms: (),
|
||||
feet: (),
|
||||
|
||||
}
|
||||
|
||||
impl Character {
|
||||
// TODO: Remove this
|
||||
pub fn test() -> Self {
|
||||
Self {
|
||||
race: Race::Human,
|
||||
gender: Gender::Unspecified,
|
||||
head: (),
|
||||
chest: (),
|
||||
belt: (),
|
||||
arms: (),
|
||||
feet: (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Character {
|
||||
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
use vek::*;
|
||||
use super::ClientState;
|
||||
use crate::comp;
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum ClientMsg {
|
||||
Connect {
|
||||
player: comp::Player,
|
||||
character: Option<comp::Character>,
|
||||
|
||||
},
|
||||
Register { player: comp::Player },
|
||||
Character(comp::Character),
|
||||
RequestState(ClientState),
|
||||
Ping,
|
||||
Pong,
|
||||
Chat(String),
|
||||
|
@ -3,6 +3,14 @@ pub mod server;
|
||||
pub mod client;
|
||||
|
||||
// Reexports
|
||||
pub use self::server::ServerMsg;
|
||||
pub use self::server::{ServerMsg, RequestStateError};
|
||||
pub use self::client::ClientMsg;
|
||||
pub use self::ecs_packet::EcsPacket;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub enum ClientState {
|
||||
Connected,
|
||||
Registered,
|
||||
Spectator,
|
||||
Character,
|
||||
}
|
||||
|
@ -3,15 +3,24 @@ use crate::{
|
||||
comp,
|
||||
terrain::TerrainChunk,
|
||||
};
|
||||
use super::EcsPacket;
|
||||
use super::{EcsPacket, ClientState};
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum RequestStateError {
|
||||
Denied,
|
||||
Already,
|
||||
Impossible,
|
||||
WrongMessage,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum ServerMsg {
|
||||
Handshake {
|
||||
InitialSync {
|
||||
ecs_state: sphynx::StatePackage<EcsPacket>,
|
||||
player_entity: u64,
|
||||
entity_uid: u64,
|
||||
},
|
||||
Shutdown,
|
||||
StateAnswer(Result<ClientState, (RequestStateError, ClientState)>),
|
||||
ForceState(ClientState),
|
||||
Ping,
|
||||
Pong,
|
||||
Chat(String),
|
||||
@ -31,4 +40,6 @@ pub enum ServerMsg {
|
||||
key: Vec3<i32>,
|
||||
chunk: Box<TerrainChunk>,
|
||||
},
|
||||
Disconnect,
|
||||
Shutdown,
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use serde_derive::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum BiomeKind {
|
||||
Void,
|
||||
Grassland,
|
||||
|
@ -19,7 +19,7 @@ use crate::{
|
||||
|
||||
// TerrainChunkSize
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TerrainChunkSize;
|
||||
|
||||
impl VolSize for TerrainChunkSize {
|
||||
@ -28,7 +28,7 @@ impl VolSize for TerrainChunkSize {
|
||||
|
||||
// TerrainChunkMeta
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TerrainChunkMeta {
|
||||
biome: BiomeKind,
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ pub enum ChunkErr {
|
||||
// V = Voxel
|
||||
// S = Size (replace when const generics are a thing)
|
||||
// M = Metadata
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Chunk<V: Vox, S: VolSize, M> {
|
||||
vox: Vec<V>,
|
||||
meta: M,
|
||||
|
@ -1,20 +1,14 @@
|
||||
use std::collections::HashMap;
|
||||
use specs::Entity as EcsEntity;
|
||||
use crate::Error;
|
||||
use common::{
|
||||
comp,
|
||||
msg::{ServerMsg, ClientMsg},
|
||||
msg::{ClientMsg, ClientState, RequestStateError, ServerMsg},
|
||||
net::PostBox,
|
||||
};
|
||||
use crate::Error;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum ClientState {
|
||||
Connecting,
|
||||
Connected,
|
||||
}
|
||||
use specs::Entity as EcsEntity;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct Client {
|
||||
pub state: ClientState,
|
||||
pub client_state: ClientState,
|
||||
pub postbox: PostBox<ServerMsg, ClientMsg>,
|
||||
pub last_ping: f64,
|
||||
}
|
||||
@ -23,6 +17,19 @@ impl Client {
|
||||
pub fn notify(&mut self, msg: ServerMsg) {
|
||||
self.postbox.send_message(msg);
|
||||
}
|
||||
pub fn allow_state(&mut self, new_state: ClientState) {
|
||||
self.client_state = new_state;
|
||||
self.postbox
|
||||
.send_message(ServerMsg::StateAnswer(Ok(new_state)));
|
||||
}
|
||||
pub fn error_state(&mut self, error: RequestStateError) {
|
||||
self.postbox
|
||||
.send_message(ServerMsg::StateAnswer(Err((error, self.client_state))));
|
||||
}
|
||||
pub fn force_state(&mut self, new_state: ClientState) {
|
||||
self.client_state = new_state;
|
||||
self.postbox.send_message(ServerMsg::ForceState(new_state));
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Clients {
|
||||
@ -50,17 +57,38 @@ impl Clients {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify_connected(&mut self, msg: ServerMsg) {
|
||||
pub fn notify_registered(&mut self, msg: ServerMsg) {
|
||||
for client in self.clients.values_mut() {
|
||||
if client.state == ClientState::Connected {
|
||||
if client.client_state != ClientState::Connected {
|
||||
client.notify(msg.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify_connected_except(&mut self, except_entity: EcsEntity, msg: ServerMsg) {
|
||||
pub fn notify_ingame(&mut self, msg: ServerMsg) {
|
||||
for client in self.clients.values_mut() {
|
||||
if client.client_state == ClientState::Spectator
|
||||
|| client.client_state == ClientState::Character
|
||||
{
|
||||
client.notify(msg.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify_registered_except(&mut self, except_entity: EcsEntity, msg: ServerMsg) {
|
||||
for (entity, client) in self.clients.iter_mut() {
|
||||
if client.state == ClientState::Connected && *entity != except_entity {
|
||||
if client.client_state != ClientState::Connected && *entity != except_entity {
|
||||
client.notify(msg.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify_ingame_except(&mut self, except_entity: EcsEntity, msg: ServerMsg) {
|
||||
for (entity, client) in self.clients.iter_mut() {
|
||||
if (client.client_state == ClientState::Spectator
|
||||
|| client.client_state == ClientState::Character)
|
||||
&& *entity != except_entity
|
||||
{
|
||||
client.notify(msg.clone());
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ use common::{comp, msg::ServerMsg};
|
||||
use specs::{join::Join, Entity as EcsEntity};
|
||||
use vek::*;
|
||||
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use scan_fmt::scan_fmt;
|
||||
/// Struct representing a command that a user can run from server chat
|
||||
|
@ -1,21 +1,24 @@
|
||||
#![feature(drain_filter)]
|
||||
|
||||
pub mod client;
|
||||
pub mod cmd;
|
||||
pub mod error;
|
||||
pub mod input;
|
||||
pub mod cmd;
|
||||
|
||||
// Reexports
|
||||
pub use crate::{error::Error, input::Input};
|
||||
|
||||
use crate::{client::{Client, ClientState, Clients}, cmd::CHAT_COMMANDS};
|
||||
use crate::{
|
||||
client::{Client, Clients},
|
||||
cmd::CHAT_COMMANDS,
|
||||
};
|
||||
use common::{
|
||||
comp,
|
||||
msg::{ClientMsg, ServerMsg},
|
||||
comp::character::Animation,
|
||||
msg::{ClientMsg, ClientState, RequestStateError, ServerMsg},
|
||||
net::PostOffice,
|
||||
state::{State, Uid},
|
||||
terrain::TerrainChunk,
|
||||
comp::character::Animation,
|
||||
};
|
||||
use specs::{
|
||||
join::Join, saveload::MarkedBuilder, world::EntityBuilder as EcsEntityBuilder, Builder,
|
||||
@ -72,7 +75,7 @@ impl Server {
|
||||
};
|
||||
|
||||
for i in 0..4 {
|
||||
this.create_character(comp::Character::test())
|
||||
this.create_npc(comp::Character::random())
|
||||
.with(comp::Agent::Wanderer(Vec2::zero()))
|
||||
.with(comp::Control::default())
|
||||
.build();
|
||||
@ -105,7 +108,7 @@ impl Server {
|
||||
|
||||
/// Build a non-player character
|
||||
#[allow(dead_code)]
|
||||
pub fn create_character(&mut self, character: comp::Character) -> EcsEntityBuilder {
|
||||
pub fn create_npc(&mut self, character: comp::Character) -> EcsEntityBuilder {
|
||||
self.state
|
||||
.ecs_mut()
|
||||
.create_entity_synced()
|
||||
@ -115,6 +118,33 @@ impl Server {
|
||||
.with(character)
|
||||
}
|
||||
|
||||
pub fn create_player_character(
|
||||
state: &mut State,
|
||||
entity: EcsEntity,
|
||||
client: &mut Client,
|
||||
character: comp::Character,
|
||||
) {
|
||||
state.write_component(entity, character);
|
||||
state.write_component(entity, comp::phys::Pos(Vec3::zero()));
|
||||
state.write_component(entity, comp::phys::Vel(Vec3::zero()));
|
||||
state.write_component(entity, comp::phys::Dir(Vec3::unit_y()));
|
||||
// Make sure everything is accepted
|
||||
state.write_component(entity, comp::phys::ForceUpdate);
|
||||
|
||||
// Set initial animation
|
||||
state.write_component(
|
||||
entity,
|
||||
comp::AnimationHistory {
|
||||
last: None,
|
||||
current: Animation::Idle,
|
||||
},
|
||||
);
|
||||
|
||||
// Tell the client his request was successful
|
||||
client.notify(ServerMsg::StateAnswer(Ok(ClientState::Character)));
|
||||
client.client_state = ClientState::Character;
|
||||
}
|
||||
|
||||
/// Execute a single server tick, handle input and update the game state by the given duration
|
||||
#[allow(dead_code)]
|
||||
pub fn tick(&mut self, input: Input, dt: Duration) -> Result<Vec<Event>, Error> {
|
||||
@ -156,10 +186,7 @@ impl Server {
|
||||
for (entity, player, pos) in (
|
||||
&self.state.ecs().entities(),
|
||||
&self.state.ecs().read_storage::<comp::Player>(),
|
||||
&self
|
||||
.state
|
||||
.ecs()
|
||||
.read_storage::<comp::phys::Pos>(),
|
||||
&self.state.ecs().read_storage::<comp::phys::Pos>(),
|
||||
)
|
||||
.join()
|
||||
{
|
||||
@ -197,15 +224,20 @@ impl Server {
|
||||
|
||||
for mut postbox in self.postoffice.new_postboxes() {
|
||||
let entity = self.state.ecs_mut().create_entity_synced().build();
|
||||
let mut client = Client {
|
||||
client_state: ClientState::Connected,
|
||||
postbox,
|
||||
last_ping: self.state.get_time(),
|
||||
};
|
||||
|
||||
self.clients.add(
|
||||
entity,
|
||||
Client {
|
||||
state: ClientState::Connecting,
|
||||
postbox,
|
||||
last_ping: self.state.get_time(),
|
||||
},
|
||||
);
|
||||
// Return the state of the current world
|
||||
// (All components Sphynx tracks)
|
||||
client.notify(ServerMsg::InitialSync {
|
||||
ecs_state: self.state.ecs().gen_state_package(),
|
||||
entity_uid: self.state.ecs().uid_from_entity(entity).unwrap().into(),
|
||||
});
|
||||
|
||||
self.clients.add(entity, client);
|
||||
|
||||
frontend_events.push(Event::ClientConnected { entity });
|
||||
}
|
||||
@ -232,26 +264,63 @@ impl Server {
|
||||
|
||||
// Process incoming messages
|
||||
for msg in new_msgs {
|
||||
match client.state {
|
||||
ClientState::Connecting => match msg {
|
||||
ClientMsg::Connect { player, character } => {
|
||||
Self::initialize_client(state, entity, client, player, character);
|
||||
}
|
||||
_ => disconnect = true,
|
||||
match msg {
|
||||
ClientMsg::RequestState(requested_state) => match requested_state {
|
||||
ClientState::Connected => disconnect = true, // Default state
|
||||
ClientState::Registered => match client.client_state {
|
||||
// Use ClientMsg::Register instead
|
||||
ClientState::Connected => client.error_state(RequestStateError::WrongMessage),
|
||||
ClientState::Registered => client.error_state(RequestStateError::Already),
|
||||
ClientState::Spectator | ClientState::Character
|
||||
=> client.allow_state(ClientState::Registered),
|
||||
},
|
||||
ClientState::Spectator => match requested_state {
|
||||
// Become Registered first
|
||||
ClientState::Connected => client.error_state(RequestStateError::Impossible),
|
||||
ClientState::Spectator => client.error_state(RequestStateError::Already),
|
||||
ClientState::Registered | ClientState::Character
|
||||
=> client.allow_state(ClientState::Spectator),
|
||||
},
|
||||
// Use ClientMsg::Character instead
|
||||
ClientState::Character => client.error_state(RequestStateError::WrongMessage),
|
||||
},
|
||||
ClientState::Connected => match msg {
|
||||
ClientMsg::Connect { .. } => disconnect = true, // Not allowed when already connected
|
||||
ClientMsg::Disconnect => disconnect = true,
|
||||
ClientMsg::Ping => client.postbox.send_message(ServerMsg::Pong),
|
||||
ClientMsg::Pong => {}
|
||||
ClientMsg::Chat(msg) => new_chat_msgs.push((entity, msg)),
|
||||
ClientMsg::PlayerAnimation(animation_history) => state.write_component(entity, animation_history),
|
||||
ClientMsg::PlayerPhysics { pos, vel, dir } => {
|
||||
ClientMsg::Register { player } => match client.client_state {
|
||||
ClientState::Connected => Self::initialize_player(state, entity, client, player),
|
||||
// Use RequestState instead (No need to send `player` again)
|
||||
_ => client.error_state(RequestStateError::Impossible),
|
||||
},
|
||||
ClientMsg::Character(character) => match client.client_state {
|
||||
// Become Registered first
|
||||
ClientState::Connected => client.error_state(RequestStateError::Impossible),
|
||||
ClientState::Registered | ClientState::Spectator =>
|
||||
Self::create_player_character(state, entity, client, character),
|
||||
ClientState::Character => client.error_state(RequestStateError::Already),
|
||||
},
|
||||
ClientMsg::Chat(msg) => match client.client_state {
|
||||
ClientState::Connected => client.error_state(RequestStateError::Impossible),
|
||||
ClientState::Registered
|
||||
| ClientState::Spectator
|
||||
| ClientState::Character => new_chat_msgs.push((entity, msg)),
|
||||
},
|
||||
ClientMsg::PlayerAnimation(animation_history) => match client.client_state {
|
||||
ClientState::Character => state.write_component(entity, animation_history),
|
||||
// Only characters can send animations
|
||||
_ => client.error_state(RequestStateError::Impossible),
|
||||
}
|
||||
ClientMsg::PlayerPhysics { pos, vel, dir } => match client.client_state {
|
||||
ClientState::Character => {
|
||||
state.write_component(entity, pos);
|
||||
state.write_component(entity, vel);
|
||||
state.write_component(entity, dir);
|
||||
}
|
||||
ClientMsg::TerrainChunkRequest { key } => {
|
||||
// Only characters send their position
|
||||
_ => client.error_state(RequestStateError::Impossible),
|
||||
},
|
||||
ClientMsg::TerrainChunkRequest { key } => match client.client_state {
|
||||
ClientState::Connected | ClientState::Registered => {
|
||||
client.error_state(RequestStateError::Impossible);
|
||||
}
|
||||
ClientState::Spectator | ClientState::Character => {
|
||||
match state.terrain().get_key(key) {
|
||||
Some(chunk) => {} /*client.postbox.send_message(ServerMsg::TerrainChunkUpdate {
|
||||
key,
|
||||
@ -261,6 +330,10 @@ impl Server {
|
||||
}
|
||||
}
|
||||
},
|
||||
// Always possible
|
||||
ClientMsg::Ping => client.postbox.send_message(ServerMsg::Pong),
|
||||
ClientMsg::Pong => {}
|
||||
ClientMsg::Disconnect => disconnect = true,
|
||||
}
|
||||
}
|
||||
} else if state.get_time() - client.last_ping > CLIENT_TIMEOUT || // Timeout
|
||||
@ -275,6 +348,7 @@ impl Server {
|
||||
|
||||
if disconnect {
|
||||
disconnected_clients.push(entity);
|
||||
client.postbox.send_message(ServerMsg::Disconnect);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
@ -288,13 +362,8 @@ impl Server {
|
||||
let argv = String::from(&msg[1..]);
|
||||
self.process_chat_cmd(entity, argv);
|
||||
} else {
|
||||
self.clients.notify_connected(ServerMsg::Chat(
|
||||
match self
|
||||
.state
|
||||
.ecs()
|
||||
.read_storage::<comp::Player>()
|
||||
.get(entity)
|
||||
{
|
||||
self.clients.notify_registered(ServerMsg::Chat(
|
||||
match self.state.ecs().read_storage::<comp::Player>().get(entity) {
|
||||
Some(player) => format!("[{}] {}", &player.alias, msg),
|
||||
None => format!("[<anon>] {}", msg),
|
||||
},
|
||||
@ -320,66 +389,39 @@ impl Server {
|
||||
}
|
||||
|
||||
/// Initialize a new client states with important information
|
||||
fn initialize_client(
|
||||
fn initialize_player(
|
||||
state: &mut State,
|
||||
entity: specs::Entity,
|
||||
client: &mut Client,
|
||||
player: comp::Player,
|
||||
character: Option<comp::Character>,
|
||||
) {
|
||||
// Save player metadata (for example the username)
|
||||
state.write_component(entity, player);
|
||||
|
||||
// Give the player it's character if he wants one
|
||||
// (Chat only clients don't need one for example)
|
||||
if let Some(character) = character {
|
||||
state.write_component(entity, character);
|
||||
|
||||
// Every character has to have these components
|
||||
state.write_component(entity, comp::phys::Pos(Vec3::zero()));
|
||||
state.write_component(entity, comp::phys::Vel(Vec3::zero()));
|
||||
state.write_component(entity, comp::phys::Dir(Vec3::unit_y()));
|
||||
// Make sure everything is accepted
|
||||
state.write_component(entity, comp::phys::ForceUpdate);
|
||||
|
||||
// Set initial animation
|
||||
state.write_component(entity, comp::AnimationHistory {
|
||||
last: None,
|
||||
current: Animation::Idle
|
||||
});
|
||||
}
|
||||
|
||||
client.state = ClientState::Connected;
|
||||
|
||||
// Return a handshake with the state of the current world
|
||||
// (All components Sphynx tracks)
|
||||
client.notify(ServerMsg::Handshake {
|
||||
ecs_state: state.ecs().gen_state_package(),
|
||||
player_entity: state
|
||||
.ecs()
|
||||
.uid_from_entity(entity)
|
||||
.unwrap()
|
||||
.into(),
|
||||
});
|
||||
|
||||
// Sync logical information other players have authority over, not the server
|
||||
for (other_entity, &uid, &animation_history) in (
|
||||
&state.ecs().entities(),
|
||||
&state.ecs().read_storage::<common::state::Uid>(),
|
||||
&state.ecs().read_storage::<comp::AnimationHistory>(),
|
||||
).join() {
|
||||
)
|
||||
.join()
|
||||
{
|
||||
// AnimationHistory
|
||||
client.postbox.send_message(ServerMsg::EntityAnimation {
|
||||
entity: uid.into(),
|
||||
animation_history: animation_history,
|
||||
});
|
||||
}
|
||||
|
||||
// Tell the client his request was successful
|
||||
client.allow_state(ClientState::Registered);
|
||||
}
|
||||
|
||||
/// Sync client states with the most up to date information
|
||||
fn sync_clients(&mut self) {
|
||||
// Sync 'logical' state using Sphynx
|
||||
self.clients.notify_connected(ServerMsg::EcsSync(self.state.ecs_mut().next_sync_package()));
|
||||
self.clients
|
||||
.notify_registered(ServerMsg::EcsSync(self.state.ecs_mut().next_sync_package()));
|
||||
|
||||
// Sync 'physical' state
|
||||
for (entity, &uid, &pos, &vel, &dir, force_update) in (
|
||||
@ -389,7 +431,9 @@ impl Server {
|
||||
&self.state.ecs().read_storage::<comp::phys::Vel>(),
|
||||
&self.state.ecs().read_storage::<comp::phys::Dir>(),
|
||||
self.state.ecs().read_storage::<comp::phys::ForceUpdate>().maybe(),
|
||||
).join() {
|
||||
)
|
||||
.join()
|
||||
{
|
||||
let msg = ServerMsg::EntityPhysics {
|
||||
entity: uid.into(),
|
||||
pos,
|
||||
@ -398,8 +442,8 @@ impl Server {
|
||||
};
|
||||
|
||||
match force_update {
|
||||
Some(_) => self.clients.notify_connected(msg),
|
||||
None => self.clients.notify_connected_except(entity, msg),
|
||||
Some(_) => self.clients.notify_ingame(msg),
|
||||
None => self.clients.notify_ingame_except(entity, msg),
|
||||
}
|
||||
}
|
||||
|
||||
@ -408,23 +452,30 @@ impl Server {
|
||||
&self.state.ecs().entities(),
|
||||
&self.state.ecs().read_storage::<Uid>(),
|
||||
&self.state.ecs().read_storage::<comp::AnimationHistory>(),
|
||||
).join() {
|
||||
)
|
||||
.join()
|
||||
{
|
||||
// Check if we need to sync
|
||||
if Some(animation_history.current) == animation_history.last {
|
||||
continue;
|
||||
}
|
||||
|
||||
self.clients.notify_connected_except(entity, ServerMsg::EntityAnimation {
|
||||
entity: uid.into(),
|
||||
animation_history,
|
||||
});
|
||||
self.clients.notify_ingame_except(
|
||||
entity,
|
||||
ServerMsg::EntityAnimation {
|
||||
entity: uid.into(),
|
||||
animation_history,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Update animation last/current state
|
||||
for (entity, mut animation_history) in (
|
||||
&self.state.ecs().entities(),
|
||||
&mut self.state.ecs().write_storage::<comp::AnimationHistory>()
|
||||
).join() {
|
||||
&mut self.state.ecs().write_storage::<comp::AnimationHistory>(),
|
||||
)
|
||||
.join()
|
||||
{
|
||||
animation_history.last = Some(animation_history.current);
|
||||
}
|
||||
|
||||
@ -468,6 +519,6 @@ impl Server {
|
||||
|
||||
impl Drop for Server {
|
||||
fn drop(&mut self) {
|
||||
self.clients.notify_connected(ServerMsg::Shutdown);
|
||||
self.clients.notify_registered(ServerMsg::Shutdown);
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,10 @@ use crate::{
|
||||
GlobalState, PlayState, PlayStateResult,
|
||||
};
|
||||
use client::{self, Client};
|
||||
use common::clock::Clock;
|
||||
use common::{
|
||||
clock::Clock,
|
||||
msg::ClientMsg,
|
||||
};
|
||||
use std::{cell::RefCell, rc::Rc, time::Duration};
|
||||
use ui::CharSelectionUi;
|
||||
use vek::*;
|
||||
@ -67,9 +70,10 @@ impl PlayState for CharSelectionState {
|
||||
global_state.singleplayer = None;
|
||||
return PlayStateResult::Pop;
|
||||
},
|
||||
ui::Event::Play => return PlayStateResult::Switch(
|
||||
Box::new(SessionState::new(&mut global_state.window, self.client.clone()))
|
||||
),
|
||||
ui::Event::Play => {
|
||||
self.client.borrow_mut().postbox.send_message(ClientMsg::Character(self.char_selection_ui.character));
|
||||
return PlayStateResult::Switch( Box::new(SessionState::new(&mut global_state.window, self.client.clone())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,21 @@ use crate::{
|
||||
ui::{self, ScaleMode, Ui},
|
||||
window::Window,
|
||||
};
|
||||
use common::assets;
|
||||
use common::{
|
||||
assets,
|
||||
comp::character::{
|
||||
Character,
|
||||
Race,
|
||||
Gender,
|
||||
Head,
|
||||
Chest,
|
||||
Belt,
|
||||
Pants,
|
||||
Hand,
|
||||
Foot,
|
||||
Weapon,
|
||||
}
|
||||
};
|
||||
use conrod_core::{
|
||||
color,
|
||||
color::TRANSPARENT,
|
||||
@ -65,8 +79,8 @@ widget_ids! {
|
||||
race_4,
|
||||
race_5,
|
||||
race_6,
|
||||
sex_1,
|
||||
sex_2,
|
||||
gender_1,
|
||||
gender_2,
|
||||
weapon_1,
|
||||
weapon_2,
|
||||
weapon_3,
|
||||
@ -315,34 +329,13 @@ enum CreationState {
|
||||
Weapon,
|
||||
Body(BodyPart),
|
||||
}
|
||||
enum Races {
|
||||
Human,
|
||||
Orc,
|
||||
Elf,
|
||||
Dwarf,
|
||||
Undead,
|
||||
Danari,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum BodyPart {
|
||||
SkinEyes,
|
||||
Hair,
|
||||
Accessories,
|
||||
}
|
||||
enum Sex {
|
||||
Male,
|
||||
Female,
|
||||
Undefined,
|
||||
}
|
||||
enum Weapons {
|
||||
Daggers,
|
||||
SwordShield,
|
||||
Sword,
|
||||
Axe,
|
||||
Hammer,
|
||||
Bow,
|
||||
Staff,
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
Logout,
|
||||
@ -360,11 +353,9 @@ pub struct CharSelectionUi {
|
||||
font_opensans: FontId,
|
||||
character_creation: bool,
|
||||
selected_char_no: Option<i32>,
|
||||
race: Races,
|
||||
sex: Sex,
|
||||
weapon: Weapons,
|
||||
creation_state: CreationState,
|
||||
character_name: String,
|
||||
pub character: Character,
|
||||
creation_state: CreationState,
|
||||
}
|
||||
|
||||
impl CharSelectionUi {
|
||||
@ -386,7 +377,8 @@ impl CharSelectionUi {
|
||||
};
|
||||
let font_opensans = load_font("/OpenSans-Regular.ttf", &mut ui);
|
||||
let font_metamorph = load_font("/Metamorphous-Regular.ttf", &mut ui);
|
||||
|
||||
|
||||
// TODO: Randomize initial values
|
||||
Self {
|
||||
ui,
|
||||
imgs,
|
||||
@ -396,9 +388,7 @@ impl CharSelectionUi {
|
||||
character_creation: false,
|
||||
selected_char_no: None,
|
||||
character_name: "Character Name".to_string(),
|
||||
race: Races::Human,
|
||||
sex: Sex::Male,
|
||||
weapon: Weapons::Sword,
|
||||
character: Character::random(),
|
||||
creation_state: CreationState::Race,
|
||||
}
|
||||
}
|
||||
@ -581,6 +571,7 @@ impl CharSelectionUi {
|
||||
.set(self.ids.create_button, ui_widgets)
|
||||
.was_clicked()
|
||||
{
|
||||
// TODO: Save character
|
||||
self.character_creation = false;
|
||||
}
|
||||
// Character Name Input
|
||||
@ -704,7 +695,7 @@ impl CharSelectionUi {
|
||||
.w_h(68.0, 68.0)
|
||||
.mid_left_of(self.ids.gender_bg)
|
||||
.set(self.ids.male, ui_widgets);
|
||||
if Button::image(if let Sex::Male = self.sex {
|
||||
if Button::image(if let Gender::Male = self.character.gender {
|
||||
self.imgs.icon_border_pressed
|
||||
} else {
|
||||
self.imgs.icon_border
|
||||
@ -712,17 +703,17 @@ impl CharSelectionUi {
|
||||
.middle_of(self.ids.male)
|
||||
.hover_image(self.imgs.icon_border_mo)
|
||||
.press_image(self.imgs.icon_border_press)
|
||||
.set(self.ids.sex_1, ui_widgets)
|
||||
.set(self.ids.gender_1, ui_widgets)
|
||||
.was_clicked()
|
||||
{
|
||||
self.sex = Sex::Male;
|
||||
self.character.gender = Gender::Male;
|
||||
}
|
||||
// Female
|
||||
Image::new(self.imgs.female)
|
||||
.w_h(68.0, 68.0)
|
||||
.right_from(self.ids.male, 16.0)
|
||||
.set(self.ids.female, ui_widgets);
|
||||
if Button::image(if let Sex::Female = self.sex {
|
||||
if Button::image(if let Gender::Female = self.character.gender {
|
||||
self.imgs.icon_border_pressed
|
||||
} else {
|
||||
self.imgs.icon_border
|
||||
@ -730,10 +721,10 @@ impl CharSelectionUi {
|
||||
.middle_of(self.ids.female)
|
||||
.hover_image(self.imgs.icon_border_mo)
|
||||
.press_image(self.imgs.icon_border_press)
|
||||
.set(self.ids.sex_2, ui_widgets)
|
||||
.set(self.ids.gender_2, ui_widgets)
|
||||
.was_clicked()
|
||||
{
|
||||
self.sex = Sex::Female;
|
||||
self.character.gender = Gender::Female;
|
||||
}
|
||||
// for alignment
|
||||
Rectangle::fill_with([458.0, 68.0], color::TRANSPARENT)
|
||||
@ -741,7 +732,7 @@ impl CharSelectionUi {
|
||||
.set(self.ids.races_bg, ui_widgets);
|
||||
// TODO: If races where in some sort of array format we could do this in a loop
|
||||
// Human
|
||||
Image::new(if let Sex::Male = self.sex {
|
||||
Image::new(if let Gender::Male = self.character.gender {
|
||||
self.imgs.human_m
|
||||
} else {
|
||||
self.imgs.human_f
|
||||
@ -749,7 +740,7 @@ impl CharSelectionUi {
|
||||
.w_h(68.0, 68.0)
|
||||
.mid_left_of(self.ids.races_bg)
|
||||
.set(self.ids.human, ui_widgets);
|
||||
if Button::image(if let Races::Human = self.race {
|
||||
if Button::image(if let Race::Human = self.character.race {
|
||||
self.imgs.icon_border_pressed
|
||||
} else {
|
||||
self.imgs.icon_border
|
||||
@ -760,11 +751,11 @@ impl CharSelectionUi {
|
||||
.set(self.ids.race_1, ui_widgets)
|
||||
.was_clicked()
|
||||
{
|
||||
self.race = Races::Human;
|
||||
self.character.race = Race::Human;
|
||||
}
|
||||
|
||||
// Orc
|
||||
Image::new(if let Sex::Male = self.sex {
|
||||
Image::new(if let Gender::Male = self.character.gender {
|
||||
self.imgs.orc_m
|
||||
} else {
|
||||
self.imgs.orc_f
|
||||
@ -772,7 +763,7 @@ impl CharSelectionUi {
|
||||
.w_h(68.0, 68.0)
|
||||
.right_from(self.ids.human, 10.0)
|
||||
.set(self.ids.orc, ui_widgets);
|
||||
if Button::image(if let Races::Orc = self.race {
|
||||
if Button::image(if let Race::Orc = self.character.race {
|
||||
self.imgs.icon_border_pressed
|
||||
} else {
|
||||
self.imgs.icon_border
|
||||
@ -783,10 +774,10 @@ impl CharSelectionUi {
|
||||
.set(self.ids.race_2, ui_widgets)
|
||||
.was_clicked()
|
||||
{
|
||||
self.race = Races::Orc;
|
||||
self.character.race = Race::Orc;
|
||||
}
|
||||
// Dwarf
|
||||
Image::new(if let Sex::Male = self.sex {
|
||||
Image::new(if let Gender::Male = self.character.gender {
|
||||
self.imgs.dwarf_m
|
||||
} else {
|
||||
self.imgs.dwarf_f
|
||||
@ -794,7 +785,7 @@ impl CharSelectionUi {
|
||||
.w_h(68.0, 68.0)
|
||||
.right_from(self.ids.human, 10.0 * 2.0 + 68.0)
|
||||
.set(self.ids.dwarf, ui_widgets);
|
||||
if Button::image(if let Races::Dwarf = self.race {
|
||||
if Button::image(if let Race::Dwarf = self.character.race {
|
||||
self.imgs.icon_border_pressed
|
||||
} else {
|
||||
self.imgs.icon_border
|
||||
@ -805,10 +796,10 @@ impl CharSelectionUi {
|
||||
.set(self.ids.race_3, ui_widgets)
|
||||
.was_clicked()
|
||||
{
|
||||
self.race = Races::Dwarf;
|
||||
self.character.race = Race::Dwarf;
|
||||
}
|
||||
// Elf
|
||||
Image::new(if let Sex::Male = self.sex {
|
||||
Image::new(if let Gender::Male = self.character.gender {
|
||||
self.imgs.elf_m
|
||||
} else {
|
||||
self.imgs.elf_f
|
||||
@ -816,7 +807,7 @@ impl CharSelectionUi {
|
||||
.w_h(68.0, 68.0)
|
||||
.right_from(self.ids.human, 10.0 * 3.0 + 68.0 * 2.0)
|
||||
.set(self.ids.elf, ui_widgets);
|
||||
if Button::image(if let Races::Elf = self.race {
|
||||
if Button::image(if let Race::Elf = self.character.race {
|
||||
self.imgs.icon_border_pressed
|
||||
} else {
|
||||
self.imgs.icon_border
|
||||
@ -827,10 +818,10 @@ impl CharSelectionUi {
|
||||
.set(self.ids.race_4, ui_widgets)
|
||||
.was_clicked()
|
||||
{
|
||||
self.race = Races::Elf;
|
||||
self.character.race = Race::Elf;
|
||||
}
|
||||
// Undead
|
||||
Image::new(if let Sex::Male = self.sex {
|
||||
Image::new(if let Gender::Male = self.character.gender {
|
||||
self.imgs.undead_m
|
||||
} else {
|
||||
self.imgs.undead_f
|
||||
@ -838,7 +829,7 @@ impl CharSelectionUi {
|
||||
.w_h(68.0, 68.0)
|
||||
.right_from(self.ids.human, 10.0 * 4.0 + 68.0 * 3.0)
|
||||
.set(self.ids.undead, ui_widgets);
|
||||
if Button::image(if let Races::Undead = self.race {
|
||||
if Button::image(if let Race::Undead = self.character.race {
|
||||
self.imgs.icon_border_pressed
|
||||
} else {
|
||||
self.imgs.icon_border
|
||||
@ -849,17 +840,17 @@ impl CharSelectionUi {
|
||||
.set(self.ids.race_5, ui_widgets)
|
||||
.was_clicked()
|
||||
{
|
||||
self.race = Races::Undead;
|
||||
self.character.race = Race::Undead;
|
||||
}
|
||||
// Danari
|
||||
Image::new(if let Sex::Male = self.sex {
|
||||
Image::new(if let Gender::Male = self.character.gender {
|
||||
self.imgs.danari_m
|
||||
} else {
|
||||
self.imgs.danari_f
|
||||
})
|
||||
.right_from(self.ids.human, 10.0 * 5.0 + 68.0 * 4.0)
|
||||
.set(self.ids.danari, ui_widgets);
|
||||
if Button::image(if let Races::Danari = self.race {
|
||||
if Button::image(if let Race::Danari = self.character.race {
|
||||
self.imgs.icon_border_pressed
|
||||
} else {
|
||||
self.imgs.icon_border
|
||||
@ -871,7 +862,7 @@ impl CharSelectionUi {
|
||||
.set(self.ids.race_6, ui_widgets)
|
||||
.was_clicked()
|
||||
{
|
||||
self.race = Races::Danari;
|
||||
self.character.race = Race::Danari;
|
||||
}
|
||||
|
||||
// Description Headline and Text
|
||||
@ -932,13 +923,13 @@ impl CharSelectionUi {
|
||||
\n\
|
||||
Outcast communities consisting of these Blessed Danari have formed all over the land.";
|
||||
|
||||
let (race_str, race_desc) = match self.race {
|
||||
Races::Human => ("Humans", HUMAN_DESC),
|
||||
Races::Orc => ("Orcs", ORC_DESC),
|
||||
Races::Dwarf => ("Dwarves", DWARF_DESC),
|
||||
Races::Undead => ("Undead", UNDEAD_DESC),
|
||||
Races::Elf => ("Elves", ELF_DESC),
|
||||
Races::Danari => ("Danari", DANARI_DESC),
|
||||
let (race_str, race_desc) = match self.character.race {
|
||||
Race::Human => ("Humans", HUMAN_DESC),
|
||||
Race::Orc => ("Orcs", ORC_DESC),
|
||||
Race::Dwarf => ("Dwarves", DWARF_DESC),
|
||||
Race::Undead => ("Undead", UNDEAD_DESC),
|
||||
Race::Elf => ("Elves", ELF_DESC),
|
||||
Race::Danari => ("Danari", DANARI_DESC),
|
||||
};
|
||||
Text::new(race_str)
|
||||
.mid_top_with_margin_on(self.ids.creation_window, 370.0)
|
||||
@ -972,7 +963,7 @@ impl CharSelectionUi {
|
||||
.w_h(60.0, 60.0)
|
||||
.mid_left_of(self.ids.weapon_bg)
|
||||
.set(self.ids.sword_shield, ui_widgets);
|
||||
if Button::image(if let Weapons::SwordShield = self.weapon {
|
||||
if Button::image(if let Weapon::SwordShield = self.character.weapon {
|
||||
self.imgs.icon_border_pressed
|
||||
} else {
|
||||
self.imgs.icon_border
|
||||
@ -983,7 +974,7 @@ impl CharSelectionUi {
|
||||
.set(self.ids.weapon_1, ui_widgets)
|
||||
.was_clicked()
|
||||
{
|
||||
self.weapon = Weapons::SwordShield;
|
||||
self.character.weapon = Weapon::SwordShield;
|
||||
}
|
||||
|
||||
// Daggers
|
||||
@ -991,7 +982,7 @@ impl CharSelectionUi {
|
||||
.w_h(60.0, 60.0)
|
||||
.right_from(self.ids.sword_shield, 8.0)
|
||||
.set(self.ids.daggers, ui_widgets);
|
||||
if Button::image(if let Weapons::Daggers = self.weapon {
|
||||
if Button::image(if let Weapon::Daggers = self.character.weapon {
|
||||
self.imgs.icon_border_pressed
|
||||
} else {
|
||||
self.imgs.icon_border
|
||||
@ -1002,7 +993,7 @@ impl CharSelectionUi {
|
||||
.set(self.ids.weapon_2, ui_widgets)
|
||||
.was_clicked()
|
||||
{
|
||||
self.weapon = Weapons::Daggers;
|
||||
self.character.weapon = Weapon::Daggers;
|
||||
}
|
||||
|
||||
// Sword
|
||||
@ -1010,7 +1001,7 @@ impl CharSelectionUi {
|
||||
.w_h(60.0, 60.0)
|
||||
.right_from(self.ids.sword_shield, 8.0 * 2.0 + 60.0 * 1.0)
|
||||
.set(self.ids.sword, ui_widgets);
|
||||
if Button::image(if let Weapons::Sword = self.weapon {
|
||||
if Button::image(if let Weapon::Sword = self.character.weapon {
|
||||
self.imgs.icon_border_pressed
|
||||
} else {
|
||||
self.imgs.icon_border
|
||||
@ -1021,14 +1012,14 @@ impl CharSelectionUi {
|
||||
.set(self.ids.weapon_3, ui_widgets)
|
||||
.was_clicked()
|
||||
{
|
||||
self.weapon = Weapons::Sword;
|
||||
self.character.weapon = Weapon::Sword;
|
||||
}
|
||||
// Axe
|
||||
Image::new(self.imgs.axe)
|
||||
.w_h(60.0, 60.0)
|
||||
.right_from(self.ids.sword_shield, 8.0 * 3.0 + 60.0 * 2.0)
|
||||
.set(self.ids.axe, ui_widgets);
|
||||
if Button::image(if let Weapons::Axe = self.weapon {
|
||||
if Button::image(if let Weapon::Axe = self.character.weapon {
|
||||
self.imgs.icon_border_pressed
|
||||
} else {
|
||||
self.imgs.icon_border
|
||||
@ -1039,14 +1030,14 @@ impl CharSelectionUi {
|
||||
.set(self.ids.weapon_4, ui_widgets)
|
||||
.was_clicked()
|
||||
{
|
||||
self.weapon = Weapons::Axe;
|
||||
self.character.weapon = Weapon::Axe;
|
||||
}
|
||||
// Hammer
|
||||
Image::new(self.imgs.hammer)
|
||||
.w_h(60.0, 60.0)
|
||||
.right_from(self.ids.sword_shield, 8.0 * 4.0 + 60.0 * 3.0)
|
||||
.set(self.ids.hammer, ui_widgets);
|
||||
if Button::image(if let Weapons::Hammer = self.weapon {
|
||||
if Button::image(if let Weapon::Hammer = self.character.weapon {
|
||||
self.imgs.icon_border_pressed
|
||||
} else {
|
||||
self.imgs.icon_border
|
||||
@ -1057,14 +1048,14 @@ impl CharSelectionUi {
|
||||
.set(self.ids.weapon_5, ui_widgets)
|
||||
.was_clicked()
|
||||
{
|
||||
self.weapon = Weapons::Hammer;
|
||||
self.character.weapon = Weapon::Hammer;
|
||||
}
|
||||
// Bow
|
||||
Image::new(self.imgs.bow)
|
||||
.w_h(60.0, 60.0)
|
||||
.right_from(self.ids.sword_shield, 8.0 * 5.0 + 60.0 * 4.0)
|
||||
.set(self.ids.bow, ui_widgets);
|
||||
if Button::image(if let Weapons::Bow = self.weapon {
|
||||
if Button::image(if let Weapon::Bow = self.character.weapon {
|
||||
self.imgs.icon_border_pressed
|
||||
} else {
|
||||
self.imgs.icon_border
|
||||
@ -1075,14 +1066,14 @@ impl CharSelectionUi {
|
||||
.set(self.ids.weapon_6, ui_widgets)
|
||||
.was_clicked()
|
||||
{
|
||||
self.weapon = Weapons::Bow;
|
||||
self.character.weapon = Weapon::Bow;
|
||||
}
|
||||
// Staff
|
||||
Image::new(self.imgs.staff)
|
||||
.w_h(60.0, 60.0)
|
||||
.right_from(self.ids.sword_shield, 8.0 * 6.0 + 60.0 * 5.0)
|
||||
.set(self.ids.staff, ui_widgets);
|
||||
if Button::image(if let Weapons::Staff = self.weapon {
|
||||
if Button::image(if let Weapon::Staff = self.character.weapon {
|
||||
self.imgs.icon_border_pressed
|
||||
} else {
|
||||
self.imgs.icon_border
|
||||
@ -1093,7 +1084,7 @@ impl CharSelectionUi {
|
||||
.set(self.ids.weapon_7, ui_widgets)
|
||||
.was_clicked()
|
||||
{
|
||||
self.weapon = Weapons::Staff;
|
||||
self.character.weapon = Weapon::Staff;
|
||||
}
|
||||
|
||||
// TODO: Load these from files (or from the server???)
|
||||
@ -1105,14 +1096,14 @@ impl CharSelectionUi {
|
||||
const BOW_DESC: &str = " MISSING ";
|
||||
const STAFF_DESC: &str = " MISSING ";
|
||||
|
||||
let (weapon_str, weapon_desc) = match self.weapon {
|
||||
Weapons::SwordShield => ("Sword and Shield", SWORDSHIELD_DESC),
|
||||
Weapons::Daggers => ("Daggers", DAGGERS_DESC),
|
||||
Weapons::Sword => ("Sword", SWORD_DESC),
|
||||
Weapons::Axe => ("Axe", AXE_DESC),
|
||||
Weapons::Hammer => ("Hammer", HAMMER_DESC),
|
||||
Weapons::Bow => ("Bow", BOW_DESC),
|
||||
Weapons::Staff => ("Staff", STAFF_DESC),
|
||||
let (weapon_str, weapon_desc) = match self.character.weapon {
|
||||
Weapon::SwordShield => ("Sword and Shield", SWORDSHIELD_DESC),
|
||||
Weapon::Daggers => ("Daggers", DAGGERS_DESC),
|
||||
Weapon::Sword => ("Sword", SWORD_DESC),
|
||||
Weapon::Axe => ("Axe", AXE_DESC),
|
||||
Weapon::Hammer => ("Hammer", HAMMER_DESC),
|
||||
Weapon::Bow => ("Bow", BOW_DESC),
|
||||
Weapon::Staff => ("Staff", STAFF_DESC),
|
||||
};
|
||||
Text::new(weapon_str)
|
||||
.mid_top_with_margin_on(self.ids.creation_window, 370.0)
|
||||
@ -1452,7 +1443,7 @@ impl CharSelectionUi {
|
||||
.was_clicked()
|
||||
{};
|
||||
// Beard -> Only active when "male" was chosen
|
||||
if let Sex::Male = self.sex {
|
||||
if let Gender::Male = self.character.gender {
|
||||
Text::new("Beard Style")
|
||||
.mid_top_with_margin_on(self.ids.hair_window, 340.0)
|
||||
.color(TEXT_COLOR)
|
||||
@ -1483,8 +1474,8 @@ impl CharSelectionUi {
|
||||
// Color -> Picker
|
||||
// Brightness -> Slider
|
||||
BodyPart::Accessories => {
|
||||
match self.race {
|
||||
Races::Human => {
|
||||
match self.character.race {
|
||||
Race::Human => {
|
||||
Text::new("Head Band")
|
||||
.mid_top_with_margin_on(self.ids.accessories_window, 60.0)
|
||||
.color(TEXT_COLOR)
|
||||
@ -1550,7 +1541,7 @@ impl CharSelectionUi {
|
||||
.font_size(14)
|
||||
.set(self.ids.warpaint_slider_text, ui_widgets);
|
||||
} // Human
|
||||
Races::Orc => {
|
||||
Race::Orc => {
|
||||
Text::new("Head Band")
|
||||
.mid_top_with_margin_on(self.ids.accessories_window, 60.0)
|
||||
.color(TEXT_COLOR)
|
||||
@ -1616,7 +1607,7 @@ impl CharSelectionUi {
|
||||
.font_size(14)
|
||||
.set(self.ids.warpaint_slider_text, ui_widgets);
|
||||
} // Orc
|
||||
Races::Elf => {
|
||||
Race::Elf => {
|
||||
Text::new("Tribe Markings")
|
||||
.mid_top_with_margin_on(self.ids.accessories_window, 60.0)
|
||||
.color(TEXT_COLOR)
|
||||
@ -1682,7 +1673,7 @@ impl CharSelectionUi {
|
||||
.font_size(14)
|
||||
.set(self.ids.warpaint_slider_text, ui_widgets);
|
||||
} // Elf
|
||||
Races::Dwarf => {
|
||||
Race::Dwarf => {
|
||||
Text::new("War Paint")
|
||||
.mid_top_with_margin_on(self.ids.accessories_window, 60.0)
|
||||
.color(TEXT_COLOR)
|
||||
@ -1748,7 +1739,7 @@ impl CharSelectionUi {
|
||||
.font_size(14)
|
||||
.set(self.ids.warpaint_slider_text, ui_widgets);
|
||||
} // Dwarf
|
||||
Races::Undead => {
|
||||
Race::Undead => {
|
||||
Text::new("Teeth")
|
||||
.mid_top_with_margin_on(self.ids.accessories_window, 60.0)
|
||||
.color(TEXT_COLOR)
|
||||
@ -1814,7 +1805,7 @@ impl CharSelectionUi {
|
||||
.font_size(14)
|
||||
.set(self.ids.warpaint_slider_text, ui_widgets);
|
||||
} // Undead
|
||||
Races::Danari => {
|
||||
Race::Danari => {
|
||||
Text::new("Horns")
|
||||
.mid_top_with_margin_on(self.ids.accessories_window, 60.0)
|
||||
.color(TEXT_COLOR)
|
||||
|
@ -22,10 +22,10 @@ pub struct ClientInit {
|
||||
impl ClientInit {
|
||||
pub fn new(
|
||||
connection_args: (String, u16, bool),
|
||||
client_args: (comp::Player, Option<comp::Character>, u64),
|
||||
client_args: (comp::Player, u64),
|
||||
) -> Self {
|
||||
let (server_address, default_port, prefer_ipv6) = connection_args;
|
||||
let (player, character, view_distance) = client_args;
|
||||
let (player, view_distance) = client_args;
|
||||
|
||||
let (tx, rx) = channel();
|
||||
|
||||
@ -44,8 +44,9 @@ impl ClientInit {
|
||||
let mut last_err = None;
|
||||
|
||||
for socket_addr in first_addrs.into_iter().chain(second_addrs) {
|
||||
match Client::new(socket_addr, player.clone(), character, view_distance) {
|
||||
Ok(client) => {
|
||||
match Client::new(socket_addr, view_distance) {
|
||||
Ok(mut client) => {
|
||||
client.register(player);
|
||||
let _ = tx.send(Ok(client));
|
||||
return;
|
||||
}
|
||||
|
@ -98,7 +98,6 @@ impl PlayState for MainMenuState {
|
||||
(server_address, DEFAULT_PORT, false),
|
||||
(
|
||||
comp::Player::new(username.clone()),
|
||||
Some(comp::Character::test()),
|
||||
300,
|
||||
),
|
||||
)));
|
||||
|
@ -6,7 +6,19 @@ use specs::{Entity as EcsEntity, Component, VecStorage, Join};
|
||||
use vek::*;
|
||||
use client::Client;
|
||||
use common::{
|
||||
comp,
|
||||
comp::{
|
||||
self,
|
||||
character::{
|
||||
Character,
|
||||
Head,
|
||||
Chest,
|
||||
Belt,
|
||||
Pants,
|
||||
Hand,
|
||||
Foot,
|
||||
Weapon,
|
||||
}
|
||||
},
|
||||
figure::Segment,
|
||||
msg,
|
||||
assets,
|
||||
@ -35,58 +47,138 @@ use crate::{
|
||||
mesh::Meshable,
|
||||
};
|
||||
|
||||
pub struct Figures {
|
||||
test_model: Model<FigurePipeline>,
|
||||
pub struct FigureCache {
|
||||
models: HashMap<Character, (Model<FigurePipeline>, u64)>,
|
||||
states: HashMap<EcsEntity, FigureState<CharacterSkeleton>>,
|
||||
}
|
||||
|
||||
impl Figures {
|
||||
pub fn new(renderer: &mut Renderer) -> Self {
|
||||
// TODO: Make a proper asset loading system
|
||||
fn load_segment(filename: &'static str) -> Segment {
|
||||
let fullpath: String = ["/voxygen/voxel/", filename].concat();
|
||||
Segment::from(dot_vox::load_bytes(
|
||||
assets::load(fullpath.as_str())
|
||||
.expect("Error loading file")
|
||||
.as_slice(),
|
||||
).unwrap())
|
||||
}
|
||||
|
||||
let bone_meshes = [
|
||||
|
||||
Some(load_segment("head.vox").generate_mesh(Vec3::new(-3.5, -7.0, -6.0))),
|
||||
Some(load_segment("chest.vox").generate_mesh(Vec3::new(-3.0, -6.0, 0.0))),
|
||||
Some(load_segment("belt.vox").generate_mesh(Vec3::new(-3.0, -5.0, 0.0))),
|
||||
Some(load_segment("pants.vox").generate_mesh(Vec3::new(-3.0, -5.0, 0.0))),
|
||||
Some(load_segment("hand.vox").generate_mesh(Vec3::new(0.0, -2.0, -6.0))),
|
||||
Some(load_segment("hand.vox").generate_mesh(Vec3::new(0.0, -2.0, -6.0))),
|
||||
Some(load_segment("foot.vox").generate_mesh(Vec3::new(-4.0, -2.5, -6.0))),
|
||||
Some(load_segment("foot.vox").generate_mesh(Vec3::new(-4.0, -2.5, -6.0))),
|
||||
Some(load_segment("sword.vox").generate_mesh(Vec3::new(0.0, -0.0, 0.0))),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
];
|
||||
|
||||
let mut mesh = Mesh::new();
|
||||
bone_meshes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, bm)| bm.as_ref().map(|bm| (i, bm)))
|
||||
.for_each(|(i, bone_mesh)| {
|
||||
mesh.push_mesh_map(bone_mesh, |vert| vert.with_bone_idx(i as u8))
|
||||
});
|
||||
|
||||
impl FigureCache {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
test_model: renderer.create_model(&mesh).unwrap(),
|
||||
models: HashMap::new(),
|
||||
states: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_or_create_model<'a>(
|
||||
models: &'a mut HashMap<Character, (Model<FigurePipeline>, u64)>,
|
||||
renderer: &mut Renderer,
|
||||
tick: u64,
|
||||
character: Character)
|
||||
-> &'a (Model<FigurePipeline>, u64) {
|
||||
match models.get_mut(&character) {
|
||||
Some((model, last_used)) => {
|
||||
*last_used = tick;
|
||||
}
|
||||
None => {
|
||||
models.insert(character, ({
|
||||
let bone_meshes = [
|
||||
Some(Self::load_head(character.head)),
|
||||
Some(Self::load_chest(character.chest)),
|
||||
Some(Self::load_belt(character.belt)),
|
||||
Some(Self::load_pants(character.pants)),
|
||||
Some(Self::load_left_hand(character.hand)),
|
||||
Some(Self::load_right_hand(character.hand)),
|
||||
Some(Self::load_left_foot(character.foot)),
|
||||
Some(Self::load_right_foot(character.foot)),
|
||||
Some(Self::load_weapon(character.weapon)),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
];
|
||||
|
||||
let mut mesh = Mesh::new();
|
||||
bone_meshes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, bm)| bm.as_ref().map(|bm| (i, bm)))
|
||||
.for_each(|(i, bone_mesh)| {
|
||||
mesh.push_mesh_map(bone_mesh, |vert| vert.with_bone_idx(i as u8))
|
||||
});
|
||||
|
||||
renderer.create_model(&mesh).unwrap()
|
||||
}, tick));
|
||||
}
|
||||
}
|
||||
|
||||
&models[&character]
|
||||
}
|
||||
|
||||
pub fn clean(&mut self, tick: u64) {
|
||||
// TODO: Don't hard-code this
|
||||
self.models.retain(|_, (_, last_used)| *last_used + 60 > tick);
|
||||
}
|
||||
|
||||
fn load_mesh(filename: &'static str, position: Vec3<f32>) -> Mesh<FigurePipeline> {
|
||||
let fullpath: String = ["/voxygen/voxel/", filename].concat();
|
||||
Segment::from(dot_vox::load_bytes(
|
||||
assets::load(fullpath.as_str())
|
||||
.expect("Error loading file")
|
||||
.as_slice(),
|
||||
).unwrap())
|
||||
.generate_mesh(position)
|
||||
}
|
||||
|
||||
fn load_head(head: Head) -> Mesh<FigurePipeline> {
|
||||
Self::load_mesh(match head {
|
||||
Head::DefaultHead => "head.vox",
|
||||
}, Vec3::new(-3.5, -7.0, -6.0))
|
||||
}
|
||||
|
||||
fn load_chest(chest: Chest) -> Mesh<FigurePipeline> {
|
||||
Self::load_mesh(match chest {
|
||||
Chest::DefaultChest => "chest.vox",
|
||||
}, Vec3::new(-3.0, -6.0, 0.0))
|
||||
}
|
||||
|
||||
fn load_belt(belt: Belt) -> Mesh<FigurePipeline> {
|
||||
Self::load_mesh(match belt {
|
||||
Belt::DefaultBelt => "belt.vox",
|
||||
}, Vec3::new(-3.0, -5.0, 0.0))
|
||||
}
|
||||
|
||||
fn load_pants(pants: Pants) -> Mesh<FigurePipeline> {
|
||||
Self::load_mesh(match pants {
|
||||
Pants::DefaultPants => "pants.vox",
|
||||
}, Vec3::new(-3.0, -5.0, 0.0))
|
||||
}
|
||||
|
||||
fn load_left_hand(hand: Hand) -> Mesh<FigurePipeline> {
|
||||
Self::load_mesh(match hand {
|
||||
Hand::DefaultHand => "hand.vox",
|
||||
}, Vec3::new(0.0, -2.0, -6.0))
|
||||
}
|
||||
|
||||
fn load_right_hand(hand: Hand) -> Mesh<FigurePipeline> {
|
||||
Self::load_mesh(match hand {
|
||||
Hand::DefaultHand => "hand.vox",
|
||||
}, Vec3::new(0.0, -2.0, -6.0))
|
||||
}
|
||||
|
||||
fn load_left_foot(foot: Foot) -> Mesh<FigurePipeline> {
|
||||
Self::load_mesh(match foot {
|
||||
Foot::DefaultFoot => "foot.vox",
|
||||
}, Vec3::new(-4.0, -2.5, -6.0))
|
||||
}
|
||||
|
||||
fn load_right_foot(foot: Foot) -> Mesh<FigurePipeline> {
|
||||
Self::load_mesh(match foot {
|
||||
Foot::DefaultFoot => "foot.vox",
|
||||
}, Vec3::new(-4.0, -2.5, -6.0))
|
||||
}
|
||||
|
||||
fn load_weapon(weapon: Weapon) -> Mesh<FigurePipeline> {
|
||||
Self::load_mesh(match weapon {
|
||||
Weapon::Sword => "sword.vox",
|
||||
// TODO actually match against other weapons and set the right model
|
||||
_ => "sword.vox",
|
||||
}, Vec3::new(0.0, 0.0, 0.0))
|
||||
}
|
||||
|
||||
pub fn maintain(&mut self, renderer: &mut Renderer, client: &mut Client) {
|
||||
let time = client.state().get_time();
|
||||
let ecs = client.state_mut().ecs_mut();
|
||||
@ -114,10 +206,19 @@ impl Figures {
|
||||
self.states.retain(|entity, _| ecs.entities().is_alive(*entity));
|
||||
}
|
||||
|
||||
pub fn render(&self, renderer: &mut Renderer, client: &Client, globals: &Consts<Globals>) {
|
||||
for state in self.states.values() {
|
||||
pub fn render(&mut self, renderer: &mut Renderer, client: &mut Client, globals: &Consts<Globals>) {
|
||||
let tick = client.get_tick();
|
||||
let ecs = client.state().ecs();
|
||||
let models = &mut self.models;
|
||||
|
||||
for (entity, &character) in (
|
||||
&ecs.entities(),
|
||||
&ecs.read_storage::<comp::Character>(),
|
||||
).join() {
|
||||
let model = Self::get_or_create_model(models, renderer, tick, character);
|
||||
let state = self.states.get(&entity).unwrap();
|
||||
renderer.render_figure(
|
||||
&self.test_model,
|
||||
&model.0,
|
||||
globals,
|
||||
&state.locals,
|
||||
&state.bone_consts,
|
||||
|
@ -21,15 +21,14 @@ use crate::{
|
||||
create_skybox_mesh,
|
||||
},
|
||||
window::Event,
|
||||
mesh::Meshable,
|
||||
anim::{
|
||||
mesh::Meshable, anim::{
|
||||
Animation,
|
||||
character::{CharacterSkeleton, RunAnimation},
|
||||
},
|
||||
};
|
||||
use self::{
|
||||
camera::Camera,
|
||||
figure::Figures,
|
||||
figure::FigureCache,
|
||||
terrain::Terrain,
|
||||
};
|
||||
|
||||
@ -47,7 +46,8 @@ pub struct Scene {
|
||||
|
||||
skybox: Skybox,
|
||||
terrain: Terrain,
|
||||
figures: Figures,
|
||||
|
||||
figure_cache: FigureCache,
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
@ -70,7 +70,7 @@ impl Scene {
|
||||
.unwrap(),
|
||||
},
|
||||
terrain: Terrain::new(),
|
||||
figures: Figures::new(renderer),
|
||||
figure_cache: FigureCache::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,7 +112,7 @@ impl Scene {
|
||||
.state()
|
||||
.ecs()
|
||||
.read_storage::<comp::phys::Pos>()
|
||||
.get(client.player())
|
||||
.get(client.entity())
|
||||
.map(|pos| pos.0)
|
||||
.unwrap_or(Vec3::zero());
|
||||
|
||||
@ -134,13 +134,18 @@ impl Scene {
|
||||
)])
|
||||
.expect("Failed to update global constants");
|
||||
|
||||
// Maintain the terrain and figures
|
||||
// Maintain the terrain
|
||||
self.terrain.maintain(renderer, client);
|
||||
self.figures.maintain(renderer, client);
|
||||
|
||||
// Maintain the figures
|
||||
self.figure_cache.maintain(renderer, client);
|
||||
|
||||
// Remove unused figures
|
||||
self.figure_cache.clean(client.get_tick());
|
||||
}
|
||||
|
||||
/// Render the scene using the provided `Renderer`
|
||||
pub fn render(&self, renderer: &mut Renderer, client: &Client) {
|
||||
pub fn render(&mut self, renderer: &mut Renderer, client: &mut Client) {
|
||||
// Render the skybox first (it appears over everything else so must be rendered first)
|
||||
renderer.render_skybox(
|
||||
&self.skybox.model,
|
||||
@ -150,6 +155,6 @@ impl Scene {
|
||||
|
||||
// Render terrain and figures
|
||||
self.terrain.render(renderer, &self.globals);
|
||||
self.figures.render(renderer, client, &self.globals);
|
||||
self.figure_cache.render(renderer, client, &self.globals);
|
||||
}
|
||||
}
|
||||
|
@ -68,6 +68,9 @@ impl SessionState {
|
||||
client::Event::Chat(msg) => {
|
||||
self.hud.new_message(msg);
|
||||
}
|
||||
client::Event::Disconnect => {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,7 +90,7 @@ impl SessionState {
|
||||
renderer.clear(BG_COLOR);
|
||||
|
||||
// Render the screen using the global renderer
|
||||
self.scene.render(renderer, &self.client.borrow());
|
||||
self.scene.render(renderer, &mut self.client.borrow_mut());
|
||||
// Draw the UI to the screen
|
||||
self.hud.render(renderer);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user