Added indicator text to terrain sprites

This commit is contained in:
Joshua Barretto 2021-03-30 01:04:23 +01:00
parent 88f99986af
commit bed863c50c
6 changed files with 121 additions and 36 deletions

View File

@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Dungeons now have multiple kinds of stairs.
- Trades now display item prices in tooltips.
- Admin designated build areas
- Indicator text to collectable terrain sprites
### Changed

View File

@ -597,6 +597,9 @@ impl Item {
SpriteKind::MediumGrass => "common.items.grasses.medium",
SpriteKind::ShortGrass => "common.items.grasses.short",
SpriteKind::Coconut => "common.items.food.coconut",
// Containers
// IMPORTANT: Add any new container to `SpriteKind::is_container`
SpriteKind::Chest => {
chosen = Lottery::<String>::load_expect(match rng.gen_range(0..7) {
0 => "common.loot_tables.loot_table_weapon_uncommon",
@ -637,6 +640,7 @@ impl Item {
.read();
chosen.choose()
},
SpriteKind::Beehive => "common.items.crafting_ing.honey",
SpriteKind::Stones => "common.items.crafting_ing.stones",
SpriteKind::Twigs => "common.items.crafting_ing.twigs",

View File

@ -240,6 +240,14 @@ impl SpriteKind {
}
}
/// Is the sprite a container that will emit a mystery item?
pub fn is_container(&self) -> bool {
matches!(
self,
SpriteKind::Chest | SpriteKind::ChestBurried | SpriteKind::Mud | SpriteKind::Crate,
)
}
pub fn mine_tool(&self) -> Option<ToolKind> {
match self {
SpriteKind::Velorite

View File

@ -53,6 +53,7 @@ use crate::{
i18n::{LanguageMetadata, Localization},
render::{Consts, Globals, RenderMode, Renderer},
scene::camera::{self, Camera},
session::Interactable,
settings::Fps,
ui::{
fonts::Fonts, img_ids::Rotations, slot, slot::SlotKey, Graphic, Ingameable, ScaleMode, Ui,
@ -67,7 +68,7 @@ use common::{
self,
item::{tool::ToolKind, ItemDesc, MaterialStatManifest, Quality},
skills::{Skill, SkillGroupKind},
BuffKind,
BuffKind, Item,
},
outcome::Outcome,
terrain::TerrainChunk,
@ -901,6 +902,7 @@ impl Hud {
dt: Duration,
info: HudInfo,
camera: &Camera,
interactable: Option<Interactable>,
) -> Vec<Event> {
span!(_guard, "update_layout", "Hud::update_layout");
let mut events = std::mem::replace(&mut self.events, Vec::new());
@ -1330,19 +1332,7 @@ impl Hud {
let mut sct_walker = self.ids.scts.walk();
let mut sct_bg_walker = self.ids.sct_bgs.walk();
// Render overitem: name, etc.
for (pos, item, distance) in (&entities, &pos, &items)
.join()
.map(|(_, pos, item)| (pos, item, pos.0.distance_squared(player_pos)))
.filter(|(_, _, distance)| distance < &common::consts::MAX_PICKUP_RANGE.powi(2))
{
let overitem_id = overitem_walker.next(
&mut self.ids.overitems,
&mut ui_widgets.widget_id_generator(),
);
let ingame_pos = pos.0 + Vec3::unit_z() * 1.2;
let make_overitem = |item: &Item, pos, distance, active, fonts| {
let text = if item.amount() > 1 {
format!("{} x {}", item.amount(), item.name())
} else {
@ -1353,17 +1343,74 @@ impl Hud {
// Item
overitem::Overitem::new(
&text,
&quality,
&distance,
&self.fonts,
text.into(),
quality,
distance,
fonts,
&global_state.settings.controls,
// If we're currently set to interact with the item...
active,
)
.x_y(0.0, 100.0)
.position_ingame(ingame_pos)
.position_ingame(pos)
};
// Render overitem: name, etc.
for (entity, pos, item, distance) in (&entities, &pos, &items)
.join()
.map(|(entity, pos, item)| (entity, pos, item, pos.0.distance_squared(player_pos)))
.filter(|(_, _, _, distance)| distance < &common::consts::MAX_PICKUP_RANGE.powi(2))
{
let overitem_id = overitem_walker.next(
&mut self.ids.overitems,
&mut ui_widgets.widget_id_generator(),
);
make_overitem(
item,
pos.0 + Vec3::unit_z() * 1.2,
distance,
interactable.as_ref().and_then(|i| i.entity()) == Some(entity),
&self.fonts,
)
.set(overitem_id, ui_widgets);
}
// Render overtime for an interactable block
if let Some(Interactable::Block(block, pos)) = interactable {
let overitem_id = overitem_walker.next(
&mut self.ids.overitems,
&mut ui_widgets.widget_id_generator(),
);
let pos = pos.map(|e| e as f32 + 0.5);
let over_pos = pos + Vec3::unit_z() * 0.7;
// 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()) {
overitem::Overitem::new(
"???".into(),
overitem::TEXT_COLOR,
pos.distance_squared(player_pos),
&self.fonts,
&global_state.settings.controls,
true,
)
.x_y(0.0, 100.0)
.position_ingame(over_pos)
.set(overitem_id, ui_widgets);
} else if let Some(item) = Item::try_reclaim_from_block(block) {
make_overitem(
&item,
over_pos,
pos.distance_squared(player_pos),
true,
&self.fonts,
)
.set(overitem_id, ui_widgets);
}
}
let speech_bubbles = &self.speech_bubbles;
// Render overhead name tags and health bars
@ -3244,6 +3291,7 @@ impl Hud {
camera: &Camera,
dt: Duration,
info: HudInfo,
interactable: Option<Interactable>,
) -> Vec<Event> {
span!(_guard, "maintain", "Hud::maintain");
// conrod eats tabs. Un-eat a tabstop so tab completion can work
@ -3272,7 +3320,15 @@ impl Hud {
if let Some(maybe_id) = self.to_focus.take() {
self.ui.focus_widget(maybe_id);
}
let events = self.update_layout(client, global_state, debug_info, dt, info, camera);
let events = self.update_layout(
client,
global_state,
debug_info,
dt,
info,
camera,
interactable,
);
let camera::Dependents {
view_mat, proj_mat, ..
} = camera.dependents();

View File

@ -7,6 +7,9 @@ use conrod_core::{
widget::{self, RoundedRectangle, Text},
widget_ids, Color, Colorable, Positionable, Widget, WidgetCommon,
};
use std::borrow::Cow;
pub const TEXT_COLOR: Color = Color::Rgba(0.61, 0.61, 0.89, 1.0);
widget_ids! {
struct Ids {
@ -23,22 +26,24 @@ widget_ids! {
/// (Item, DistanceFromPlayer, Rarity, etc.)
#[derive(WidgetCommon)]
pub struct Overitem<'a> {
name: &'a str,
quality: &'a Color,
distance_from_player_sqr: &'a f32,
name: Cow<'a, str>,
quality: Color,
distance_from_player_sqr: f32,
fonts: &'a Fonts,
controls: &'a ControlSettings,
#[conrod(common_builder)]
common: widget::CommonBuilder,
active: bool,
}
impl<'a> Overitem<'a> {
pub fn new(
name: &'a str,
quality: &'a Color,
distance_from_player_sqr: &'a f32,
name: Cow<'a, str>,
quality: Color,
distance_from_player_sqr: f32,
fonts: &'a Fonts,
controls: &'a ControlSettings,
active: bool,
) -> Self {
Self {
name,
@ -47,6 +52,7 @@ impl<'a> Overitem<'a> {
fonts,
controls,
common: widget::CommonBuilder::default(),
active,
}
}
}
@ -84,7 +90,6 @@ impl<'a> Widget for Overitem<'a> {
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
let widget::UpdateArgs { id, state, ui, .. } = args;
let text_color = Color::Rgba(0.61, 0.61, 0.89, 1.0);
let btn_color = Color::Rgba(0.0, 0.0, 0.0, 0.4);
// Example:
@ -121,14 +126,18 @@ impl<'a> Widget for Overitem<'a> {
Text::new(&self.name)
.font_id(self.fonts.cyri.conrod_id)
.font_size(text_font_size as u32)
.color(*self.quality)
.color(self.quality)
.x_y(0.0, text_pos_y)
.depth(self.distance_from_player_sqr + 3.0)
.parent(id)
.set(state.ids.name, ui);
// Pickup Button
if let Some(key_button) = self.controls.get_binding(GameInput::Interact) {
if let Some(key_button) = self
.controls
.get_binding(GameInput::Interact)
.filter(|_| self.active)
{
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 + 1.0)
@ -137,7 +146,7 @@ impl<'a> Widget for Overitem<'a> {
Text::new(&format!("{}", key_button))
.font_id(self.fonts.cyri.conrod_id)
.font_size(btn_font_size as u32)
.color(text_color)
.color(TEXT_COLOR)
.x_y(0.0, btn_text_pos_y)
.depth(self.distance_from_player_sqr + 2.0)
.parent(id)

View File

@ -877,6 +877,7 @@ impl PlayState for SessionState {
target_entity: self.target_entity,
selected_entity: self.selected_entity,
},
self.interactable,
);
// Look for changes in the localization files
@ -1678,11 +1679,12 @@ fn under_cursor(
&ecs.entities(),
&positions,
scales.maybe(),
&ecs.read_storage::<comp::Body>()
&ecs.read_storage::<comp::Body>(),
ecs.read_storage::<comp::Item>().maybe(),
)
.join()
.filter(|(e, _, _, _)| *e != player_entity)
.map(|(e, p, s, b)| {
.filter(|(e, _, _, _, _)| *e != player_entity)
.filter_map(|(e, p, s, b, i)| {
const RADIUS_SCALE: f32 = 3.0;
// TODO: use collider radius instead of body radius?
let radius = s.map_or(1.0, |s| s.0) * b.radius() * RADIUS_SCALE;
@ -1690,7 +1692,12 @@ fn under_cursor(
let pos = Vec3::new(p.0.x, p.0.y, p.0.z + radius);
// Distance squared from camera to the entity
let dist_sqr = pos.distance_squared(cam_pos);
(e, pos, radius, dist_sqr)
// We only care about interacting with entities that contain items, or are not inanimate (to trade with)
if i.is_some() || !matches!(b, comp::Body::Object(_)) {
Some((e, pos, radius, dist_sqr))
} else {
None
}
})
// Roughly filter out entities farther than ray distance
.filter(|(_, _, r, d_sqr)| *d_sqr <= cast_dist.powi(2) + 2.0 * cast_dist * r + r.powi(2))
@ -1730,13 +1737,13 @@ fn under_cursor(
}
#[derive(Clone, Copy)]
enum Interactable {
pub enum Interactable {
Block(Block, Vec3<i32>),
Entity(specs::Entity),
}
impl Interactable {
fn entity(self) -> Option<specs::Entity> {
pub fn entity(self) -> Option<specs::Entity> {
match self {
Self::Entity(e) => Some(e),
Self::Block(_, _) => None,