mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
spectate mode :D
This commit is contained in:
@ -3,6 +3,7 @@ char_selection-delete_permanently = Permanently delete this Character?
|
|||||||
char_selection-deleting_character = Deleting Character...
|
char_selection-deleting_character = Deleting Character...
|
||||||
char_selection-change_server = Change Server
|
char_selection-change_server = Change Server
|
||||||
char_selection-enter_world = Enter World
|
char_selection-enter_world = Enter World
|
||||||
|
char_selection-spectate = Spectate World
|
||||||
char_selection-logout = Logout
|
char_selection-logout = Logout
|
||||||
char_selection-create_new_character = Create New Character
|
char_selection-create_new_character = Create New Character
|
||||||
char_selection-creating_character = Creating Character...
|
char_selection-creating_character = Creating Character...
|
||||||
|
@ -30,7 +30,7 @@ use common::{
|
|||||||
slot::{EquipSlot, InvSlotId, Slot},
|
slot::{EquipSlot, InvSlotId, Slot},
|
||||||
CharacterState, ChatMode, ControlAction, ControlEvent, Controller, ControllerInputs,
|
CharacterState, ChatMode, ControlAction, ControlEvent, Controller, ControllerInputs,
|
||||||
GroupManip, InputKind, InventoryAction, InventoryEvent, InventoryUpdateEvent,
|
GroupManip, InputKind, InventoryAction, InventoryEvent, InventoryUpdateEvent,
|
||||||
MapMarkerChange, UtteranceKind,
|
MapMarkerChange, Pos, UtteranceKind,
|
||||||
},
|
},
|
||||||
event::{EventBus, LocalEvent},
|
event::{EventBus, LocalEvent},
|
||||||
grid::Grid,
|
grid::Grid,
|
||||||
@ -107,6 +107,7 @@ pub enum Event {
|
|||||||
CharacterEdited(CharacterId),
|
CharacterEdited(CharacterId),
|
||||||
CharacterError(String),
|
CharacterError(String),
|
||||||
MapMarker(comp::MapMarkerUpdate),
|
MapMarker(comp::MapMarkerUpdate),
|
||||||
|
SpectatePosition(Vec3<f32>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WorldData {
|
pub struct WorldData {
|
||||||
@ -816,7 +817,8 @@ impl Client {
|
|||||||
| ClientGeneral::RequestPlayerPhysics { .. }
|
| ClientGeneral::RequestPlayerPhysics { .. }
|
||||||
| ClientGeneral::RequestLossyTerrainCompression { .. }
|
| ClientGeneral::RequestLossyTerrainCompression { .. }
|
||||||
| ClientGeneral::AcknowledgePersistenceLoadError
|
| ClientGeneral::AcknowledgePersistenceLoadError
|
||||||
| ClientGeneral::UpdateMapMarker(_) => {
|
| ClientGeneral::UpdateMapMarker(_)
|
||||||
|
| ClientGeneral::SpectatePosition(_) => {
|
||||||
#[cfg(feature = "tracy")]
|
#[cfg(feature = "tracy")]
|
||||||
{
|
{
|
||||||
ingame = 1.0;
|
ingame = 1.0;
|
||||||
@ -881,6 +883,14 @@ impl Client {
|
|||||||
self.presence = Some(PresenceKind::Character(character_id));
|
self.presence = Some(PresenceKind::Character(character_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Request a state transition to `ClientState::Spectate`.
|
||||||
|
pub fn request_spectate(&mut self) {
|
||||||
|
self.send_msg(ClientGeneral::Spectate);
|
||||||
|
|
||||||
|
//Assume we are in_game unless server tells us otherwise
|
||||||
|
self.presence = Some(PresenceKind::Spectator);
|
||||||
|
}
|
||||||
|
|
||||||
/// Load the current players character list
|
/// Load the current players character list
|
||||||
pub fn load_character_list(&mut self) {
|
pub fn load_character_list(&mut self) {
|
||||||
self.character_list.loading = true;
|
self.character_list.loading = true;
|
||||||
@ -1334,6 +1344,18 @@ impl Client {
|
|||||||
self.send_msg(ClientGeneral::UpdateMapMarker(event));
|
self.send_msg(ClientGeneral::UpdateMapMarker(event));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn spectate_position(&mut self, pos: Vec3<f32>) {
|
||||||
|
if let Some(position) = self
|
||||||
|
.state
|
||||||
|
.ecs()
|
||||||
|
.write_storage::<comp::Pos>()
|
||||||
|
.get_mut(self.entity())
|
||||||
|
{
|
||||||
|
position.0 = pos;
|
||||||
|
}
|
||||||
|
self.send_msg(ClientGeneral::SpectatePosition(pos));
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks whether a player can swap their weapon+ability `Loadout` settings
|
/// Checks whether a player can swap their weapon+ability `Loadout` settings
|
||||||
/// and sends the `ControlAction` event that signals to do the swap.
|
/// and sends the `ControlAction` event that signals to do the swap.
|
||||||
pub fn swap_loadout(&mut self) { self.control_action(ControlAction::SwapEquippedWeapons) }
|
pub fn swap_loadout(&mut self) { self.control_action(ControlAction::SwapEquippedWeapons) }
|
||||||
@ -2256,6 +2278,9 @@ impl Client {
|
|||||||
ServerGeneral::WeatherUpdate(weather) => {
|
ServerGeneral::WeatherUpdate(weather) => {
|
||||||
self.weather.weather_update(weather);
|
self.weather.weather_update(weather);
|
||||||
},
|
},
|
||||||
|
ServerGeneral::SpectatePosition(pos) => {
|
||||||
|
frontend_events.push(Event::SpectatePosition(pos));
|
||||||
|
},
|
||||||
_ => unreachable!("Not a in_game message"),
|
_ => unreachable!("Not a in_game message"),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -2319,6 +2344,18 @@ impl Client {
|
|||||||
self.set_view_distance(vd);
|
self.set_view_distance(vd);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
ServerGeneral::SpectatorSuccess(spawn_point) => {
|
||||||
|
if let Some(vd) = self.view_distance {
|
||||||
|
self.state
|
||||||
|
.ecs()
|
||||||
|
.write_storage()
|
||||||
|
.insert(self.entity(), Pos(spawn_point))
|
||||||
|
.expect("This shouldn't exist");
|
||||||
|
events.push(Event::SpectatePosition(spawn_point));
|
||||||
|
debug!("client is now in ingame state on server");
|
||||||
|
self.set_view_distance(vd);
|
||||||
|
}
|
||||||
|
},
|
||||||
_ => unreachable!("Not a character_screen msg"),
|
_ => unreachable!("Not a character_screen msg"),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -79,6 +79,8 @@ pub enum ClientGeneral {
|
|||||||
UnlockSkillGroup(SkillGroupKind),
|
UnlockSkillGroup(SkillGroupKind),
|
||||||
RequestSiteInfo(SiteId),
|
RequestSiteInfo(SiteId),
|
||||||
UpdateMapMarker(comp::MapMarkerChange),
|
UpdateMapMarker(comp::MapMarkerChange),
|
||||||
|
|
||||||
|
SpectatePosition(Vec3<f32>),
|
||||||
//Only in Game, via terrain stream
|
//Only in Game, via terrain stream
|
||||||
TerrainChunkRequest {
|
TerrainChunkRequest {
|
||||||
key: Vec2<i32>,
|
key: Vec2<i32>,
|
||||||
@ -138,7 +140,8 @@ impl ClientMsg {
|
|||||||
| ClientGeneral::RequestPlayerPhysics { .. }
|
| ClientGeneral::RequestPlayerPhysics { .. }
|
||||||
| ClientGeneral::RequestLossyTerrainCompression { .. }
|
| ClientGeneral::RequestLossyTerrainCompression { .. }
|
||||||
| ClientGeneral::AcknowledgePersistenceLoadError
|
| ClientGeneral::AcknowledgePersistenceLoadError
|
||||||
| ClientGeneral::UpdateMapMarker(_) => {
|
| ClientGeneral::UpdateMapMarker(_)
|
||||||
|
| ClientGeneral::SpectatePosition(_) => {
|
||||||
c_type == ClientType::Game && presence.is_some()
|
c_type == ClientType::Game && presence.is_some()
|
||||||
},
|
},
|
||||||
//Always possible
|
//Always possible
|
||||||
|
@ -140,6 +140,7 @@ pub enum ServerGeneral {
|
|||||||
CharacterCreated(character::CharacterId),
|
CharacterCreated(character::CharacterId),
|
||||||
CharacterEdited(character::CharacterId),
|
CharacterEdited(character::CharacterId),
|
||||||
CharacterSuccess,
|
CharacterSuccess,
|
||||||
|
SpectatorSuccess(Vec3<f32>),
|
||||||
//Ingame related
|
//Ingame related
|
||||||
GroupUpdate(comp::group::ChangeNotification<Uid>),
|
GroupUpdate(comp::group::ChangeNotification<Uid>),
|
||||||
/// Indicate to the client that they are invited to join a group
|
/// Indicate to the client that they are invited to join a group
|
||||||
@ -199,6 +200,9 @@ pub enum ServerGeneral {
|
|||||||
SiteEconomy(EconomyInfo),
|
SiteEconomy(EconomyInfo),
|
||||||
MapMarker(comp::MapMarkerUpdate),
|
MapMarker(comp::MapMarkerUpdate),
|
||||||
WeatherUpdate(WeatherGrid),
|
WeatherUpdate(WeatherGrid),
|
||||||
|
/// Suggest the client to spectate a position. Called after client has
|
||||||
|
/// requested teleport etc.
|
||||||
|
SpectatePosition(Vec3<f32>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerGeneral {
|
impl ServerGeneral {
|
||||||
@ -292,7 +296,7 @@ impl ServerMsg {
|
|||||||
| ServerGeneral::CharacterCreated(_) => {
|
| ServerGeneral::CharacterCreated(_) => {
|
||||||
c_type != ClientType::ChatOnly && presence.is_none()
|
c_type != ClientType::ChatOnly && presence.is_none()
|
||||||
},
|
},
|
||||||
ServerGeneral::CharacterSuccess => {
|
ServerGeneral::CharacterSuccess | ServerGeneral::SpectatorSuccess(_) => {
|
||||||
c_type == ClientType::Game && presence.is_none()
|
c_type == ClientType::Game && presence.is_none()
|
||||||
},
|
},
|
||||||
//Ingame related
|
//Ingame related
|
||||||
@ -312,7 +316,8 @@ impl ServerMsg {
|
|||||||
| ServerGeneral::FinishedTrade(_)
|
| ServerGeneral::FinishedTrade(_)
|
||||||
| ServerGeneral::SiteEconomy(_)
|
| ServerGeneral::SiteEconomy(_)
|
||||||
| ServerGeneral::MapMarker(_)
|
| ServerGeneral::MapMarker(_)
|
||||||
| ServerGeneral::WeatherUpdate(_) => {
|
| ServerGeneral::WeatherUpdate(_)
|
||||||
|
| ServerGeneral::SpectatePosition(_) => {
|
||||||
c_type == ClientType::Game && presence.is_some()
|
c_type == ClientType::Game && presence.is_some()
|
||||||
},
|
},
|
||||||
// Always possible
|
// Always possible
|
||||||
|
@ -108,6 +108,7 @@ pub enum ServerEvent {
|
|||||||
entity: EcsEntity,
|
entity: EcsEntity,
|
||||||
character_id: CharacterId,
|
character_id: CharacterId,
|
||||||
},
|
},
|
||||||
|
InitSpectator(EcsEntity),
|
||||||
UpdateCharacterData {
|
UpdateCharacterData {
|
||||||
entity: EcsEntity,
|
entity: EcsEntity,
|
||||||
components: (
|
components: (
|
||||||
|
@ -171,7 +171,8 @@ impl Client {
|
|||||||
| ServerGeneral::CharacterActionError(_)
|
| ServerGeneral::CharacterActionError(_)
|
||||||
| ServerGeneral::CharacterCreated(_)
|
| ServerGeneral::CharacterCreated(_)
|
||||||
| ServerGeneral::CharacterEdited(_)
|
| ServerGeneral::CharacterEdited(_)
|
||||||
| ServerGeneral::CharacterSuccess => {
|
| ServerGeneral::CharacterSuccess
|
||||||
|
| ServerGeneral::SpectatorSuccess(_) => {
|
||||||
PreparedMsg::new(1, &g, &self.character_screen_stream_params)
|
PreparedMsg::new(1, &g, &self.character_screen_stream_params)
|
||||||
},
|
},
|
||||||
//In-game related
|
//In-game related
|
||||||
@ -188,7 +189,8 @@ impl Client {
|
|||||||
| ServerGeneral::UpdatePendingTrade(_, _, _)
|
| ServerGeneral::UpdatePendingTrade(_, _, _)
|
||||||
| ServerGeneral::FinishedTrade(_)
|
| ServerGeneral::FinishedTrade(_)
|
||||||
| ServerGeneral::MapMarker(_)
|
| ServerGeneral::MapMarker(_)
|
||||||
| ServerGeneral::WeatherUpdate(_) => {
|
| ServerGeneral::WeatherUpdate(_)
|
||||||
|
| ServerGeneral::SpectatePosition(_) => {
|
||||||
PreparedMsg::new(2, &g, &self.in_game_stream_params)
|
PreparedMsg::new(2, &g, &self.in_game_stream_params)
|
||||||
},
|
},
|
||||||
//In-game related, terrain
|
//In-game related, terrain
|
||||||
|
@ -6,6 +6,7 @@ use crate::{
|
|||||||
client::Client,
|
client::Client,
|
||||||
location::Locations,
|
location::Locations,
|
||||||
login_provider::LoginProvider,
|
login_provider::LoginProvider,
|
||||||
|
presence::Presence,
|
||||||
settings::{
|
settings::{
|
||||||
Ban, BanAction, BanInfo, EditableSetting, SettingError, WhitelistInfo, WhitelistRecord,
|
Ban, BanAction, BanInfo, EditableSetting, SettingError, WhitelistInfo, WhitelistRecord,
|
||||||
},
|
},
|
||||||
@ -49,7 +50,7 @@ use common::{
|
|||||||
weather, Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
|
weather, Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
|
||||||
};
|
};
|
||||||
use common_net::{
|
use common_net::{
|
||||||
msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneral},
|
msg::{DisconnectReason, Notification, PlayerListUpdate, PresenceKind, ServerGeneral},
|
||||||
sync::WorldSyncExt,
|
sync::WorldSyncExt,
|
||||||
};
|
};
|
||||||
use common_state::{BuildAreaError, BuildAreas};
|
use common_state::{BuildAreaError, BuildAreas};
|
||||||
@ -231,20 +232,38 @@ fn position_mut<T>(
|
|||||||
})
|
})
|
||||||
.unwrap_or(entity);
|
.unwrap_or(entity);
|
||||||
|
|
||||||
|
let mut maybe_pos = None;
|
||||||
|
|
||||||
let res = server
|
let res = server
|
||||||
.state
|
.state
|
||||||
.ecs()
|
.ecs()
|
||||||
.write_storage::<comp::Pos>()
|
.write_storage::<comp::Pos>()
|
||||||
.get_mut(entity)
|
.get_mut(entity)
|
||||||
.map(f)
|
.map(|pos| {
|
||||||
|
let res = f(pos);
|
||||||
|
maybe_pos = Some(pos.0);
|
||||||
|
res
|
||||||
|
})
|
||||||
.ok_or_else(|| format!("Cannot get position for {:?}!", descriptor));
|
.ok_or_else(|| format!("Cannot get position for {:?}!", descriptor));
|
||||||
if res.is_ok() {
|
|
||||||
|
if let Some(pos) = maybe_pos {
|
||||||
|
if server
|
||||||
|
.state
|
||||||
|
.ecs()
|
||||||
|
.read_storage::<Presence>()
|
||||||
|
.get(entity)
|
||||||
|
.map(|presence| presence.kind == PresenceKind::Spectator)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
server.notify_client(entity, ServerGeneral::SpectatePosition(pos));
|
||||||
|
} else {
|
||||||
let _ = server
|
let _ = server
|
||||||
.state
|
.state
|
||||||
.ecs()
|
.ecs()
|
||||||
.write_storage::<comp::ForceUpdate>()
|
.write_storage::<comp::ForceUpdate>()
|
||||||
.insert(entity, comp::ForceUpdate);
|
.insert(entity, comp::ForceUpdate);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,11 @@ pub fn handle_initialize_character(
|
|||||||
server.state.initialize_character_data(entity, character_id);
|
server.state.initialize_character_data(entity, character_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle_initialize_spectator(server: &mut Server, entity: EcsEntity) {
|
||||||
|
server.state.initialize_spectator_data(entity);
|
||||||
|
sys::subscription::initialize_region_subscription(server.state.ecs(), entity);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn handle_loaded_character_data(
|
pub fn handle_loaded_character_data(
|
||||||
server: &mut Server,
|
server: &mut Server,
|
||||||
entity: EcsEntity,
|
entity: EcsEntity,
|
||||||
|
@ -6,7 +6,8 @@ use common::event::{EventBus, ServerEvent, ServerEventDiscriminants};
|
|||||||
use common_base::span;
|
use common_base::span;
|
||||||
use entity_creation::{
|
use entity_creation::{
|
||||||
handle_beam, handle_create_npc, handle_create_ship, handle_create_waypoint,
|
handle_beam, handle_create_npc, handle_create_ship, handle_create_waypoint,
|
||||||
handle_initialize_character, handle_loaded_character_data, handle_shockwave, handle_shoot,
|
handle_initialize_character, handle_initialize_spectator, handle_loaded_character_data,
|
||||||
|
handle_shockwave, handle_shoot,
|
||||||
};
|
};
|
||||||
use entity_manipulation::{
|
use entity_manipulation::{
|
||||||
handle_aura, handle_bonk, handle_buff, handle_change_ability, handle_combo_change,
|
handle_aura, handle_bonk, handle_buff, handle_change_ability, handle_combo_change,
|
||||||
@ -139,6 +140,7 @@ impl Server {
|
|||||||
entity,
|
entity,
|
||||||
character_id,
|
character_id,
|
||||||
} => handle_initialize_character(self, entity, character_id),
|
} => handle_initialize_character(self, entity, character_id),
|
||||||
|
ServerEvent::InitSpectator(entity) => handle_initialize_spectator(self, entity),
|
||||||
ServerEvent::UpdateCharacterData { entity, components } => {
|
ServerEvent::UpdateCharacterData { entity, components } => {
|
||||||
let (
|
let (
|
||||||
body,
|
body,
|
||||||
|
@ -107,6 +107,8 @@ pub trait StateExt {
|
|||||||
) -> EcsEntityBuilder;
|
) -> EcsEntityBuilder;
|
||||||
/// Insert common/default components for a new character joining the server
|
/// Insert common/default components for a new character joining the server
|
||||||
fn initialize_character_data(&mut self, entity: EcsEntity, character_id: CharacterId);
|
fn initialize_character_data(&mut self, entity: EcsEntity, character_id: CharacterId);
|
||||||
|
/// Insert common/default components for a new spectator joining the server
|
||||||
|
fn initialize_spectator_data(&mut self, entity: EcsEntity);
|
||||||
/// Update the components associated with the entity's current character.
|
/// Update the components associated with the entity's current character.
|
||||||
/// Performed after loading component data from the database
|
/// Performed after loading component data from the database
|
||||||
fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents);
|
fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents);
|
||||||
@ -523,6 +525,32 @@ impl StateExt for State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn initialize_spectator_data(&mut self, entity: EcsEntity) {
|
||||||
|
let spawn_point = self.ecs().read_resource::<SpawnPoint>().0;
|
||||||
|
|
||||||
|
if self.read_component_copied::<Uid>(entity).is_some() {
|
||||||
|
// NOTE: By fetching the player_uid, we validated that the entity exists, and we
|
||||||
|
// call nothing that can delete it in any of the subsequent
|
||||||
|
// commands, so we can assume that all of these calls succeed,
|
||||||
|
// justifying ignoring the result of insertion.
|
||||||
|
self.write_component_ignore_entity_dead(entity, comp::Pos(spawn_point));
|
||||||
|
|
||||||
|
// Make sure physics components are updated
|
||||||
|
self.write_component_ignore_entity_dead(entity, comp::ForceUpdate);
|
||||||
|
|
||||||
|
const INITIAL_VD: u32 = 5; //will be changed after login
|
||||||
|
self.write_component_ignore_entity_dead(
|
||||||
|
entity,
|
||||||
|
Presence::new(INITIAL_VD, PresenceKind::Spectator),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Tell the client its request was successful.
|
||||||
|
if let Some(client) = self.ecs().read_storage::<Client>().get(entity) {
|
||||||
|
client.send_fallible(ServerGeneral::SpectatorSuccess(spawn_point));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents) {
|
fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents) {
|
||||||
let PersistedComponents {
|
let PersistedComponents {
|
||||||
body,
|
body,
|
||||||
|
@ -7,7 +7,7 @@ use crate::{
|
|||||||
EditableSettings,
|
EditableSettings,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{ChatType, Player, UnresolvedChatMsg},
|
comp::{Admin, AdminRole, ChatType, Player, UnresolvedChatMsg},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
};
|
};
|
||||||
@ -15,7 +15,7 @@ use common_ecs::{Job, Origin, Phase, System};
|
|||||||
use common_net::msg::{ClientGeneral, ServerGeneral};
|
use common_net::msg::{ClientGeneral, ServerGeneral};
|
||||||
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, WriteExpect};
|
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, WriteExpect};
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use tracing::{debug, warn};
|
use tracing::debug;
|
||||||
|
|
||||||
impl Sys {
|
impl Sys {
|
||||||
fn handle_client_character_screen_msg(
|
fn handle_client_character_screen_msg(
|
||||||
@ -26,18 +26,43 @@ impl Sys {
|
|||||||
character_updater: &mut WriteExpect<'_, CharacterUpdater>,
|
character_updater: &mut WriteExpect<'_, CharacterUpdater>,
|
||||||
uids: &ReadStorage<'_, Uid>,
|
uids: &ReadStorage<'_, Uid>,
|
||||||
players: &ReadStorage<'_, Player>,
|
players: &ReadStorage<'_, Player>,
|
||||||
|
admins: &ReadStorage<'_, Admin>,
|
||||||
presences: &ReadStorage<'_, Presence>,
|
presences: &ReadStorage<'_, Presence>,
|
||||||
editable_settings: &ReadExpect<'_, EditableSettings>,
|
editable_settings: &ReadExpect<'_, EditableSettings>,
|
||||||
alias_validator: &ReadExpect<'_, AliasValidator>,
|
alias_validator: &ReadExpect<'_, AliasValidator>,
|
||||||
msg: ClientGeneral,
|
msg: ClientGeneral,
|
||||||
) -> Result<(), crate::error::Error> {
|
) -> Result<(), crate::error::Error> {
|
||||||
|
let mut send_join_messages = || -> Result<(), crate::error::Error> {
|
||||||
|
// Give the player a welcome message
|
||||||
|
if !editable_settings.server_description.is_empty() {
|
||||||
|
client.send(ServerGeneral::server_msg(
|
||||||
|
ChatType::CommandInfo,
|
||||||
|
&*editable_settings.server_description,
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !client.login_msg_sent.load(Ordering::Relaxed) {
|
||||||
|
if let Some(player_uid) = uids.get(entity) {
|
||||||
|
server_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg {
|
||||||
|
chat_type: ChatType::Online(*player_uid),
|
||||||
|
message: "".to_string(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
client.login_msg_sent.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
match msg {
|
match msg {
|
||||||
// Request spectator state
|
// Request spectator state
|
||||||
ClientGeneral::Spectate => {
|
ClientGeneral::Spectate => {
|
||||||
if players.contains(entity) {
|
if let Some(admin) = admins.get(entity) && admin.0 >= AdminRole::Moderator {
|
||||||
warn!("Spectator mode not yet implemented on server");
|
send_join_messages()?;
|
||||||
|
|
||||||
|
server_emitter.emit(ServerEvent::InitSpectator(entity));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
debug!("dropped Spectate msg from unregistered client")
|
debug!("dropped Spectate msg from unprivileged client")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ClientGeneral::Character(character_id) => {
|
ClientGeneral::Character(character_id) => {
|
||||||
@ -76,31 +101,14 @@ impl Sys {
|
|||||||
character_id,
|
character_id,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
send_join_messages()?;
|
||||||
|
|
||||||
// Start inserting non-persisted/default components for the entity
|
// Start inserting non-persisted/default components for the entity
|
||||||
// while we load the DB data
|
// while we load the DB data
|
||||||
server_emitter.emit(ServerEvent::InitCharacterData {
|
server_emitter.emit(ServerEvent::InitCharacterData {
|
||||||
entity,
|
entity,
|
||||||
character_id,
|
character_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Give the player a welcome message
|
|
||||||
if !editable_settings.server_description.is_empty() {
|
|
||||||
client.send(ServerGeneral::server_msg(
|
|
||||||
ChatType::CommandInfo,
|
|
||||||
&*editable_settings.server_description,
|
|
||||||
))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !client.login_msg_sent.load(Ordering::Relaxed) {
|
|
||||||
if let Some(player_uid) = uids.get(entity) {
|
|
||||||
server_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg {
|
|
||||||
chat_type: ChatType::Online(*player_uid),
|
|
||||||
message: "".to_string(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
client.login_msg_sent.store(true, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
debug!("Client is not yet registered");
|
debug!("Client is not yet registered");
|
||||||
@ -199,6 +207,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
ReadStorage<'a, Uid>,
|
ReadStorage<'a, Uid>,
|
||||||
ReadStorage<'a, Client>,
|
ReadStorage<'a, Client>,
|
||||||
ReadStorage<'a, Player>,
|
ReadStorage<'a, Player>,
|
||||||
|
ReadStorage<'a, Admin>,
|
||||||
ReadStorage<'a, Presence>,
|
ReadStorage<'a, Presence>,
|
||||||
ReadExpect<'a, EditableSettings>,
|
ReadExpect<'a, EditableSettings>,
|
||||||
ReadExpect<'a, AliasValidator>,
|
ReadExpect<'a, AliasValidator>,
|
||||||
@ -218,6 +227,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
uids,
|
uids,
|
||||||
clients,
|
clients,
|
||||||
players,
|
players,
|
||||||
|
admins,
|
||||||
presences,
|
presences,
|
||||||
editable_settings,
|
editable_settings,
|
||||||
alias_validator,
|
alias_validator,
|
||||||
@ -235,6 +245,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
&mut character_updater,
|
&mut character_updater,
|
||||||
&uids,
|
&uids,
|
||||||
&players,
|
&players,
|
||||||
|
&admins,
|
||||||
&presences,
|
&presences,
|
||||||
&editable_settings,
|
&editable_settings,
|
||||||
&alias_validator,
|
&alias_validator,
|
||||||
|
@ -3,8 +3,8 @@ use crate::TerrainPersistence;
|
|||||||
use crate::{client::Client, presence::Presence, Settings};
|
use crate::{client::Client, presence::Presence, Settings};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{
|
comp::{
|
||||||
Admin, CanBuild, ControlEvent, Controller, ForceUpdate, Health, Ori, Player, Pos, SkillSet,
|
Admin, AdminRole, CanBuild, ControlEvent, Controller, ForceUpdate, Health, Ori, Player,
|
||||||
Vel,
|
Pos, SkillSet, Vel,
|
||||||
},
|
},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
link::Is,
|
link::Is,
|
||||||
@ -14,7 +14,7 @@ use common::{
|
|||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
};
|
};
|
||||||
use common_ecs::{Job, Origin, Phase, System};
|
use common_ecs::{Job, Origin, Phase, System};
|
||||||
use common_net::msg::{ClientGeneral, ServerGeneral};
|
use common_net::msg::{ClientGeneral, PresenceKind, ServerGeneral};
|
||||||
use common_state::{BlockChange, BuildAreas};
|
use common_state::{BlockChange, BuildAreas};
|
||||||
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, Write, WriteStorage};
|
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, Write, WriteStorage};
|
||||||
use tracing::{debug, trace, warn};
|
use tracing::{debug, trace, warn};
|
||||||
@ -288,6 +288,13 @@ impl Sys {
|
|||||||
ClientGeneral::UpdateMapMarker(update) => {
|
ClientGeneral::UpdateMapMarker(update) => {
|
||||||
server_emitter.emit(ServerEvent::UpdateMapMarker { entity, update });
|
server_emitter.emit(ServerEvent::UpdateMapMarker { entity, update });
|
||||||
},
|
},
|
||||||
|
ClientGeneral::SpectatePosition(pos) => {
|
||||||
|
if let Some(admin) = maybe_admin && admin.0 >= AdminRole::Moderator && presence.kind == PresenceKind::Spectator {
|
||||||
|
if let Some(position) = positions.get_mut(entity) {
|
||||||
|
position.0 = pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
ClientGeneral::RequestCharacterList
|
ClientGeneral::RequestCharacterList
|
||||||
| ClientGeneral::CreateCharacter { .. }
|
| ClientGeneral::CreateCharacter { .. }
|
||||||
| ClientGeneral::EditCharacter { .. }
|
| ClientGeneral::EditCharacter { .. }
|
||||||
|
@ -1136,7 +1136,7 @@ impl Hud {
|
|||||||
|
|
||||||
let character_id = match client.presence().unwrap() {
|
let character_id = match client.presence().unwrap() {
|
||||||
PresenceKind::Character(id) => Some(id),
|
PresenceKind::Character(id) => Some(id),
|
||||||
PresenceKind::Spectator => unreachable!("HUD creation in Spectator mode!"),
|
PresenceKind::Spectator => None,
|
||||||
PresenceKind::Possessor => None,
|
PresenceKind::Possessor => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -144,6 +144,18 @@ impl PlayState for CharSelectionState {
|
|||||||
Rc::clone(&self.client),
|
Rc::clone(&self.client),
|
||||||
)));
|
)));
|
||||||
},
|
},
|
||||||
|
ui::Event::Spectate => {
|
||||||
|
{
|
||||||
|
let mut c = self.client.borrow_mut();
|
||||||
|
c.request_spectate();
|
||||||
|
c.set_view_distance(global_state.settings.graphics.view_distance);
|
||||||
|
c.set_lod_distance(global_state.settings.graphics.lod_distance);
|
||||||
|
}
|
||||||
|
return PlayStateResult::Switch(Box::new(SessionState::new(
|
||||||
|
global_state,
|
||||||
|
Rc::clone(&self.client),
|
||||||
|
)));
|
||||||
|
},
|
||||||
ui::Event::ClearCharacterListError => {
|
ui::Event::ClearCharacterListError => {
|
||||||
self.char_selection_ui.error = None;
|
self.char_selection_ui.error = None;
|
||||||
},
|
},
|
||||||
|
@ -127,6 +127,7 @@ image_ids_ice! {
|
|||||||
pub enum Event {
|
pub enum Event {
|
||||||
Logout,
|
Logout,
|
||||||
Play(CharacterId),
|
Play(CharacterId),
|
||||||
|
Spectate,
|
||||||
AddCharacter {
|
AddCharacter {
|
||||||
alias: String,
|
alias: String,
|
||||||
mainhand: Option<String>,
|
mainhand: Option<String>,
|
||||||
@ -152,6 +153,7 @@ enum Mode {
|
|||||||
new_character_button: button::State,
|
new_character_button: button::State,
|
||||||
logout_button: button::State,
|
logout_button: button::State,
|
||||||
enter_world_button: button::State,
|
enter_world_button: button::State,
|
||||||
|
spectate_button: button::State,
|
||||||
yes_button: button::State,
|
yes_button: button::State,
|
||||||
no_button: button::State,
|
no_button: button::State,
|
||||||
},
|
},
|
||||||
@ -185,6 +187,7 @@ impl Mode {
|
|||||||
new_character_button: Default::default(),
|
new_character_button: Default::default(),
|
||||||
logout_button: Default::default(),
|
logout_button: Default::default(),
|
||||||
enter_world_button: Default::default(),
|
enter_world_button: Default::default(),
|
||||||
|
spectate_button: Default::default(),
|
||||||
yes_button: Default::default(),
|
yes_button: Default::default(),
|
||||||
no_button: Default::default(),
|
no_button: Default::default(),
|
||||||
}
|
}
|
||||||
@ -283,6 +286,7 @@ enum Message {
|
|||||||
Back,
|
Back,
|
||||||
Logout,
|
Logout,
|
||||||
EnterWorld,
|
EnterWorld,
|
||||||
|
Spectate,
|
||||||
Select(CharacterId),
|
Select(CharacterId),
|
||||||
Delete(usize),
|
Delete(usize),
|
||||||
Edit(usize),
|
Edit(usize),
|
||||||
@ -397,6 +401,7 @@ impl Controls {
|
|||||||
ref mut new_character_button,
|
ref mut new_character_button,
|
||||||
ref mut logout_button,
|
ref mut logout_button,
|
||||||
ref mut enter_world_button,
|
ref mut enter_world_button,
|
||||||
|
ref mut spectate_button,
|
||||||
ref mut yes_button,
|
ref mut yes_button,
|
||||||
ref mut no_button,
|
ref mut no_button,
|
||||||
} => {
|
} => {
|
||||||
@ -676,36 +681,52 @@ impl Controls {
|
|||||||
.padding(15)
|
.padding(15)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Fill);
|
.height(Length::Fill);
|
||||||
|
let mut bottom_content = vec![
|
||||||
let logout = neat_button(
|
Container::new(neat_button(
|
||||||
logout_button,
|
logout_button,
|
||||||
i18n.get("char_selection.logout").into_owned(),
|
i18n.get("char_selection.logout").into_owned(),
|
||||||
FILL_FRAC_ONE,
|
FILL_FRAC_ONE,
|
||||||
button_style,
|
button_style,
|
||||||
Some(Message::Logout),
|
Some(Message::Logout),
|
||||||
);
|
))
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Units(SMALL_BUTTON_HEIGHT))
|
||||||
|
.into(),
|
||||||
|
];
|
||||||
|
|
||||||
let enter_world = neat_button(
|
if client.is_moderator() {
|
||||||
|
bottom_content.push(
|
||||||
|
Container::new(neat_button(
|
||||||
|
spectate_button,
|
||||||
|
i18n.get("char_selection.spectate").into_owned(),
|
||||||
|
FILL_FRAC_TWO,
|
||||||
|
button_style,
|
||||||
|
Some(Message::Spectate),
|
||||||
|
))
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Units(52))
|
||||||
|
.center_x()
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bottom_content.push(
|
||||||
|
Container::new(neat_button(
|
||||||
enter_world_button,
|
enter_world_button,
|
||||||
i18n.get("char_selection.enter_world").into_owned(),
|
i18n.get("char_selection.enter_world").into_owned(),
|
||||||
FILL_FRAC_TWO,
|
FILL_FRAC_TWO,
|
||||||
button_style,
|
button_style,
|
||||||
selected.map(|_| Message::EnterWorld),
|
selected.map(|_| Message::EnterWorld),
|
||||||
);
|
))
|
||||||
|
|
||||||
let bottom = Row::with_children(vec![
|
|
||||||
Container::new(logout)
|
|
||||||
.width(Length::Fill)
|
|
||||||
.height(Length::Units(SMALL_BUTTON_HEIGHT))
|
|
||||||
.into(),
|
|
||||||
Container::new(enter_world)
|
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Units(52))
|
.height(Length::Units(52))
|
||||||
.center_x()
|
.center_x()
|
||||||
.into(),
|
.into(),
|
||||||
Space::new(Length::Fill, Length::Shrink).into(),
|
);
|
||||||
])
|
|
||||||
.align_items(Align::End);
|
bottom_content.push(Space::new(Length::Fill, Length::Shrink).into());
|
||||||
|
|
||||||
|
let bottom = Row::with_children(bottom_content).align_items(Align::End);
|
||||||
|
|
||||||
let content = Column::with_children(vec![top.into(), bottom.into()])
|
let content = Column::with_children(vec![top.into(), bottom.into()])
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
@ -1410,6 +1431,11 @@ impl Controls {
|
|||||||
events.push(Event::Play(selected));
|
events.push(Event::Play(selected));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Message::Spectate => {
|
||||||
|
if matches!(self.mode, Mode::Select { .. }) {
|
||||||
|
events.push(Event::Spectate);
|
||||||
|
}
|
||||||
|
},
|
||||||
Message::Select(id) => {
|
Message::Select(id) => {
|
||||||
if let Mode::Select { .. } = &mut self.mode {
|
if let Mode::Select { .. } = &mut self.mode {
|
||||||
self.selected = Some(id);
|
self.selected = Some(id);
|
||||||
|
@ -130,11 +130,7 @@ fn clamp_and_modulate(ori: Vec3<f32>) -> Vec3<f32> {
|
|||||||
/// e = floor(ln(near/(far - near))/ln(2))
|
/// e = floor(ln(near/(far - near))/ln(2))
|
||||||
/// db/dz = 2^(2-e) / ((1 / far - 1 / near) * (far)^2)
|
/// db/dz = 2^(2-e) / ((1 / far - 1 / near) * (far)^2)
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///CameraMode::ThirdPerson
|
||||||
/// Then the maximum precision you can safely use to get a change in the
|
|
||||||
/// integer representation of the mantissa (assuming 32-bit floating points)
|
|
||||||
/// is around:
|
|
||||||
///
|
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// abs(2^(-23) / (db/dz)).
|
/// abs(2^(-23) / (db/dz)).
|
||||||
/// ```
|
/// ```
|
||||||
@ -320,13 +316,18 @@ impl Camera {
|
|||||||
// Make sure aspect is valid
|
// Make sure aspect is valid
|
||||||
let aspect = if aspect.is_normal() { aspect } else { 1.0 };
|
let aspect = if aspect.is_normal() { aspect } else { 1.0 };
|
||||||
|
|
||||||
|
let dist = match mode {
|
||||||
|
CameraMode::ThirdPerson => 10.0,
|
||||||
|
CameraMode::FirstPerson | CameraMode::Freefly => MIN_ZOOM,
|
||||||
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
tgt_focus: Vec3::unit_z() * 10.0,
|
tgt_focus: Vec3::unit_z() * 10.0,
|
||||||
focus: Vec3::unit_z() * 10.0,
|
focus: Vec3::unit_z() * 10.0,
|
||||||
tgt_ori: Vec3::zero(),
|
tgt_ori: Vec3::zero(),
|
||||||
ori: Vec3::zero(),
|
ori: Vec3::zero(),
|
||||||
tgt_dist: 10.0,
|
tgt_dist: dist,
|
||||||
dist: 10.0,
|
dist,
|
||||||
tgt_fov: 1.1,
|
tgt_fov: 1.1,
|
||||||
fov: 1.1,
|
fov: 1.1,
|
||||||
tgt_fixate: 1.0,
|
tgt_fixate: 1.0,
|
||||||
@ -652,6 +653,12 @@ impl Camera {
|
|||||||
/// Set the focus position of the camera.
|
/// Set the focus position of the camera.
|
||||||
pub fn set_focus_pos(&mut self, focus: Vec3<f32>) { self.tgt_focus = focus; }
|
pub fn set_focus_pos(&mut self, focus: Vec3<f32>) { self.tgt_focus = focus; }
|
||||||
|
|
||||||
|
/// Set the focus position of the camera, without lerping.
|
||||||
|
pub fn force_focus_pos(&mut self, focus: Vec3<f32>) {
|
||||||
|
self.tgt_focus = focus;
|
||||||
|
self.focus = focus;
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the aspect ratio of the camera.
|
/// Get the aspect ratio of the camera.
|
||||||
pub fn get_aspect_ratio(&self) -> f32 { self.aspect }
|
pub fn get_aspect_ratio(&self) -> f32 { self.aspect }
|
||||||
|
|
||||||
@ -695,7 +702,7 @@ impl Camera {
|
|||||||
self.set_distance(MIN_ZOOM);
|
self.set_distance(MIN_ZOOM);
|
||||||
},
|
},
|
||||||
CameraMode::Freefly => {
|
CameraMode::Freefly => {
|
||||||
self.zoom_by(0.0, None);
|
self.set_distance(MIN_ZOOM);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -714,7 +721,10 @@ impl Camera {
|
|||||||
|
|
||||||
/// Cycle the camera to its next valid mode. If is_admin is false then only
|
/// Cycle the camera to its next valid mode. If is_admin is false then only
|
||||||
/// modes which are accessible without admin access will be cycled to.
|
/// modes which are accessible without admin access will be cycled to.
|
||||||
pub fn next_mode(&mut self, is_admin: bool) {
|
pub fn next_mode(&mut self, is_admin: bool, is_spectator: bool) {
|
||||||
|
if is_spectator && is_admin {
|
||||||
|
self.set_mode(CameraMode::Freefly);
|
||||||
|
} else {
|
||||||
self.set_mode(match self.mode {
|
self.set_mode(match self.mode {
|
||||||
CameraMode::ThirdPerson => CameraMode::FirstPerson,
|
CameraMode::ThirdPerson => CameraMode::FirstPerson,
|
||||||
CameraMode::FirstPerson => {
|
CameraMode::FirstPerson => {
|
||||||
@ -727,6 +737,7 @@ impl Camera {
|
|||||||
CameraMode::Freefly => CameraMode::ThirdPerson,
|
CameraMode::Freefly => CameraMode::ThirdPerson,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return a unit vector in the forward direction for the current camera
|
/// Return a unit vector in the forward direction for the current camera
|
||||||
/// orientation
|
/// orientation
|
||||||
|
@ -37,6 +37,7 @@ use common::{
|
|||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
};
|
};
|
||||||
use common_base::{prof_span, span};
|
use common_base::{prof_span, span};
|
||||||
|
use common_net::msg::PresenceKind;
|
||||||
use common_state::State;
|
use common_state::State;
|
||||||
use comp::item::Reagent;
|
use comp::item::Reagent;
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
@ -299,10 +300,15 @@ impl Scene {
|
|||||||
|
|
||||||
let terrain = Terrain::new(renderer, &data, lod.get_data(), sprite_render_context);
|
let terrain = Terrain::new(renderer, &data, lod.get_data(), sprite_render_context);
|
||||||
|
|
||||||
|
let camera_mode = match client.presence() {
|
||||||
|
Some(PresenceKind::Spectator) => CameraMode::Freefly,
|
||||||
|
_ => CameraMode::ThirdPerson,
|
||||||
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
data,
|
data,
|
||||||
globals_bind_group,
|
globals_bind_group,
|
||||||
camera: Camera::new(resolution.x / resolution.y, CameraMode::ThirdPerson),
|
camera: Camera::new(resolution.x / resolution.y, camera_mode),
|
||||||
camera_input_state: Vec2::zero(),
|
camera_input_state: Vec2::zero(),
|
||||||
event_lights: Vec::new(),
|
event_lights: Vec::new(),
|
||||||
|
|
||||||
|
@ -358,6 +358,9 @@ impl SessionState {
|
|||||||
client::Event::MapMarker(event) => {
|
client::Event::MapMarker(event) => {
|
||||||
self.hud.show.update_map_markers(event);
|
self.hud.show.update_map_markers(event);
|
||||||
},
|
},
|
||||||
|
client::Event::SpectatePosition(pos) => {
|
||||||
|
self.scene.camera_mut().force_focus_pos(pos);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,14 +389,13 @@ impl PlayState for SessionState {
|
|||||||
fn tick(&mut self, global_state: &mut GlobalState, events: Vec<Event>) -> PlayStateResult {
|
fn tick(&mut self, global_state: &mut GlobalState, events: Vec<Event>) -> PlayStateResult {
|
||||||
span!(_guard, "tick", "<Session as PlayState>::tick");
|
span!(_guard, "tick", "<Session as PlayState>::tick");
|
||||||
// TODO: let mut client = self.client.borrow_mut();
|
// TODO: let mut client = self.client.borrow_mut();
|
||||||
|
|
||||||
// TODO: can this be a method on the session or are there borrowcheck issues?
|
// TODO: can this be a method on the session or are there borrowcheck issues?
|
||||||
let (client_presence, client_registered) = {
|
let (client_presence, client_registered) = {
|
||||||
let client = self.client.borrow();
|
let client = self.client.borrow();
|
||||||
(client.presence(), client.registered())
|
(client.presence(), client.registered())
|
||||||
};
|
};
|
||||||
|
|
||||||
if client_presence.is_some() {
|
if let Some(presence) = client_presence {
|
||||||
let camera = self.scene.camera_mut();
|
let camera = self.scene.camera_mut();
|
||||||
|
|
||||||
// Clamp camera's vertical angle if the toggle is enabled
|
// Clamp camera's vertical angle if the toggle is enabled
|
||||||
@ -425,7 +427,7 @@ impl PlayState for SessionState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compute camera data
|
// Compute camera data
|
||||||
camera.compute_dependents(&*self.client.borrow().state().terrain());
|
camera.compute_dependents(&*client.state().terrain());
|
||||||
let camera::Dependents {
|
let camera::Dependents {
|
||||||
cam_pos, cam_dir, ..
|
cam_pos, cam_dir, ..
|
||||||
} = self.scene.camera().dependents();
|
} = self.scene.camera().dependents();
|
||||||
@ -480,6 +482,10 @@ impl PlayState for SessionState {
|
|||||||
|
|
||||||
drop(client);
|
drop(client);
|
||||||
|
|
||||||
|
if presence == PresenceKind::Spectator {
|
||||||
|
self.client.borrow_mut().spectate_position(cam_pos);
|
||||||
|
}
|
||||||
|
|
||||||
// Nearest block to consider with GameInput primary or secondary key.
|
// Nearest block to consider with GameInput primary or secondary key.
|
||||||
let nearest_block_dist = find_shortest_distance(&[
|
let nearest_block_dist = find_shortest_distance(&[
|
||||||
mine_target.filter(|_| is_mining).map(|t| t.distance),
|
mine_target.filter(|_| is_mining).map(|t| t.distance),
|
||||||
@ -887,7 +893,14 @@ impl PlayState for SessionState {
|
|||||||
// The server should do its own filtering of which entities are sent
|
// The server should do its own filtering of which entities are sent
|
||||||
// to clients to prevent abuse.
|
// to clients to prevent abuse.
|
||||||
let camera = self.scene.camera_mut();
|
let camera = self.scene.camera_mut();
|
||||||
camera.next_mode(self.client.borrow().is_moderator());
|
let client = self.client.borrow();
|
||||||
|
camera.next_mode(
|
||||||
|
client.is_moderator(),
|
||||||
|
client
|
||||||
|
.presence()
|
||||||
|
.map(|presence| presence == PresenceKind::Spectator)
|
||||||
|
.unwrap_or(false),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
GameInput::Select => {
|
GameInput::Select => {
|
||||||
if !state {
|
if !state {
|
||||||
|
Reference in New Issue
Block a user