Implement ClientStates

Former-commit-id: f6167fd6de6fd1a3309224409cac77193be982e2
This commit is contained in:
timokoesters 2019-04-19 21:32:47 +02:00
parent 802c484a38
commit 794b9cafad
9 changed files with 193 additions and 140 deletions

View File

@ -5,6 +5,7 @@ pub mod input;
// Reexports
pub use specs::Entity as EcsEntity;
pub use specs::join::Join;
pub use crate::{
error::Error,
input::Input,
@ -23,7 +24,7 @@ use common::{
state::State,
terrain::TerrainChunk,
net::PostBox,
msg::{ClientMsg, ServerMsg},
msg::{ClientState, ClientMsg, ServerMsg},
};
const SERVER_TIMEOUT: f64 = 20.0; // Seconds
@ -33,14 +34,15 @@ pub enum Event {
}
pub struct Client {
client_state: 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>>,
@ -55,24 +57,24 @@ impl Client {
view_distance: u64,
) -> Result<Self, Error> {
let mut client_state = ClientState::Disconnected;
let mut postbox = PostBox::to(addr)?;
// Send connection request
postbox.send_message(ClientMsg::Connect {
player,
});
postbox.send_message(ClientMsg::Connect { player });
// 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, player_entity) = match postbox.next_message() {
Some(ServerMsg::InitialSync { ecs_state, player_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)?;
let player_entity = state.ecs().entity_from_uid(player_entity_uid).ok_or(Error::ServerWentMad)?;
(state, player_entity)
},
_ => return Err(Error::ServerWentMad),
};
Ok(Self {
client_state,
thread_pool: threadpool::Builder::new()
.thread_name("veloren-worker".into())
.build(),
@ -82,7 +84,7 @@ impl Client {
tick: 0,
state,
player,
entity: player_entity,
view_distance,
pending_chunks: HashSet::new(),
@ -103,10 +105,10 @@ impl Client {
#[allow(dead_code)]
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.
@ -146,7 +148,7 @@ impl Client {
println!("Chunk at {:?}", k);
});
self.state.write_component(self.player, comp::Control {
self.state.write_component(self.entity, comp::Control {
move_dir: input.move_dir,
});
@ -155,9 +157,9 @@ impl Client {
// 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 });
@ -166,14 +168,14 @@ impl Client {
}
// 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));
}
}
// 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 {
@ -213,12 +215,12 @@ 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::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) => {
@ -238,6 +240,18 @@ impl Client {
self.state.insert_chunk(key, *chunk);
self.pending_chunks.remove(&key);
},
ServerMsg::StateAnswer(Ok(state)) => {
println!("ok state: {:?}", state);
self.client_state = state;
},
ServerMsg::StateAnswer(Err((error, state))) => {
println!("err state: {:?}", state);
self.client_state = state;
},
ServerMsg::ForceState { state } => {
println!("forced state: {:?}", state);
self.client_state = state;
},
}
}
} else if let Some(err) = self.postbox.error() {

View File

@ -1,15 +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 {
character: comp::Character,
},
Connect { player: comp::Player },
Character(comp::Character),
RequestState(ClientState),
Ping,
Pong,
Chat(String),

View File

@ -3,6 +3,13 @@ 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, PartialEq, Serialize, Deserialize)]
pub enum ClientState {
Disconnected,
Spectator,
Character,
}

View File

@ -3,15 +3,25 @@ use crate::{
comp,
terrain::TerrainChunk,
};
use super::EcsPacket;
use super::{EcsPacket, ClientState};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RequestStateError {
Denied,
Already,
Impossible,
}
#[derive(Clone, Serialize, Deserialize)]
pub enum ServerMsg {
Handshake {
ecs_state: sphynx::StatePackage<EcsPacket>,
player_entity: u64,
StateAnswer(Result<ClientState, (RequestStateError, ClientState)>),
ForceState {
state: ClientState,
},
InitialSync {
ecs_state: sphynx::StatePackage<EcsPacket>,
player_entity_uid: u64,
},
Shutdown,
Ping,
Pong,
Chat(String),
@ -31,4 +41,5 @@ pub enum ServerMsg {
key: Vec3<i32>,
chunk: Box<TerrainChunk>,
},
Shutdown,
}

View File

@ -2,19 +2,13 @@ use std::collections::HashMap;
use specs::Entity as EcsEntity;
use common::{
comp,
msg::{ServerMsg, ClientMsg},
msg::{ServerMsg, ClientMsg, ClientState},
net::PostBox,
};
use crate::Error;
#[derive(PartialEq)]
pub enum ClientState {
Connecting,
Connected,
}
pub struct Client {
pub state: ClientState,
pub client_state: ClientState,
pub postbox: PostBox<ServerMsg, ClientMsg>,
pub last_ping: f64,
}
@ -52,7 +46,7 @@ impl Clients {
pub fn notify_connected(&mut self, msg: ServerMsg) {
for client in self.clients.values_mut() {
if client.state == ClientState::Connected {
if client.client_state != ClientState::Disconnected {
client.notify(msg.clone());
}
}
@ -60,7 +54,7 @@ impl Clients {
pub fn notify_connected_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::Disconnected && *entity != except_entity {
client.notify(msg.clone());
}
}

View File

@ -8,10 +8,10 @@ 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},
msg::{ClientState, ClientMsg, ServerMsg, RequestStateError},
net::PostOffice,
state::{State, Uid},
terrain::TerrainChunk,
@ -115,7 +115,7 @@ impl Server {
.with(character)
}
pub fn create_player_character(state: &mut State, entity: EcsEntity, character: comp::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()));
@ -128,6 +128,10 @@ impl Server {
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
@ -171,10 +175,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()
{
@ -216,7 +217,7 @@ impl Server {
self.clients.add(
entity,
Client {
state: ClientState::Connecting,
client_state: ClientState::Disconnected,
postbox,
last_ping: self.state.get_time(),
},
@ -247,27 +248,62 @@ impl Server {
// Process incoming messages
for msg in new_msgs {
match client.state {
ClientState::Connecting => match msg {
ClientMsg::Connect { player } => {
Self::initialize_client(state, entity, client, player);
match msg {
ClientMsg::RequestState(requested_state) => match requested_state {
ClientState::Spectator => match client.client_state {
// Use ClientMsg::Connect instead
ClientState::Disconnected => {},
ClientState::Spectator => {
// Already
client.postbox.send_message(ServerMsg::StateAnswer(
Err((RequestStateError::Already, ClientState::Spectator))));
},
ClientState::Character => {
// Always allow
client.postbox.send_message(ServerMsg::StateAnswer(
Ok(ClientState::Spectator)));
},
},
// Use ClientMsg::Character instead
ClientState::Character => { unimplemented!("TODO: Check for previously used character"); },
ClientState::Disconnected => disconnect = true,
},
ClientMsg::Connect { player } => match client.client_state {
ClientState::Disconnected => Self::initialize_client(state, entity, client, player),
_ => {},
},
ClientMsg::Character(character) => match client.client_state {
ClientState::Spectator => Self::create_player_character(state, entity, client, character),
// Currently only possible from spectator
_ => disconnect = true,
},
// Always possible
ClientMsg::Ping => client.postbox.send_message(ServerMsg::Pong),
ClientMsg::Pong => {}
ClientMsg::Disconnect => disconnect = true,
ClientMsg::Chat(msg) => match client.client_state {
ClientState::Disconnected => {}
_ => new_chat_msgs.push((entity, msg)),
},
ClientMsg::PlayerAnimation(animation_history) => match client.client_state {
ClientState::Character => {
state.write_component(entity, animation_history);
}
_ => disconnect = true,
},
ClientState::Connected => match msg {
ClientMsg::Connect { .. } => disconnect = true, // Not allowed when already connected
ClientMsg::Disconnect => disconnect = true,
ClientMsg::Character { character } => Self::create_player_character(state, entity, character),
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::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 } => {
},
_ => disconnect = true,
},
ClientMsg::TerrainChunkRequest { key } => match client.client_state {
ClientState::Spectator | ClientState::Character => {
match state.terrain().get_key(key) {
Some(chunk) => {} /*client.postbox.send_message(ServerMsg::TerrainChunkUpdate {
key,
@ -275,8 +311,9 @@ impl Server {
}),*/
None => requested_chunks.push(key),
}
}
},
},
ClientState::Disconnected => {},
}
}
}
} else if state.get_time() - client.last_ping > CLIENT_TIMEOUT || // Timeout
@ -290,7 +327,10 @@ impl Server {
}
if disconnect {
println!("Someone disconnected!");
disconnected_clients.push(entity);
client.postbox.send_message(ServerMsg::StateAnswer(
Err((RequestStateError::Impossible, ClientState::Disconnected))));
true
} else {
false
@ -345,13 +385,11 @@ impl Server {
// Save player metadata (for example the username)
state.write_component(entity, player);
client.state = ClientState::Connected;
// Return a handshake with the state of the current world
// Return the state of the current world
// (All components Sphynx tracks)
client.notify(ServerMsg::Handshake {
client.notify(ServerMsg::InitialSync {
ecs_state: state.ecs().gen_state_package(),
player_entity: state
player_entity_uid: state
.ecs()
.uid_from_entity(entity)
.unwrap()
@ -370,6 +408,10 @@ impl Server {
animation_history: animation_history,
});
}
// Tell the client his request was successful
client.notify(ServerMsg::StateAnswer(Ok(ClientState::Spectator)));
client.client_state = ClientState::Spectator;
}
/// Sync client states with the most up to date information

View File

@ -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(
ui::Event::Play => {
self.client.borrow_mut().postbox.send_message(ClientMsg::Character(self.char_selection_ui.character));
Box::new(SessionState::new(&mut global_state.window, self.client.clone()))
),
},
}
}

View File

@ -6,7 +6,7 @@ use crate::{
use common::{
assets,
comp::character::{
self,
Character,
Race,
Gender,
Head,
@ -353,17 +353,9 @@ pub struct CharSelectionUi {
font_opensans: FontId,
character_creation: bool,
selected_char_no: Option<i32>,
race: Race,
gender: Gender,
head: Head,
chest: Chest,
belt: Belt,
pants: Pants,
hand: Hand,
foot: Foot,
weapon: Weapon,
creation_state: CreationState,
character_name: String,
pub character: Character,
creation_state: CreationState,
}
impl CharSelectionUi {
@ -396,15 +388,7 @@ impl CharSelectionUi {
character_creation: false,
selected_char_no: None,
character_name: "Character Name".to_string(),
race: Race::Human,
gender: Gender::Male,
head: Head::DefaultHead,
chest: Chest::DefaultChest,
belt: Belt::DefaultBelt,
pants: Pants::DefaultPants,
hand: Hand::DefaultHand,
foot: Foot::DefaultFoot,
weapon: Weapon::Sword,
character: Character::random(),
creation_state: CreationState::Race,
}
}
@ -711,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 Gender::Male = self.gender {
if Button::image(if let Gender::Male = self.character.gender {
self.imgs.icon_border_pressed
} else {
self.imgs.icon_border
@ -722,14 +706,14 @@ impl CharSelectionUi {
.set(self.ids.gender_1, ui_widgets)
.was_clicked()
{
self.gender = Gender::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 Gender::Female = self.gender {
if Button::image(if let Gender::Female = self.character.gender {
self.imgs.icon_border_pressed
} else {
self.imgs.icon_border
@ -740,7 +724,7 @@ impl CharSelectionUi {
.set(self.ids.gender_2, ui_widgets)
.was_clicked()
{
self.gender = Gender::Female;
self.character.gender = Gender::Female;
}
// for alignment
Rectangle::fill_with([458.0, 68.0], color::TRANSPARENT)
@ -748,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 Gender::Male = self.gender {
Image::new(if let Gender::Male = self.character.gender {
self.imgs.human_m
} else {
self.imgs.human_f
@ -756,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 Race::Human = self.race {
if Button::image(if let Race::Human = self.character.race {
self.imgs.icon_border_pressed
} else {
self.imgs.icon_border
@ -767,11 +751,11 @@ impl CharSelectionUi {
.set(self.ids.race_1, ui_widgets)
.was_clicked()
{
self.race = Race::Human;
self.character.race = Race::Human;
}
// Orc
Image::new(if let Gender::Male = self.gender {
Image::new(if let Gender::Male = self.character.gender {
self.imgs.orc_m
} else {
self.imgs.orc_f
@ -779,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 Race::Orc = self.race {
if Button::image(if let Race::Orc = self.character.race {
self.imgs.icon_border_pressed
} else {
self.imgs.icon_border
@ -790,10 +774,10 @@ impl CharSelectionUi {
.set(self.ids.race_2, ui_widgets)
.was_clicked()
{
self.race = Race::Orc;
self.character.race = Race::Orc;
}
// Dwarf
Image::new(if let Gender::Male = self.gender {
Image::new(if let Gender::Male = self.character.gender {
self.imgs.dwarf_m
} else {
self.imgs.dwarf_f
@ -801,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 Race::Dwarf = self.race {
if Button::image(if let Race::Dwarf = self.character.race {
self.imgs.icon_border_pressed
} else {
self.imgs.icon_border
@ -812,10 +796,10 @@ impl CharSelectionUi {
.set(self.ids.race_3, ui_widgets)
.was_clicked()
{
self.race = Race::Dwarf;
self.character.race = Race::Dwarf;
}
// Elf
Image::new(if let Gender::Male = self.gender {
Image::new(if let Gender::Male = self.character.gender {
self.imgs.elf_m
} else {
self.imgs.elf_f
@ -823,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 Race::Elf = self.race {
if Button::image(if let Race::Elf = self.character.race {
self.imgs.icon_border_pressed
} else {
self.imgs.icon_border
@ -834,10 +818,10 @@ impl CharSelectionUi {
.set(self.ids.race_4, ui_widgets)
.was_clicked()
{
self.race = Race::Elf;
self.character.race = Race::Elf;
}
// Undead
Image::new(if let Gender::Male = self.gender {
Image::new(if let Gender::Male = self.character.gender {
self.imgs.undead_m
} else {
self.imgs.undead_f
@ -845,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 Race::Undead = self.race {
if Button::image(if let Race::Undead = self.character.race {
self.imgs.icon_border_pressed
} else {
self.imgs.icon_border
@ -856,17 +840,17 @@ impl CharSelectionUi {
.set(self.ids.race_5, ui_widgets)
.was_clicked()
{
self.race = Race::Undead;
self.character.race = Race::Undead;
}
// Danari
Image::new(if let Gender::Male = self.gender {
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 Race::Danari = self.race {
if Button::image(if let Race::Danari = self.character.race {
self.imgs.icon_border_pressed
} else {
self.imgs.icon_border
@ -878,7 +862,7 @@ impl CharSelectionUi {
.set(self.ids.race_6, ui_widgets)
.was_clicked()
{
self.race = Race::Danari;
self.character.race = Race::Danari;
}
// Description Headline and Text
@ -939,7 +923,7 @@ impl CharSelectionUi {
\n\
Outcast communities consisting of these Blessed Danari have formed all over the land.";
let (race_str, race_desc) = match self.race {
let (race_str, race_desc) = match self.character.race {
Race::Human => ("Humans", HUMAN_DESC),
Race::Orc => ("Orcs", ORC_DESC),
Race::Dwarf => ("Dwarves", DWARF_DESC),
@ -979,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 Weapon::SwordShield = self.weapon {
if Button::image(if let Weapon::SwordShield = self.character.weapon {
self.imgs.icon_border_pressed
} else {
self.imgs.icon_border
@ -990,7 +974,7 @@ impl CharSelectionUi {
.set(self.ids.weapon_1, ui_widgets)
.was_clicked()
{
self.weapon = Weapon::SwordShield;
self.character.weapon = Weapon::SwordShield;
}
// Daggers
@ -998,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 Weapon::Daggers = self.weapon {
if Button::image(if let Weapon::Daggers = self.character.weapon {
self.imgs.icon_border_pressed
} else {
self.imgs.icon_border
@ -1009,7 +993,7 @@ impl CharSelectionUi {
.set(self.ids.weapon_2, ui_widgets)
.was_clicked()
{
self.weapon = Weapon::Daggers;
self.character.weapon = Weapon::Daggers;
}
// Sword
@ -1017,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 Weapon::Sword = self.weapon {
if Button::image(if let Weapon::Sword = self.character.weapon {
self.imgs.icon_border_pressed
} else {
self.imgs.icon_border
@ -1028,14 +1012,14 @@ impl CharSelectionUi {
.set(self.ids.weapon_3, ui_widgets)
.was_clicked()
{
self.weapon = Weapon::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 Weapon::Axe = self.weapon {
if Button::image(if let Weapon::Axe = self.character.weapon {
self.imgs.icon_border_pressed
} else {
self.imgs.icon_border
@ -1046,14 +1030,14 @@ impl CharSelectionUi {
.set(self.ids.weapon_4, ui_widgets)
.was_clicked()
{
self.weapon = Weapon::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 Weapon::Hammer = self.weapon {
if Button::image(if let Weapon::Hammer = self.character.weapon {
self.imgs.icon_border_pressed
} else {
self.imgs.icon_border
@ -1064,14 +1048,14 @@ impl CharSelectionUi {
.set(self.ids.weapon_5, ui_widgets)
.was_clicked()
{
self.weapon = Weapon::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 Weapon::Bow = self.weapon {
if Button::image(if let Weapon::Bow = self.character.weapon {
self.imgs.icon_border_pressed
} else {
self.imgs.icon_border
@ -1082,14 +1066,14 @@ impl CharSelectionUi {
.set(self.ids.weapon_6, ui_widgets)
.was_clicked()
{
self.weapon = Weapon::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 Weapon::Staff = self.weapon {
if Button::image(if let Weapon::Staff = self.character.weapon {
self.imgs.icon_border_pressed
} else {
self.imgs.icon_border
@ -1100,7 +1084,7 @@ impl CharSelectionUi {
.set(self.ids.weapon_7, ui_widgets)
.was_clicked()
{
self.weapon = Weapon::Staff;
self.character.weapon = Weapon::Staff;
}
// TODO: Load these from files (or from the server???)
@ -1112,7 +1096,7 @@ impl CharSelectionUi {
const BOW_DESC: &str = " MISSING ";
const STAFF_DESC: &str = " MISSING ";
let (weapon_str, weapon_desc) = match self.weapon {
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),
@ -1459,7 +1443,7 @@ impl CharSelectionUi {
.was_clicked()
{};
// Beard -> Only active when "male" was chosen
if let Gender::Male = self.gender {
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)
@ -1490,7 +1474,7 @@ impl CharSelectionUi {
// Color -> Picker
// Brightness -> Slider
BodyPart::Accessories => {
match self.race {
match self.character.race {
Race::Human => {
Text::new("Head Band")
.mid_top_with_margin_on(self.ids.accessories_window, 60.0)

View File

@ -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());