Merge branch 'isse/shared-waypoints' into 'master'

Shared and persistent waypoints

See merge request veloren/veloren!3162
This commit is contained in:
Marcel 2022-02-20 10:10:18 +00:00
commit df7cd2da1a
34 changed files with 448 additions and 135 deletions

View File

@ -58,6 +58,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Convert giant trees to site2
- Add new upgraded travelers
- Wallrunning
- Waypoints saved between sessions and shared with group members.
### Changed

Binary file not shown.

View File

@ -32,6 +32,7 @@
"hud.map.toggle_minimap_voxel": "Toggle Minimap Voxel View",
"hud.map.zoom_minimap_explanation": "Zoom in the Minimap to see\nthe area around you in higher detail",
"hud.map.gnarling": "Gnarling Fortification",
"hud.map.placed_by": "Placed by {name}",
},

View File

@ -30,7 +30,7 @@ use common::{
slot::{EquipSlot, InvSlotId, Slot},
CharacterState, ChatMode, ControlAction, ControlEvent, Controller, ControllerInputs,
GroupManip, InputKind, InventoryAction, InventoryEvent, InventoryUpdateEvent,
UtteranceKind,
MapMarkerChange, UtteranceKind,
},
event::{EventBus, LocalEvent},
grid::Grid,
@ -112,6 +112,7 @@ pub enum Event {
CharacterCreated(CharacterId),
CharacterEdited(CharacterId),
CharacterError(String),
MapMarker(comp::MapMarkerUpdate),
}
pub struct WorldData {
@ -759,7 +760,8 @@ impl Client {
| ClientGeneral::UnlockSkillGroup(_)
| ClientGeneral::RequestPlayerPhysics { .. }
| ClientGeneral::RequestLossyTerrainCompression { .. }
| ClientGeneral::AcknowledgePersistenceLoadError => {
| ClientGeneral::AcknowledgePersistenceLoadError
| ClientGeneral::UpdateMapMarker(_) => {
#[cfg(feature = "tracy")]
{
ingame = 1.0;
@ -1190,6 +1192,10 @@ impl Client {
}
}
pub fn map_marker_event(&mut self, event: MapMarkerChange) {
self.send_msg(ClientGeneral::UpdateMapMarker(event));
}
/// 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) }
@ -1909,6 +1915,9 @@ impl Client {
self.personalize_alias(uid, player_info.player_alias.clone())
)),
));
frontend_events.push(Event::MapMarker(
comp::MapMarkerUpdate::GroupMember(uid, MapMarkerChange::Remove),
));
}
if self.group_members.remove(&uid).is_none() {
warn!(
@ -1931,10 +1940,12 @@ impl Client {
if let Some(uid) = self.uid() {
self.group_members.remove(&uid);
}
frontend_events.push(Event::MapMarker(comp::MapMarkerUpdate::ClearGroup));
},
NoGroup => {
self.group_leader = None;
self.group_members = HashMap::new();
frontend_events.push(Event::MapMarker(comp::MapMarkerUpdate::ClearGroup));
},
}
},
@ -2032,6 +2043,9 @@ impl Client {
rich.economy = Some(economy);
}
},
ServerGeneral::MapMarker(event) => {
frontend_events.push(Event::MapMarker(event));
},
_ => unreachable!("Not a in_game message"),
}
Ok(())

View File

@ -78,6 +78,7 @@ pub enum ClientGeneral {
UnlockSkill(Skill),
UnlockSkillGroup(SkillGroupKind),
RequestSiteInfo(SiteId),
UpdateMapMarker(comp::MapMarkerChange),
//Only in Game, via terrain stream
TerrainChunkRequest {
key: Vec2<i32>,
@ -132,7 +133,8 @@ impl ClientMsg {
| ClientGeneral::UnlockSkillGroup(_)
| ClientGeneral::RequestPlayerPhysics { .. }
| ClientGeneral::RequestLossyTerrainCompression { .. }
| ClientGeneral::AcknowledgePersistenceLoadError => {
| ClientGeneral::AcknowledgePersistenceLoadError
| ClientGeneral::UpdateMapMarker(_) => {
c_type == ClientType::Game && presence.is_some()
},
//Always possible

View File

@ -189,6 +189,7 @@ pub enum ServerGeneral {
FinishedTrade(TradeResult),
/// Economic information about sites
SiteEconomy(EconomyInfo),
MapMarker(comp::MapMarkerUpdate),
}
impl ServerGeneral {
@ -298,7 +299,8 @@ impl ServerMsg {
| ServerGeneral::Knockback(_)
| ServerGeneral::UpdatePendingTrade(_, _, _)
| ServerGeneral::FinishedTrade(_)
| ServerGeneral::SiteEconomy(_) => {
| ServerGeneral::SiteEconomy(_)
| ServerGeneral::MapMarker(_) => {
c_type == ClientType::Game && presence.is_some()
},
// Always possible

View File

@ -62,9 +62,9 @@ pub enum ChangeNotification<E> {
// Also note when the same notification is sent to multiple destinations the
// mapping might be duplicated effort
impl<E> ChangeNotification<E> {
pub fn try_map<T>(self, f: impl Fn(E) -> Option<T>) -> Option<ChangeNotification<T>> {
pub fn try_map_ref<T>(&self, f: impl Fn(&E) -> Option<T>) -> Option<ChangeNotification<T>> {
match self {
Self::Added(e, r) => f(e).map(|t| ChangeNotification::Added(t, r)),
Self::Added(e, r) => f(e).map(|t| ChangeNotification::Added(t, *r)),
Self::Removed(e) => f(e).map(ChangeNotification::Removed),
Self::NewLeader(e) => f(e).map(ChangeNotification::NewLeader),
// Note just discards members that fail map
@ -72,8 +72,8 @@ impl<E> ChangeNotification<E> {
f(leader).map(|leader| ChangeNotification::NewGroup {
leader,
members: members
.into_iter()
.filter_map(|(e, r)| f(e).map(|t| (t, r)))
.iter()
.filter_map(|(e, r)| f(e).map(|t| (t, *r)))
.collect(),
})
},

View File

@ -1,4 +1,4 @@
use crate::resources::Time;
use crate::{resources::Time, uid::Uid};
use serde::{Deserialize, Serialize};
use specs::Component;
use specs_idvs::IdvStorage;
@ -45,3 +45,25 @@ impl Component for WaypointArea {
impl Default for WaypointArea {
fn default() -> Self { Self(5.0) }
}
/// Marker on the map, used for sharing waypoint with group and
/// persisting it server side.
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct MapMarker(pub Vec2<i32>);
impl Component for MapMarker {
type Storage = IdvStorage<Self>;
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub enum MapMarkerChange {
Update(Vec2<i32>),
Remove,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum MapMarkerUpdate {
Owned(MapMarkerChange),
GroupMember(Uid, MapMarkerChange),
ClearGroup,
}

View File

@ -90,7 +90,7 @@ pub use self::{
slot, Inventory, InventoryUpdate, InventoryUpdateEvent,
},
last::Last,
location::{Waypoint, WaypointArea},
location::{MapMarker, MapMarkerChange, MapMarkerUpdate, Waypoint, WaypointArea},
melee::{Melee, MeleeConstructor},
misc::Object,
ori::Ori,

View File

@ -115,6 +115,7 @@ pub enum ServerEvent {
Option<comp::Waypoint>,
Vec<(comp::Pet, comp::Body, comp::Stats)>,
comp::ActiveAbilities,
Option<comp::MapMarker>,
),
},
ExitIngame {
@ -209,6 +210,10 @@ pub enum ServerEvent {
auxiliary_key: comp::ability::AuxiliaryKey,
new_ability: comp::ability::AuxiliaryAbility,
},
UpdateMapMarker {
entity: EcsEntity,
update: comp::MapMarkerChange,
},
}
pub struct EventBus<E> {

View File

@ -191,6 +191,7 @@ impl State {
ecs.register::<comp::InventoryUpdate>();
ecs.register::<comp::Admin>();
ecs.register::<comp::Waypoint>();
ecs.register::<comp::MapMarker>();
ecs.register::<comp::Projectile>();
ecs.register::<comp::Melee>();
ecs.register::<comp::ItemDrop>();

View File

@ -64,6 +64,7 @@ pub fn create_character(
.expect("Inventory has at least 1 slot left!");
let waypoint = None;
let map_marker = None;
character_updater.create_character(entity, player_uuid, character_alias, PersistedComponents {
body,
@ -73,6 +74,7 @@ pub fn create_character(
waypoint,
pets: Vec::new(),
active_abilities: Default::default(),
map_marker,
});
Ok(())
}

View File

@ -185,7 +185,8 @@ impl Client {
| ServerGeneral::Knockback(_)
| ServerGeneral::SiteEconomy(_)
| ServerGeneral::UpdatePendingTrade(_, _, _)
| ServerGeneral::FinishedTrade(_) => {
| ServerGeneral::FinishedTrade(_)
| ServerGeneral::MapMarker(_) => {
PreparedMsg::new(2, &g, &self.in_game_stream_params)
},
//Ingame related, terrain

View File

@ -22,6 +22,8 @@ use specs::{Builder, Entity as EcsEntity, WorldExt};
use std::time::Duration;
use vek::{Rgb, Vec3};
use super::group_manip::update_map_markers;
pub fn handle_initialize_character(
server: &mut Server,
entity: EcsEntity,
@ -35,6 +37,14 @@ pub fn handle_loaded_character_data(
entity: EcsEntity,
loaded_components: PersistedComponents,
) {
if let Some(marker) = loaded_components.map_marker {
server.notify_client(
entity,
ServerGeneral::MapMarker(comp::MapMarkerUpdate::Owned(comp::MapMarkerChange::Update(
marker.0,
))),
);
}
server
.state
.update_character_data(entity, loaded_components);
@ -103,6 +113,7 @@ pub fn handle_create_npc(
let uids = state.ecs().read_storage::<Uid>();
let mut group_manager = state.ecs().write_resource::<comp::group::GroupManager>();
if let Some(owner) = state.ecs().entity_from_uid(owner_uid.into()) {
let map_markers = state.ecs().read_storage::<comp::MapMarker>();
group_manager.new_pet(
new_entity,
owner,
@ -115,10 +126,13 @@ pub fn handle_create_npc(
.get(entity)
.and_then(|c| {
group_change
.try_map(|e| uids.get(e).copied())
.try_map_ref(|e| uids.get(*e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| {
// Might be unneccessary, but maybe pets can somehow have map
// markers in the future
update_map_markers(&map_markers, &uids, c, &group_change);
c.send_fallible(ServerGeneral::GroupUpdate(g));
});
},

View File

@ -1267,3 +1267,41 @@ pub fn handle_change_ability(
);
}
}
pub fn handle_update_map_marker(
server: &mut Server,
entity: EcsEntity,
update: comp::MapMarkerChange,
) {
use comp::{MapMarker, MapMarkerChange::*};
match update {
Update(waypoint) => {
server
.state
.write_component_ignore_entity_dead(entity, MapMarker(waypoint));
},
Remove => {
server.state.delete_component::<MapMarker>(entity);
},
}
let ecs = server.state.ecs_mut();
// Send updated waypoint to group members
let groups = ecs.read_storage();
let uids = ecs.read_storage();
if let Some((group_id, uid)) = groups.get(entity).zip(uids.get(entity)) {
let clients = ecs.read_storage::<Client>();
for client in comp::group::members(
*group_id,
&groups,
&ecs.entities(),
&ecs.read_storage(),
&uids,
)
.filter_map(|(e, _)| if e != entity { clients.get(e) } else { None })
{
client.send_fallible(ServerGeneral::MapMarker(
comp::MapMarkerUpdate::GroupMember(*uid, update),
));
}
}
}

View File

@ -2,7 +2,7 @@ use crate::{client::Client, Server};
use common::{
comp::{
self,
group::{self, Group, GroupManager},
group::{self, ChangeNotification, Group, GroupManager},
invite::{InviteKind, PendingInvites},
ChatType, GroupManip,
},
@ -81,6 +81,37 @@ pub fn can_invite(
true
}
pub fn update_map_markers<'a>(
map_markers: &ReadStorage<'a, comp::MapMarker>,
uids: &ReadStorage<'a, Uid>,
client: &Client,
change: &ChangeNotification<Entity>,
) {
use comp::group::ChangeNotification::*;
let send_update = |entity| {
if let (Some(map_marker), Some(uid)) = (map_markers.get(entity), uids.get(entity)) {
client.send_fallible(ServerGeneral::MapMarker(
comp::MapMarkerUpdate::GroupMember(
*uid,
comp::MapMarkerChange::Update(map_marker.0),
),
));
}
};
match change {
&Added(entity, _) => {
send_update(entity);
},
NewGroup { leader: _, members } => {
for (entity, _) in members {
send_update(*entity);
}
},
// Removed and NoGroup can be inferred by the client, NewLeader does not affect map markers
Removed(_) | NoGroup | NewLeader(_) => {},
}
}
// TODO: turn chat messages into enums
pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupManip) {
match manip {
@ -89,6 +120,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
let clients = state.ecs().read_storage::<Client>();
let uids = state.ecs().read_storage::<Uid>();
let mut group_manager = state.ecs().write_resource::<GroupManager>();
let map_markers = state.ecs().read_storage::<comp::MapMarker>();
group_manager.leave_group(
entity,
&mut state.ecs().write_storage(),
@ -100,10 +132,13 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
.get(entity)
.and_then(|c| {
group_change
.try_map(|e| uids.get(e).copied())
.try_map_ref(|e| uids.get(*e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| c.send(ServerGeneral::GroupUpdate(g)));
.map(|(g, c)| {
update_map_markers(&map_markers, &uids, c, &group_change);
c.send_fallible(ServerGeneral::GroupUpdate(g));
});
},
);
},
@ -151,6 +186,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
let mut groups = state.ecs().write_storage::<group::Group>();
let mut group_manager = state.ecs().write_resource::<GroupManager>();
let map_markers = state.ecs().read_storage::<comp::MapMarker>();
// Make sure kicker is the group leader
match groups
.get(target)
@ -169,10 +205,13 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
.get(entity)
.and_then(|c| {
group_change
.try_map(|e| uids.get(e).copied())
.try_map_ref(|e| uids.get(*e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| c.send(ServerGeneral::GroupUpdate(g)));
.map(|(g, c)| {
update_map_markers(&map_markers, &uids, c, &group_change);
c.send_fallible(ServerGeneral::GroupUpdate(g));
});
},
);
@ -230,6 +269,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
};
let groups = state.ecs().read_storage::<group::Group>();
let mut group_manager = state.ecs().write_resource::<GroupManager>();
let map_markers = state.ecs().read_storage::<comp::MapMarker>();
// Make sure assigner is the group leader
match groups
.get(target)
@ -248,10 +288,13 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
.get(entity)
.and_then(|c| {
group_change
.try_map(|e| uids.get(e).copied())
.try_map_ref(|e| uids.get(*e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| c.send(ServerGeneral::GroupUpdate(g)));
.map(|(g, c)| {
update_map_markers(&map_markers, &uids, c, &group_change);
c.send_fallible(ServerGeneral::GroupUpdate(g));
});
},
);
// Tell them they are the leader

View File

@ -1,4 +1,4 @@
use super::group_manip;
use super::group_manip::{self, update_map_markers};
use crate::{client::Client, Server};
use common::{
comp::{
@ -202,6 +202,7 @@ pub fn handle_invite_accept(server: &mut Server, entity: specs::Entity) {
match kind {
InviteKind::Group => {
let map_markers = state.ecs().read_storage::<comp::MapMarker>();
let mut group_manager = state.ecs().write_resource::<GroupManager>();
group_manager.add_group_member(
inviter,
@ -215,10 +216,13 @@ pub fn handle_invite_accept(server: &mut Server, entity: specs::Entity) {
.get(entity)
.and_then(|c| {
group_change
.try_map(|e| uids.get(e).copied())
.try_map_ref(|e| uids.get(*e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| c.send(ServerGeneral::GroupUpdate(g)));
.map(|(g, c)| {
update_map_markers(&map_markers, &uids, c, &group_change);
c.send_fallible(ServerGeneral::GroupUpdate(g));
});
},
);
},

View File

@ -12,7 +12,7 @@ use entity_manipulation::{
handle_aura, handle_bonk, handle_buff, handle_change_ability, handle_combo_change,
handle_delete, handle_destroy, handle_energy_change, handle_entity_attacked_hook,
handle_explosion, handle_health_change, handle_knockback, handle_land_on_ground, handle_parry,
handle_poise, handle_respawn, handle_teleport_to,
handle_poise, handle_respawn, handle_teleport_to, handle_update_map_marker,
};
use group_manip::handle_group;
use information::handle_site_info;
@ -26,6 +26,8 @@ use player::{handle_client_disconnect, handle_exit_ingame};
use specs::{Builder, Entity as EcsEntity, WorldExt};
use trade::{cancel_trade_for, handle_process_trade_action};
pub use group_manip::update_map_markers;
mod entity_creation;
mod entity_manipulation;
mod group_manip;
@ -132,8 +134,16 @@ impl Server {
character_id,
} => handle_initialize_character(self, entity, character_id),
ServerEvent::UpdateCharacterData { entity, components } => {
let (body, stats, skill_set, inventory, waypoint, pets, active_abilities) =
components;
let (
body,
stats,
skill_set,
inventory,
waypoint,
pets,
active_abilities,
map_marker,
) = components;
let components = PersistedComponents {
body,
stats,
@ -142,6 +152,7 @@ impl Server {
waypoint,
pets,
active_abilities,
map_marker,
};
handle_loaded_character_data(self, entity, components);
},
@ -253,6 +264,9 @@ impl Server {
auxiliary_key,
new_ability,
} => handle_change_ability(self, entity, slot, auxiliary_key, new_ability),
ServerEvent::UpdateMapMarker { entity, update } => {
handle_update_map_marker(self, entity, update)
},
}
}

View File

@ -226,6 +226,11 @@ fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity {
.read_storage::<common::comp::Waypoint>()
.get(entity)
.cloned();
let map_marker = state
.ecs()
.read_storage::<common::comp::MapMarker>()
.get(entity)
.cloned();
// Store last battle mode change
if let Some(change) = player_info.last_battlemode_change {
let mode = player_info.battle_mode;
@ -261,6 +266,7 @@ fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity {
pets,
waypoint,
active_abilities.clone(),
map_marker,
),
);
},

View File

@ -887,6 +887,7 @@ impl Server {
waypoint,
pets,
active_abilities,
map_marker,
} = character_data;
let character_data = (
body,
@ -896,6 +897,7 @@ impl Server {
waypoint,
pets,
active_abilities,
map_marker,
);
ServerEvent::UpdateCharacterData {
entity: query_result.entity,

View File

@ -148,19 +148,22 @@ pub fn load_character_data(
},
)?;
let char_waypoint = character_data.waypoint.as_ref().and_then(|x| {
match convert_waypoint_from_database_json(x) {
Ok(w) => Some(w),
Err(e) => {
let (char_waypoint, char_map_marker) = match character_data
.waypoint
.as_ref()
.map(|x| convert_waypoint_from_database_json(x))
{
Some(Ok(w)) => w,
Some(Err(e)) => {
warn!(
"Error reading waypoint from database for character ID
{}, error: {}",
char_id, e
);
None
(None, None)
},
}
});
None => (None, None),
};
let mut stmt = connection.prepare_cached(
"
@ -261,6 +264,7 @@ pub fn load_character_data(
waypoint: char_waypoint,
pets,
active_abilities: convert_active_abilities_from_database(&ability_set_data),
map_marker: char_map_marker,
})
}
@ -354,6 +358,7 @@ pub fn create_character(
waypoint,
pets: _,
active_abilities,
map_marker,
} = persisted_components;
// Fetch new entity IDs for character, inventory and loadout
@ -438,7 +443,7 @@ pub fn create_character(
&character_id as &dyn ToSql,
&uuid,
&character_alias,
&convert_waypoint_to_database_json(waypoint),
&convert_waypoint_to_database_json(waypoint, map_marker),
])?;
drop(stmt);
@ -935,6 +940,8 @@ fn delete_pets(
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn update(
char_id: CharacterId,
char_skill_set: comp::SkillSet,
@ -942,6 +949,7 @@ pub fn update(
pets: Vec<PetPersistenceData>,
char_waypoint: Option<comp::Waypoint>,
active_abilities: comp::ability::ActiveAbilities,
map_marker: Option<comp::MapMarker>,
transaction: &mut Transaction,
) -> Result<(), PersistenceError> {
// Run pet persistence
@ -1066,7 +1074,7 @@ pub fn update(
])?;
}
let db_waypoint = convert_waypoint_to_database_json(char_waypoint);
let db_waypoint = convert_waypoint_to_database_json(char_waypoint, map_marker);
let mut stmt = transaction.prepare_cached(
"
@ -1103,7 +1111,7 @@ pub fn update(
if ability_sets_count != 1 {
return Err(PersistenceError::OtherError(format!(
"Error updating ability_set table for char_id {}",
char_id
char_id,
)));
}

View File

@ -217,28 +217,30 @@ pub fn convert_body_to_database_json(
})
}
pub fn convert_waypoint_to_database_json(waypoint: Option<Waypoint>) -> Option<String> {
match waypoint {
Some(w) => {
pub fn convert_waypoint_to_database_json(
waypoint: Option<Waypoint>,
map_marker: Option<MapMarker>,
) -> Option<String> {
if waypoint.is_some() || map_marker.is_some() {
let charpos = CharacterPosition {
waypoint: w.get_pos(),
waypoint: waypoint.map(|w| w.get_pos()),
map_marker: map_marker.map(|m| m.0),
};
Some(
serde_json::to_string(&charpos)
.map_err(|err| {
PersistenceError::ConversionError(format!(
"Error encoding waypoint: {:?}",
err
))
PersistenceError::ConversionError(format!("Error encoding waypoint: {:?}", err))
})
.ok()?,
)
},
None => None,
} else {
None
}
}
pub fn convert_waypoint_from_database_json(position: &str) -> Result<Waypoint, PersistenceError> {
pub fn convert_waypoint_from_database_json(
position: &str,
) -> Result<(Option<Waypoint>, Option<MapMarker>), PersistenceError> {
let character_position =
serde_json::de::from_str::<CharacterPosition>(position).map_err(|err| {
PersistenceError::ConversionError(format!(
@ -246,7 +248,12 @@ pub fn convert_waypoint_from_database_json(position: &str) -> Result<Waypoint, P
position, err
))
})?;
Ok(Waypoint::new(character_position.waypoint, Time(0.0)))
Ok((
character_position
.waypoint
.map(|pos| Waypoint::new(pos, Time(0.0))),
character_position.map_marker.map(MapMarker),
))
}
/// Properly-recursive items (currently modular weapons) occupy the same

View File

@ -25,6 +25,7 @@ pub type CharacterUpdateData = (
Vec<PetPersistenceData>,
Option<comp::Waypoint>,
comp::ability::ActiveAbilities,
Option<comp::MapMarker>,
);
pub type PetPersistenceData = (comp::Pet, comp::Body, comp::Stats);
@ -332,12 +333,21 @@ impl CharacterUpdater {
Vec<PetPersistenceData>,
Option<&'a comp::Waypoint>,
&'a comp::ability::ActiveAbilities,
Option<&'a comp::MapMarker>,
),
>,
) {
let updates = updates
.map(
|(character_id, skill_set, inventory, pets, waypoint, active_abilities)| {
|(
character_id,
skill_set,
inventory,
pets,
waypoint,
active_abilities,
map_marker,
)| {
(
character_id,
(
@ -346,6 +356,7 @@ impl CharacterUpdater {
pets,
waypoint.cloned(),
active_abilities.clone(),
map_marker.cloned(),
),
)
},
@ -388,7 +399,7 @@ fn execute_batch_update(
transaction.set_drop_behavior(DropBehavior::Rollback);
trace!("Transaction started for character batch update");
updates.into_iter().try_for_each(
|(character_id, (stats, inventory, pets, waypoint, active_abilities))| {
|(character_id, (stats, inventory, pets, waypoint, active_abilities, map_marker))| {
super::character::update(
character_id,
stats,
@ -396,6 +407,7 @@ fn execute_batch_update(
pets,
waypoint,
active_abilities,
map_marker,
&mut transaction,
)
},

View File

@ -3,7 +3,7 @@ use common_base::dev_panic;
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
use std::string::ToString;
use vek::Vec3;
use vek::{Vec2, Vec3};
#[derive(Serialize, Deserialize)]
pub struct HumanoidBody {
@ -62,7 +62,8 @@ generic_body_from_impl!(comp::quadruped_small::Body);
#[derive(Serialize, Deserialize)]
pub struct CharacterPosition {
pub waypoint: Vec3<f32>,
pub waypoint: Option<Vec3<f32>>,
pub map_marker: Option<Vec2<i32>>,
}
pub fn skill_group_to_db_string(skill_group: comp::skillset::SkillGroupKind) -> String {

View File

@ -31,6 +31,7 @@ pub struct PersistedComponents {
pub waypoint: Option<comp::Waypoint>,
pub pets: Vec<PetPersistenceData>,
pub active_abilities: comp::ActiveAbilities,
pub map_marker: Option<comp::MapMarker>,
}
pub type EditableComponents = (comp::Body,);

View File

@ -1,6 +1,6 @@
use crate::client::Client;
use crate::{client::Client, events::update_map_markers};
use common::{
comp::{anchor::Anchor, group::GroupManager, Agent, Alignment, Pet},
comp::{self, anchor::Anchor, group::GroupManager, Agent, Alignment, Pet},
uid::Uid,
};
use common_net::msg::ServerGeneral;
@ -59,6 +59,7 @@ fn tame_pet_internal(ecs: &specs::World, pet_entity: Entity, owner: Entity, pet:
// Add to group system
let clients = ecs.read_storage::<Client>();
let mut group_manager = ecs.write_resource::<GroupManager>();
let map_markers = ecs.read_storage::<comp::MapMarker>();
group_manager.new_pet(
pet_entity,
owner,
@ -71,10 +72,15 @@ fn tame_pet_internal(ecs: &specs::World, pet_entity: Entity, owner: Entity, pet:
.get(entity)
.and_then(|c| {
group_change
.try_map(|e| uids.get(e).copied())
.try_map_ref(|e| uids.get(*e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| c.send_fallible(ServerGeneral::GroupUpdate(g)));
.map(|(g, c)| {
// Might be unneccessary, but maybe pets can somehow have map
// markers in the future
update_map_markers(&map_markers, &uids, c, &group_change);
c.send_fallible(ServerGeneral::GroupUpdate(g));
});
},
);
}

View File

@ -1,5 +1,6 @@
use crate::{
client::Client,
events::update_map_markers,
persistence::PersistedComponents,
pet::restore_pet,
presence::{Presence, RepositionOnChunkLoad},
@ -526,6 +527,7 @@ impl StateExt for State {
waypoint,
pets,
active_abilities,
map_marker,
} = components;
if let Some(player_uid) = self.read_component_copied::<Uid>(entity) {
@ -572,6 +574,10 @@ impl StateExt for State {
self.write_component_ignore_entity_dead(entity, comp::ForceUpdate);
}
if let Some(map_marker) = map_marker {
self.write_component_ignore_entity_dead(entity, map_marker);
}
let player_pos = self.ecs().read_storage::<comp::Pos>().get(entity).copied();
if let Some(player_pos) = player_pos {
trace!(
@ -885,6 +891,7 @@ impl StateExt for State {
let clients = self.ecs().read_storage::<Client>();
let uids = self.ecs().read_storage::<Uid>();
let mut group_manager = self.ecs().write_resource::<comp::group::GroupManager>();
let map_markers = self.ecs().read_storage::<comp::MapMarker>();
group_manager.entity_deleted(
entity,
&mut self.ecs().write_storage(),
@ -896,10 +903,13 @@ impl StateExt for State {
.get(entity)
.and_then(|c| {
group_change
.try_map(|e| uids.get(e).copied())
.try_map_ref(|e| uids.get(*e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| c.send(ServerGeneral::GroupUpdate(g)));
.map(|(g, c)| {
update_map_markers(&map_markers, &uids, c, &group_change);
c.send_fallible(ServerGeneral::GroupUpdate(g));
});
},
);
}

View File

@ -285,6 +285,9 @@ impl Sys {
.get_mut(entity)
.map(|mut skill_set| skill_set.persistence_load_error = None);
},
ClientGeneral::UpdateMapMarker(update) => {
server_emitter.emit(ServerEvent::UpdateMapMarker { entity, update });
},
ClientGeneral::RequestCharacterList
| ClientGeneral::CreateCharacter { .. }
| ClientGeneral::EditCharacter { .. }

View File

@ -2,7 +2,7 @@ use crate::{persistence::character_updater, presence::Presence, sys::SysSchedule
use common::{
comp::{
pet::{is_tameable, Pet},
ActiveAbilities, Alignment, Body, Inventory, SkillSet, Stats, Waypoint,
ActiveAbilities, Alignment, Body, Inventory, MapMarker, SkillSet, Stats, Waypoint,
},
uid::Uid,
};
@ -22,6 +22,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Inventory>,
ReadStorage<'a, Uid>,
ReadStorage<'a, Waypoint>,
ReadStorage<'a, MapMarker>,
ReadStorage<'a, Pet>,
ReadStorage<'a, Stats>,
ReadStorage<'a, ActiveAbilities>,
@ -43,6 +44,7 @@ impl<'a> System<'a> for Sys {
player_inventories,
uids,
player_waypoints,
map_markers,
pets,
stats,
active_abilities,
@ -59,6 +61,7 @@ impl<'a> System<'a> for Sys {
&uids,
player_waypoints.maybe(),
&active_abilities,
map_markers.maybe(),
)
.join()
.filter_map(
@ -69,6 +72,7 @@ impl<'a> System<'a> for Sys {
player_uid,
waypoint,
active_abilities,
map_marker,
)| match presence.kind {
PresenceKind::Character(id) => {
let pets = (&alignments, &bodies, &stats, &pets)
@ -86,7 +90,15 @@ impl<'a> System<'a> for Sys {
})
.collect();
Some((id, skill_set, inventory, pets, waypoint, active_abilities))
Some((
id,
skill_set,
inventory,
pets,
waypoint,
active_abilities,
map_marker,
))
},
PresenceKind::Spectator => None,
},

View File

@ -368,6 +368,7 @@ image_ids! {
indicator_group_up: "voxygen.element.ui.map.buttons.group_indicator_arrow_up",
indicator_group_down: "voxygen.element.ui.map.buttons.group_indicator_arrow_down",
location_marker: "voxygen.element.ui.map.buttons.location_marker",
location_marker_group: "voxygen.element.ui.map.buttons.location_marker_group",
map_mode_overlay: "voxygen.element.ui.map.buttons.map_modes",
minimap_mode_overlay: "voxygen.element.ui.map.buttons.minimap_modes",

View File

@ -1,6 +1,6 @@
use super::{
img_ids::{Imgs, ImgsRot},
Show, QUALITY_COMMON, QUALITY_EPIC, QUALITY_HIGH, QUALITY_LOW, QUALITY_MODERATE, TEXT_BG,
MapMarkers, QUALITY_COMMON, QUALITY_EPIC, QUALITY_HIGH, QUALITY_LOW, QUALITY_MODERATE, TEXT_BG,
TEXT_BLUE_COLOR, TEXT_COLOR, TEXT_GRAY_COLOR, TEXT_VELORITE, UI_HIGHLIGHT_0, UI_MAIN,
};
use crate::{
@ -51,6 +51,7 @@ widget_ids! {
member_indicators[],
member_height_indicators[],
location_marker,
location_marker_group[],
map_settings_align,
show_towns_img,
show_towns_box,
@ -98,7 +99,6 @@ const SHOW_ECONOMY: bool = false; // turn this display off (for 0.9) until we ha
#[derive(WidgetCommon)]
pub struct Map<'a> {
show: &'a Show,
client: &'a Client,
world_map: &'a (Vec<img_ids::Rotations>, Vec2<u32>),
imgs: &'a Imgs,
@ -110,12 +110,11 @@ pub struct Map<'a> {
global_state: &'a GlobalState,
rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager,
location_marker: Option<Vec2<f32>>,
location_markers: &'a MapMarkers,
map_drag: Vec2<f64>,
}
impl<'a> Map<'a> {
pub fn new(
show: &'a Show,
client: &'a Client,
imgs: &'a Imgs,
rot_imgs: &'a ImgsRot,
@ -125,11 +124,10 @@ impl<'a> Map<'a> {
localized_strings: &'a Localization,
global_state: &'a GlobalState,
tooltip_manager: &'a mut TooltipManager,
location_marker: Option<Vec2<f32>>,
location_markers: &'a MapMarkers,
map_drag: Vec2<f64>,
) -> Self {
Self {
show,
imgs,
rot_imgs,
world_map,
@ -140,7 +138,7 @@ impl<'a> Map<'a> {
localized_strings,
global_state,
tooltip_manager,
location_marker,
location_markers,
map_drag,
}
}
@ -154,9 +152,9 @@ pub enum Event {
SettingsChange(InterfaceChange),
Close,
RequestSiteInfo(SiteId),
SetLocationMarker(Vec2<f32>),
SetLocationMarker(Vec2<i32>),
MapDrag(Vec2<f64>),
ToggleMarker,
RemoveMarker,
}
fn get_site_economy(site_rich: &SiteInfoRich) -> String {
@ -370,16 +368,15 @@ impl<'a> Widget for Map<'a> {
.next()
{
match wpos {
Some(ref wpos) => events.push(Event::SetLocationMarker(*wpos)),
Some(ref wpos) => events.push(Event::SetLocationMarker(wpos.as_())),
None => {
let tmp: Vec2<f64> = Vec2::<f64>::from(click.xy) / zoom - drag;
let wpos = tmp
.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as f32 * sz as f32)
+ player_pos;
events.push(Event::SetLocationMarker(wpos));
events.push(Event::SetLocationMarker(wpos.as_()));
},
}
events.push(Event::ToggleMarker);
}
// Handle zooming with the mousewheel
@ -1220,11 +1217,62 @@ impl<'a> Widget for Map<'a> {
}
}
// Location marker
if self.show.map_marker {
let factor = 1.4;
let side_length = 20.0 * factor;
if let Some((lm, (rpos, fade))) = self.location_marker.and_then(|lm| {
// Groups location markers
if state.ids.location_marker_group.len() < self.location_markers.group.len() {
state.update(|s| {
s.ids.location_marker_group.resize(
self.location_markers.group.len(),
&mut ui.widget_id_generator(),
)
})
};
for (i, (&uid, &rpos)) in self.location_markers.group.iter().enumerate() {
let lm = rpos.as_();
if let Some((rpos, fade)) =
wpos_to_rpos_fade(lm, Vec2::from(side_length / 2.0), side_length / 2.0)
{
let name = self
.client
.player_list()
.get(&uid)
.map(|info| info.player_alias.as_str())
.or_else(|| {
uid_allocator
.retrieve_entity_internal(uid.into())
.and_then(|entity| stats.get(entity))
.map(|stats| stats.name.as_str())
})
.unwrap_or("");
Button::image(self.imgs.location_marker_group)
.x_y_position_relative_to(
state.ids.map_layers[0],
position::Relative::Scalar(rpos.x as f64),
position::Relative::Scalar(rpos.y as f64 + 10.0 * factor as f64),
)
.w_h(side_length as f64, side_length as f64)
.image_color(Color::Rgba(1.0, 1.0, 1.0, fade))
.floating(true)
.with_tooltip(
self.tooltip_manager,
i18n.get("hud.map.marked_location"),
&format!(
"X: {}, Y: {}\n\n{}",
lm.x as i32,
lm.y as i32,
i18n.get("hud.map.placed_by").replace("{name}", name),
),
&site_tooltip,
TEXT_VELORITE,
)
.set(state.ids.location_marker_group[i], ui);
}
}
// Location marker
if let Some((lm, (rpos, fade))) = self.location_markers.owned.and_then(|lm| {
let lm = lm.as_();
Some(lm).zip(wpos_to_rpos_fade(
lm,
Vec2::from(side_length / 2.0),
@ -1255,7 +1303,7 @@ impl<'a> Widget for Map<'a> {
.set(state.ids.location_marker, ui)
.was_clicked()
{
events.push(Event::ToggleMarker);
events.push(Event::RemoveMarker);
}
handle_widget_mouse_events(
@ -1266,7 +1314,6 @@ impl<'a> Widget for Map<'a> {
state.ids.map_layers[0],
);
}
}
// Cursor pos relative to playerpos and widget size
// Cursor stops moving on an axis as soon as it's position exceeds the maximum

View File

@ -1,7 +1,7 @@
use super::{
img_ids::{Imgs, ImgsRot},
Show, QUALITY_COMMON, QUALITY_DEBUG, QUALITY_EPIC, QUALITY_HIGH, QUALITY_LOW, QUALITY_MODERATE,
TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
MapMarkers, QUALITY_COMMON, QUALITY_DEBUG, QUALITY_EPIC, QUALITY_HIGH, QUALITY_LOW,
QUALITY_MODERATE, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
};
use crate::{
hud::{Graphic, Ui},
@ -375,7 +375,6 @@ widget_ids! {
#[derive(WidgetCommon)]
pub struct MiniMap<'a> {
show: &'a Show,
client: &'a Client,
imgs: &'a Imgs,
rot_imgs: &'a ImgsRot,
@ -385,13 +384,12 @@ pub struct MiniMap<'a> {
common: widget::CommonBuilder,
ori: Vec3<f32>,
global_state: &'a GlobalState,
location_marker: Option<Vec2<f32>>,
location_markers: &'a MapMarkers,
voxel_minimap: &'a VoxelMinimap,
}
impl<'a> MiniMap<'a> {
pub fn new(
show: &'a Show,
client: &'a Client,
imgs: &'a Imgs,
rot_imgs: &'a ImgsRot,
@ -399,11 +397,10 @@ impl<'a> MiniMap<'a> {
fonts: &'a Fonts,
ori: Vec3<f32>,
global_state: &'a GlobalState,
location_marker: Option<Vec2<f32>>,
location_markers: &'a MapMarkers,
voxel_minimap: &'a VoxelMinimap,
) -> Self {
Self {
show,
client,
imgs,
rot_imgs,
@ -412,7 +409,7 @@ impl<'a> MiniMap<'a> {
common: widget::CommonBuilder::default(),
ori,
global_state,
location_marker,
location_markers,
voxel_minimap,
}
}
@ -792,8 +789,11 @@ impl<'a> Widget for MiniMap<'a> {
}
// Location marker
if self.show.map_marker {
if let Some(rpos) = self.location_marker.and_then(|lm| wpos_to_rpos(lm, true)) {
if let Some(rpos) = self
.location_markers
.owned
.and_then(|lm| wpos_to_rpos(lm.as_(), true))
{
let factor = 1.2;
Button::image(self.imgs.location_marker)
@ -807,7 +807,6 @@ impl<'a> Widget for MiniMap<'a> {
.floating(true)
.set(state.ids.location_marker, ui);
}
}
// Indicator
let ind_scale = 0.4;
let ind_rotation = if is_facing_north {

View File

@ -83,7 +83,7 @@ use common::{
inventory::{slot::InvSlotId, trade_pricing::TradePricing},
item::{tool::ToolKind, ItemDesc, MaterialStatManifest, Quality},
skillset::{skills::Skill, SkillGroupKind},
BuffData, BuffKind, Item,
BuffData, BuffKind, Item, MapMarkerChange,
},
consts::MAX_PICKUP_RANGE,
link::Is,
@ -546,6 +546,7 @@ pub enum Event {
SettingsChange(SettingsChange),
AcknowledgePersistenceLoadError,
MapMarkerEvent(MapMarkerChange),
}
// TODO: Are these the possible layouts we want?
@ -638,6 +639,12 @@ impl PressBehavior {
}
}
#[derive(Default, Clone)]
pub struct MapMarkers {
owned: Option<Vec2<i32>>,
group: HashMap<Uid, Vec2<i32>>,
}
pub struct Show {
ui: bool,
intro: bool,
@ -667,8 +674,7 @@ pub struct Show {
auto_walk: bool,
camera_clamp: bool,
prompt_dialog: Option<PromptDialogSettings>,
location_marker: Option<Vec2<f32>>,
map_marker: bool,
location_markers: MapMarkers,
salvage: bool,
}
impl Show {
@ -889,6 +895,26 @@ impl Show {
global_state.window.center_cursor();
}
}
pub fn update_map_markers(&mut self, event: comp::MapMarkerUpdate) {
match event {
comp::MapMarkerUpdate::Owned(event) => match event {
MapMarkerChange::Update(waypoint) => self.location_markers.owned = Some(waypoint),
MapMarkerChange::Remove => self.location_markers.owned = None,
},
comp::MapMarkerUpdate::GroupMember(user, event) => match event {
MapMarkerChange::Update(waypoint) => {
self.location_markers.group.insert(user, waypoint);
},
MapMarkerChange::Remove => {
self.location_markers.group.remove(&user);
},
},
comp::MapMarkerUpdate::ClearGroup => {
self.location_markers.group.clear();
},
}
}
}
pub struct PromptDialogSettings {
@ -1060,8 +1086,7 @@ impl Hud {
auto_walk: false,
camera_clamp: false,
prompt_dialog: None,
location_marker: None,
map_marker: false,
location_markers: MapMarkers::default(),
salvage: false,
},
to_focus: None,
@ -2756,7 +2781,6 @@ impl Hud {
// MiniMap
for event in MiniMap::new(
&self.show,
client,
&self.imgs,
&self.rot_imgs,
@ -2764,7 +2788,7 @@ impl Hud {
&self.fonts,
camera.get_orientation(),
global_state,
self.show.location_marker,
&self.show.location_markers,
&self.voxel_minimap,
)
.set(self.ids.minimap, ui_widgets)
@ -3272,7 +3296,6 @@ impl Hud {
// Map
if self.show.map {
for event in Map::new(
&self.show,
client,
&self.imgs,
&self.rot_imgs,
@ -3282,7 +3305,7 @@ impl Hud {
i18n,
global_state,
tooltip_manager,
self.show.location_marker,
&self.show.location_markers,
self.map_drag,
)
.set(self.ids.map, ui_widgets)
@ -3300,13 +3323,15 @@ impl Hud {
events.push(Event::RequestSiteInfo(id));
},
map::Event::SetLocationMarker(pos) => {
self.show.location_marker = Some(pos);
events.push(Event::MapMarkerEvent(MapMarkerChange::Update(pos)));
self.show.location_markers.owned = Some(pos);
},
map::Event::MapDrag(new_drag) => {
self.map_drag = new_drag;
},
map::Event::ToggleMarker => {
self.show.map_marker = !self.show.map_marker;
map::Event::RemoveMarker => {
self.show.location_markers.owned = None;
events.push(Event::MapMarkerEvent(MapMarkerChange::Remove));
},
}
}

View File

@ -308,6 +308,9 @@ impl SessionState {
client::Event::CharacterError(error) => {
global_state.client_error = Some(error);
},
client::Event::MapMarker(event) => {
self.hud.show.update_map_markers(event);
},
}
}
@ -1446,6 +1449,9 @@ impl PlayState for SessionState {
.borrow_mut()
.acknolwedge_persistence_load_error();
},
HudEvent::MapMarkerEvent(event) => {
self.client.borrow_mut().map_marker_event(event);
},
}
}