mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'isse/spectate' into 'master'
Spectate mode Closes #1379 See merge request veloren/veloren!3457
This commit is contained in:
commit
877b4b06d7
@ -57,6 +57,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Cave biomes
|
||||
- Updated the Polish translation
|
||||
- Setting for disabling flashing lights
|
||||
- Spectate mode for moderators.
|
||||
|
||||
### Changed
|
||||
|
||||
@ -101,6 +102,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Modular weapons now have a selling price
|
||||
- Closing a subwindow now only regrabs the cursor if no other subwindow requires it.
|
||||
- Fixed npc not handling interactions while fighting (especially merchants in trade)
|
||||
- Fixed bug where you would still be burning after dying in lava.
|
||||
|
||||
## [0.12.0] - 2022-02-19
|
||||
|
||||
|
@ -3,6 +3,7 @@ char_selection-delete_permanently = Permanently delete this Character?
|
||||
char_selection-deleting_character = Deleting Character...
|
||||
char_selection-change_server = Change Server
|
||||
char_selection-enter_world = Enter World
|
||||
char_selection-spectate = Spectate World
|
||||
char_selection-logout = Logout
|
||||
char_selection-create_new_character = Create New Character
|
||||
char_selection-creating_character = Creating Character...
|
||||
|
@ -63,4 +63,6 @@ gameinput-swimup = Swim upwards
|
||||
gameinput-mapzoomin = Increase map zoom
|
||||
gameinput-mapzoomout = Decrease map zoom
|
||||
gameinput-greet = Greet
|
||||
gameinput-map-locationmarkerbutton = Set a waypoint in the Map
|
||||
gameinput-map-locationmarkerbutton = Set a waypoint in the Map
|
||||
gameinput-spectatespeedboost = Spectate speed boost
|
||||
gameinput-spectateviewpoint = Spectate viewpoint
|
@ -30,7 +30,7 @@ use common::{
|
||||
slot::{EquipSlot, InvSlotId, Slot},
|
||||
CharacterState, ChatMode, ControlAction, ControlEvent, Controller, ControllerInputs,
|
||||
GroupManip, InputKind, InventoryAction, InventoryEvent, InventoryUpdateEvent,
|
||||
MapMarkerChange, UtteranceKind,
|
||||
MapMarkerChange, Pos, UtteranceKind,
|
||||
},
|
||||
event::{EventBus, LocalEvent},
|
||||
grid::Grid,
|
||||
@ -107,6 +107,7 @@ pub enum Event {
|
||||
CharacterEdited(CharacterId),
|
||||
CharacterError(String),
|
||||
MapMarker(comp::MapMarkerUpdate),
|
||||
SpectatePosition(Vec3<f32>),
|
||||
}
|
||||
|
||||
pub struct WorldData {
|
||||
@ -216,6 +217,7 @@ pub struct Client {
|
||||
available_recipes: HashMap<String, Option<SpriteKind>>,
|
||||
lod_zones: HashMap<Vec2<i32>, lod::Zone>,
|
||||
lod_last_requested: Option<Instant>,
|
||||
force_update_counter: u64,
|
||||
|
||||
max_group_size: u32,
|
||||
// Client has received an invite (inviter uid, time out instant)
|
||||
@ -678,6 +680,8 @@ impl Client {
|
||||
lod_zones: HashMap::new(),
|
||||
lod_last_requested: None,
|
||||
|
||||
force_update_counter: 0,
|
||||
|
||||
max_group_size,
|
||||
invite: None,
|
||||
group_leader: None,
|
||||
@ -816,7 +820,8 @@ impl Client {
|
||||
| ClientGeneral::RequestPlayerPhysics { .. }
|
||||
| ClientGeneral::RequestLossyTerrainCompression { .. }
|
||||
| ClientGeneral::AcknowledgePersistenceLoadError
|
||||
| ClientGeneral::UpdateMapMarker(_) => {
|
||||
| ClientGeneral::UpdateMapMarker(_)
|
||||
| ClientGeneral::SpectatePosition(_) => {
|
||||
#[cfg(feature = "tracy")]
|
||||
{
|
||||
ingame = 1.0;
|
||||
@ -881,6 +886,14 @@ impl Client {
|
||||
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
|
||||
pub fn load_character_list(&mut self) {
|
||||
self.character_list.loading = true;
|
||||
@ -1334,6 +1347,18 @@ impl Client {
|
||||
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
|
||||
/// and sends the `ControlAction` event that signals to do the swap.
|
||||
pub fn swap_loadout(&mut self) { self.control_action(ControlAction::SwapEquippedWeapons) }
|
||||
@ -1732,8 +1757,12 @@ impl Client {
|
||||
self.state.read_storage().get(self.entity()).cloned(),
|
||||
self.state.read_storage().get(self.entity()).cloned(),
|
||||
) {
|
||||
self.in_game_stream
|
||||
.send(ClientGeneral::PlayerPhysics { pos, vel, ori })?;
|
||||
self.in_game_stream.send(ClientGeneral::PlayerPhysics {
|
||||
pos,
|
||||
vel,
|
||||
ori,
|
||||
force_counter: self.force_update_counter,
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2049,7 +2078,8 @@ impl Client {
|
||||
.ecs_mut()
|
||||
.apply_entity_sync_package(entity_sync_package);
|
||||
},
|
||||
ServerGeneral::CompSync(comp_sync_package) => {
|
||||
ServerGeneral::CompSync(comp_sync_package, force_counter) => {
|
||||
self.force_update_counter = force_counter;
|
||||
self.state
|
||||
.ecs_mut()
|
||||
.apply_comp_sync_package(comp_sync_package);
|
||||
@ -2256,6 +2286,9 @@ impl Client {
|
||||
ServerGeneral::WeatherUpdate(weather) => {
|
||||
self.weather.weather_update(weather);
|
||||
},
|
||||
ServerGeneral::SpectatePosition(pos) => {
|
||||
frontend_events.push(Event::SpectatePosition(pos));
|
||||
},
|
||||
_ => unreachable!("Not a in_game message"),
|
||||
}
|
||||
Ok(())
|
||||
@ -2319,6 +2352,18 @@ impl Client {
|
||||
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"),
|
||||
}
|
||||
Ok(())
|
||||
|
@ -74,11 +74,14 @@ pub enum ClientGeneral {
|
||||
pos: comp::Pos,
|
||||
vel: comp::Vel,
|
||||
ori: comp::Ori,
|
||||
force_counter: u64,
|
||||
},
|
||||
UnlockSkill(Skill),
|
||||
UnlockSkillGroup(SkillGroupKind),
|
||||
RequestSiteInfo(SiteId),
|
||||
UpdateMapMarker(comp::MapMarkerChange),
|
||||
|
||||
SpectatePosition(Vec3<f32>),
|
||||
//Only in Game, via terrain stream
|
||||
TerrainChunkRequest {
|
||||
key: Vec2<i32>,
|
||||
@ -138,7 +141,8 @@ impl ClientMsg {
|
||||
| ClientGeneral::RequestPlayerPhysics { .. }
|
||||
| ClientGeneral::RequestLossyTerrainCompression { .. }
|
||||
| ClientGeneral::AcknowledgePersistenceLoadError
|
||||
| ClientGeneral::UpdateMapMarker(_) => {
|
||||
| ClientGeneral::UpdateMapMarker(_)
|
||||
| ClientGeneral::SpectatePosition(_) => {
|
||||
c_type == ClientType::Game && presence.is_some()
|
||||
},
|
||||
//Always possible
|
||||
|
@ -140,6 +140,7 @@ pub enum ServerGeneral {
|
||||
CharacterCreated(character::CharacterId),
|
||||
CharacterEdited(character::CharacterId),
|
||||
CharacterSuccess,
|
||||
SpectatorSuccess(Vec3<f32>),
|
||||
//Ingame related
|
||||
GroupUpdate(comp::group::ChangeNotification<Uid>),
|
||||
/// Indicate to the client that they are invited to join a group
|
||||
@ -187,7 +188,7 @@ pub enum ServerGeneral {
|
||||
SetPlayerEntity(Uid),
|
||||
TimeOfDay(TimeOfDay, Calendar),
|
||||
EntitySync(sync::EntitySyncPackage),
|
||||
CompSync(sync::CompSyncPackage<EcsCompPacket>),
|
||||
CompSync(sync::CompSyncPackage<EcsCompPacket>, u64),
|
||||
CreateEntity(sync::EntityPackage<EcsCompPacket>),
|
||||
DeleteEntity(Uid),
|
||||
Disconnect(DisconnectReason),
|
||||
@ -199,6 +200,9 @@ pub enum ServerGeneral {
|
||||
SiteEconomy(EconomyInfo),
|
||||
MapMarker(comp::MapMarkerUpdate),
|
||||
WeatherUpdate(WeatherGrid),
|
||||
/// Suggest the client to spectate a position. Called after client has
|
||||
/// requested teleport etc.
|
||||
SpectatePosition(Vec3<f32>),
|
||||
}
|
||||
|
||||
impl ServerGeneral {
|
||||
@ -292,7 +296,7 @@ impl ServerMsg {
|
||||
| ServerGeneral::CharacterCreated(_) => {
|
||||
c_type != ClientType::ChatOnly && presence.is_none()
|
||||
},
|
||||
ServerGeneral::CharacterSuccess => {
|
||||
ServerGeneral::CharacterSuccess | ServerGeneral::SpectatorSuccess(_) => {
|
||||
c_type == ClientType::Game && presence.is_none()
|
||||
},
|
||||
//Ingame related
|
||||
@ -312,7 +316,8 @@ impl ServerMsg {
|
||||
| ServerGeneral::FinishedTrade(_)
|
||||
| ServerGeneral::SiteEconomy(_)
|
||||
| ServerGeneral::MapMarker(_)
|
||||
| ServerGeneral::WeatherUpdate(_) => {
|
||||
| ServerGeneral::WeatherUpdate(_)
|
||||
| ServerGeneral::SpectatePosition(_) => {
|
||||
c_type == ClientType::Game && presence.is_some()
|
||||
},
|
||||
// Always possible
|
||||
@ -322,7 +327,7 @@ impl ServerMsg {
|
||||
| ServerGeneral::SetPlayerEntity(_)
|
||||
| ServerGeneral::TimeOfDay(_, _)
|
||||
| ServerGeneral::EntitySync(_)
|
||||
| ServerGeneral::CompSync(_)
|
||||
| ServerGeneral::CompSync(_, _)
|
||||
| ServerGeneral::CreateEntity(_)
|
||||
| ServerGeneral::DeleteEntity(_)
|
||||
| ServerGeneral::Disconnect(_)
|
||||
|
@ -213,9 +213,32 @@ impl Component for PhysicsState {
|
||||
|
||||
/// Used to forcefully update the position, velocity, and orientation of the
|
||||
/// client
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct ForceUpdate;
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ForceUpdate {
|
||||
flag: bool,
|
||||
counter: u64,
|
||||
}
|
||||
|
||||
impl ForceUpdate {
|
||||
pub fn forced() -> Self {
|
||||
Self {
|
||||
flag: true,
|
||||
counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self) {
|
||||
self.flag = true;
|
||||
self.counter = self.counter.wrapping_add(1);
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) { self.flag = false; }
|
||||
|
||||
pub fn is_forced(&self) -> bool { self.flag }
|
||||
|
||||
pub fn counter(&self) -> u64 { self.counter }
|
||||
}
|
||||
|
||||
impl Component for ForceUpdate {
|
||||
type Storage = NullStorage<Self>;
|
||||
type Storage = VecStorage<Self>;
|
||||
}
|
||||
|
@ -108,6 +108,7 @@ pub enum ServerEvent {
|
||||
entity: EcsEntity,
|
||||
character_id: CharacterId,
|
||||
},
|
||||
InitSpectator(EcsEntity),
|
||||
UpdateCharacterData {
|
||||
entity: EcsEntity,
|
||||
components: (
|
||||
|
@ -148,7 +148,9 @@ impl Link for Mounting {
|
||||
.map(|p| p.0.map(|e| e.floor()))
|
||||
.unwrap_or_else(|| terrain.find_space(old_pos).map(|e| e as f32))
|
||||
+ Vec3::new(0.5, 0.5, 0.0);
|
||||
let _ = force_update.insert(rider, comp::ForceUpdate);
|
||||
if let Some(force_update) = force_update.get_mut(rider) {
|
||||
force_update.update();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -171,7 +171,8 @@ impl Client {
|
||||
| ServerGeneral::CharacterActionError(_)
|
||||
| ServerGeneral::CharacterCreated(_)
|
||||
| ServerGeneral::CharacterEdited(_)
|
||||
| ServerGeneral::CharacterSuccess => {
|
||||
| ServerGeneral::CharacterSuccess
|
||||
| ServerGeneral::SpectatorSuccess(_) => {
|
||||
PreparedMsg::new(1, &g, &self.character_screen_stream_params)
|
||||
},
|
||||
//In-game related
|
||||
@ -188,7 +189,8 @@ impl Client {
|
||||
| ServerGeneral::UpdatePendingTrade(_, _, _)
|
||||
| ServerGeneral::FinishedTrade(_)
|
||||
| ServerGeneral::MapMarker(_)
|
||||
| ServerGeneral::WeatherUpdate(_) => {
|
||||
| ServerGeneral::WeatherUpdate(_)
|
||||
| ServerGeneral::SpectatePosition(_) => {
|
||||
PreparedMsg::new(2, &g, &self.in_game_stream_params)
|
||||
},
|
||||
//In-game related, terrain
|
||||
@ -204,7 +206,7 @@ impl Client {
|
||||
| ServerGeneral::SetPlayerEntity(_)
|
||||
| ServerGeneral::TimeOfDay(_, _)
|
||||
| ServerGeneral::EntitySync(_)
|
||||
| ServerGeneral::CompSync(_)
|
||||
| ServerGeneral::CompSync(_, _)
|
||||
| ServerGeneral::CreateEntity(_)
|
||||
| ServerGeneral::DeleteEntity(_)
|
||||
| ServerGeneral::Disconnect(_)
|
||||
|
@ -6,6 +6,7 @@ use crate::{
|
||||
client::Client,
|
||||
location::Locations,
|
||||
login_provider::LoginProvider,
|
||||
presence::Presence,
|
||||
settings::{
|
||||
Ban, BanAction, BanInfo, EditableSetting, SettingError, WhitelistInfo, WhitelistRecord,
|
||||
},
|
||||
@ -49,7 +50,7 @@ use common::{
|
||||
weather, Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
|
||||
};
|
||||
use common_net::{
|
||||
msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneral},
|
||||
msg::{DisconnectReason, Notification, PlayerListUpdate, PresenceKind, ServerGeneral},
|
||||
sync::WorldSyncExt,
|
||||
};
|
||||
use common_state::{BuildAreaError, BuildAreas};
|
||||
@ -231,19 +232,38 @@ fn position_mut<T>(
|
||||
})
|
||||
.unwrap_or(entity);
|
||||
|
||||
let mut maybe_pos = None;
|
||||
|
||||
let res = server
|
||||
.state
|
||||
.ecs()
|
||||
.write_storage::<comp::Pos>()
|
||||
.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));
|
||||
if res.is_ok() {
|
||||
let _ = server
|
||||
|
||||
if let Some(pos) = maybe_pos {
|
||||
if server
|
||||
.state
|
||||
.ecs()
|
||||
.write_storage::<comp::ForceUpdate>()
|
||||
.insert(entity, comp::ForceUpdate);
|
||||
.read_storage::<Presence>()
|
||||
.get(entity)
|
||||
.map(|presence| presence.kind == PresenceKind::Spectator)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
server.notify_client(entity, ServerGeneral::SpectatePosition(pos));
|
||||
} else {
|
||||
server
|
||||
.state
|
||||
.ecs()
|
||||
.write_storage::<comp::ForceUpdate>()
|
||||
.get_mut(entity)
|
||||
.map(|force_update| force_update.update());
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
@ -2058,7 +2078,7 @@ fn handle_light(
|
||||
.ecs_mut()
|
||||
.create_entity_synced()
|
||||
.with(pos)
|
||||
.with(comp::ForceUpdate)
|
||||
.with(comp::ForceUpdate::forced())
|
||||
.with(light_emitter);
|
||||
if let Some(light_offset) = light_offset_opt {
|
||||
builder.with(light_offset).build();
|
||||
|
@ -33,6 +33,11 @@ pub fn handle_initialize_character(
|
||||
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(
|
||||
server: &mut Server,
|
||||
entity: EcsEntity,
|
||||
|
@ -410,10 +410,9 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, last_change: Healt
|
||||
.map(|e| error!(?e, ?entity, "Failed to set zero vel on dead client"));
|
||||
state
|
||||
.ecs()
|
||||
.write_storage()
|
||||
.insert(entity, comp::ForceUpdate)
|
||||
.err()
|
||||
.map(|e| error!(?e, ?entity, "Failed to insert ForceUpdate on dead client"));
|
||||
.write_storage::<comp::ForceUpdate>()
|
||||
.get_mut(entity)
|
||||
.map(|force_update| force_update.update());
|
||||
state
|
||||
.ecs()
|
||||
.write_storage::<Energy>()
|
||||
@ -648,15 +647,14 @@ pub fn handle_respawn(server: &Server, entity: EcsEntity) {
|
||||
.map(|pos| pos.0 = respawn_point);
|
||||
state
|
||||
.ecs()
|
||||
.write_storage()
|
||||
.insert(entity, comp::ForceUpdate)
|
||||
.err()
|
||||
.map(|e| {
|
||||
error!(
|
||||
?e,
|
||||
"Error inserting ForceUpdate component when respawning client"
|
||||
)
|
||||
});
|
||||
.write_storage::<comp::PhysicsState>()
|
||||
.get_mut(entity)
|
||||
.map(|phys_state| phys_state.reset());
|
||||
state
|
||||
.ecs()
|
||||
.write_storage::<comp::ForceUpdate>()
|
||||
.get_mut(entity)
|
||||
.map(|force_update| force_update.update());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1259,15 +1257,9 @@ pub fn handle_teleport_to(server: &Server, entity: EcsEntity, target: Uid, max_r
|
||||
if let (Some(pos), Some(target_pos)) = (positions.get_mut(entity), target_pos) {
|
||||
if max_range.map_or(true, |r| pos.0.distance_squared(target_pos.0) < r.powi(2)) {
|
||||
*pos = target_pos;
|
||||
ecs.write_storage()
|
||||
.insert(entity, comp::ForceUpdate)
|
||||
.err()
|
||||
.map(|e| {
|
||||
error!(
|
||||
?e,
|
||||
"Error inserting ForceUpdate component when teleporting client"
|
||||
)
|
||||
});
|
||||
ecs.write_storage::<comp::ForceUpdate>()
|
||||
.get_mut(entity)
|
||||
.map(|force_update| force_update.update());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,8 @@ use common::event::{EventBus, ServerEvent, ServerEventDiscriminants};
|
||||
use common_base::span;
|
||||
use entity_creation::{
|
||||
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::{
|
||||
handle_aura, handle_bonk, handle_buff, handle_change_ability, handle_combo_change,
|
||||
@ -139,6 +140,7 @@ impl Server {
|
||||
entity,
|
||||
character_id,
|
||||
} => handle_initialize_character(self, entity, character_id),
|
||||
ServerEvent::InitSpectator(entity) => handle_initialize_spectator(self, entity),
|
||||
ServerEvent::UpdateCharacterData { entity, components } => {
|
||||
let (
|
||||
body,
|
||||
|
@ -486,7 +486,7 @@ pub fn handle_possess(server: &mut Server, possessor_uid: Uid, possessee_uid: Ui
|
||||
possessee,
|
||||
);
|
||||
if !comp_sync_package.is_empty() {
|
||||
client.send_fallible(ServerGeneral::CompSync(comp_sync_package));
|
||||
client.send_fallible(ServerGeneral::CompSync(comp_sync_package, 0)); // TODO: Check if this should be zero
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,6 +107,8 @@ pub trait StateExt {
|
||||
) -> EcsEntityBuilder;
|
||||
/// Insert common/default components for a new character joining the server
|
||||
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.
|
||||
/// Performed after loading component data from the database
|
||||
fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents);
|
||||
@ -508,7 +510,7 @@ impl StateExt for State {
|
||||
self.write_component_ignore_entity_dead(entity, comp::Combo::default());
|
||||
|
||||
// Make sure physics components are updated
|
||||
self.write_component_ignore_entity_dead(entity, comp::ForceUpdate);
|
||||
self.write_component_ignore_entity_dead(entity, comp::ForceUpdate::forced());
|
||||
|
||||
const INITIAL_VD: u32 = 5; //will be changed after login
|
||||
self.write_component_ignore_entity_dead(
|
||||
@ -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::forced());
|
||||
|
||||
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) {
|
||||
let PersistedComponents {
|
||||
body,
|
||||
@ -576,7 +604,7 @@ impl StateExt for State {
|
||||
self.write_component_ignore_entity_dead(entity, waypoint);
|
||||
self.write_component_ignore_entity_dead(entity, comp::Pos(waypoint.get_pos()));
|
||||
self.write_component_ignore_entity_dead(entity, comp::Vel(Vec3::zero()));
|
||||
self.write_component_ignore_entity_dead(entity, comp::ForceUpdate);
|
||||
self.write_component_ignore_entity_dead(entity, comp::ForceUpdate::forced());
|
||||
}
|
||||
|
||||
if let Some(map_marker) = map_marker {
|
||||
|
@ -208,12 +208,15 @@ impl<'a> System<'a> for Sys {
|
||||
// We lazily initialize the the synchronization messages in case there are no
|
||||
// clients.
|
||||
let mut entity_comp_sync = Either::Left((entity_sync_package, comp_sync_package));
|
||||
for (client, _, _, _) in &mut subscribers {
|
||||
for (client, _, client_entity, _) in &mut subscribers {
|
||||
let msg = entity_comp_sync.right_or_else(
|
||||
|(entity_sync_package, comp_sync_package)| {
|
||||
(
|
||||
client.prepare(ServerGeneral::EntitySync(entity_sync_package)),
|
||||
client.prepare(ServerGeneral::CompSync(comp_sync_package)),
|
||||
client.prepare(ServerGeneral::CompSync(
|
||||
comp_sync_package,
|
||||
force_updates.get(*client_entity).map_or(0, |f| f.counter()),
|
||||
)),
|
||||
)
|
||||
},
|
||||
);
|
||||
@ -234,7 +237,7 @@ impl<'a> System<'a> for Sys {
|
||||
(&positions, last_pos.mask().maybe()),
|
||||
(&velocities, last_vel.mask().maybe()).maybe(),
|
||||
(&orientations, last_vel.mask().maybe()).maybe(),
|
||||
force_updates.mask().maybe(),
|
||||
force_updates.maybe(),
|
||||
colliders.maybe(),
|
||||
)
|
||||
.join()
|
||||
@ -250,7 +253,7 @@ impl<'a> System<'a> for Sys {
|
||||
// Don't send client physics updates about itself unless force update is
|
||||
// set or the client is subject to
|
||||
// server-authoritative physics
|
||||
force_update.is_some()
|
||||
force_update.map_or(false, |f| f.is_forced())
|
||||
|| player_physics_setting.server_authoritative()
|
||||
|| is_rider.get(entity).is_some()
|
||||
} else if matches!(collider, Some(Collider::Voxel { .. })) {
|
||||
@ -305,7 +308,10 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
}
|
||||
|
||||
client.send_fallible(ServerGeneral::CompSync(comp_sync_package));
|
||||
client.send_fallible(ServerGeneral::CompSync(
|
||||
comp_sync_package,
|
||||
force_updates.get(*client_entity).map_or(0, |f| f.counter()),
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
@ -363,7 +369,10 @@ impl<'a> System<'a> for Sys {
|
||||
let comp_sync_package =
|
||||
trackers.create_sync_from_client_package(&tracked_storages, entity);
|
||||
if !comp_sync_package.is_empty() {
|
||||
client.send_fallible(ServerGeneral::CompSync(comp_sync_package));
|
||||
client.send_fallible(ServerGeneral::CompSync(
|
||||
comp_sync_package,
|
||||
force_updates.get(entity).map_or(0, |f| f.counter()),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -392,7 +401,9 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
|
||||
// Remove all force flags.
|
||||
force_updates.clear();
|
||||
for force_update in (&mut force_updates).join() {
|
||||
force_update.clear();
|
||||
}
|
||||
inventory_updates.clear();
|
||||
|
||||
// Sync resources
|
||||
|
@ -7,7 +7,7 @@ use crate::{
|
||||
EditableSettings,
|
||||
};
|
||||
use common::{
|
||||
comp::{ChatType, Player, UnresolvedChatMsg},
|
||||
comp::{Admin, AdminRole, ChatType, Player, UnresolvedChatMsg},
|
||||
event::{EventBus, ServerEvent},
|
||||
uid::Uid,
|
||||
};
|
||||
@ -15,7 +15,7 @@ use common_ecs::{Job, Origin, Phase, System};
|
||||
use common_net::msg::{ClientGeneral, ServerGeneral};
|
||||
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, WriteExpect};
|
||||
use std::sync::atomic::Ordering;
|
||||
use tracing::{debug, warn};
|
||||
use tracing::debug;
|
||||
|
||||
impl Sys {
|
||||
fn handle_client_character_screen_msg(
|
||||
@ -26,18 +26,43 @@ impl Sys {
|
||||
character_updater: &mut WriteExpect<'_, CharacterUpdater>,
|
||||
uids: &ReadStorage<'_, Uid>,
|
||||
players: &ReadStorage<'_, Player>,
|
||||
admins: &ReadStorage<'_, Admin>,
|
||||
presences: &ReadStorage<'_, Presence>,
|
||||
editable_settings: &ReadExpect<'_, EditableSettings>,
|
||||
alias_validator: &ReadExpect<'_, AliasValidator>,
|
||||
msg: ClientGeneral,
|
||||
) -> 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 {
|
||||
// Request spectator state
|
||||
ClientGeneral::Spectate => {
|
||||
if players.contains(entity) {
|
||||
warn!("Spectator mode not yet implemented on server");
|
||||
if let Some(admin) = admins.get(entity) && admin.0 >= AdminRole::Moderator {
|
||||
send_join_messages()?;
|
||||
|
||||
server_emitter.emit(ServerEvent::InitSpectator(entity));
|
||||
|
||||
} else {
|
||||
debug!("dropped Spectate msg from unregistered client")
|
||||
debug!("dropped Spectate msg from unprivileged client")
|
||||
}
|
||||
},
|
||||
ClientGeneral::Character(character_id) => {
|
||||
@ -76,31 +101,14 @@ impl Sys {
|
||||
character_id,
|
||||
);
|
||||
|
||||
send_join_messages()?;
|
||||
|
||||
// Start inserting non-persisted/default components for the entity
|
||||
// while we load the DB data
|
||||
server_emitter.emit(ServerEvent::InitCharacterData {
|
||||
entity,
|
||||
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 {
|
||||
debug!("Client is not yet registered");
|
||||
@ -199,6 +207,7 @@ impl<'a> System<'a> for Sys {
|
||||
ReadStorage<'a, Uid>,
|
||||
ReadStorage<'a, Client>,
|
||||
ReadStorage<'a, Player>,
|
||||
ReadStorage<'a, Admin>,
|
||||
ReadStorage<'a, Presence>,
|
||||
ReadExpect<'a, EditableSettings>,
|
||||
ReadExpect<'a, AliasValidator>,
|
||||
@ -218,6 +227,7 @@ impl<'a> System<'a> for Sys {
|
||||
uids,
|
||||
clients,
|
||||
players,
|
||||
admins,
|
||||
presences,
|
||||
editable_settings,
|
||||
alias_validator,
|
||||
@ -235,6 +245,7 @@ impl<'a> System<'a> for Sys {
|
||||
&mut character_updater,
|
||||
&uids,
|
||||
&players,
|
||||
&admins,
|
||||
&presences,
|
||||
&editable_settings,
|
||||
&alias_validator,
|
||||
|
@ -3,8 +3,8 @@ use crate::TerrainPersistence;
|
||||
use crate::{client::Client, presence::Presence, Settings};
|
||||
use common::{
|
||||
comp::{
|
||||
Admin, CanBuild, ControlEvent, Controller, ForceUpdate, Health, Ori, Player, Pos, SkillSet,
|
||||
Vel,
|
||||
Admin, AdminRole, CanBuild, ControlEvent, Controller, ForceUpdate, Health, Ori, Player,
|
||||
Pos, SkillSet, Vel,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
link::Is,
|
||||
@ -14,7 +14,7 @@ use common::{
|
||||
vol::ReadVol,
|
||||
};
|
||||
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 specs::{Entities, Join, Read, ReadExpect, ReadStorage, Write, WriteStorage};
|
||||
use tracing::{debug, trace, warn};
|
||||
@ -111,7 +111,7 @@ impl Sys {
|
||||
}
|
||||
}
|
||||
},
|
||||
ClientGeneral::PlayerPhysics { pos, vel, ori } => {
|
||||
ClientGeneral::PlayerPhysics { pos, vel, ori, force_counter } => {
|
||||
let player_physics_setting = maybe_player.map(|p| {
|
||||
player_physics_settings
|
||||
.settings
|
||||
@ -120,7 +120,7 @@ impl Sys {
|
||||
});
|
||||
|
||||
if presence.kind.controlling_char()
|
||||
&& force_updates.get(entity).is_none()
|
||||
&& force_updates.get(entity).map_or(true, |force_update| force_update.counter() == force_counter)
|
||||
&& healths.get(entity).map_or(true, |h| !h.is_dead)
|
||||
&& is_rider.get(entity).is_none()
|
||||
&& player_physics_setting
|
||||
@ -288,6 +288,13 @@ impl Sys {
|
||||
ClientGeneral::UpdateMapMarker(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::CreateCharacter { .. }
|
||||
| ClientGeneral::EditCharacter { .. }
|
||||
|
@ -243,7 +243,9 @@ impl<'a> System<'a> for Sys {
|
||||
.map(|x| x.as_::<f32>())
|
||||
.unwrap_or_else(|| chunk.find_accessible_pos(pos.0.xy().as_::<i32>(), false));
|
||||
repositioned.push(entity);
|
||||
let _ = force_update.insert(entity, ForceUpdate);
|
||||
force_update
|
||||
.get_mut(entity)
|
||||
.map(|force_update| force_update.update());
|
||||
let _ = waypoints.insert(entity, Waypoint::new(pos.0, *time));
|
||||
}
|
||||
}
|
||||
|
@ -148,6 +148,10 @@ pub enum GameInput {
|
||||
MapZoomOut,
|
||||
#[strum(serialize = "gameinput.map.locationmarkerbutton")]
|
||||
MapSetMarker,
|
||||
#[strum(serialize = "gameinput.spectatespeedboost")]
|
||||
SpectateSpeedBoost,
|
||||
#[strum(serialize = "gameinput.spectateviewpoint")]
|
||||
SpectateViewpoint,
|
||||
}
|
||||
|
||||
impl GameInput {
|
||||
|
@ -3,7 +3,7 @@ use super::{
|
||||
img_ids::{Imgs, ImgsRot},
|
||||
item_imgs::ItemImgs,
|
||||
slots::{ArmorSlot, EquipSlot, InventorySlot, SlotManager},
|
||||
Show, CRITICAL_HP_COLOR, LOW_HP_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
|
||||
HudInfo, Show, CRITICAL_HP_COLOR, LOW_HP_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
|
||||
};
|
||||
use crate::{
|
||||
game_input::GameInput,
|
||||
@ -563,6 +563,7 @@ widget_ids! {
|
||||
#[derive(WidgetCommon)]
|
||||
pub struct Bag<'a> {
|
||||
client: &'a Client,
|
||||
info: &'a HudInfo,
|
||||
global_state: &'a GlobalState,
|
||||
imgs: &'a Imgs,
|
||||
item_imgs: &'a ItemImgs,
|
||||
@ -589,6 +590,7 @@ impl<'a> Bag<'a> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
client: &'a Client,
|
||||
info: &'a HudInfo,
|
||||
global_state: &'a GlobalState,
|
||||
imgs: &'a Imgs,
|
||||
item_imgs: &'a ItemImgs,
|
||||
@ -610,6 +612,7 @@ impl<'a> Bag<'a> {
|
||||
) -> Self {
|
||||
Self {
|
||||
client,
|
||||
info,
|
||||
global_state,
|
||||
imgs,
|
||||
item_imgs,
|
||||
@ -695,7 +698,7 @@ impl<'a> Widget for Bag<'a> {
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.desc_text_color(TEXT_COLOR);
|
||||
let inventories = self.client.inventories();
|
||||
let inventory = match inventories.get(self.client.entity()) {
|
||||
let inventory = match inventories.get(self.info.viewpoint_entity) {
|
||||
Some(l) => l,
|
||||
None => return None,
|
||||
};
|
||||
@ -733,6 +736,7 @@ impl<'a> Widget for Bag<'a> {
|
||||
)
|
||||
},
|
||||
self.client,
|
||||
self.info,
|
||||
self.imgs,
|
||||
self.item_imgs,
|
||||
self.pulse,
|
||||
@ -759,7 +763,7 @@ impl<'a> Widget for Bag<'a> {
|
||||
true,
|
||||
&item_tooltip,
|
||||
self.stats.name.to_string(),
|
||||
self.client.entity(),
|
||||
self.info.viewpoint_entity,
|
||||
true,
|
||||
inventory,
|
||||
&state.bg_ids,
|
||||
|
@ -1,6 +1,6 @@
|
||||
use super::{
|
||||
img_ids::{Imgs, ImgsRot},
|
||||
BLACK, CRITICAL_HP_COLOR, LOW_HP_COLOR, QUALITY_LEGENDARY, TEXT_COLOR,
|
||||
HudInfo, BLACK, CRITICAL_HP_COLOR, LOW_HP_COLOR, QUALITY_LEGENDARY, TEXT_COLOR,
|
||||
};
|
||||
use crate::{
|
||||
game_input::GameInput,
|
||||
@ -51,6 +51,7 @@ widget_ids! {
|
||||
#[derive(WidgetCommon)]
|
||||
pub struct Buttons<'a> {
|
||||
client: &'a Client,
|
||||
info: &'a HudInfo,
|
||||
show_bag: bool,
|
||||
imgs: &'a Imgs,
|
||||
fonts: &'a Fonts,
|
||||
@ -68,6 +69,7 @@ pub struct Buttons<'a> {
|
||||
impl<'a> Buttons<'a> {
|
||||
pub fn new(
|
||||
client: &'a Client,
|
||||
info: &'a HudInfo,
|
||||
show_bag: bool,
|
||||
imgs: &'a Imgs,
|
||||
fonts: &'a Fonts,
|
||||
@ -81,6 +83,7 @@ impl<'a> Buttons<'a> {
|
||||
) -> Self {
|
||||
Self {
|
||||
client,
|
||||
info,
|
||||
show_bag,
|
||||
imgs,
|
||||
fonts,
|
||||
@ -192,7 +195,7 @@ impl<'a> Widget for Buttons<'a> {
|
||||
);
|
||||
}
|
||||
let invs = self.client.inventories();
|
||||
let inventory = match invs.get(self.client.entity()) {
|
||||
let inventory = match invs.get(self.info.viewpoint_entity) {
|
||||
Some(inv) => inv,
|
||||
None => return None,
|
||||
};
|
||||
|
@ -3,7 +3,7 @@ use super::{
|
||||
img_ids::{Imgs, ImgsRot},
|
||||
item_imgs::{animate_by_pulse, ItemImgs},
|
||||
slots::{CraftSlot, CraftSlotInfo, SlotManager},
|
||||
Show, TEXT_COLOR, TEXT_DULL_RED_COLOR, TEXT_GRAY_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
|
||||
HudInfo, Show, TEXT_COLOR, TEXT_DULL_RED_COLOR, TEXT_GRAY_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
|
||||
};
|
||||
use crate::ui::{
|
||||
fonts::Fonts,
|
||||
@ -137,6 +137,7 @@ impl Default for CraftingShow {
|
||||
#[derive(WidgetCommon)]
|
||||
pub struct Crafting<'a> {
|
||||
client: &'a Client,
|
||||
info: &'a HudInfo,
|
||||
imgs: &'a Imgs,
|
||||
fonts: &'a Fonts,
|
||||
localized_strings: &'a Localization,
|
||||
@ -156,6 +157,7 @@ pub struct Crafting<'a> {
|
||||
impl<'a> Crafting<'a> {
|
||||
pub fn new(
|
||||
client: &'a Client,
|
||||
info: &'a HudInfo,
|
||||
imgs: &'a Imgs,
|
||||
fonts: &'a Fonts,
|
||||
localized_strings: &'a Localization,
|
||||
@ -171,6 +173,7 @@ impl<'a> Crafting<'a> {
|
||||
) -> Self {
|
||||
Self {
|
||||
client,
|
||||
info,
|
||||
imgs,
|
||||
fonts,
|
||||
localized_strings,
|
||||
@ -321,6 +324,7 @@ impl<'a> Widget for Crafting<'a> {
|
||||
)
|
||||
},
|
||||
self.client,
|
||||
self.info,
|
||||
self.imgs,
|
||||
self.item_imgs,
|
||||
self.pulse,
|
||||
|
@ -4,6 +4,8 @@ use common::comp::{
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::HudInfo;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum Slot {
|
||||
One = 0,
|
||||
@ -63,22 +65,22 @@ impl State {
|
||||
// TODO: remove pending UI
|
||||
// Adds ability slots if missing and should be present
|
||||
// Removes ability slots if not there and shouldn't be present
|
||||
pub fn maintain_abilities(&mut self, client: &client::Client) {
|
||||
pub fn maintain_abilities(&mut self, client: &client::Client, info: &HudInfo) {
|
||||
use specs::WorldExt;
|
||||
if let Some(active_abilities) = client
|
||||
.state()
|
||||
.ecs()
|
||||
.read_storage::<comp::ActiveAbilities>()
|
||||
.get(client.entity())
|
||||
.get(info.viewpoint_entity)
|
||||
{
|
||||
use common::comp::ability::AuxiliaryAbility;
|
||||
for ((i, ability), hotbar_slot) in active_abilities
|
||||
.auxiliary_set(
|
||||
client.inventories().get(client.entity()),
|
||||
client.inventories().get(info.viewpoint_entity),
|
||||
client
|
||||
.state()
|
||||
.read_storage::<comp::SkillSet>()
|
||||
.get(client.entity()),
|
||||
.get(info.viewpoint_entity),
|
||||
)
|
||||
.iter()
|
||||
.enumerate()
|
||||
|
@ -2,7 +2,7 @@ use super::{
|
||||
animate_by_pulse, get_quality_col,
|
||||
img_ids::{Imgs, ImgsRot},
|
||||
item_imgs::ItemImgs,
|
||||
Show, Windows, TEXT_COLOR,
|
||||
HudInfo, Show, Windows, TEXT_COLOR,
|
||||
};
|
||||
use crate::ui::{fonts::Fonts, ImageFrame, ItemTooltip, ItemTooltipManager, ItemTooltipable};
|
||||
use client::Client;
|
||||
@ -51,6 +51,7 @@ pub struct LootScroller<'a> {
|
||||
new_messages: &'a mut VecDeque<LootMessage>,
|
||||
|
||||
client: &'a Client,
|
||||
info: &'a HudInfo,
|
||||
show: &'a Show,
|
||||
imgs: &'a Imgs,
|
||||
item_imgs: &'a ItemImgs,
|
||||
@ -68,6 +69,7 @@ impl<'a> LootScroller<'a> {
|
||||
pub fn new(
|
||||
new_messages: &'a mut VecDeque<LootMessage>,
|
||||
client: &'a Client,
|
||||
info: &'a HudInfo,
|
||||
show: &'a Show,
|
||||
imgs: &'a Imgs,
|
||||
item_imgs: &'a ItemImgs,
|
||||
@ -81,6 +83,7 @@ impl<'a> LootScroller<'a> {
|
||||
Self {
|
||||
new_messages,
|
||||
client,
|
||||
info,
|
||||
show,
|
||||
imgs,
|
||||
item_imgs,
|
||||
@ -143,6 +146,7 @@ impl<'a> Widget for LootScroller<'a> {
|
||||
)
|
||||
},
|
||||
self.client,
|
||||
self.info,
|
||||
self.imgs,
|
||||
self.item_imgs,
|
||||
self.pulse,
|
||||
|
@ -501,6 +501,8 @@ pub struct DebugInfo {
|
||||
pub struct HudInfo {
|
||||
pub is_aiming: bool,
|
||||
pub is_first_person: bool,
|
||||
pub viewpoint_entity: specs::Entity,
|
||||
pub mutable_viewpoint: bool,
|
||||
pub target_entity: Option<specs::Entity>,
|
||||
pub selected_entity: Option<(specs::Entity, Instant)>,
|
||||
}
|
||||
@ -1136,7 +1138,7 @@ impl Hud {
|
||||
|
||||
let character_id = match client.presence().unwrap() {
|
||||
PresenceKind::Character(id) => Some(id),
|
||||
PresenceKind::Spectator => unreachable!("HUD creation in Spectator mode!"),
|
||||
PresenceKind::Spectator => None,
|
||||
PresenceKind::Possessor => None,
|
||||
};
|
||||
|
||||
@ -1278,7 +1280,7 @@ impl Hud {
|
||||
let players = ecs.read_storage::<comp::Player>();
|
||||
let msm = ecs.read_resource::<MaterialStatManifest>();
|
||||
let entities = ecs.entities();
|
||||
let me = client.entity();
|
||||
let me = info.viewpoint_entity;
|
||||
let poises = ecs.read_storage::<comp::Poise>();
|
||||
let alignments = ecs.read_storage::<comp::Alignment>();
|
||||
let is_mount = ecs.read_storage::<Is<Mount>>();
|
||||
@ -2585,7 +2587,7 @@ impl Hud {
|
||||
|
||||
// Bag button and nearby icons
|
||||
let ecs = client.state().ecs();
|
||||
let entity = client.entity();
|
||||
let entity = info.viewpoint_entity;
|
||||
let stats = ecs.read_storage::<comp::Stats>();
|
||||
let skill_sets = ecs.read_storage::<comp::SkillSet>();
|
||||
let buffs = ecs.read_storage::<comp::Buffs>();
|
||||
@ -2593,6 +2595,7 @@ impl Hud {
|
||||
if let (Some(player_stats), Some(skill_set)) = (stats.get(entity), skill_sets.get(entity)) {
|
||||
match Buttons::new(
|
||||
client,
|
||||
&info,
|
||||
self.show.bag,
|
||||
&self.imgs,
|
||||
&self.fonts,
|
||||
@ -2705,14 +2708,12 @@ impl Hud {
|
||||
// Skillbar
|
||||
// Get player stats
|
||||
let ecs = client.state().ecs();
|
||||
let entity = client.entity();
|
||||
let entity = info.viewpoint_entity;
|
||||
let healths = ecs.read_storage::<Health>();
|
||||
let inventories = ecs.read_storage::<comp::Inventory>();
|
||||
let energies = ecs.read_storage::<comp::Energy>();
|
||||
let skillsets = ecs.read_storage::<comp::SkillSet>();
|
||||
let active_abilities = ecs.read_storage::<comp::ActiveAbilities>();
|
||||
let character_states = ecs.read_storage::<comp::CharacterState>();
|
||||
let controllers = ecs.read_storage::<comp::Controller>();
|
||||
let bodies = ecs.read_storage::<comp::Body>();
|
||||
let poises = ecs.read_storage::<comp::Poise>();
|
||||
// Combo floater stuffs
|
||||
@ -2722,25 +2723,16 @@ impl Hud {
|
||||
});
|
||||
self.floaters.combo_floater = self.floaters.combo_floater.filter(|f| f.timer > 0_f64);
|
||||
|
||||
if let (
|
||||
Some(health),
|
||||
Some(inventory),
|
||||
Some(energy),
|
||||
Some(skillset),
|
||||
Some(body),
|
||||
Some(_character_state),
|
||||
Some(_controller),
|
||||
) = (
|
||||
if let (Some(health), Some(inventory), Some(energy), Some(skillset), Some(body)) = (
|
||||
healths.get(entity),
|
||||
inventories.get(entity),
|
||||
energies.get(entity),
|
||||
skillsets.get(entity),
|
||||
bodies.get(entity),
|
||||
character_states.get(entity),
|
||||
controllers.get(entity).map(|c| &c.inputs),
|
||||
) {
|
||||
Skillbar::new(
|
||||
client,
|
||||
&info,
|
||||
global_state,
|
||||
&self.imgs,
|
||||
&self.item_imgs,
|
||||
@ -2775,8 +2767,8 @@ impl Hud {
|
||||
Some(body),
|
||||
Some(poise),
|
||||
) = (
|
||||
stats.get(client.entity()),
|
||||
skill_sets.get(client.entity()),
|
||||
stats.get(info.viewpoint_entity),
|
||||
skill_sets.get(info.viewpoint_entity),
|
||||
healths.get(entity),
|
||||
energies.get(entity),
|
||||
bodies.get(entity),
|
||||
@ -2784,6 +2776,7 @@ impl Hud {
|
||||
) {
|
||||
match Bag::new(
|
||||
client,
|
||||
&info,
|
||||
global_state,
|
||||
&self.imgs,
|
||||
&self.item_imgs,
|
||||
@ -2828,6 +2821,7 @@ impl Hud {
|
||||
if self.show.trade {
|
||||
if let Some(action) = Trade::new(
|
||||
client,
|
||||
&info,
|
||||
&self.imgs,
|
||||
&self.item_imgs,
|
||||
&self.fonts,
|
||||
@ -2871,14 +2865,10 @@ impl Hud {
|
||||
}
|
||||
|
||||
// Buffs
|
||||
let ecs = client.state().ecs();
|
||||
let entity = client.entity();
|
||||
let health = ecs.read_storage::<Health>();
|
||||
let energy = ecs.read_storage::<comp::Energy>();
|
||||
if let (Some(player_buffs), Some(health), Some(energy)) = (
|
||||
buffs.get(client.entity()),
|
||||
health.get(entity),
|
||||
energy.get(entity),
|
||||
buffs.get(info.viewpoint_entity),
|
||||
healths.get(entity),
|
||||
energies.get(entity),
|
||||
) {
|
||||
for event in BuffsBar::new(
|
||||
&self.imgs,
|
||||
@ -2905,6 +2895,7 @@ impl Hud {
|
||||
for event in Crafting::new(
|
||||
//&self.show,
|
||||
client,
|
||||
&info,
|
||||
&self.imgs,
|
||||
&self.fonts,
|
||||
&*i18n,
|
||||
@ -3038,6 +3029,7 @@ impl Hud {
|
||||
LootScroller::new(
|
||||
&mut self.new_loot_messages,
|
||||
client,
|
||||
&info,
|
||||
&self.show,
|
||||
&self.imgs,
|
||||
&self.item_imgs,
|
||||
@ -3107,49 +3099,45 @@ impl Hud {
|
||||
if self.show.social {
|
||||
let ecs = client.state().ecs();
|
||||
let _stats = ecs.read_storage::<comp::Stats>();
|
||||
let me = client.entity();
|
||||
if let Some(_stats) = stats.get(me) {
|
||||
for event in Social::new(
|
||||
&self.show,
|
||||
client,
|
||||
&self.imgs,
|
||||
&self.fonts,
|
||||
i18n,
|
||||
info.selected_entity,
|
||||
&self.rot_imgs,
|
||||
tooltip_manager,
|
||||
)
|
||||
.set(self.ids.social_window, ui_widgets)
|
||||
{
|
||||
match event {
|
||||
social::Event::Close => {
|
||||
self.show.social(false);
|
||||
if !self.show.bag {
|
||||
self.show.want_grab = true;
|
||||
self.force_ungrab = false;
|
||||
} else {
|
||||
self.force_ungrab = true
|
||||
};
|
||||
},
|
||||
social::Event::Focus(widget_id) => {
|
||||
self.to_focus = Some(Some(widget_id));
|
||||
},
|
||||
social::Event::Invite(uid) => events.push(Event::InviteMember(uid)),
|
||||
social::Event::SearchPlayers(search_key) => {
|
||||
self.show.search_social_players(search_key)
|
||||
},
|
||||
}
|
||||
for event in Social::new(
|
||||
&self.show,
|
||||
client,
|
||||
&self.imgs,
|
||||
&self.fonts,
|
||||
i18n,
|
||||
info.selected_entity,
|
||||
&self.rot_imgs,
|
||||
tooltip_manager,
|
||||
)
|
||||
.set(self.ids.social_window, ui_widgets)
|
||||
{
|
||||
match event {
|
||||
social::Event::Close => {
|
||||
self.show.social(false);
|
||||
if !self.show.bag {
|
||||
self.show.want_grab = true;
|
||||
self.force_ungrab = false;
|
||||
} else {
|
||||
self.force_ungrab = true
|
||||
};
|
||||
},
|
||||
social::Event::Focus(widget_id) => {
|
||||
self.to_focus = Some(Some(widget_id));
|
||||
},
|
||||
social::Event::Invite(uid) => events.push(Event::InviteMember(uid)),
|
||||
social::Event::SearchPlayers(search_key) => {
|
||||
self.show.search_social_players(search_key)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Diary
|
||||
if self.show.diary {
|
||||
let entity = client.entity();
|
||||
let entity = info.viewpoint_entity;
|
||||
let skill_sets = ecs.read_storage::<comp::SkillSet>();
|
||||
if let (
|
||||
Some(skill_set),
|
||||
Some(active_abilities),
|
||||
Some(inventory),
|
||||
Some(health),
|
||||
Some(energy),
|
||||
@ -3157,7 +3145,6 @@ impl Hud {
|
||||
Some(poise),
|
||||
) = (
|
||||
skill_sets.get(entity),
|
||||
active_abilities.get(entity),
|
||||
inventories.get(entity),
|
||||
healths.get(entity),
|
||||
energies.get(entity),
|
||||
@ -3169,7 +3156,7 @@ impl Hud {
|
||||
client,
|
||||
global_state,
|
||||
skill_set,
|
||||
active_abilities,
|
||||
active_abilities.get(entity).unwrap_or(&Default::default()),
|
||||
inventory,
|
||||
health,
|
||||
energy,
|
||||
@ -3386,7 +3373,7 @@ impl Hud {
|
||||
) = (a, b)
|
||||
{
|
||||
if let Some(item) = inventories
|
||||
.get(client.entity())
|
||||
.get(info.viewpoint_entity)
|
||||
.and_then(|inv| inv.get(slot))
|
||||
{
|
||||
self.hotbar.add_inventory_link(h, item);
|
||||
@ -3423,7 +3410,7 @@ impl Hud {
|
||||
events.push(Event::ChangeAbility(index, ability));
|
||||
},
|
||||
(AbilitySlot::Slot(a), AbilitySlot::Slot(b)) => {
|
||||
let me = client.entity();
|
||||
let me = info.viewpoint_entity;
|
||||
if let Some(active_abilities) = active_abilities.get(me) {
|
||||
let ability_a = active_abilities
|
||||
.auxiliary_set(inventories.get(me), skill_sets.get(me))
|
||||
@ -3447,7 +3434,7 @@ impl Hud {
|
||||
} else if let (Inventory(i), Crafting(c)) = (a, b) {
|
||||
// Add item to crafting input
|
||||
if inventories
|
||||
.get(client.entity())
|
||||
.get(info.viewpoint_entity)
|
||||
.and_then(|inv| inv.get(i.slot))
|
||||
.map_or(false, |item| {
|
||||
(c.requirement)(item, client.component_recipe_book(), c.info)
|
||||
@ -3508,7 +3495,7 @@ impl Hud {
|
||||
});
|
||||
} else if let (Inventory(i), Hotbar(h)) = (a, b) {
|
||||
if let Some(item) = inventories
|
||||
.get(client.entity())
|
||||
.get(info.viewpoint_entity)
|
||||
.and_then(|inv| inv.get(i.slot))
|
||||
{
|
||||
self.hotbar.add_inventory_link(h, item);
|
||||
@ -3545,7 +3532,7 @@ impl Hud {
|
||||
events.push(Event::ChangeAbility(index, ability));
|
||||
},
|
||||
(AbilitySlot::Slot(a), AbilitySlot::Slot(b)) => {
|
||||
let me = client.entity();
|
||||
let me = info.viewpoint_entity;
|
||||
if let Some(active_abilities) = active_abilities.get(me) {
|
||||
let ability_a = active_abilities
|
||||
.auxiliary_set(inventories.get(me), skill_sets.get(me))
|
||||
@ -3592,7 +3579,7 @@ impl Hud {
|
||||
// Used from hotbar
|
||||
self.hotbar.get(h).map(|s| match s {
|
||||
hotbar::SlotContents::Inventory(i, _) => {
|
||||
if let Some(inv) = inventories.get(client.entity()) {
|
||||
if let Some(inv) = inventories.get(info.viewpoint_entity) {
|
||||
// If the item in the inactive main hand is the same as the item
|
||||
// pressed in the hotbar, then swap active and inactive hands
|
||||
// instead of looking for
|
||||
@ -3643,7 +3630,7 @@ impl Hud {
|
||||
};
|
||||
}
|
||||
let who = match ecs
|
||||
.uid_from_entity(client.entity())
|
||||
.uid_from_entity(info.viewpoint_entity)
|
||||
.and_then(|uid| trade.which_party(uid))
|
||||
{
|
||||
Some(who) => who,
|
||||
@ -3752,7 +3739,7 @@ impl Hud {
|
||||
},
|
||||
}
|
||||
}
|
||||
self.hotbar.maintain_abilities(client);
|
||||
self.hotbar.maintain_abilities(client, &info);
|
||||
|
||||
// Temporary Example Quest
|
||||
let arrow_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8; //Animation timer
|
||||
|
@ -2,8 +2,8 @@ use super::{
|
||||
hotbar,
|
||||
img_ids::{Imgs, ImgsRot},
|
||||
item_imgs::ItemImgs,
|
||||
slots, util, BarNumbers, ShortcutNumbers, BLACK, CRITICAL_HP_COLOR, HP_COLOR, LOW_HP_COLOR,
|
||||
QUALITY_EPIC, STAMINA_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0,
|
||||
slots, util, BarNumbers, HudInfo, ShortcutNumbers, BLACK, CRITICAL_HP_COLOR, HP_COLOR,
|
||||
LOW_HP_COLOR, QUALITY_EPIC, STAMINA_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0,
|
||||
};
|
||||
use crate::{
|
||||
game_input::GameInput,
|
||||
@ -242,6 +242,7 @@ fn slot_entries(state: &State, slot_offset: f64) -> [SlotEntry; 10] {
|
||||
#[derive(WidgetCommon)]
|
||||
pub struct Skillbar<'a> {
|
||||
client: &'a Client,
|
||||
info: &'a HudInfo,
|
||||
global_state: &'a GlobalState,
|
||||
imgs: &'a Imgs,
|
||||
item_imgs: &'a ItemImgs,
|
||||
@ -271,6 +272,7 @@ impl<'a> Skillbar<'a> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
client: &'a Client,
|
||||
info: &'a HudInfo,
|
||||
global_state: &'a GlobalState,
|
||||
imgs: &'a Imgs,
|
||||
item_imgs: &'a ItemImgs,
|
||||
@ -295,6 +297,7 @@ impl<'a> Skillbar<'a> {
|
||||
) -> Self {
|
||||
Self {
|
||||
client,
|
||||
info,
|
||||
global_state,
|
||||
imgs,
|
||||
item_imgs,
|
||||
@ -576,6 +579,7 @@ impl<'a> Skillbar<'a> {
|
||||
)
|
||||
},
|
||||
self.client,
|
||||
self.info,
|
||||
self.imgs,
|
||||
self.item_imgs,
|
||||
self.pulse,
|
||||
|
@ -31,7 +31,7 @@ use super::{
|
||||
img_ids::{Imgs, ImgsRot},
|
||||
item_imgs::ItemImgs,
|
||||
slots::{SlotKind, SlotManager, TradeSlot},
|
||||
Hud, Show, TradeAmountInput, TEXT_COLOR, TEXT_GRAY_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
|
||||
Hud, HudInfo, Show, TradeAmountInput, TEXT_COLOR, TEXT_GRAY_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
|
||||
@ -75,6 +75,7 @@ widget_ids! {
|
||||
#[derive(WidgetCommon)]
|
||||
pub struct Trade<'a> {
|
||||
client: &'a Client,
|
||||
info: &'a HudInfo,
|
||||
imgs: &'a Imgs,
|
||||
item_imgs: &'a ItemImgs,
|
||||
fonts: &'a Fonts,
|
||||
@ -92,6 +93,7 @@ pub struct Trade<'a> {
|
||||
impl<'a> Trade<'a> {
|
||||
pub fn new(
|
||||
client: &'a Client,
|
||||
info: &'a HudInfo,
|
||||
imgs: &'a Imgs,
|
||||
item_imgs: &'a ItemImgs,
|
||||
fonts: &'a Fonts,
|
||||
@ -105,6 +107,7 @@ impl<'a> Trade<'a> {
|
||||
) -> Self {
|
||||
Self {
|
||||
client,
|
||||
info,
|
||||
imgs,
|
||||
item_imgs,
|
||||
fonts,
|
||||
@ -313,6 +316,7 @@ impl<'a> Trade<'a> {
|
||||
)
|
||||
},
|
||||
self.client,
|
||||
self.info,
|
||||
self.imgs,
|
||||
self.item_imgs,
|
||||
self.pulse,
|
||||
|
@ -144,6 +144,18 @@ impl PlayState for CharSelectionState {
|
||||
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 => {
|
||||
self.char_selection_ui.error = None;
|
||||
},
|
||||
|
@ -127,6 +127,7 @@ image_ids_ice! {
|
||||
pub enum Event {
|
||||
Logout,
|
||||
Play(CharacterId),
|
||||
Spectate,
|
||||
AddCharacter {
|
||||
alias: String,
|
||||
mainhand: Option<String>,
|
||||
@ -152,6 +153,7 @@ enum Mode {
|
||||
new_character_button: button::State,
|
||||
logout_button: button::State,
|
||||
enter_world_button: button::State,
|
||||
spectate_button: button::State,
|
||||
yes_button: button::State,
|
||||
no_button: button::State,
|
||||
},
|
||||
@ -185,6 +187,7 @@ impl Mode {
|
||||
new_character_button: Default::default(),
|
||||
logout_button: Default::default(),
|
||||
enter_world_button: Default::default(),
|
||||
spectate_button: Default::default(),
|
||||
yes_button: Default::default(),
|
||||
no_button: Default::default(),
|
||||
}
|
||||
@ -283,6 +286,7 @@ enum Message {
|
||||
Back,
|
||||
Logout,
|
||||
EnterWorld,
|
||||
Spectate,
|
||||
Select(CharacterId),
|
||||
Delete(usize),
|
||||
Edit(usize),
|
||||
@ -397,6 +401,7 @@ impl Controls {
|
||||
ref mut new_character_button,
|
||||
ref mut logout_button,
|
||||
ref mut enter_world_button,
|
||||
ref mut spectate_button,
|
||||
ref mut yes_button,
|
||||
ref mut no_button,
|
||||
} => {
|
||||
@ -676,36 +681,52 @@ impl Controls {
|
||||
.padding(15)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill);
|
||||
let mut bottom_content = vec![
|
||||
Container::new(neat_button(
|
||||
logout_button,
|
||||
i18n.get("char_selection.logout").into_owned(),
|
||||
FILL_FRAC_ONE,
|
||||
button_style,
|
||||
Some(Message::Logout),
|
||||
))
|
||||
.width(Length::Fill)
|
||||
.height(Length::Units(SMALL_BUTTON_HEIGHT))
|
||||
.into(),
|
||||
];
|
||||
|
||||
let logout = neat_button(
|
||||
logout_button,
|
||||
i18n.get("char_selection.logout").into_owned(),
|
||||
FILL_FRAC_ONE,
|
||||
button_style,
|
||||
Some(Message::Logout),
|
||||
);
|
||||
|
||||
let enter_world = neat_button(
|
||||
enter_world_button,
|
||||
i18n.get("char_selection.enter_world").into_owned(),
|
||||
FILL_FRAC_TWO,
|
||||
button_style,
|
||||
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)
|
||||
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(),
|
||||
Space::new(Length::Fill, Length::Shrink).into(),
|
||||
])
|
||||
.align_items(Align::End);
|
||||
);
|
||||
}
|
||||
|
||||
bottom_content.push(
|
||||
Container::new(neat_button(
|
||||
enter_world_button,
|
||||
i18n.get("char_selection.enter_world").into_owned(),
|
||||
FILL_FRAC_TWO,
|
||||
button_style,
|
||||
selected.map(|_| Message::EnterWorld),
|
||||
))
|
||||
.width(Length::Fill)
|
||||
.height(Length::Units(52))
|
||||
.center_x()
|
||||
.into(),
|
||||
);
|
||||
|
||||
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()])
|
||||
.width(Length::Fill)
|
||||
@ -1410,6 +1431,11 @@ impl Controls {
|
||||
events.push(Event::Play(selected));
|
||||
}
|
||||
},
|
||||
Message::Spectate => {
|
||||
if matches!(self.mode, Mode::Select { .. }) {
|
||||
events.push(Event::Spectate);
|
||||
}
|
||||
},
|
||||
Message::Select(id) => {
|
||||
if let Mode::Select { .. } = &mut self.mode {
|
||||
self.selected = Some(id);
|
||||
|
@ -16,7 +16,7 @@ const CLIPPING_MODE_DISTANCE: f32 = 20.0;
|
||||
pub const MIN_ZOOM: f32 = 0.1;
|
||||
|
||||
// Possible TODO: Add more modes
|
||||
#[derive(PartialEq, Clone, Copy, Eq, Hash)]
|
||||
#[derive(PartialEq, Debug, Clone, Copy, Eq, Hash)]
|
||||
pub enum CameraMode {
|
||||
FirstPerson = 0,
|
||||
ThirdPerson = 1,
|
||||
@ -320,13 +320,18 @@ impl Camera {
|
||||
// Make sure aspect is valid
|
||||
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 {
|
||||
tgt_focus: Vec3::unit_z() * 10.0,
|
||||
focus: Vec3::unit_z() * 10.0,
|
||||
tgt_ori: Vec3::zero(),
|
||||
ori: Vec3::zero(),
|
||||
tgt_dist: 10.0,
|
||||
dist: 10.0,
|
||||
tgt_dist: dist,
|
||||
dist,
|
||||
tgt_fov: 1.1,
|
||||
fov: 1.1,
|
||||
tgt_fixate: 1.0,
|
||||
@ -652,6 +657,12 @@ impl Camera {
|
||||
/// Set the focus position of the camera.
|
||||
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.
|
||||
pub fn get_aspect_ratio(&self) -> f32 { self.aspect }
|
||||
|
||||
@ -695,7 +706,7 @@ impl Camera {
|
||||
self.set_distance(MIN_ZOOM);
|
||||
},
|
||||
CameraMode::Freefly => {
|
||||
self.zoom_by(0.0, None);
|
||||
self.set_distance(MIN_ZOOM);
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -714,18 +725,22 @@ impl Camera {
|
||||
|
||||
/// 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.
|
||||
pub fn next_mode(&mut self, is_admin: bool) {
|
||||
self.set_mode(match self.mode {
|
||||
CameraMode::ThirdPerson => CameraMode::FirstPerson,
|
||||
CameraMode::FirstPerson => {
|
||||
if is_admin {
|
||||
CameraMode::Freefly
|
||||
} else {
|
||||
CameraMode::ThirdPerson
|
||||
}
|
||||
},
|
||||
CameraMode::Freefly => CameraMode::ThirdPerson,
|
||||
});
|
||||
pub fn next_mode(&mut self, is_admin: bool, has_target: bool) {
|
||||
if has_target {
|
||||
self.set_mode(match self.mode {
|
||||
CameraMode::ThirdPerson => CameraMode::FirstPerson,
|
||||
CameraMode::FirstPerson => {
|
||||
if is_admin {
|
||||
CameraMode::Freefly
|
||||
} else {
|
||||
CameraMode::ThirdPerson
|
||||
}
|
||||
},
|
||||
CameraMode::Freefly => CameraMode::ThirdPerson,
|
||||
});
|
||||
} else {
|
||||
self.set_mode(CameraMode::Freefly);
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a unit vector in the forward direction for the current camera
|
||||
|
@ -707,7 +707,7 @@ impl FigureMgr {
|
||||
// Get player position.
|
||||
let player_pos = ecs
|
||||
.read_storage::<Pos>()
|
||||
.get(scene_data.player_entity)
|
||||
.get(scene_data.viewpoint_entity)
|
||||
.map_or(anim::vek::Vec3::zero(), |pos| anim::vek::Vec3::from(pos.0));
|
||||
let visible_aabb = anim::vek::Aabb {
|
||||
min: player_pos - 2.0,
|
||||
@ -716,7 +716,7 @@ impl FigureMgr {
|
||||
let camera_mode = camera.get_mode();
|
||||
let character_state_storage = state.read_storage::<CharacterState>();
|
||||
let slow_jobs = state.slow_job_pool();
|
||||
let character_state = character_state_storage.get(scene_data.player_entity);
|
||||
let character_state = character_state_storage.get(scene_data.viewpoint_entity);
|
||||
|
||||
let focus_pos = anim::vek::Vec3::<f32>::from(camera.get_focus_pos());
|
||||
|
||||
@ -771,13 +771,13 @@ impl FigureMgr {
|
||||
let rel_vel = anim::vek::Vec3::<f32>::from(vel.0 - physics.ground_vel);
|
||||
|
||||
let look_dir = controller.map(|c| c.inputs.look_dir).unwrap_or_default();
|
||||
let is_player = scene_data.player_entity == entity;
|
||||
let player_camera_mode = if is_player {
|
||||
let is_viewpoint = scene_data.viewpoint_entity == entity;
|
||||
let viewpoint_camera_mode = if is_viewpoint {
|
||||
camera_mode
|
||||
} else {
|
||||
CameraMode::default()
|
||||
};
|
||||
let player_character_state = if is_player { character_state } else { None };
|
||||
let viewpoint_character_state = if is_viewpoint { character_state } else { None };
|
||||
|
||||
let (pos, ori) = interpolated
|
||||
.map(|i| {
|
||||
@ -941,7 +941,7 @@ impl FigureMgr {
|
||||
dt,
|
||||
_lpindex: lpindex,
|
||||
_visible: in_frustum,
|
||||
is_player,
|
||||
is_player: is_viewpoint,
|
||||
_camera: camera,
|
||||
terrain,
|
||||
ground_vel: physics.ground_vel,
|
||||
@ -956,8 +956,8 @@ impl FigureMgr {
|
||||
inventory,
|
||||
(),
|
||||
tick,
|
||||
player_camera_mode,
|
||||
player_character_state,
|
||||
viewpoint_camera_mode,
|
||||
viewpoint_character_state,
|
||||
&slow_jobs,
|
||||
None,
|
||||
);
|
||||
@ -1856,8 +1856,8 @@ impl FigureMgr {
|
||||
inventory,
|
||||
(),
|
||||
tick,
|
||||
player_camera_mode,
|
||||
player_character_state,
|
||||
viewpoint_camera_mode,
|
||||
viewpoint_character_state,
|
||||
&slow_jobs,
|
||||
None,
|
||||
);
|
||||
@ -2056,8 +2056,8 @@ impl FigureMgr {
|
||||
inventory,
|
||||
(),
|
||||
tick,
|
||||
player_camera_mode,
|
||||
player_character_state,
|
||||
viewpoint_camera_mode,
|
||||
viewpoint_character_state,
|
||||
&slow_jobs,
|
||||
None,
|
||||
);
|
||||
@ -2373,8 +2373,8 @@ impl FigureMgr {
|
||||
inventory,
|
||||
(),
|
||||
tick,
|
||||
player_camera_mode,
|
||||
player_character_state,
|
||||
viewpoint_camera_mode,
|
||||
viewpoint_character_state,
|
||||
&slow_jobs,
|
||||
None,
|
||||
);
|
||||
@ -2730,8 +2730,8 @@ impl FigureMgr {
|
||||
inventory,
|
||||
(),
|
||||
tick,
|
||||
player_camera_mode,
|
||||
player_character_state,
|
||||
viewpoint_camera_mode,
|
||||
viewpoint_character_state,
|
||||
&slow_jobs,
|
||||
None,
|
||||
);
|
||||
@ -2834,8 +2834,8 @@ impl FigureMgr {
|
||||
inventory,
|
||||
(),
|
||||
tick,
|
||||
player_camera_mode,
|
||||
player_character_state,
|
||||
viewpoint_camera_mode,
|
||||
viewpoint_character_state,
|
||||
&slow_jobs,
|
||||
None,
|
||||
);
|
||||
@ -2917,8 +2917,8 @@ impl FigureMgr {
|
||||
inventory,
|
||||
(),
|
||||
tick,
|
||||
player_camera_mode,
|
||||
player_character_state,
|
||||
viewpoint_camera_mode,
|
||||
viewpoint_character_state,
|
||||
&slow_jobs,
|
||||
None,
|
||||
);
|
||||
@ -3444,8 +3444,8 @@ impl FigureMgr {
|
||||
inventory,
|
||||
(),
|
||||
tick,
|
||||
player_camera_mode,
|
||||
player_character_state,
|
||||
viewpoint_camera_mode,
|
||||
viewpoint_character_state,
|
||||
&slow_jobs,
|
||||
None,
|
||||
);
|
||||
@ -3531,8 +3531,8 @@ impl FigureMgr {
|
||||
inventory,
|
||||
(),
|
||||
tick,
|
||||
player_camera_mode,
|
||||
player_character_state,
|
||||
viewpoint_camera_mode,
|
||||
viewpoint_character_state,
|
||||
&slow_jobs,
|
||||
None,
|
||||
);
|
||||
@ -3710,8 +3710,8 @@ impl FigureMgr {
|
||||
inventory,
|
||||
(),
|
||||
tick,
|
||||
player_camera_mode,
|
||||
player_character_state,
|
||||
viewpoint_camera_mode,
|
||||
viewpoint_character_state,
|
||||
&slow_jobs,
|
||||
None,
|
||||
);
|
||||
@ -4002,8 +4002,8 @@ impl FigureMgr {
|
||||
inventory,
|
||||
(),
|
||||
tick,
|
||||
player_camera_mode,
|
||||
player_character_state,
|
||||
viewpoint_camera_mode,
|
||||
viewpoint_character_state,
|
||||
&slow_jobs,
|
||||
None,
|
||||
);
|
||||
@ -4325,8 +4325,8 @@ impl FigureMgr {
|
||||
inventory,
|
||||
(),
|
||||
tick,
|
||||
player_camera_mode,
|
||||
player_character_state,
|
||||
viewpoint_camera_mode,
|
||||
viewpoint_character_state,
|
||||
&slow_jobs,
|
||||
None,
|
||||
);
|
||||
@ -4408,8 +4408,8 @@ impl FigureMgr {
|
||||
inventory,
|
||||
(),
|
||||
tick,
|
||||
player_camera_mode,
|
||||
player_character_state,
|
||||
viewpoint_camera_mode,
|
||||
viewpoint_character_state,
|
||||
&slow_jobs,
|
||||
None,
|
||||
);
|
||||
@ -5030,8 +5030,8 @@ impl FigureMgr {
|
||||
inventory,
|
||||
(),
|
||||
tick,
|
||||
player_camera_mode,
|
||||
player_character_state,
|
||||
viewpoint_camera_mode,
|
||||
viewpoint_character_state,
|
||||
&slow_jobs,
|
||||
None,
|
||||
);
|
||||
@ -5271,8 +5271,8 @@ impl FigureMgr {
|
||||
inventory,
|
||||
(),
|
||||
tick,
|
||||
player_camera_mode,
|
||||
player_character_state,
|
||||
viewpoint_camera_mode,
|
||||
viewpoint_character_state,
|
||||
&slow_jobs,
|
||||
None,
|
||||
);
|
||||
@ -5393,8 +5393,8 @@ impl FigureMgr {
|
||||
inventory,
|
||||
(),
|
||||
tick,
|
||||
player_camera_mode,
|
||||
player_character_state,
|
||||
viewpoint_camera_mode,
|
||||
viewpoint_character_state,
|
||||
&slow_jobs,
|
||||
item_key,
|
||||
);
|
||||
@ -5453,8 +5453,8 @@ impl FigureMgr {
|
||||
inventory,
|
||||
Arc::clone(vol),
|
||||
tick,
|
||||
player_camera_mode,
|
||||
player_character_state,
|
||||
viewpoint_camera_mode,
|
||||
viewpoint_character_state,
|
||||
&slow_jobs,
|
||||
None,
|
||||
);
|
||||
@ -5484,8 +5484,8 @@ impl FigureMgr {
|
||||
inventory,
|
||||
(),
|
||||
tick,
|
||||
player_camera_mode,
|
||||
player_character_state,
|
||||
viewpoint_camera_mode,
|
||||
viewpoint_character_state,
|
||||
&slow_jobs,
|
||||
None,
|
||||
)
|
||||
|
@ -37,6 +37,7 @@ use common::{
|
||||
vol::ReadVol,
|
||||
};
|
||||
use common_base::{prof_span, span};
|
||||
use common_net::msg::PresenceKind;
|
||||
use common_state::State;
|
||||
use comp::item::Reagent;
|
||||
use hashbrown::HashMap;
|
||||
@ -115,7 +116,8 @@ pub struct Scene {
|
||||
pub struct SceneData<'a> {
|
||||
pub client: &'a Client,
|
||||
pub state: &'a State,
|
||||
pub player_entity: specs::Entity,
|
||||
pub viewpoint_entity: specs::Entity,
|
||||
pub mutable_viewpoint: bool,
|
||||
pub target_entity: Option<specs::Entity>,
|
||||
pub loaded_distance: f32,
|
||||
pub view_distance: u32,
|
||||
@ -299,10 +301,15 @@ impl Scene {
|
||||
|
||||
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 {
|
||||
data,
|
||||
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(),
|
||||
event_lights: Vec::new(),
|
||||
|
||||
@ -496,71 +503,104 @@ impl Scene {
|
||||
|
||||
let dt = ecs.fetch::<DeltaTime>().0;
|
||||
|
||||
let player_pos = ecs
|
||||
.read_storage::<comp::Pos>()
|
||||
.get(scene_data.player_entity)
|
||||
.map_or(Vec3::zero(), |pos| pos.0);
|
||||
let positions = ecs.read_storage::<comp::Pos>();
|
||||
|
||||
let player_rolling = ecs
|
||||
.read_storage::<comp::CharacterState>()
|
||||
.get(scene_data.player_entity)
|
||||
.map_or(false, |cs| cs.is_dodge());
|
||||
let viewpoint_pos = if let Some(viewpoint_pos) =
|
||||
positions.get(scene_data.viewpoint_entity).map(|pos| pos.0)
|
||||
{
|
||||
let viewpoint_ori = ecs
|
||||
.read_storage::<comp::Ori>()
|
||||
.get(scene_data.viewpoint_entity)
|
||||
.map_or(Quaternion::identity(), |ori| ori.to_quat());
|
||||
|
||||
let is_running = ecs
|
||||
.read_storage::<comp::Vel>()
|
||||
.get(scene_data.player_entity)
|
||||
.map(|v| v.0.magnitude_squared() > RUNNING_THRESHOLD.powi(2))
|
||||
.unwrap_or(false);
|
||||
let viewpoint_rolling = ecs
|
||||
.read_storage::<comp::CharacterState>()
|
||||
.get(scene_data.viewpoint_entity)
|
||||
.map_or(false, |cs| cs.is_dodge());
|
||||
|
||||
let on_ground = ecs
|
||||
.read_storage::<comp::PhysicsState>()
|
||||
.get(scene_data.player_entity)
|
||||
.map(|p| p.on_ground.is_some());
|
||||
let is_running = ecs
|
||||
.read_storage::<comp::Vel>()
|
||||
.get(scene_data.viewpoint_entity)
|
||||
.map(|v| v.0.magnitude_squared() > RUNNING_THRESHOLD.powi(2))
|
||||
.unwrap_or(false);
|
||||
|
||||
let (player_height, player_eye_height) = scene_data
|
||||
.state
|
||||
.ecs()
|
||||
.read_storage::<comp::Body>()
|
||||
.get(scene_data.player_entity)
|
||||
.map_or((1.0, 0.0), |b| (b.height(), b.eye_height()));
|
||||
let on_ground = ecs
|
||||
.read_storage::<comp::PhysicsState>()
|
||||
.get(scene_data.viewpoint_entity)
|
||||
.map(|p| p.on_ground.is_some());
|
||||
|
||||
// Add the analog input to camera
|
||||
self.camera
|
||||
.rotate_by(Vec3::from([self.camera_input_state.x, 0.0, 0.0]));
|
||||
self.camera
|
||||
.rotate_by(Vec3::from([0.0, self.camera_input_state.y, 0.0]));
|
||||
let (viewpoint_height, viewpoint_eye_height) = scene_data
|
||||
.state
|
||||
.ecs()
|
||||
.read_storage::<comp::Body>()
|
||||
.get(scene_data.viewpoint_entity)
|
||||
.map_or((1.0, 0.0), |b| (b.height(), b.eye_height()));
|
||||
|
||||
// Alter camera position to match player.
|
||||
let tilt = self.camera.get_orientation().y;
|
||||
let dist = self.camera.get_distance();
|
||||
if scene_data.mutable_viewpoint || matches!(self.camera.get_mode(), CameraMode::Freefly)
|
||||
{
|
||||
// Add the analog input to camera if it's a mutable viewpoint
|
||||
self.camera
|
||||
.rotate_by(Vec3::from([self.camera_input_state.x, 0.0, 0.0]));
|
||||
self.camera
|
||||
.rotate_by(Vec3::from([0.0, self.camera_input_state.y, 0.0]));
|
||||
} else {
|
||||
// Otherwise set the cameras rotation to the viewpoints
|
||||
let q = viewpoint_ori;
|
||||
let sinr_cosp = 2.0 * (q.w * q.x + q.y * q.z);
|
||||
let cosr_cosp = 1.0 - 2.0 * (q.x * q.x + q.y * q.y);
|
||||
let roll = sinr_cosp.atan2(cosr_cosp);
|
||||
|
||||
let up = match self.camera.get_mode() {
|
||||
CameraMode::FirstPerson => {
|
||||
if player_rolling {
|
||||
player_height * 0.42
|
||||
} else if is_running && on_ground.unwrap_or(false) {
|
||||
player_eye_height + (scene_data.state.get_time() as f32 * 17.0).sin() * 0.05
|
||||
let sinp = 2.0 * (q.w * q.y - q.z * q.x);
|
||||
let pitch = if sinp.abs() >= 1.0 {
|
||||
std::f32::consts::FRAC_PI_2.copysign(sinp)
|
||||
} else {
|
||||
player_eye_height
|
||||
}
|
||||
},
|
||||
CameraMode::ThirdPerson if scene_data.is_aiming => player_height * 1.16,
|
||||
CameraMode::ThirdPerson => player_eye_height,
|
||||
CameraMode::Freefly => 0.0,
|
||||
};
|
||||
sinp.asin()
|
||||
};
|
||||
|
||||
match self.camera.get_mode() {
|
||||
CameraMode::FirstPerson | CameraMode::ThirdPerson => {
|
||||
self.camera.set_focus_pos(
|
||||
player_pos + Vec3::unit_z() * (up - tilt.min(0.0).sin() * dist * 0.6),
|
||||
);
|
||||
},
|
||||
CameraMode::Freefly => {},
|
||||
};
|
||||
let siny_cosp = 2.0 * (q.w * q.z + q.x * q.y);
|
||||
let cosy_cosp = 1.0 - 2.0 * (q.y * q.y + q.z * q.z);
|
||||
let yaw = siny_cosp.atan2(cosy_cosp);
|
||||
|
||||
// Tick camera for interpolation.
|
||||
self.camera
|
||||
.update(scene_data.state.get_time(), dt, scene_data.mouse_smoothing);
|
||||
self.camera
|
||||
.set_orientation_instant(Vec3::new(yaw, pitch, -roll));
|
||||
}
|
||||
|
||||
// Alter camera position to match player.
|
||||
let tilt = self.camera.get_orientation().y;
|
||||
let dist = self.camera.get_distance();
|
||||
|
||||
let up = match self.camera.get_mode() {
|
||||
CameraMode::FirstPerson => {
|
||||
if viewpoint_rolling {
|
||||
viewpoint_height * 0.42
|
||||
} else if is_running && on_ground.unwrap_or(false) {
|
||||
viewpoint_eye_height
|
||||
+ (scene_data.state.get_time() as f32 * 17.0).sin() * 0.05
|
||||
} else {
|
||||
viewpoint_eye_height
|
||||
}
|
||||
},
|
||||
CameraMode::ThirdPerson if scene_data.is_aiming => viewpoint_height * 1.16,
|
||||
CameraMode::ThirdPerson => viewpoint_eye_height,
|
||||
CameraMode::Freefly => 0.0,
|
||||
};
|
||||
|
||||
match self.camera.get_mode() {
|
||||
CameraMode::FirstPerson | CameraMode::ThirdPerson => {
|
||||
self.camera.set_focus_pos(
|
||||
viewpoint_pos + Vec3::unit_z() * (up - tilt.min(0.0).sin() * dist * 0.6),
|
||||
);
|
||||
},
|
||||
CameraMode::Freefly => {},
|
||||
};
|
||||
|
||||
// Tick camera for interpolation.
|
||||
self.camera
|
||||
.update(scene_data.state.get_time(), dt, scene_data.mouse_smoothing);
|
||||
viewpoint_pos
|
||||
} else {
|
||||
Vec3::zero()
|
||||
};
|
||||
|
||||
// Compute camera matrices.
|
||||
self.camera.compute_dependents(&*scene_data.state.terrain());
|
||||
@ -611,7 +651,7 @@ impl Scene {
|
||||
.filter(|(pos, _, light_anim, h)| {
|
||||
light_anim.col != Rgb::zero()
|
||||
&& light_anim.strength > 0.0
|
||||
&& (pos.0.distance_squared(player_pos) as f32)
|
||||
&& (pos.0.distance_squared(viewpoint_pos) as f32)
|
||||
< loaded_distance.powi(2) + LIGHT_DIST_RADIUS
|
||||
&& h.map_or(true, |h| !h.is_dead)
|
||||
})
|
||||
@ -626,7 +666,7 @@ impl Scene {
|
||||
.map(|el| el.light.with_strength((el.fadeout)(el.timeout))),
|
||||
),
|
||||
);
|
||||
lights.sort_by_key(|light| light.get_pos().distance_squared(player_pos) as i32);
|
||||
lights.sort_by_key(|light| light.get_pos().distance_squared(viewpoint_pos) as i32);
|
||||
lights.truncate(MAX_LIGHT_COUNT);
|
||||
renderer.update_consts(&mut self.data.lights, lights);
|
||||
|
||||
@ -651,7 +691,7 @@ impl Scene {
|
||||
.join()
|
||||
.filter(|(_, _, _, _, health)| !health.is_dead)
|
||||
.filter(|(pos, _, _, _, _)| {
|
||||
(pos.0.distance_squared(player_pos) as f32)
|
||||
(pos.0.distance_squared(viewpoint_pos) as f32)
|
||||
< (loaded_distance.min(SHADOW_MAX_DIST) + SHADOW_DIST_RADIUS).powi(2)
|
||||
})
|
||||
.map(|(pos, interpolated, scale, _, _)| {
|
||||
@ -662,7 +702,7 @@ impl Scene {
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
shadows.sort_by_key(|shadow| shadow.get_pos().distance_squared(player_pos) as i32);
|
||||
shadows.sort_by_key(|shadow| shadow.get_pos().distance_squared(viewpoint_pos) as i32);
|
||||
shadows.truncate(MAX_SHADOW_COUNT);
|
||||
renderer.update_consts(&mut self.data.shadows, &shadows);
|
||||
|
||||
@ -1133,7 +1173,7 @@ impl Scene {
|
||||
self.sfx_mgr.maintain(
|
||||
audio,
|
||||
scene_data.state,
|
||||
scene_data.player_entity,
|
||||
scene_data.viewpoint_entity,
|
||||
&self.camera,
|
||||
&self.terrain,
|
||||
client,
|
||||
|
@ -1166,7 +1166,7 @@ impl ParticleMgr {
|
||||
let time = scene_data.state.get_time();
|
||||
let player_pos = scene_data
|
||||
.state
|
||||
.read_component_copied::<Interpolated>(scene_data.player_entity)
|
||||
.read_component_copied::<Interpolated>(scene_data.viewpoint_entity)
|
||||
.map(|i| i.pos)
|
||||
.unwrap_or_default();
|
||||
let player_chunk = player_pos.xy().map2(TerrainChunk::RECT_SIZE, |e, sz| {
|
||||
|
@ -88,6 +88,7 @@ pub struct SessionState {
|
||||
is_aiming: bool,
|
||||
target_entity: Option<specs::Entity>,
|
||||
selected_entity: Option<(specs::Entity, std::time::Instant)>,
|
||||
viewpoint_entity: Option<specs::Entity>,
|
||||
interactable: Option<Interactable>,
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
mumble_link: SharedLink,
|
||||
@ -149,6 +150,7 @@ impl SessionState {
|
||||
is_aiming: false,
|
||||
target_entity: None,
|
||||
selected_entity: None,
|
||||
viewpoint_entity: None,
|
||||
interactable: None,
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
mumble_link,
|
||||
@ -162,6 +164,14 @@ impl SessionState {
|
||||
self.key_state.auto_walk = false;
|
||||
}
|
||||
|
||||
/// Gets the entity that is the current viewpoint, and a bool if the client
|
||||
/// is allowed to edit it's data.
|
||||
fn viewpoint_entity(&self) -> (specs::Entity, bool) {
|
||||
self.viewpoint_entity
|
||||
.map(|e| (e, false))
|
||||
.unwrap_or_else(|| (self.client.borrow().entity(), true))
|
||||
}
|
||||
|
||||
/// Tick the session (and the client attached to it).
|
||||
fn tick(
|
||||
&mut self,
|
||||
@ -358,6 +368,9 @@ impl SessionState {
|
||||
client::Event::MapMarker(event) => {
|
||||
self.hud.show.update_map_markers(event);
|
||||
},
|
||||
client::Event::SpectatePosition(pos) => {
|
||||
self.scene.camera_mut().force_focus_pos(pos);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -386,14 +399,13 @@ impl PlayState for SessionState {
|
||||
fn tick(&mut self, global_state: &mut GlobalState, events: Vec<Event>) -> PlayStateResult {
|
||||
span!(_guard, "tick", "<Session as PlayState>::tick");
|
||||
// TODO: let mut client = self.client.borrow_mut();
|
||||
|
||||
// TODO: can this be a method on the session or are there borrowcheck issues?
|
||||
let (client_presence, client_registered) = {
|
||||
let client = self.client.borrow();
|
||||
(client.presence(), client.registered())
|
||||
};
|
||||
|
||||
if client_presence.is_some() {
|
||||
if let Some(presence) = client_presence {
|
||||
let camera = self.scene.camera_mut();
|
||||
|
||||
// Clamp camera's vertical angle if the toggle is enabled
|
||||
@ -425,7 +437,7 @@ impl PlayState for SessionState {
|
||||
}
|
||||
|
||||
// Compute camera data
|
||||
camera.compute_dependents(&*self.client.borrow().state().terrain());
|
||||
camera.compute_dependents(&*client.state().terrain());
|
||||
let camera::Dependents {
|
||||
cam_pos, cam_dir, ..
|
||||
} = self.scene.camera().dependents();
|
||||
@ -480,6 +492,10 @@ impl PlayState for SessionState {
|
||||
|
||||
drop(client);
|
||||
|
||||
if presence == PresenceKind::Spectator {
|
||||
self.client.borrow_mut().spectate_position(cam_pos);
|
||||
}
|
||||
|
||||
// Nearest block to consider with GameInput primary or secondary key.
|
||||
let nearest_block_dist = find_shortest_distance(&[
|
||||
mine_target.filter(|_| is_mining).map(|t| t.distance),
|
||||
@ -542,12 +558,11 @@ impl PlayState for SessionState {
|
||||
if !self.inputs_state.insert(input) {
|
||||
self.inputs_state.remove(&input);
|
||||
}
|
||||
|
||||
match input {
|
||||
GameInput::Primary => {
|
||||
let mut client = self.client.borrow_mut();
|
||||
// Mine and build targets can be the same block. make building take
|
||||
// precedence.
|
||||
// Mine and build targets can be the same block. make building
|
||||
// take precedence.
|
||||
// Order of precedence: build, then mining, then attack.
|
||||
if let Some(build_target) = build_target.filter(|bt| {
|
||||
state && can_build && nearest_block_dist == Some(bt.distance)
|
||||
@ -884,10 +899,19 @@ impl PlayState for SessionState {
|
||||
// Prevent accessing camera modes which aren't available in
|
||||
// multiplayer unless you are an
|
||||
// admin. This is an easily bypassed clientside check.
|
||||
// The server should do its own filtering of which entities are sent
|
||||
// to clients to prevent abuse.
|
||||
// The server should do its own filtering of which entities are
|
||||
// sent to clients to
|
||||
// prevent abuse.
|
||||
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(true)
|
||||
|| self.viewpoint_entity.is_some(),
|
||||
);
|
||||
},
|
||||
GameInput::Select => {
|
||||
if !state {
|
||||
@ -907,6 +931,24 @@ impl PlayState for SessionState {
|
||||
client.decline_invite();
|
||||
}
|
||||
},
|
||||
GameInput::SpectateViewpoint if state => {
|
||||
if self.viewpoint_entity.is_some() {
|
||||
self.viewpoint_entity = None;
|
||||
self.scene.camera_mut().set_mode(CameraMode::Freefly);
|
||||
} else if let Some(interactable) = self.interactable {
|
||||
if self.scene.camera().get_mode() == CameraMode::Freefly {
|
||||
match interactable {
|
||||
Interactable::Block(_, _, _) => {},
|
||||
Interactable::Entity(entity) => {
|
||||
self.viewpoint_entity = Some(entity);
|
||||
self.scene
|
||||
.camera_mut()
|
||||
.set_mode(CameraMode::FirstPerson);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
},
|
||||
@ -936,103 +978,128 @@ impl PlayState for SessionState {
|
||||
}
|
||||
}
|
||||
|
||||
// If auto-gliding, point camera into the wind
|
||||
if let Some(dir) = self
|
||||
.auto_walk
|
||||
.then_some(self.client.borrow())
|
||||
.filter(|client| client.is_gliding())
|
||||
.and_then(|client| {
|
||||
let ecs = client.state().ecs();
|
||||
let entity = client.entity();
|
||||
let fluid = ecs
|
||||
.read_storage::<comp::PhysicsState>()
|
||||
.get(entity)?
|
||||
.in_fluid?;
|
||||
ecs.read_storage::<Vel>()
|
||||
.get(entity)
|
||||
.map(|vel| fluid.relative_flow(vel).0)
|
||||
.map(|rel_flow| {
|
||||
let is_wind_downwards = rel_flow.dot(Vec3::unit_z()).is_sign_negative();
|
||||
if !self.free_look {
|
||||
if is_wind_downwards {
|
||||
self.scene.camera().forward_xy().into()
|
||||
} else {
|
||||
let windwards = rel_flow
|
||||
* self
|
||||
.scene
|
||||
.camera()
|
||||
.forward_xy()
|
||||
.dot(rel_flow.xy())
|
||||
.signum();
|
||||
Plane::from(Dir::new(self.scene.camera().right()))
|
||||
.projection(windwards)
|
||||
}
|
||||
} else if is_wind_downwards {
|
||||
Vec3::from(-rel_flow.xy())
|
||||
} else {
|
||||
-rel_flow
|
||||
}
|
||||
})
|
||||
.and_then(Dir::from_unnormalized)
|
||||
})
|
||||
{
|
||||
self.key_state.auto_walk = false;
|
||||
self.inputs.move_dir = Vec2::zero();
|
||||
self.inputs.look_dir = dir;
|
||||
} else {
|
||||
self.key_state.auto_walk = self.auto_walk;
|
||||
if !self.free_look {
|
||||
self.walk_forward_dir = self.scene.camera().forward_xy();
|
||||
self.walk_right_dir = self.scene.camera().right_xy();
|
||||
self.inputs.look_dir =
|
||||
Dir::from_unnormalized(cam_dir + aim_dir_offset).unwrap();
|
||||
}
|
||||
if self.viewpoint_entity.map_or(false, |entity| {
|
||||
!self
|
||||
.client
|
||||
.borrow()
|
||||
.state()
|
||||
.ecs()
|
||||
.read_storage::<Pos>()
|
||||
.contains(entity)
|
||||
}) {
|
||||
self.viewpoint_entity = None;
|
||||
self.scene.camera_mut().set_mode(CameraMode::Freefly);
|
||||
}
|
||||
self.inputs.strafing = matches!(
|
||||
self.scene.camera().get_mode(),
|
||||
camera::CameraMode::FirstPerson
|
||||
);
|
||||
|
||||
let (viewpoint_entity, mutable_viewpoint) = self.viewpoint_entity();
|
||||
|
||||
// Get the current state of movement related inputs
|
||||
let input_vec = self.key_state.dir_vec();
|
||||
let (axis_right, axis_up) = (input_vec[0], input_vec[1]);
|
||||
let dt = global_state.clock.get_stable_dt().as_secs_f32();
|
||||
|
||||
// Auto camera mode
|
||||
if global_state.settings.gameplay.auto_camera
|
||||
&& matches!(
|
||||
if mutable_viewpoint {
|
||||
// If auto-gliding, point camera into the wind
|
||||
if let Some(dir) = self
|
||||
.auto_walk
|
||||
.then_some(self.client.borrow())
|
||||
.filter(|client| client.is_gliding())
|
||||
.and_then(|client| {
|
||||
let ecs = client.state().ecs();
|
||||
let entity = client.entity();
|
||||
let fluid = ecs
|
||||
.read_storage::<comp::PhysicsState>()
|
||||
.get(entity)?
|
||||
.in_fluid?;
|
||||
ecs.read_storage::<Vel>()
|
||||
.get(entity)
|
||||
.map(|vel| fluid.relative_flow(vel).0)
|
||||
.map(|rel_flow| {
|
||||
let is_wind_downwards =
|
||||
rel_flow.dot(Vec3::unit_z()).is_sign_negative();
|
||||
if !self.free_look {
|
||||
if is_wind_downwards {
|
||||
self.scene.camera().forward_xy().into()
|
||||
} else {
|
||||
let windwards = rel_flow
|
||||
* self
|
||||
.scene
|
||||
.camera()
|
||||
.forward_xy()
|
||||
.dot(rel_flow.xy())
|
||||
.signum();
|
||||
Plane::from(Dir::new(self.scene.camera().right()))
|
||||
.projection(windwards)
|
||||
}
|
||||
} else if is_wind_downwards {
|
||||
Vec3::from(-rel_flow.xy())
|
||||
} else {
|
||||
-rel_flow
|
||||
}
|
||||
})
|
||||
.and_then(Dir::from_unnormalized)
|
||||
})
|
||||
{
|
||||
self.key_state.auto_walk = false;
|
||||
self.inputs.move_dir = Vec2::zero();
|
||||
self.inputs.look_dir = dir;
|
||||
} else {
|
||||
self.key_state.auto_walk = self.auto_walk;
|
||||
if !self.free_look {
|
||||
self.walk_forward_dir = self.scene.camera().forward_xy();
|
||||
self.walk_right_dir = self.scene.camera().right_xy();
|
||||
self.inputs.look_dir =
|
||||
Dir::from_unnormalized(cam_dir + aim_dir_offset).unwrap();
|
||||
}
|
||||
}
|
||||
self.inputs.strafing = matches!(
|
||||
self.scene.camera().get_mode(),
|
||||
camera::CameraMode::ThirdPerson | camera::CameraMode::FirstPerson
|
||||
)
|
||||
&& input_vec.magnitude_squared() > 0.0
|
||||
{
|
||||
let camera = self.scene.camera_mut();
|
||||
let ori = camera.get_orientation();
|
||||
camera.set_orientation_instant(Vec3::new(
|
||||
ori.x
|
||||
+ input_vec.x
|
||||
* (3.0 - input_vec.y * 1.5 * if is_aiming { 1.5 } else { 1.0 })
|
||||
* dt,
|
||||
std::f32::consts::PI * if is_aiming { 0.015 } else { 0.1 },
|
||||
0.0,
|
||||
));
|
||||
camera::CameraMode::FirstPerson
|
||||
);
|
||||
|
||||
// Auto camera mode
|
||||
if global_state.settings.gameplay.auto_camera
|
||||
&& matches!(
|
||||
self.scene.camera().get_mode(),
|
||||
camera::CameraMode::ThirdPerson | camera::CameraMode::FirstPerson
|
||||
)
|
||||
&& input_vec.magnitude_squared() > 0.0
|
||||
{
|
||||
let camera = self.scene.camera_mut();
|
||||
let ori = camera.get_orientation();
|
||||
camera.set_orientation_instant(Vec3::new(
|
||||
ori.x
|
||||
+ input_vec.x
|
||||
* (3.0 - input_vec.y * 1.5 * if is_aiming { 1.5 } else { 1.0 })
|
||||
* dt,
|
||||
std::f32::consts::PI * if is_aiming { 0.015 } else { 0.1 },
|
||||
0.0,
|
||||
));
|
||||
}
|
||||
|
||||
self.inputs.climb = self.key_state.climb();
|
||||
self.inputs.move_z =
|
||||
self.key_state.swim_up as i32 as f32 - self.key_state.swim_down as i32 as f32;
|
||||
}
|
||||
|
||||
match self.scene.camera().get_mode() {
|
||||
CameraMode::FirstPerson | CameraMode::ThirdPerson => {
|
||||
// Move the player character based on their walking direction.
|
||||
// This could be different from the camera direction if free look is enabled.
|
||||
self.inputs.move_dir =
|
||||
self.walk_right_dir * axis_right + self.walk_forward_dir * axis_up;
|
||||
if mutable_viewpoint {
|
||||
// Move the player character based on their walking direction.
|
||||
// This could be different from the camera direction if free look is
|
||||
// enabled.
|
||||
self.inputs.move_dir =
|
||||
self.walk_right_dir * axis_right + self.walk_forward_dir * axis_up;
|
||||
}
|
||||
self.freefly_vel = Vec3::zero();
|
||||
},
|
||||
|
||||
CameraMode::Freefly => {
|
||||
// Move the camera freely in 3d space. Apply acceleration so that
|
||||
// the movement feels more natural and controlled.
|
||||
const FREEFLY_ACCEL: f32 = 120.0;
|
||||
const FREEFLY_DAMPING: f32 = 80.0;
|
||||
const FREEFLY_MAX_SPEED: f32 = 50.0;
|
||||
const FREEFLY_SPEED_BOOST: f32 = 5.0;
|
||||
|
||||
let forward = self.scene.camera().forward();
|
||||
let right = self.scene.camera().right();
|
||||
@ -1054,20 +1121,22 @@ impl PlayState for SessionState {
|
||||
}
|
||||
}
|
||||
|
||||
let boost = if self.inputs_state.contains(&GameInput::SpectateSpeedBoost) {
|
||||
FREEFLY_SPEED_BOOST
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
let pos = self.scene.camera().get_focus_pos();
|
||||
self.scene
|
||||
.camera_mut()
|
||||
.set_focus_pos(pos + self.freefly_vel * dt);
|
||||
.set_focus_pos(pos + self.freefly_vel * dt * boost);
|
||||
|
||||
// Do not apply any movement to the player character
|
||||
self.inputs.move_dir = Vec2::zero();
|
||||
},
|
||||
};
|
||||
|
||||
self.inputs.climb = self.key_state.climb();
|
||||
self.inputs.move_z =
|
||||
self.key_state.swim_up as i32 as f32 - self.key_state.swim_down as i32 as f32;
|
||||
|
||||
let mut outcomes = Vec::new();
|
||||
|
||||
// Runs if either in a multiplayer server or the singleplayer server is unpaused
|
||||
@ -1157,6 +1226,8 @@ impl PlayState for SessionState {
|
||||
self.scene.camera().get_mode(),
|
||||
camera::CameraMode::FirstPerson
|
||||
),
|
||||
viewpoint_entity,
|
||||
mutable_viewpoint,
|
||||
target_entity: self.target_entity,
|
||||
selected_entity: self.selected_entity,
|
||||
},
|
||||
@ -1588,7 +1659,8 @@ impl PlayState for SessionState {
|
||||
let scene_data = SceneData {
|
||||
client: &client,
|
||||
state: client.state(),
|
||||
player_entity: client.entity(),
|
||||
viewpoint_entity,
|
||||
mutable_viewpoint: mutable_viewpoint || self.free_look,
|
||||
// Only highlight if interactable
|
||||
target_entity: self.interactable.and_then(Interactable::entity),
|
||||
loaded_distance: client.loaded_distance(),
|
||||
@ -1668,10 +1740,13 @@ impl PlayState for SessionState {
|
||||
|
||||
let client = self.client.borrow();
|
||||
|
||||
let (viewpoint_entity, mutable_viewpoint) = self.viewpoint_entity();
|
||||
|
||||
let scene_data = SceneData {
|
||||
client: &client,
|
||||
state: client.state(),
|
||||
player_entity: client.entity(),
|
||||
viewpoint_entity,
|
||||
mutable_viewpoint,
|
||||
// Only highlight if interactable
|
||||
target_entity: self.interactable.and_then(Interactable::entity),
|
||||
loaded_distance: client.loaded_distance(),
|
||||
|
@ -189,6 +189,8 @@ impl ControlSettings {
|
||||
GameInput::MapZoomIn => KeyMouse::Key(VirtualKeyCode::Plus),
|
||||
GameInput::MapZoomOut => KeyMouse::Key(VirtualKeyCode::Minus),
|
||||
GameInput::MapSetMarker => KeyMouse::Mouse(MouseButton::Middle),
|
||||
GameInput::SpectateSpeedBoost => KeyMouse::Key(VirtualKeyCode::LControl),
|
||||
GameInput::SpectateViewpoint => KeyMouse::Mouse(MouseButton::Middle),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
mod pixel_art;
|
||||
mod renderer;
|
||||
|
||||
pub use renderer::{SampleStrat, Transform};
|
||||
|
||||
use crate::{
|
||||
|
@ -3,7 +3,7 @@ use crate::hud::{
|
||||
get_quality_col,
|
||||
img_ids::Imgs,
|
||||
item_imgs::{animate_by_pulse, ItemImgs},
|
||||
util,
|
||||
util, HudInfo,
|
||||
};
|
||||
use client::Client;
|
||||
use common::{
|
||||
@ -291,6 +291,7 @@ pub struct ItemTooltip<'a> {
|
||||
transparency: f32,
|
||||
image_frame: ImageFrame,
|
||||
client: &'a Client,
|
||||
info: &'a HudInfo,
|
||||
imgs: &'a Imgs,
|
||||
item_imgs: &'a ItemImgs,
|
||||
pulse: f32,
|
||||
@ -352,6 +353,7 @@ impl<'a> ItemTooltip<'a> {
|
||||
pub fn new(
|
||||
image_frame: ImageFrame,
|
||||
client: &'a Client,
|
||||
info: &'a HudInfo,
|
||||
imgs: &'a Imgs,
|
||||
item_imgs: &'a ItemImgs,
|
||||
pulse: f32,
|
||||
@ -369,6 +371,7 @@ impl<'a> ItemTooltip<'a> {
|
||||
image: None,
|
||||
image_dims: None,
|
||||
client,
|
||||
info,
|
||||
imgs,
|
||||
item_imgs,
|
||||
pulse,
|
||||
@ -448,7 +451,7 @@ impl<'a> Widget for ItemTooltip<'a> {
|
||||
let i18n = &self.localized_strings;
|
||||
|
||||
let inventories = self.client.inventories();
|
||||
let inventory = match inventories.get(self.client.entity()) {
|
||||
let inventory = match inventories.get(self.info.viewpoint_entity) {
|
||||
Some(l) => l,
|
||||
None => return,
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user