Added overhead hints, smoother mount movement

This commit is contained in:
Joshua Barretto
2022-01-16 13:57:38 +00:00
parent f33c12e976
commit 504e2a38d5
11 changed files with 169 additions and 34 deletions

View File

@ -46,6 +46,13 @@ Whenever you feel ready, try to get even better equipment from the many challeng
"hud.free_look_indicator": "Free look active. Press {key} to disable.", "hud.free_look_indicator": "Free look active. Press {key} to disable.",
"hud.camera_clamp_indicator": "Camera vertical clamp active. Press {key} to disable.", "hud.camera_clamp_indicator": "Camera vertical clamp active. Press {key} to disable.",
"hud.auto_walk_indicator": "Auto walk/swim active", "hud.auto_walk_indicator": "Auto walk/swim active",
"hud.collect": "Collect",
"hud.pick_up": "Pick up",
"hud.open": "Open",
"hud.use": "Use",
"hud.talk": "Talk",
"hud.trade": "Trade",
"hud.mount": "Mount",
}, },

View File

@ -44,6 +44,7 @@ sum_type! {
Ori(comp::Ori), Ori(comp::Ori),
Shockwave(comp::Shockwave), Shockwave(comp::Shockwave),
BeamSegment(comp::BeamSegment), BeamSegment(comp::BeamSegment),
Alignment(comp::Alignment),
} }
} }
// Automatically derive From<T> for EcsCompPhantom // Automatically derive From<T> for EcsCompPhantom
@ -80,6 +81,7 @@ sum_type! {
Ori(PhantomData<comp::Ori>), Ori(PhantomData<comp::Ori>),
Shockwave(PhantomData<comp::Shockwave>), Shockwave(PhantomData<comp::Shockwave>),
BeamSegment(PhantomData<comp::BeamSegment>), BeamSegment(PhantomData<comp::BeamSegment>),
Alignment(PhantomData<comp::Alignment>),
} }
} }
impl sync::CompPacket for EcsCompPacket { impl sync::CompPacket for EcsCompPacket {
@ -127,6 +129,7 @@ impl sync::CompPacket for EcsCompPacket {
}, },
EcsCompPacket::Shockwave(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Shockwave(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::BeamSegment(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::BeamSegment(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Alignment(comp) => sync::handle_insert(comp, entity, world),
} }
} }
@ -172,6 +175,7 @@ impl sync::CompPacket for EcsCompPacket {
}, },
EcsCompPacket::Shockwave(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Shockwave(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::BeamSegment(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::BeamSegment(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Alignment(comp) => sync::handle_modify(comp, entity, world),
} }
} }
@ -211,7 +215,8 @@ impl sync::CompPacket for EcsCompPacket {
EcsCompPhantom::Vel(_) => sync::handle_interp_remove::<comp::Vel>(entity, world), EcsCompPhantom::Vel(_) => sync::handle_interp_remove::<comp::Vel>(entity, world),
EcsCompPhantom::Ori(_) => sync::handle_interp_remove::<comp::Ori>(entity, world), EcsCompPhantom::Ori(_) => sync::handle_interp_remove::<comp::Ori>(entity, world),
EcsCompPhantom::Shockwave(_) => sync::handle_remove::<comp::Shockwave>(entity, world), EcsCompPhantom::Shockwave(_) => sync::handle_remove::<comp::Shockwave>(entity, world),
EcsCompPhantom::BeamSegment(_) => sync::handle_remove::<comp::Ori>(entity, world), EcsCompPhantom::BeamSegment(_) => sync::handle_remove::<comp::BeamSegment>(entity, world),
EcsCompPhantom::Alignment(_) => sync::handle_remove::<comp::Alignment>(entity, world),
} }
} }
} }

View File

@ -8,8 +8,8 @@ use crate::{
trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult}, trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult},
uid::Uid, uid::Uid,
}; };
use serde::Deserialize; use serde::{Serialize, Deserialize};
use specs::{Component, Entity as EcsEntity}; use specs::{Component, Entity as EcsEntity, DerefFlaggedStorage};
use specs_idvs::IdvStorage; use specs_idvs::IdvStorage;
use std::{collections::VecDeque, fmt}; use std::{collections::VecDeque, fmt};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@ -21,7 +21,7 @@ use super::dialogue::Subject;
pub const DEFAULT_INTERACTION_TIME: f32 = 3.0; pub const DEFAULT_INTERACTION_TIME: f32 = 3.0;
pub const TRADE_INTERACTION_TIME: f32 = 300.0; pub const TRADE_INTERACTION_TIME: f32 = 300.0;
#[derive(Copy, Clone, Debug, PartialEq, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Alignment { pub enum Alignment {
/// Wild animals and gentle giants /// Wild animals and gentle giants
Wild, Wild,
@ -79,7 +79,7 @@ impl Alignment {
} }
impl Component for Alignment { impl Component for Alignment {
type Storage = IdvStorage<Self>; type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
} }
bitflags::bitflags! { bitflags::bitflags! {

View File

@ -10,7 +10,7 @@ use crate::{
util::Dir, util::Dir,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage}; use specs::Component;
use specs_idvs::IdvStorage; use specs_idvs::IdvStorage;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use vek::*; use vek::*;

View File

@ -1,6 +1,6 @@
// The limit on distance between the entity and a collectible (squared) // The limit on distance between the entity and a collectible (squared)
pub const MAX_PICKUP_RANGE: f32 = 5.0; pub const MAX_PICKUP_RANGE: f32 = 5.0;
pub const MAX_MOUNT_RANGE: f32 = 14.0; pub const MAX_MOUNT_RANGE: f32 = 5.0;
pub const MAX_TRADE_RANGE: f32 = 20.0; pub const MAX_TRADE_RANGE: f32 = 20.0;
pub const GRAVITY: f32 = 25.0; pub const GRAVITY: f32 = 25.0;

View File

@ -158,6 +158,7 @@ impl State {
ecs.register::<comp::Shockwave>(); ecs.register::<comp::Shockwave>();
ecs.register::<comp::ShockwaveHitEntities>(); ecs.register::<comp::ShockwaveHitEntities>();
ecs.register::<comp::BeamSegment>(); ecs.register::<comp::BeamSegment>();
ecs.register::<comp::Alignment>();
// Register components send from clients -> server // Register components send from clients -> server
ecs.register::<comp::Controller>(); ecs.register::<comp::Controller>();
@ -187,7 +188,6 @@ impl State {
ecs.register::<comp::Last<comp::Pos>>(); ecs.register::<comp::Last<comp::Pos>>();
ecs.register::<comp::Last<comp::Vel>>(); ecs.register::<comp::Last<comp::Vel>>();
ecs.register::<comp::Last<comp::Ori>>(); ecs.register::<comp::Last<comp::Ori>>();
ecs.register::<comp::Alignment>();
ecs.register::<comp::Agent>(); ecs.register::<comp::Agent>();
ecs.register::<comp::WaypointArea>(); ecs.register::<comp::WaypointArea>();
ecs.register::<comp::ForceUpdate>(); ecs.register::<comp::ForceUpdate>();

View File

@ -400,6 +400,7 @@ impl<'a> PhysicsData<'a> {
mass, mass,
collider, collider,
read.char_states.get(entity), read.char_states.get(entity),
read.is_ridings.get(entity),
)) ))
}) })
.for_each( .for_each(
@ -411,6 +412,7 @@ impl<'a> PhysicsData<'a> {
mass_other, mass_other,
collider_other, collider_other,
char_state_other_maybe, char_state_other_maybe,
other_is_riding_maybe,
)| { )| {
let collision_boundary = previous_cache.collision_boundary let collision_boundary = previous_cache.collision_boundary
+ previous_cache_other.collision_boundary; + previous_cache_other.collision_boundary;
@ -473,7 +475,7 @@ impl<'a> PhysicsData<'a> {
*mass, *mass,
*mass_other, *mass_other,
vel, vel,
is_riding.is_some(), is_riding.is_some() || other_is_riding_maybe.is_some(),
); );
} }
}, },

View File

@ -4,7 +4,7 @@ use common::{
item::{tool::AbilityMap, MaterialStatManifest}, item::{tool::AbilityMap, MaterialStatManifest},
ActiveAbilities, Auras, BeamSegment, Body, Buffs, CanBuild, CharacterState, Collider, ActiveAbilities, Auras, BeamSegment, Body, Buffs, CanBuild, CharacterState, Collider,
Combo, Density, Energy, Group, Health, Inventory, Item, LightEmitter, Mass, Combo, Density, Energy, Group, Health, Inventory, Item, LightEmitter, Mass,
Ori, Player, Poise, Pos, Scale, Shockwave, SkillSet, Stats, Sticky, Vel, Ori, Player, Poise, Pos, Scale, Shockwave, SkillSet, Stats, Sticky, Vel, Alignment,
}, },
uid::Uid, uid::Uid,
mounting::{Mount, Rider}, mounting::{Mount, Rider},
@ -69,6 +69,8 @@ pub struct TrackedComps<'a> {
pub character_state: ReadStorage<'a, CharacterState>, pub character_state: ReadStorage<'a, CharacterState>,
pub shockwave: ReadStorage<'a, Shockwave>, pub shockwave: ReadStorage<'a, Shockwave>,
pub beam_segment: ReadStorage<'a, BeamSegment>, pub beam_segment: ReadStorage<'a, BeamSegment>,
pub alignment: ReadStorage<'a, Alignment>,
pub ability_map: ReadExpect<'a, AbilityMap>, pub ability_map: ReadExpect<'a, AbilityMap>,
pub msm: ReadExpect<'a, MaterialStatManifest>, pub msm: ReadExpect<'a, MaterialStatManifest>,
} }
@ -180,6 +182,10 @@ impl<'a> TrackedComps<'a> {
.get(entity) .get(entity)
.cloned() .cloned()
.map(|c| comps.push(c.into())); .map(|c| comps.push(c.into()));
self.alignment
.get(entity)
.cloned()
.map(|c| comps.push(c.into()));
// Add untracked comps // Add untracked comps
pos.map(|c| comps.push(c.into())); pos.map(|c| comps.push(c.into()));
vel.map(|c| comps.push(c.into())); vel.map(|c| comps.push(c.into()));
@ -217,6 +223,7 @@ pub struct ReadTrackers<'a> {
pub character_state: ReadExpect<'a, UpdateTracker<CharacterState>>, pub character_state: ReadExpect<'a, UpdateTracker<CharacterState>>,
pub shockwave: ReadExpect<'a, UpdateTracker<Shockwave>>, pub shockwave: ReadExpect<'a, UpdateTracker<Shockwave>>,
pub beam_segment: ReadExpect<'a, UpdateTracker<BeamSegment>>, pub beam_segment: ReadExpect<'a, UpdateTracker<BeamSegment>>,
pub alignment: ReadExpect<'a, UpdateTracker<Alignment>>,
} }
impl<'a> ReadTrackers<'a> { impl<'a> ReadTrackers<'a> {
pub fn create_sync_packages( pub fn create_sync_packages(
@ -268,7 +275,8 @@ impl<'a> ReadTrackers<'a> {
filter, filter,
) )
.with_component(&comps.uid, &*self.shockwave, &comps.shockwave, filter) .with_component(&comps.uid, &*self.shockwave, &comps.shockwave, filter)
.with_component(&comps.uid, &*self.beam_segment, &comps.beam_segment, filter); .with_component(&comps.uid, &*self.beam_segment, &comps.beam_segment, filter)
.with_component(&comps.uid, &*self.alignment, &comps.alignment, filter);
(entity_sync_package, comp_sync_package) (entity_sync_package, comp_sync_package)
} }
@ -303,6 +311,7 @@ pub struct WriteTrackers<'a> {
character_state: WriteExpect<'a, UpdateTracker<CharacterState>>, character_state: WriteExpect<'a, UpdateTracker<CharacterState>>,
shockwave: WriteExpect<'a, UpdateTracker<Shockwave>>, shockwave: WriteExpect<'a, UpdateTracker<Shockwave>>,
beam: WriteExpect<'a, UpdateTracker<BeamSegment>>, beam: WriteExpect<'a, UpdateTracker<BeamSegment>>,
alignment: WriteExpect<'a, UpdateTracker<Alignment>>,
} }
fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) { fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
@ -338,6 +347,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
.record_changes(&comps.character_state); .record_changes(&comps.character_state);
trackers.shockwave.record_changes(&comps.shockwave); trackers.shockwave.record_changes(&comps.shockwave);
trackers.beam.record_changes(&comps.beam_segment); trackers.beam.record_changes(&comps.beam_segment);
trackers.alignment.record_changes(&comps.alignment);
// Debug how many updates are being sent // Debug how many updates are being sent
/* /*
macro_rules! log_counts { macro_rules! log_counts {
@ -378,6 +388,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
log_counts!(character_state, "Character States"); log_counts!(character_state, "Character States");
log_counts!(shockwave, "Shockwaves"); log_counts!(shockwave, "Shockwaves");
log_counts!(beam, "Beams"); log_counts!(beam, "Beams");
log_counts!(alignment, "Alignments");
*/ */
} }
@ -409,6 +420,7 @@ pub fn register_trackers(world: &mut World) {
world.register_tracker::<CharacterState>(); world.register_tracker::<CharacterState>();
world.register_tracker::<Shockwave>(); world.register_tracker::<Shockwave>();
world.register_tracker::<BeamSegment>(); world.register_tracker::<BeamSegment>();
world.register_tracker::<Alignment>();
} }
/// Deleted entities grouped by region /// Deleted entities grouped by region

View File

@ -90,6 +90,8 @@ use common::{
uid::Uid, uid::Uid,
util::{srgba_to_linear, Dir}, util::{srgba_to_linear, Dir},
vol::RectRasterableVol, vol::RectRasterableVol,
mounting::Mount,
link::Is,
}; };
use common_base::{prof_span, span}; use common_base::{prof_span, span};
use common_net::{ use common_net::{
@ -1140,6 +1142,8 @@ impl Hud {
let entities = ecs.entities(); let entities = ecs.entities();
let me = client.entity(); let me = client.entity();
let poises = ecs.read_storage::<comp::Poise>(); let poises = ecs.read_storage::<comp::Poise>();
let alignments = ecs.read_storage::<comp::Alignment>();
let is_mount = ecs.read_storage::<Is<Mount>>();
// Check if there was a persistence load error of the skillset, and if so // Check if there was a persistence load error of the skillset, and if so
// display a dialog prompt // display a dialog prompt
@ -1664,7 +1668,7 @@ impl Hud {
let mut sct_bg_walker = self.ids.sct_bgs.walk(); let mut sct_bg_walker = self.ids.sct_bgs.walk();
let pulse = self.pulse; let pulse = self.pulse;
let make_overitem = |item: &Item, pos, distance, properties, fonts| { let make_overitem = |item: &Item, pos, distance, properties, fonts, interaction_options| {
let text = if item.amount() > 1 { let text = if item.amount() > 1 {
format!("{} x {}", item.amount(), item.name()) format!("{} x {}", item.amount(), item.name())
} else { } else {
@ -1684,6 +1688,7 @@ impl Hud {
properties, properties,
pulse, pulse,
&global_state.window.key_layout, &global_state.window.key_layout,
interaction_options,
) )
.x_y(0.0, 100.0) .x_y(0.0, 100.0)
.position_ingame(pos) .position_ingame(pos)
@ -1714,6 +1719,7 @@ impl Hud {
pickup_failed_pulse: self.failed_entity_pickups.get(&entity).copied(), pickup_failed_pulse: self.failed_entity_pickups.get(&entity).copied(),
}, },
&self.fonts, &self.fonts,
vec![(GameInput::Interact, i18n.get("hud.pick_up").to_string())],
) )
.set(overitem_id, ui_widgets); .set(overitem_id, ui_widgets);
} }
@ -1733,9 +1739,9 @@ impl Hud {
let over_pos = pos + Vec3::unit_z() * 0.7; let over_pos = pos + Vec3::unit_z() * 0.7;
// This is only done once per frame, so it's not a performance issue // This is only done once per frame, so it's not a performance issue
if block.get_sprite().map_or(false, |s| s.is_container()) { if let Some(sprite) = block.get_sprite().filter(|s| s.is_container()) {
overitem::Overitem::new( overitem::Overitem::new(
"???".into(), format!("{:?}", sprite).into(),
overitem::TEXT_COLOR, overitem::TEXT_COLOR,
pos.distance_squared(player_pos), pos.distance_squared(player_pos),
&self.fonts, &self.fonts,
@ -1744,6 +1750,7 @@ impl Hud {
overitem_properties, overitem_properties,
self.pulse, self.pulse,
&global_state.window.key_layout, &global_state.window.key_layout,
vec![(GameInput::Interact, i18n.get("hud.open").to_string())],
) )
.x_y(0.0, 100.0) .x_y(0.0, 100.0)
.position_ingame(over_pos) .position_ingame(over_pos)
@ -1755,6 +1762,7 @@ impl Hud {
pos.distance_squared(player_pos), pos.distance_squared(player_pos),
overitem_properties, overitem_properties,
&self.fonts, &self.fonts,
vec![(GameInput::Interact, i18n.get("hud.collect").to_string())],
) )
.set(overitem_id, ui_widgets); .set(overitem_id, ui_widgets);
} else if let Some(desc) = block.get_sprite().and_then(|s| get_sprite_desc(s, i18n)) } else if let Some(desc) = block.get_sprite().and_then(|s| get_sprite_desc(s, i18n))
@ -1769,6 +1777,7 @@ impl Hud {
overitem_properties, overitem_properties,
self.pulse, self.pulse,
&global_state.window.key_layout, &global_state.window.key_layout,
vec![(GameInput::Interact, i18n.get("hud.use").to_string())],
) )
.x_y(0.0, 100.0) .x_y(0.0, 100.0)
.position_ingame(over_pos) .position_ingame(over_pos)
@ -1779,7 +1788,7 @@ impl Hud {
let speech_bubbles = &self.speech_bubbles; let speech_bubbles = &self.speech_bubbles;
// Render overhead name tags and health bars // Render overhead name tags and health bars
for (pos, info, bubble, _, _, health, _, height_offset, hpfl, in_group) in ( for (entity, pos, info, bubble, _, _, health, _, height_offset, hpfl, in_group, dist_sqr, alignment, is_mount) in (
&entities, &entities,
&pos, &pos,
interpolated.maybe(), interpolated.maybe(),
@ -1795,6 +1804,7 @@ impl Hud {
&inventories, &inventories,
players.maybe(), players.maybe(),
poises.maybe(), poises.maybe(),
(alignments.maybe(), is_mount.maybe()),
) )
.join() .join()
.filter(|t| { .filter(|t| {
@ -1818,6 +1828,7 @@ impl Hud {
inventory, inventory,
player, player,
poise, poise,
(alignment, is_mount),
)| { )| {
// Use interpolated position if available // Use interpolated position if available
let pos = interpolated.map_or(pos.0, |i| i.pos); let pos = interpolated.map_or(pos.0, |i| i.pos);
@ -1879,6 +1890,7 @@ impl Hud {
(info.is_some() || bubble.is_some()).then(|| { (info.is_some() || bubble.is_some()).then(|| {
( (
entity,
pos, pos,
info, info,
bubble, bubble,
@ -1889,6 +1901,9 @@ impl Hud {
body.height() * scale.map_or(1.0, |s| s.0) + 0.5, body.height() * scale.map_or(1.0, |s| s.0) + 0.5,
hpfl, hpfl,
in_group, in_group,
dist_sqr,
alignment,
is_mount,
) )
}) })
}, },
@ -1911,8 +1926,24 @@ impl Hud {
&global_state.settings.interface, &global_state.settings.interface,
self.pulse, self.pulse,
i18n, i18n,
&global_state.settings.controls,
&self.imgs, &self.imgs,
&self.fonts, &self.fonts,
&global_state.window.key_layout,
match alignment {
// TODO: Don't use `MAX_MOUNT_RANGE` here, add dedicated interaction range
Some(comp::Alignment::Npc) if dist_sqr < common::consts::MAX_MOUNT_RANGE.powi(2)
&& interactable.as_ref().and_then(|i| i.entity()) == Some(entity) =>
vec![
(GameInput::Interact, i18n.get("hud.talk").to_string()),
(GameInput::Trade, i18n.get("hud.trade").to_string()),
],
Some(comp::Alignment::Owned(owner)) if Some(*owner) == client.uid()
&& is_mount.is_none()
&& dist_sqr < common::consts::MAX_MOUNT_RANGE.powi(2) =>
vec![(GameInput::Mount, i18n.get("hud.mount").to_string())],
_ => Vec::new(),
},
) )
.x_y(0.0, 100.0) .x_y(0.0, 100.0)
.position_ingame(ingame_pos) .position_ingame(ingame_pos)

View File

@ -4,15 +4,17 @@ use super::{
TEXT_BG, TEXT_COLOR, TEXT_BG, TEXT_COLOR,
}; };
use crate::{ use crate::{
game_input::GameInput,
hud::{get_buff_image, get_buff_info}, hud::{get_buff_image, get_buff_info},
settings::InterfaceSettings, settings::{ControlSettings, InterfaceSettings},
ui::{fonts::Fonts, Ingameable}, ui::{fonts::Fonts, Ingameable},
}; };
use keyboard_keynames::key_layout::KeyLayout;
use common::comp::{Buffs, Energy, Health, SpeechBubble, SpeechBubbleType}; use common::comp::{Buffs, Energy, Health, SpeechBubble, SpeechBubbleType};
use conrod_core::{ use conrod_core::{
color, color,
position::Align, position::Align,
widget::{self, Image, Rectangle, Text}, widget::{self, Image, Rectangle, Text, RoundedRectangle},
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
}; };
use i18n::Localization; use i18n::Localization;
@ -53,6 +55,10 @@ widget_ids! {
buffs_align, buffs_align,
buffs[], buffs[],
buff_timers[], buff_timers[],
// Interaction hints
interaction_hints,
interaction_hints_bg,
} }
} }
@ -84,8 +90,11 @@ pub struct Overhead<'a> {
settings: &'a InterfaceSettings, settings: &'a InterfaceSettings,
pulse: f32, pulse: f32,
i18n: &'a Localization, i18n: &'a Localization,
controls: &'a ControlSettings,
imgs: &'a Imgs, imgs: &'a Imgs,
fonts: &'a Fonts, fonts: &'a Fonts,
key_layout: &'a Option<KeyLayout>,
interaction_options: Vec<(GameInput, String)>,
#[conrod(common_builder)] #[conrod(common_builder)]
common: widget::CommonBuilder, common: widget::CommonBuilder,
@ -99,8 +108,11 @@ impl<'a> Overhead<'a> {
settings: &'a InterfaceSettings, settings: &'a InterfaceSettings,
pulse: f32, pulse: f32,
i18n: &'a Localization, i18n: &'a Localization,
controls: &'a ControlSettings,
imgs: &'a Imgs, imgs: &'a Imgs,
fonts: &'a Fonts, fonts: &'a Fonts,
key_layout: &'a Option<KeyLayout>,
interaction_options: Vec<(GameInput, String)>,
) -> Self { ) -> Self {
Self { Self {
info, info,
@ -109,8 +121,11 @@ impl<'a> Overhead<'a> {
settings, settings,
pulse, pulse,
i18n, i18n,
controls,
imgs, imgs,
fonts, fonts,
key_layout,
interaction_options,
common: widget::CommonBuilder::default(), common: widget::CommonBuilder::default(),
} }
} }
@ -157,6 +172,7 @@ impl<'a> Ingameable for Overhead<'a> {
} else { } else {
0 0
} }
+ (!self.interaction_options.is_empty()) as usize * 2
}) + if self.bubble.is_some() { 13 } else { 0 } }) + if self.bubble.is_some() { 13 } else { 0 }
} }
} }
@ -448,6 +464,55 @@ impl<'a> Widget for Overhead<'a> {
}, },
_ => {}, _ => {},
} }
// Interaction hints
if !self.interaction_options.is_empty() {
let text = self.interaction_options
.iter()
.filter_map(|(input, action)| Some((self
.controls
.get_binding(*input)?, action)))
.map(|(input, action)| format!("{} {}", input.display_string(self.key_layout).as_str(), action))
.collect::<Vec<_>>()
.join("\n");
let scale = 30.0;
let btn_rect_size = scale * 0.8;
let btn_font_size = scale * 0.6;
let btn_rect_pos_y = 0.0;
let btn_text_pos_y = btn_rect_pos_y + ((btn_rect_size - btn_font_size) * 0.5);
let btn_radius = btn_rect_size / 5.0;
let btn_color = Color::Rgba(0.0, 0.0, 0.0, 0.8);
// RoundedRectangle::fill_with([btn_rect_size, btn_rect_size], btn_radius, btn_color)
// .x_y(0.0, btn_rect_pos_y)
// .depth(self.distance_from_player_sqr + 2.0)
// .parent(id)
// .set(state.ids.btn_bg, ui);
let hints_text = Text::new(&text)
.font_id(self.fonts.cyri.conrod_id)
.font_size(btn_font_size as u32)
.color(TEXT_COLOR)
.parent(id)
.down_from(self.info.map_or(state.ids.name, |info| if info.health.map_or(false, should_show_healthbar) {
if info.energy.is_some() { state.ids.mana_bar } else { state.ids.health_bar }
} else {
state.ids.name
}), 12.0)
.align_middle_x_of(state.ids.name)
.depth(1.0);
let [w, h] = hints_text.get_wh(ui).unwrap_or([btn_rect_size; 2]);
hints_text.set(state.ids.interaction_hints, ui);
RoundedRectangle::fill_with([w + btn_radius * 2.0, h + btn_radius * 2.0], btn_radius, btn_color)
.depth(2.0)
.middle_of(state.ids.interaction_hints)
.align_middle_y_of(state.ids.interaction_hints)
.parent(id)
.set(state.ids.interaction_hints_bg, ui);
}
} }
// Speech bubble // Speech bubble
if let Some(bubble) = self.bubble { if let Some(bubble) = self.bubble {

View File

@ -6,7 +6,7 @@ use crate::{
use conrod_core::{ use conrod_core::{
color, color,
widget::{self, RoundedRectangle, Text}, widget::{self, RoundedRectangle, Text},
widget_ids, Color, Colorable, Positionable, Widget, WidgetCommon, widget_ids, Color, Colorable, Positionable, Widget, WidgetCommon, Sizeable,
}; };
use i18n::Localization; use i18n::Localization;
use std::borrow::Cow; use std::borrow::Cow;
@ -21,7 +21,7 @@ widget_ids! {
// Name // Name
name_bg, name_bg,
name, name,
// Key // Interaction hints
btn_bg, btn_bg,
btn, btn,
// Inventory full // Inventory full
@ -45,6 +45,7 @@ pub struct Overitem<'a> {
properties: OveritemProperties, properties: OveritemProperties,
pulse: f32, pulse: f32,
key_layout: &'a Option<KeyLayout>, key_layout: &'a Option<KeyLayout>,
interaction_options: Vec<(GameInput, String)>,
} }
impl<'a> Overitem<'a> { impl<'a> Overitem<'a> {
@ -58,6 +59,7 @@ impl<'a> Overitem<'a> {
properties: OveritemProperties, properties: OveritemProperties,
pulse: f32, pulse: f32,
key_layout: &'a Option<KeyLayout>, key_layout: &'a Option<KeyLayout>,
interaction_options: Vec<(GameInput, String)>,
) -> Self { ) -> Self {
Self { Self {
name, name,
@ -70,6 +72,7 @@ impl<'a> Overitem<'a> {
properties, properties,
pulse, pulse,
key_layout, key_layout,
interaction_options,
} }
} }
} }
@ -120,7 +123,7 @@ impl<'a> Widget for Overitem<'a> {
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event { fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
let widget::UpdateArgs { id, state, ui, .. } = args; let widget::UpdateArgs { id, state, ui, .. } = args;
let btn_color = Color::Rgba(0.0, 0.0, 0.0, 0.4); let btn_color = Color::Rgba(0.0, 0.0, 0.0, 0.8);
// Example: // Example:
// MUSHROOM // MUSHROOM
@ -167,25 +170,35 @@ impl<'a> Widget for Overitem<'a> {
.parent(id) .parent(id)
.set(state.ids.name, ui); .set(state.ids.name, ui);
// Pickup Button // Interaction hints
if let Some(key_button) = self if !self.interaction_options.is_empty() {
.controls let text = self.interaction_options
.get_binding(GameInput::Interact) .iter()
.filter(|_| self.properties.active) .filter_map(|(input, action)| Some((self
{ .controls
RoundedRectangle::fill_with([btn_rect_size, btn_rect_size], btn_radius, btn_color) .get_binding(*input)
.x_y(0.0, btn_rect_pos_y) .filter(|_| self.properties.active)?, action)))
.depth(self.distance_from_player_sqr + 1.0) .map(|(input, action)| format!("{} {}", input.display_string(self.key_layout).as_str(), action))
.parent(id) .collect::<Vec<_>>()
.set(state.ids.btn_bg, ui); .join("\n");
Text::new(key_button.display_string(self.key_layout).as_str())
let hints_text = Text::new(&text)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.font_size(btn_font_size as u32) .font_size(btn_font_size as u32)
.color(TEXT_COLOR) .color(TEXT_COLOR)
.x_y(0.0, btn_text_pos_y) .x_y(0.0, btn_text_pos_y)
.depth(self.distance_from_player_sqr + 1.0)
.parent(id);
let [w, h] = hints_text.get_wh(ui).unwrap_or([btn_rect_size; 2]);
hints_text.set(state.ids.btn, ui);
RoundedRectangle::fill_with([w + btn_radius * 2.0, h + btn_radius * 2.0], btn_radius, btn_color)
.x_y(0.0, btn_rect_pos_y)
.depth(self.distance_from_player_sqr + 2.0) .depth(self.distance_from_player_sqr + 2.0)
.parent(id) .parent(id)
.set(state.ids.btn, ui); .set(state.ids.btn_bg, ui);
} }
if let Some(time) = self.properties.pickup_failed_pulse { if let Some(time) = self.properties.pickup_failed_pulse {
//should never exceed 1.0, but just in case //should never exceed 1.0, but just in case