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

@ -26,7 +26,7 @@
"hud.tutorial_elements": r#"Crafting"#, "hud.tutorial_elements": r#"Crafting"#,
"hud.temp_quest_headline": r#"Greetings Traveller!"#, "hud.temp_quest_headline": r#"Greetings Traveller!"#,
"hud.temp_quest_text": r#"To begin your journey you could start looking through this village and gather some supplies. "hud.temp_quest_text": r#"To begin your journey you could start looking through this village and gather some supplies.
You are welcome to take whatever you need on your journey! You are welcome to take whatever you need on your journey!
@ -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