mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Stop using Character presence kind when possessing so that persistence doesn't get messed up.
This commit is contained in:
parent
c2ad763b9c
commit
10803a9735
@ -1837,12 +1837,35 @@ impl Client {
|
|||||||
let mut controllers = self.state.ecs().write_storage::<Controller>();
|
let mut controllers = self.state.ecs().write_storage::<Controller>();
|
||||||
if let Some(controller) = controllers.remove(old_entity) {
|
if let Some(controller) = controllers.remove(old_entity) {
|
||||||
if let Err(e) = controllers.insert(entity, controller) {
|
if let Err(e) = controllers.insert(entity, controller) {
|
||||||
error!(?e, "Failed to insert controller when setting new player entity!");
|
error!(
|
||||||
|
?e,
|
||||||
|
"Failed to insert controller when setting new player entity!"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let uids = self.state.ecs().read_storage::<Uid>();
|
||||||
|
if let Some((prev_uid, presence)) =
|
||||||
|
uids.get(old_entity).copied().zip(self.presence)
|
||||||
|
{
|
||||||
|
self.presence = Some(match presence {
|
||||||
|
PresenceKind::Character(char_id) => {
|
||||||
|
PresenceKind::Possessor(char_id, prev_uid)
|
||||||
|
},
|
||||||
|
PresenceKind::Spectator => PresenceKind::Spectator,
|
||||||
|
PresenceKind::Possessor(old_char_id, old_uid) => {
|
||||||
|
if old_uid == uid {
|
||||||
|
// Returning to original entity
|
||||||
|
PresenceKind::Character(old_char_id)
|
||||||
|
} else {
|
||||||
|
PresenceKind::Possessor(old_char_id, old_uid)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::Other("Failed to find entity from uid.".to_owned()));
|
return Err(Error::Other("Failed to find entity from uid.".into()));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ServerGeneral::TimeOfDay(time_of_day, calendar) => {
|
ServerGeneral::TimeOfDay(time_of_day, calendar) => {
|
||||||
|
@ -19,13 +19,30 @@ pub use self::{
|
|||||||
},
|
},
|
||||||
world_msg::WorldMapMsg,
|
world_msg::WorldMapMsg,
|
||||||
};
|
};
|
||||||
use common::character::CharacterId;
|
use common::{character::CharacterId, uid::Uid};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum PresenceKind {
|
pub enum PresenceKind {
|
||||||
Spectator,
|
Spectator,
|
||||||
Character(CharacterId),
|
Character(CharacterId),
|
||||||
|
Possessor(
|
||||||
|
/// The original character Id before possession began. Used to revert
|
||||||
|
/// back to original `Character` presence if original entity is
|
||||||
|
/// re-possessed.
|
||||||
|
CharacterId,
|
||||||
|
/// The original entity Uid.
|
||||||
|
Uid,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PresenceKind {
|
||||||
|
/// Check if the presence represents a control of a character, and thus
|
||||||
|
/// certain in-game messages from the client such as control inputs
|
||||||
|
/// should be handled.
|
||||||
|
pub fn controlling_char(&self) -> bool {
|
||||||
|
matches!(self, Self::Character(_) | Self::Possessor(_, _))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use specs::{world::WorldExt, Builder, Entity as EcsEntity, Join};
|
use specs::{world::WorldExt, Builder, Entity as EcsEntity, Join};
|
||||||
use tracing::error;
|
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
use common::{
|
use common::{
|
||||||
@ -9,8 +8,6 @@ use common::{
|
|||||||
agent::{AgentEvent, Sound, SoundKind},
|
agent::{AgentEvent, Sound, SoundKind},
|
||||||
dialogue::Subject,
|
dialogue::Subject,
|
||||||
inventory::slot::EquipSlot,
|
inventory::slot::EquipSlot,
|
||||||
item,
|
|
||||||
slot::Slot,
|
|
||||||
tool::ToolKind,
|
tool::ToolKind,
|
||||||
Inventory, Pos, SkillGroupKind,
|
Inventory, Pos, SkillGroupKind,
|
||||||
},
|
},
|
||||||
@ -18,19 +15,13 @@ use common::{
|
|||||||
link::Is,
|
link::Is,
|
||||||
mounting::{Mount, Mounting, Rider},
|
mounting::{Mount, Mounting, Rider},
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
region::RegionMap,
|
|
||||||
terrain::{Block, SpriteKind},
|
terrain::{Block, SpriteKind},
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
};
|
};
|
||||||
use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
|
use common_net::sync::WorldSyncExt;
|
||||||
|
|
||||||
use crate::{
|
use crate::{state_ext::StateExt, Server};
|
||||||
client::Client,
|
|
||||||
presence::{Presence, RegionSubscription},
|
|
||||||
state_ext::StateExt,
|
|
||||||
Server,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::pet::tame_pet;
|
use crate::pet::tame_pet;
|
||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
@ -144,175 +135,6 @@ pub fn handle_unmount(server: &mut Server, rider: EcsEntity) {
|
|||||||
state.ecs().write_storage::<Is<Rider>>().remove(rider);
|
state.ecs().write_storage::<Is<Rider>>().remove(rider);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// FIXME: This code is dangerous and needs to be refactored. We can't just
|
|
||||||
/// comment it out, but it needs to be fixed for a variety of reasons. Get rid
|
|
||||||
/// of this ASAP!
|
|
||||||
pub fn handle_possess(server: &mut Server, possessor_uid: Uid, possesse_uid: Uid) {
|
|
||||||
let ecs = server.state.ecs();
|
|
||||||
if let (Some(possessor), Some(possesse)) = (
|
|
||||||
ecs.entity_from_uid(possessor_uid.into()),
|
|
||||||
ecs.entity_from_uid(possesse_uid.into()),
|
|
||||||
) {
|
|
||||||
// Check that entities still exist
|
|
||||||
if !possessor.gen().is_alive()
|
|
||||||
|| !ecs.is_alive(possessor)
|
|
||||||
|| !possesse.gen().is_alive()
|
|
||||||
|| !ecs.is_alive(possesse)
|
|
||||||
{
|
|
||||||
error!(
|
|
||||||
"Error possessing! either the possessor entity or possesse entity no longer exists"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut clients = ecs.write_storage::<Client>();
|
|
||||||
let mut players = ecs.write_storage::<comp::Player>();
|
|
||||||
|
|
||||||
if clients.contains(possesse) || players.contains(possesse) {
|
|
||||||
error!("Can't possess other players!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limit possessible entities to those in the client's subscribed regions (so
|
|
||||||
// that the entity already exists on the client, reduces the amount of
|
|
||||||
// syncing edge cases to consider).
|
|
||||||
let mut subscriptions = ecs.write_storage::<RegionSubscription>();
|
|
||||||
let region_map = ecs.read_resource::<RegionMap>();
|
|
||||||
let possesse_in_subscribed_region = subscriptions
|
|
||||||
.get(possessor)
|
|
||||||
.iter()
|
|
||||||
.flat_map(|s| s.regions.iter())
|
|
||||||
.filter_map(|key| region_map.get(*key))
|
|
||||||
.any(|region| region.entities().contains(possesse.id()));
|
|
||||||
if !possesse_in_subscribed_region {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transfer client component. Note: we require this component for possession.
|
|
||||||
if let Some(client) = clients.remove(possessor) {
|
|
||||||
client.send_fallible(ServerGeneral::SetPlayerEntity(possesse_uid));
|
|
||||||
// Note: we check that the `possessor` and `possesse` entities exist above, so
|
|
||||||
// this should never panic.
|
|
||||||
clients
|
|
||||||
.insert(possesse, client)
|
|
||||||
.expect("Checked entity was alive!");
|
|
||||||
} else {
|
|
||||||
error!("Error posessing, no `Client` component on the possessor!");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Other components to transfer if they exist.
|
|
||||||
// TODO: don't transfer character id, TODO: consider how this could relate to
|
|
||||||
// database duplications, might need to model this like the player
|
|
||||||
// logging out. Note: logging back in is delayed because it would
|
|
||||||
// re-load from the database before old information is saved, if you are
|
|
||||||
// able to reposess the same entity, this should not be an issue,
|
|
||||||
// although the logout save being duplicated with the batch save may need to be
|
|
||||||
// considered, the logout save would be outdated. If you could cause
|
|
||||||
// your old entity to drop items while possessing another entity that
|
|
||||||
// would cause duplication in the database (on the other hand this ability
|
|
||||||
// should be strictly limited to admins, and not intended to be a normal
|
|
||||||
// gameplay ability).
|
|
||||||
fn transfer_component<C: specs::Component>(
|
|
||||||
storage: &mut specs::WriteStorage<'_, C>,
|
|
||||||
possessor: EcsEntity,
|
|
||||||
possesse: EcsEntity,
|
|
||||||
) {
|
|
||||||
if let Some(c) = storage.remove(possessor) {
|
|
||||||
// Note: we check that the `possessor` and `possesse` entities exist above, so
|
|
||||||
// this should never panic.
|
|
||||||
storage
|
|
||||||
.insert(possesse, c)
|
|
||||||
.expect("Checked entity was alive!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut presence = ecs.write_storage::<Presence>();
|
|
||||||
let mut admins = ecs.write_storage::<comp::Admin>();
|
|
||||||
let mut waypoints = ecs.write_storage::<comp::Waypoint>();
|
|
||||||
|
|
||||||
transfer_component(&mut players, possessor, possesse);
|
|
||||||
transfer_component(&mut presence, possessor, possesse);
|
|
||||||
transfer_component(&mut subscriptions, possessor, possesse);
|
|
||||||
transfer_component(&mut admins, possessor, possesse);
|
|
||||||
transfer_component(&mut waypoints, possessor, possesse);
|
|
||||||
|
|
||||||
// If a player is posessing, add possesse to playerlist as player and remove old
|
|
||||||
// player.
|
|
||||||
// Fetches from possesse entity here since we have transferred over the `Player`
|
|
||||||
// component.
|
|
||||||
if let Some(player) = players.get(possesse) {
|
|
||||||
use common_net::msg;
|
|
||||||
|
|
||||||
let add_player_msg = ServerGeneral::PlayerListUpdate(
|
|
||||||
msg::server::PlayerListUpdate::Add(possesse_uid, msg::server::PlayerInfo {
|
|
||||||
player_alias: player.alias.clone(),
|
|
||||||
is_online: true,
|
|
||||||
is_moderator: admins.contains(possesse),
|
|
||||||
character: ecs.read_storage::<comp::Stats>().get(possesse).map(|s| {
|
|
||||||
msg::CharacterInfo {
|
|
||||||
name: s.name.clone(),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
let remove_player_msg = ServerGeneral::PlayerListUpdate(
|
|
||||||
msg::server::PlayerListUpdate::Remove(possessor_uid),
|
|
||||||
);
|
|
||||||
|
|
||||||
drop((clients, players)); // need to drop so we can use `notify_players` below
|
|
||||||
server.state().notify_players(remove_player_msg);
|
|
||||||
server.state().notify_players(add_player_msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put possess item into loadout
|
|
||||||
let mut inventories = ecs.write_storage::<Inventory>();
|
|
||||||
let mut inventory = inventories
|
|
||||||
.entry(possesse)
|
|
||||||
.expect("Nobody has &mut World, so there's no way to delete an entity.")
|
|
||||||
.or_insert(Inventory::new_empty());
|
|
||||||
|
|
||||||
let debug_item = comp::Item::new_from_asset_expect("common.items.debug.admin_stick");
|
|
||||||
if let item::ItemKind::Tool(_) = debug_item.kind() {
|
|
||||||
let leftover_items = inventory.swap(
|
|
||||||
Slot::Equip(EquipSlot::ActiveMainhand),
|
|
||||||
Slot::Equip(EquipSlot::InactiveMainhand),
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
leftover_items.is_empty(),
|
|
||||||
"Swapping active and inactive mainhands never results in leftover items"
|
|
||||||
);
|
|
||||||
inventory.replace_loadout_item(EquipSlot::ActiveMainhand, Some(debug_item));
|
|
||||||
}
|
|
||||||
drop(inventories);
|
|
||||||
|
|
||||||
// Remove will of the entity
|
|
||||||
ecs.write_storage::<comp::Agent>().remove(possesse);
|
|
||||||
// Reset controller of former shell
|
|
||||||
if let Some(c) = ecs.write_storage::<comp::Controller>().get_mut(possessor) {
|
|
||||||
*c = Default::default();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send client new `SyncFrom::ClientEntity` components and tell it to
|
|
||||||
// deletes these on the old entity.
|
|
||||||
let clients = ecs.read_storage::<Client>();
|
|
||||||
let client = clients
|
|
||||||
.get(possesse)
|
|
||||||
.expect("We insert this component above and have exclusive access to the world.");
|
|
||||||
use crate::sys::sentinel::TrackedStorages;
|
|
||||||
use specs::SystemData;
|
|
||||||
let tracked_storages = TrackedStorages::fetch(ecs);
|
|
||||||
let comp_sync_package = tracked_storages.create_sync_from_client_entity_switch(
|
|
||||||
possessor_uid,
|
|
||||||
possesse_uid,
|
|
||||||
possesse,
|
|
||||||
);
|
|
||||||
if !comp_sync_package.is_empty() {
|
|
||||||
client.send_fallible(ServerGeneral::CompSync(comp_sync_package));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn within_mounting_range(player_position: Option<&Pos>, mount_position: Option<&Pos>) -> bool {
|
fn within_mounting_range(player_position: Option<&Pos>, mount_position: Option<&Pos>) -> bool {
|
||||||
match (player_position, mount_position) {
|
match (player_position, mount_position) {
|
||||||
(Some(ppos), Some(ipos)) => ppos.0.distance_squared(ipos.0) < MAX_MOUNT_RANGE.powi(2),
|
(Some(ppos), Some(ipos)) => ppos.0.distance_squared(ipos.0) < MAX_MOUNT_RANGE.powi(2),
|
||||||
|
@ -18,11 +18,11 @@ use group_manip::handle_group;
|
|||||||
use information::handle_site_info;
|
use information::handle_site_info;
|
||||||
use interaction::{
|
use interaction::{
|
||||||
handle_create_sprite, handle_lantern, handle_mine_block, handle_mount, handle_npc_interaction,
|
handle_create_sprite, handle_lantern, handle_mine_block, handle_mount, handle_npc_interaction,
|
||||||
handle_possess, handle_sound, handle_unmount,
|
handle_sound, handle_unmount,
|
||||||
};
|
};
|
||||||
use inventory_manip::handle_inventory;
|
use inventory_manip::handle_inventory;
|
||||||
use invite::{handle_invite, handle_invite_response};
|
use invite::{handle_invite, handle_invite_response};
|
||||||
use player::{handle_client_disconnect, handle_exit_ingame};
|
use player::{handle_client_disconnect, handle_exit_ingame, handle_possess};
|
||||||
use specs::{Builder, Entity as EcsEntity, WorldExt};
|
use specs::{Builder, Entity as EcsEntity, WorldExt};
|
||||||
use trade::{cancel_trade_for, handle_process_trade_action};
|
use trade::{cancel_trade_for, handle_process_trade_action};
|
||||||
|
|
||||||
|
@ -271,8 +271,222 @@ fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
PresenceKind::Spectator => { /* Do nothing, spectators do not need persisting */ },
|
PresenceKind::Spectator => { /* Do nothing, spectators do not need persisting */ },
|
||||||
|
PresenceKind::Possessor(_, _) => { /* Do nothing, possessor's are not persisted */ },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
entity
|
entity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// FIXME: This code is dangerous and needs to be refactored. We can't just
|
||||||
|
/// comment it out, but it needs to be fixed for a variety of reasons. Get rid
|
||||||
|
/// of this ASAP!
|
||||||
|
pub fn handle_possess(server: &mut Server, possessor_uid: Uid, possesse_uid: Uid) {
|
||||||
|
use crate::presence::RegionSubscription;
|
||||||
|
use common::{
|
||||||
|
comp::{inventory::slot::EquipSlot, item, slot::Slot, Inventory},
|
||||||
|
region::RegionMap,
|
||||||
|
};
|
||||||
|
use common_net::sync::WorldSyncExt;
|
||||||
|
|
||||||
|
let ecs = server.state.ecs();
|
||||||
|
|
||||||
|
if let (Some(possessor), Some(possesse)) = (
|
||||||
|
ecs.entity_from_uid(possessor_uid.into()),
|
||||||
|
ecs.entity_from_uid(possesse_uid.into()),
|
||||||
|
) {
|
||||||
|
// In this section we check various invariants and can return early if any of
|
||||||
|
// them are not met.
|
||||||
|
{
|
||||||
|
// Check that entities still exist
|
||||||
|
if !possessor.gen().is_alive()
|
||||||
|
|| !ecs.is_alive(possessor)
|
||||||
|
|| !possesse.gen().is_alive()
|
||||||
|
|| !ecs.is_alive(possesse)
|
||||||
|
{
|
||||||
|
error!(
|
||||||
|
"Error possessing! either the possessor entity or possesse entity no longer \
|
||||||
|
exists"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let clients = ecs.read_storage::<Client>();
|
||||||
|
let players = ecs.read_storage::<comp::Player>();
|
||||||
|
|
||||||
|
if clients.contains(possesse) || players.contains(possesse) {
|
||||||
|
error!("Can't possess other players!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit possessible entities to those in the client's subscribed regions (so
|
||||||
|
// that the entity already exists on the client, this reduces the
|
||||||
|
// amount of syncing edge cases to consider).
|
||||||
|
let subscriptions = ecs.read_storage::<RegionSubscription>();
|
||||||
|
let region_map = ecs.read_resource::<RegionMap>();
|
||||||
|
let possesse_in_subscribed_region = subscriptions
|
||||||
|
.get(possessor)
|
||||||
|
.iter()
|
||||||
|
.flat_map(|s| s.regions.iter())
|
||||||
|
.filter_map(|key| region_map.get(*key))
|
||||||
|
.any(|region| region.entities().contains(possesse.id()));
|
||||||
|
if !possesse_in_subscribed_region {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !clients.contains(possessor) {
|
||||||
|
error!("Error posessing, no `Client` component on the possessor!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No early returns after this.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync the player's character data to the database. This must be done before
|
||||||
|
// moving any components from the entity.
|
||||||
|
drop(ecs);
|
||||||
|
let state = server.state_mut();
|
||||||
|
let possessor = persist_entity(state, possessor);
|
||||||
|
drop(state);
|
||||||
|
// TODO: delete old entity (if PresenceKind::Character) as if logging out.
|
||||||
|
|
||||||
|
let ecs = server.state.ecs();
|
||||||
|
let mut clients = ecs.write_storage::<Client>();
|
||||||
|
|
||||||
|
// Transfer client component. Note: we require this component for possession.
|
||||||
|
let client = clients.remove(possessor).expect("Checked client component was present above!");
|
||||||
|
client.send_fallible(ServerGeneral::SetPlayerEntity(possesse_uid));
|
||||||
|
// Note: we check that the `possessor` and `possesse` entities exist above, so
|
||||||
|
// this should never panic.
|
||||||
|
clients.insert(possesse, client).expect("Checked entity was alive!");
|
||||||
|
|
||||||
|
// Other components to transfer if they exist.
|
||||||
|
// TODO: don't transfer character id, TODO: consider how this could relate to
|
||||||
|
// database duplications, might need to model this like the player
|
||||||
|
// logging out. Note: logging back in is delayed because it would
|
||||||
|
// re-load from the database before old information is saved, if you are
|
||||||
|
// able to reposess the same entity, this should not be an issue,
|
||||||
|
// although the logout save being duplicated with the batch save may need to be
|
||||||
|
// considered, the logout save would be outdated. If you could cause
|
||||||
|
// your old entity to drop items while possessing another entity that
|
||||||
|
// would cause duplication in the database (on the other hand this ability
|
||||||
|
// should be strictly limited to admins, and not intended to be a normal
|
||||||
|
// gameplay ability).
|
||||||
|
fn transfer_component<C: specs::Component>(
|
||||||
|
storage: &mut specs::WriteStorage<'_, C>,
|
||||||
|
possessor: EcsEntity,
|
||||||
|
possesse: EcsEntity,
|
||||||
|
transform: impl FnOnce(C) -> C,
|
||||||
|
) {
|
||||||
|
if let Some(c) = storage.remove(possessor) {
|
||||||
|
// Note: we check that the `possessor` and `possesse` entities exist above, so
|
||||||
|
// this should never panic.
|
||||||
|
storage
|
||||||
|
.insert(possesse, transform(c))
|
||||||
|
.expect("Checked entity was alive!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut players = ecs.write_storage::<comp::Player>();
|
||||||
|
let mut presence = ecs.write_storage::<Presence>();
|
||||||
|
let mut subscriptions = ecs.write_storage::<RegionSubscription>();
|
||||||
|
let mut admins = ecs.write_storage::<comp::Admin>();
|
||||||
|
let mut waypoints = ecs.write_storage::<comp::Waypoint>();
|
||||||
|
|
||||||
|
transfer_component(&mut players, possessor, possesse, |x| x);
|
||||||
|
transfer_component(&mut presence, possessor, possesse, |mut presence| {
|
||||||
|
presence.kind = match presence.kind {
|
||||||
|
PresenceKind::Spectator => PresenceKind::Spectator,
|
||||||
|
// TODO: also perform this transition on the client in response to entity switch.
|
||||||
|
// Disable persistence by changing the presence.
|
||||||
|
PresenceKind::Character(char_id) => PresenceKind::Possessor(char_id, possessor_uid),
|
||||||
|
PresenceKind::Possessor(old_char_id, old_uid) => if old_uid == possesse_uid {
|
||||||
|
// If moving back to the original entity, shift back to the character
|
||||||
|
// presence which will re-enable persistence.
|
||||||
|
PresenceKind::Character(old_char_id)
|
||||||
|
} else {
|
||||||
|
PresenceKind::Possessor(old_char_id, old_uid)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
presence
|
||||||
|
});
|
||||||
|
transfer_component(&mut subscriptions, possessor, possesse, |x| x);
|
||||||
|
transfer_component(&mut admins, possessor, possesse, |x| x);
|
||||||
|
transfer_component(&mut waypoints, possessor, possesse, |x| x);
|
||||||
|
|
||||||
|
// If a player is posessing, add possesse to playerlist as player and remove old
|
||||||
|
// player.
|
||||||
|
// Fetches from possesse entity here since we have transferred over the `Player`
|
||||||
|
// component.
|
||||||
|
if let Some(player) = players.get(possesse) {
|
||||||
|
use common_net::msg;
|
||||||
|
|
||||||
|
let add_player_msg = ServerGeneral::PlayerListUpdate(
|
||||||
|
msg::server::PlayerListUpdate::Add(possesse_uid, msg::server::PlayerInfo {
|
||||||
|
player_alias: player.alias.clone(),
|
||||||
|
is_online: true,
|
||||||
|
is_moderator: admins.contains(possesse),
|
||||||
|
character: ecs.read_storage::<comp::Stats>().get(possesse).map(|s| {
|
||||||
|
msg::CharacterInfo {
|
||||||
|
name: s.name.clone(),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let remove_player_msg = ServerGeneral::PlayerListUpdate(
|
||||||
|
msg::server::PlayerListUpdate::Remove(possessor_uid),
|
||||||
|
);
|
||||||
|
|
||||||
|
drop((clients, players)); // need to drop so we can use `notify_players` below
|
||||||
|
server.state().notify_players(remove_player_msg);
|
||||||
|
server.state().notify_players(add_player_msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put possess item into loadout
|
||||||
|
let mut inventories = ecs.write_storage::<Inventory>();
|
||||||
|
let mut inventory = inventories
|
||||||
|
.entry(possesse)
|
||||||
|
.expect("Nobody has &mut World, so there's no way to delete an entity.")
|
||||||
|
.or_insert(Inventory::new_empty());
|
||||||
|
|
||||||
|
let debug_item = comp::Item::new_from_asset_expect("common.items.debug.admin_stick");
|
||||||
|
if let item::ItemKind::Tool(_) = debug_item.kind() {
|
||||||
|
let leftover_items = inventory.swap(
|
||||||
|
Slot::Equip(EquipSlot::ActiveMainhand),
|
||||||
|
Slot::Equip(EquipSlot::InactiveMainhand),
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
leftover_items.is_empty(),
|
||||||
|
"Swapping active and inactive mainhands never results in leftover items"
|
||||||
|
);
|
||||||
|
inventory.replace_loadout_item(EquipSlot::ActiveMainhand, Some(debug_item));
|
||||||
|
}
|
||||||
|
drop(inventories);
|
||||||
|
|
||||||
|
// Remove will of the entity
|
||||||
|
ecs.write_storage::<comp::Agent>().remove(possesse);
|
||||||
|
// Reset controller of former shell
|
||||||
|
if let Some(c) = ecs.write_storage::<comp::Controller>().get_mut(possessor) {
|
||||||
|
*c = Default::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send client new `SyncFrom::ClientEntity` components and tell it to
|
||||||
|
// deletes these on the old entity.
|
||||||
|
let clients = ecs.read_storage::<Client>();
|
||||||
|
let client = clients
|
||||||
|
.get(possesse)
|
||||||
|
.expect("We insert this component above and have exclusive access to the world.");
|
||||||
|
use crate::sys::sentinel::TrackedStorages;
|
||||||
|
use specs::SystemData;
|
||||||
|
let tracked_storages = TrackedStorages::fetch(ecs);
|
||||||
|
let comp_sync_package = tracked_storages.create_sync_from_client_entity_switch(
|
||||||
|
possessor_uid,
|
||||||
|
possesse_uid,
|
||||||
|
possesse,
|
||||||
|
);
|
||||||
|
if !comp_sync_package.is_empty() {
|
||||||
|
client.send_fallible(ServerGeneral::CompSync(comp_sync_package));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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, PresenceKind, ServerGeneral};
|
use common_net::msg::{ClientGeneral, 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};
|
||||||
@ -84,14 +84,14 @@ impl Sys {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
ClientGeneral::ControllerInputs(inputs) => {
|
ClientGeneral::ControllerInputs(inputs) => {
|
||||||
if matches!(presence.kind, PresenceKind::Character(_)) {
|
if presence.kind.controlling_char() {
|
||||||
if let Some(controller) = controllers.get_mut(entity) {
|
if let Some(controller) = controllers.get_mut(entity) {
|
||||||
controller.inputs.update_with_new(*inputs);
|
controller.inputs.update_with_new(*inputs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ClientGeneral::ControlEvent(event) => {
|
ClientGeneral::ControlEvent(event) => {
|
||||||
if matches!(presence.kind, PresenceKind::Character(_)) {
|
if presence.kind.controlling_char() {
|
||||||
// Skip respawn if client entity is alive
|
// Skip respawn if client entity is alive
|
||||||
if let ControlEvent::Respawn = event {
|
if let ControlEvent::Respawn = event {
|
||||||
if healths.get(entity).map_or(true, |h| !h.is_dead) {
|
if healths.get(entity).map_or(true, |h| !h.is_dead) {
|
||||||
@ -105,7 +105,7 @@ impl Sys {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
ClientGeneral::ControlAction(event) => {
|
ClientGeneral::ControlAction(event) => {
|
||||||
if matches!(presence.kind, PresenceKind::Character(_)) {
|
if presence.kind.controlling_char() {
|
||||||
if let Some(controller) = controllers.get_mut(entity) {
|
if let Some(controller) = controllers.get_mut(entity) {
|
||||||
controller.push_action(event);
|
controller.push_action(event);
|
||||||
}
|
}
|
||||||
@ -119,7 +119,7 @@ impl Sys {
|
|||||||
.or_default()
|
.or_default()
|
||||||
});
|
});
|
||||||
|
|
||||||
if matches!(presence.kind, PresenceKind::Character(_))
|
if presence.kind.controlling_char()
|
||||||
&& force_updates.get(entity).is_none()
|
&& force_updates.get(entity).is_none()
|
||||||
&& healths.get(entity).map_or(true, |h| !h.is_dead)
|
&& healths.get(entity).map_or(true, |h| !h.is_dead)
|
||||||
&& is_rider.get(entity).is_none()
|
&& is_rider.get(entity).is_none()
|
||||||
|
@ -100,7 +100,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
map_marker,
|
map_marker,
|
||||||
))
|
))
|
||||||
},
|
},
|
||||||
PresenceKind::Spectator => None,
|
PresenceKind::Spectator | PresenceKind::Possessor(_, _) => None,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1022,8 +1022,9 @@ impl Hud {
|
|||||||
// point.
|
// point.
|
||||||
|
|
||||||
let character_id = match client.presence().unwrap() {
|
let character_id = match client.presence().unwrap() {
|
||||||
PresenceKind::Character(id) => id,
|
PresenceKind::Character(id) => Some(id),
|
||||||
PresenceKind::Spectator => unreachable!("HUD creation in Spectator mode!"),
|
PresenceKind::Spectator => unreachable!("HUD creation in Spectator mode!"),
|
||||||
|
PresenceKind::Possessor(_, _) => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create a new HotbarState from the persisted slots.
|
// Create a new HotbarState from the persisted slots.
|
||||||
|
@ -8,7 +8,8 @@
|
|||||||
bool_to_option,
|
bool_to_option,
|
||||||
drain_filter,
|
drain_filter,
|
||||||
once_cell,
|
once_cell,
|
||||||
trait_alias
|
trait_alias,
|
||||||
|
option_get_or_insert_default
|
||||||
)]
|
)]
|
||||||
#![recursion_limit = "2048"]
|
#![recursion_limit = "2048"]
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ impl Default for CharacterProfile {
|
|||||||
pub struct ServerProfile {
|
pub struct ServerProfile {
|
||||||
/// A map of character's by id to their CharacterProfile.
|
/// A map of character's by id to their CharacterProfile.
|
||||||
pub characters: HashMap<CharacterId, CharacterProfile>,
|
pub characters: HashMap<CharacterId, CharacterProfile>,
|
||||||
// Selected character in the chararacter selection screen
|
/// Selected character in the chararacter selection screen
|
||||||
pub selected_character: Option<CharacterId>,
|
pub selected_character: Option<CharacterId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,18 +53,14 @@ impl Default for ServerProfile {
|
|||||||
/// Initially it is just for persisting things that don't belong in
|
/// Initially it is just for persisting things that don't belong in
|
||||||
/// settings.ron - like the state of hotbar and any other character level
|
/// settings.ron - like the state of hotbar and any other character level
|
||||||
/// configuration.
|
/// configuration.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Default, Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct Profile {
|
pub struct Profile {
|
||||||
pub servers: HashMap<String, ServerProfile>,
|
pub servers: HashMap<String, ServerProfile>,
|
||||||
}
|
/// Temporary character profiler, used when it should
|
||||||
|
/// not be persisted to the disk.
|
||||||
impl Default for Profile {
|
#[serde(skip)]
|
||||||
fn default() -> Self {
|
pub transient_character: Option<CharacterProfile>,
|
||||||
Profile {
|
|
||||||
servers: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Profile {
|
impl Profile {
|
||||||
@ -112,17 +108,22 @@ impl Profile {
|
|||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * server - current server the character is on.
|
/// * server - current server the character is on.
|
||||||
/// * character_id - id of the character.
|
/// * character_id - id of the character, passing `None` indicates the
|
||||||
|
/// transient character profile should be used.
|
||||||
pub fn get_hotbar_slots(
|
pub fn get_hotbar_slots(
|
||||||
&self,
|
&self,
|
||||||
server: &str,
|
server: &str,
|
||||||
character_id: CharacterId,
|
character_id: Option<CharacterId>,
|
||||||
) -> [Option<hud::HotbarSlotContents>; 10] {
|
) -> [Option<hud::HotbarSlotContents>; 10] {
|
||||||
self.servers
|
match character_id {
|
||||||
.get(server)
|
Some(character_id) => self
|
||||||
.and_then(|s| s.characters.get(&character_id))
|
.servers
|
||||||
.map(|c| c.hotbar_slots.clone())
|
.get(server)
|
||||||
.unwrap_or_else(default_slots)
|
.and_then(|s| s.characters.get(&character_id)),
|
||||||
|
None => self.transient_character.as_ref(),
|
||||||
|
}
|
||||||
|
.map(|c| c.hotbar_slots.clone())
|
||||||
|
.unwrap_or_else(default_slots)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the hotbar_slots for the requested character_id.
|
/// Set the hotbar_slots for the requested character_id.
|
||||||
@ -133,22 +134,26 @@ impl Profile {
|
|||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * server - current server the character is on.
|
/// * server - current server the character is on.
|
||||||
/// * character_id - id of the character.
|
/// * character_id - id of the character, passing `None` indicates the
|
||||||
|
/// transient character profile should be used.
|
||||||
/// * slots - array of hotbar_slots to save.
|
/// * slots - array of hotbar_slots to save.
|
||||||
pub fn set_hotbar_slots(
|
pub fn set_hotbar_slots(
|
||||||
&mut self,
|
&mut self,
|
||||||
server: &str,
|
server: &str,
|
||||||
character_id: CharacterId,
|
character_id: Option<CharacterId>,
|
||||||
slots: [Option<hud::HotbarSlotContents>; 10],
|
slots: [Option<hud::HotbarSlotContents>; 10],
|
||||||
) {
|
) {
|
||||||
self.servers
|
match character_id {
|
||||||
.entry(server.to_string())
|
Some(character_id) => self.servers
|
||||||
.or_insert(ServerProfile::default())
|
.entry(server.to_string())
|
||||||
// Get or update the CharacterProfile.
|
.or_insert(ServerProfile::default())
|
||||||
.characters
|
// Get or update the CharacterProfile.
|
||||||
.entry(character_id)
|
.characters
|
||||||
.or_insert(CharacterProfile::default())
|
.entry(character_id)
|
||||||
.hotbar_slots = slots;
|
.or_default(),
|
||||||
|
None => self.transient_character.get_or_insert_default(),
|
||||||
|
}
|
||||||
|
.hotbar_slots = slots;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the selected_character for the provided server.
|
/// Get the selected_character for the provided server.
|
||||||
|
@ -1361,10 +1361,11 @@ impl PlayState for SessionState {
|
|||||||
let server_name = &client.server_info().name;
|
let server_name = &client.server_info().name;
|
||||||
// If we are changing the hotbar state this CANNOT be None.
|
// If we are changing the hotbar state this CANNOT be None.
|
||||||
let character_id = match client.presence().unwrap() {
|
let character_id = match client.presence().unwrap() {
|
||||||
PresenceKind::Character(id) => id,
|
PresenceKind::Character(id) => Some(id),
|
||||||
PresenceKind::Spectator => {
|
PresenceKind::Spectator => {
|
||||||
unreachable!("HUD adaption in Spectator mode!")
|
unreachable!("HUD adaption in Spectator mode!")
|
||||||
},
|
},
|
||||||
|
PresenceKind::Possessor(_, _) => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get or update the ServerProfile.
|
// Get or update the ServerProfile.
|
||||||
|
Loading…
Reference in New Issue
Block a user