Merge branch 'imbris/item-images' into 'master'

Item icons, chests and more

See merge request veloren/veloren!573
This commit is contained in:
Monty Marz 2019-10-09 19:28:05 +00:00
commit e40ffa4b02
57 changed files with 667 additions and 126 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -0,0 +1,56 @@
// Png(specifier),
// Vox(specier),
// VoxTrans(specifier, offset, (x_rot, y_rot, z_rot), zoom)
({ // Debug Items
Debug(Boost): VoxTrans(
"voxel.weapon.debug_wand",
(0.0, -7.0, 0.0), (90.0, 90.0, 0.0), 1.6,
),
// Weapons
Tool(Bow): VoxTrans(
"voxel.weapon.bow.simple-bow",
(0.0, 0.0, 0.0), (90.0, 90.0, 0.0), 1.0,
),
Tool(Dagger): VoxTrans(
"voxel.weapon.dagger.dagger_rusty",
(0.0, 0.0, -4.0), (-120.0, 90.0, 0.0), 1.1,
),
Tool(Sword): VoxTrans(
"voxel.weapon.sword.rusty_2h",
(0.0, 9.0, 0.0), (-90.0, 90.0, 0.0), 2.4,
),
Tool(Axe): VoxTrans(
"voxel.weapon.axe.rusty_2h",
(0.0, -8.0, 0.0), (-90.0, 90.0, 0.0), 2.0,
),
Tool(Hammer): VoxTrans(
"voxel.weapon.hammer.rusty_2h",
(0.0, -8.0, 0.0), (-90.0, 90.0, 0.0), 2.0,
),
// Consumables
Consumable(Apple): VoxTrans(
"element.icons.item_apple",
(0.0, 0.0, 0.0), (-90.0, 90.0, 0.0), 1.0,
),
Consumable(Potion): VoxTrans(
"voxel.object.potion_red",
(0.0, 0.0, 0.0), (90.0, 90.0, 0.0), 1.0,
),
Consumable(Mushroom): VoxTrans(
"voxel.sprite.mushrooms.mushroom-4",
(0.0, 0.0, 0.0), (-50.0, 70.0, 40.0), 1.0,
),
Consumable(Velorite): VoxTrans(
"voxel.sprite.velorite.velorite_ore",
(0.0, -1.0, 0.0), (-50.0, 40.0, 20.0), 0.8,
),
// Ingredients
Ingredient(Flower): VoxTrans(
"voxel.sprite.flowers.flower_red_2",
(0.0, -1.0, 0.0), (-50.0, 40.0, 20.0), 0.8,
),
Ingredient(Grass): VoxTrans(
"voxel.sprite.grass.grass_long_5",
(0.0, 0.0, 0.0), (-90.0, 50.0, 0.0), 1.0,
),
})

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -3,13 +3,14 @@ use crate::{
effect::Effect,
terrain::{Block, BlockKind},
};
use rand::prelude::*;
use specs::{Component, FlaggedStorage};
use specs_idvs::IDVStorage;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Tool {
Daggers,
SwordShield,
Dagger,
Shield,
Sword,
Axe,
Hammer,
@ -20,20 +21,43 @@ pub enum Tool {
impl Tool {
pub fn name(&self) -> &'static str {
match self {
Tool::Daggers => "daggers",
Tool::SwordShield => "sword and shield",
Tool::Sword => "sword",
Tool::Axe => "axe",
Tool::Hammer => "hammer",
Tool::Bow => "bow",
Tool::Staff => "staff",
Tool::Dagger => "Dagger",
Tool::Shield => "Shield",
Tool::Sword => "Sword",
Tool::Axe => "Axe",
Tool::Hammer => "Hammer",
Tool::Bow => "Bow",
Tool::Staff => "Staff",
}
}
pub fn description(&self) -> &'static str {
match self {
Tool::Dagger => "A basic kitchen knife.",
Tool::Shield => {
"This shield belonged to many adventurers.\n\
Now it's yours.\n\
NOT YET AVAILABLE."
}
Tool::Sword => "When closing one eye it's nearly like it wasn't rusty at all!",
Tool::Axe => {
"It has a name written on it.\n\
Sounds dwarvish."
}
Tool::Hammer => "Use with caution around nails.",
Tool::Bow => "An old but sturdy hunting bow.",
Tool::Staff => {
"A carved stick.\n\
The wood smells like magic.\n\
NOT YET AVAILABLE."
}
}
}
}
pub const ALL_TOOLS: [Tool; 7] = [
Tool::Daggers,
Tool::SwordShield,
Tool::Dagger,
Tool::Shield,
Tool::Sword,
Tool::Axe,
Tool::Hammer,
@ -43,7 +67,7 @@ pub const ALL_TOOLS: [Tool; 7] = [
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Armor {
// TODO: Don't make armor be a body part. Wearing enemy's head is funny but also creepy thing to do.
// TODO: Don't make armor be a body part. Wearing enemy's head is funny but also a creepy thing to do.
Helmet,
Shoulders,
Chestplate,
@ -60,19 +84,23 @@ pub enum Armor {
impl Armor {
pub fn name(&self) -> &'static str {
match self {
Armor::Helmet => "helmet",
Armor::Shoulders => "shoulder pads",
Armor::Chestplate => "chestplate",
Armor::Belt => "belt",
Armor::Gloves => "gloves",
Armor::Pants => "pants",
Armor::Boots => "boots",
Armor::Back => "back",
Armor::Tabard => "tabard",
Armor::Gem => "gem",
Armor::Necklace => "necklace",
Armor::Helmet => "Helmet",
Armor::Shoulders => "Shoulder Pads",
Armor::Chestplate => "Chestplate",
Armor::Belt => "Belt",
Armor::Gloves => "Gloves",
Armor::Pants => "Pants",
Armor::Boots => "Boots",
Armor::Back => "Back",
Armor::Tabard => "Tabard",
Armor::Gem => "Gem",
Armor::Necklace => "Necklace",
}
}
pub fn description(&self) -> &'static str {
self.name()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
@ -86,10 +114,19 @@ pub enum Consumable {
impl Consumable {
pub fn name(&self) -> &'static str {
match self {
Consumable::Apple => "apple",
Consumable::Potion => "potion",
Consumable::Mushroom => "mushroom",
Consumable::Velorite => "velorite",
Consumable::Apple => "Apple",
Consumable::Potion => "Potion",
Consumable::Mushroom => "Mushroom",
Consumable::Velorite => "Velorite",
}
}
pub fn description(&self) -> &'static str {
match self {
Consumable::Apple => "A tasty Apple.",
Consumable::Potion => "This Potion contains the essence of Life.",
Consumable::Mushroom => "A common Mushroom.",
Consumable::Velorite => "Has a subtle turqoise glow.",
}
}
}
@ -103,8 +140,15 @@ pub enum Ingredient {
impl Ingredient {
pub fn name(&self) -> &'static str {
match self {
Ingredient::Flower => "flower",
Ingredient::Grass => "grass",
Ingredient::Flower => "Flower",
Ingredient::Grass => "Grass",
}
}
pub fn description(&self) -> &'static str {
match self {
Ingredient::Flower => "It smells great.",
Ingredient::Grass => "Greener than an orc's snout.",
}
}
}
@ -112,6 +156,7 @@ impl Ingredient {
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Debug {
Boost,
Possess,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
@ -119,11 +164,17 @@ pub enum Item {
Tool {
kind: Tool,
power: u32,
stamina: i32,
strength: i32,
dexterity: i32,
intelligence: i32,
},
Armor {
kind: Armor,
defense: i32,
health_bonus: i32,
stamina: i32,
strength: i32,
dexterity: i32,
intelligence: i32,
},
Consumable {
kind: Consumable,
@ -141,23 +192,43 @@ impl Item {
Item::Tool { kind, .. } => kind.name(),
Item::Armor { kind, .. } => kind.name(),
Item::Consumable { kind, .. } => kind.name(),
Item::Ingredient { kind } => kind.name(),
Item::Ingredient { kind, .. } => kind.name(),
Item::Debug(_) => "Debugging item",
}
}
pub fn title(&self) -> String {
format!("{} ({})", self.name(), self.category())
}
pub fn info(&self) -> String {
match self {
Item::Tool { power, .. } => format!("{:+} attack", power),
Item::Armor { .. } => String::new(),
Item::Consumable { effect, .. } => format!("{}", effect.info()),
Item::Ingredient { .. } => String::new(),
Item::Debug(_) => format!("+99999 insanity"),
}
}
pub fn category(&self) -> &'static str {
match self {
Item::Tool { .. } => "tool",
Item::Armor { .. } => "armour",
Item::Consumable { .. } => "consumable",
Item::Ingredient { .. } => "ingredient",
Item::Debug(_) => "debug",
Item::Tool { .. } => "Tool",
Item::Armor { .. } => "Armor",
Item::Consumable { .. } => "Consumable",
Item::Ingredient { .. } => "Ingredient",
Item::Debug(_) => "Debug",
}
}
pub fn description(&self) -> String {
format!("{} ({})", self.name(), self.category())
match self {
Item::Tool { kind, .. } => format!("{}", kind.description()),
Item::Armor { kind, .. } => format!("{}", kind.description()),
Item::Consumable { kind, .. } => format!("{}", kind.description()),
Item::Ingredient { kind, .. } => format!("{}", kind.description()),
Item::Debug(_) => format!("Debugging item"),
}
}
pub fn try_reclaim_from_block(block: Block) -> Option<Self> {
@ -175,6 +246,19 @@ impl Item {
BlockKind::LongGrass => Some(Self::grass()),
BlockKind::MediumGrass => Some(Self::grass()),
BlockKind::ShortGrass => Some(Self::grass()),
BlockKind::Chest => Some(match rand::random::<usize>() % 3 {
0 => Self::apple(),
1 => Self::velorite(),
2 => Item::Tool {
kind: *(&ALL_TOOLS).choose(&mut rand::thread_rng()).unwrap(),
power: 8 + rand::random::<u32>() % (rand::random::<u32>() % 30),
stamina: 0,
strength: 0,
dexterity: 0,
intelligence: 0,
},
_ => unreachable!(),
}),
_ => None,
}
}
@ -184,7 +268,7 @@ impl Item {
pub fn apple() -> Self {
Item::Consumable {
kind: Consumable::Apple,
effect: Effect::Health(20, comp::HealthSource::Item),
effect: Effect::Health(50, comp::HealthSource::Item),
}
}
@ -197,8 +281,8 @@ impl Item {
pub fn velorite() -> Self {
Item::Consumable {
kind: Consumable::Mushroom,
effect: Effect::Xp(250),
kind: Consumable::Velorite,
effect: Effect::Xp(50),
}
}
@ -220,6 +304,10 @@ impl Default for Item {
Item::Tool {
kind: Tool::Hammer,
power: 0,
stamina: 0,
strength: 0,
dexterity: 0,
intelligence: 0,
}
}
}

View File

@ -1,4 +1,3 @@
//Re-Exports
pub mod item;
// Reexports
@ -77,22 +76,42 @@ impl Default for Inventory {
inventory.push(Item::Tool {
kind: Tool::Bow,
power: 10,
stamina: 0,
strength: 0,
dexterity: 0,
intelligence: 0,
});
inventory.push(Item::Tool {
kind: Tool::Daggers,
kind: Tool::Dagger,
power: 10,
stamina: 0,
strength: 0,
dexterity: 0,
intelligence: 0,
});
inventory.push(Item::Tool {
kind: Tool::Sword,
power: 10,
stamina: 0,
strength: 0,
dexterity: 0,
intelligence: 0,
});
inventory.push(Item::Tool {
kind: Tool::Axe,
power: 10,
stamina: 0,
strength: 0,
dexterity: 0,
intelligence: 0,
});
inventory.push(Item::Tool {
kind: Tool::Hammer,
power: 10,
stamina: 0,
strength: 0,
dexterity: 0,
intelligence: 0,
});
inventory

View File

@ -6,3 +6,12 @@ pub enum Effect {
Health(i32, comp::HealthSource),
Xp(i64),
}
impl Effect {
pub fn info(&self) -> String {
match self {
Effect::Health(n, _) => format!("{:+} health", n),
Effect::Xp(n) => format!("{:+} exp", n),
}
}
}

View File

@ -30,6 +30,7 @@ pub enum BlockKind {
Mushroom,
Liana,
Velorite,
Chest,
}
impl BlockKind {
@ -63,6 +64,7 @@ impl BlockKind {
BlockKind::Mushroom => true,
BlockKind::Liana => true,
BlockKind::Velorite => true,
BlockKind::Chest => true,
_ => false,
}
}
@ -98,6 +100,7 @@ impl BlockKind {
BlockKind::Mushroom => false,
BlockKind::Liana => false,
BlockKind::Velorite => false,
BlockKind::Chest => false,
_ => true,
}
}
@ -125,6 +128,7 @@ impl BlockKind {
BlockKind::Apple => true,
BlockKind::Mushroom => false,
BlockKind::Liana => false,
BlockKind::Chest => true,
_ => true,
}
}
@ -144,6 +148,7 @@ impl BlockKind {
BlockKind::Apple => true,
BlockKind::Mushroom => true,
BlockKind::Velorite => true,
BlockKind::Chest => true,
_ => false,
}
}

View File

@ -20,6 +20,7 @@ pub enum StructureBlock {
Water,
GreenSludge,
Fruit,
Chest,
Hollow,
Liana,
Normal(Rgb<u8>),
@ -116,6 +117,7 @@ impl Asset for Structure {
6 => StructureBlock::GreenSludge,
7 => StructureBlock::Fruit,
9 => StructureBlock::Liana,
10 => StructureBlock::Chest,
15 => StructureBlock::Hollow,
index => {
let color = palette

View File

@ -567,6 +567,10 @@ impl Server {
Some(comp::Item::Tool {
kind: comp::item::Tool::Sword,
power: 5,
stamina: 0,
strength: 0,
dexterity: 0,
intelligence: 0,
}),
);
let body = comp::Body::Humanoid(comp::humanoid::Body::random());
@ -588,6 +592,10 @@ impl Server {
Some(comp::Item::Tool {
kind: comp::item::Tool::Sword,
power: 10,
stamina: 0,
strength: 0,
dexterity: 0,
intelligence: 0,
}),
);
body = comp::Body::Humanoid(comp::humanoid::Body::random());
@ -1020,7 +1028,14 @@ impl Server {
client,
name,
body,
main.map(|t| comp::Item::Tool { kind: t, power: 10 }),
main.map(|t| comp::Item::Tool {
kind: t,
power: 10,
stamina: 0,
strength: 0,
dexterity: 0,
intelligence: 0,
}),
&server_settings,
);
}

View File

@ -153,7 +153,7 @@ impl Animation for BlockAnimation {
* Quaternion::rotation_z(0.0);
next.weapon.scale = Vec3::one();
}
Tool::SwordShield => {
Tool::Shield => {
next.l_hand.offset = Vec3::new(
-6.0 + wave_ultra_slow_cos * 1.0,
3.5 + wave_ultra_slow_cos * 0.5,
@ -203,7 +203,7 @@ impl Animation for BlockAnimation {
* Quaternion::rotation_z(0.0);
next.weapon.scale = Vec3::one();
}
Tool::Daggers => {
Tool::Dagger => {
next.l_hand.offset = Vec3::new(
-6.0 + wave_ultra_slow_cos * 1.0,
3.5 + wave_ultra_slow_cos * 0.5,

View File

@ -152,7 +152,7 @@ impl Animation for BlockIdleAnimation {
* Quaternion::rotation_z(0.0);
next.weapon.scale = Vec3::one();
}
Tool::SwordShield => {
Tool::Shield => {
next.l_hand.offset = Vec3::new(
-6.0 + wave_ultra_slow_cos * 1.0,
3.5 + wave_ultra_slow_cos * 0.5,
@ -202,7 +202,7 @@ impl Animation for BlockIdleAnimation {
* Quaternion::rotation_z(0.0);
next.weapon.scale = Vec3::one();
}
Tool::Daggers => {
Tool::Dagger => {
next.l_hand.offset = Vec3::new(
-6.0 + wave_ultra_slow_cos * 1.0,
3.5 + wave_ultra_slow_cos * 0.5,

View File

@ -160,7 +160,7 @@ impl Animation for CidleAnimation {
* Quaternion::rotation_z(0.0);
next.weapon.scale = Vec3::one();
}
Tool::SwordShield => {
Tool::Shield => {
next.l_hand.offset = Vec3::new(
-6.0 + wave_ultra_slow_cos * 1.0,
3.5 + wave_ultra_slow_cos * 0.5,
@ -214,7 +214,7 @@ impl Animation for CidleAnimation {
* Quaternion::rotation_z(0.85);
next.weapon.scale = Vec3::one();
}
Tool::Daggers => {
Tool::Dagger => {
next.l_hand.offset = Vec3::new(
-6.0 + wave_ultra_slow_cos * 1.0,
3.5 + wave_ultra_slow_cos * 0.5,

View File

@ -96,7 +96,7 @@ impl Animation for WieldAnimation {
* Quaternion::rotation_z(0.0);
next.weapon.scale = Vec3::one();
}
Tool::SwordShield => {
Tool::Shield => {
next.l_hand.offset = Vec3::new(-6.0, 3.5, 0.0);
next.l_hand.ori = Quaternion::rotation_x(-0.3);
next.l_hand.scale = Vec3::one() * 1.01;
@ -134,7 +134,7 @@ impl Animation for WieldAnimation {
* Quaternion::rotation_z(0.85);
next.weapon.scale = Vec3::one();
}
Tool::Daggers => {
Tool::Dagger => {
next.l_hand.offset = Vec3::new(-6.0, 3.5, 0.0);
next.l_hand.ori = Quaternion::rotation_x(-0.3);
next.l_hand.scale = Vec3::one() * 1.01;

View File

@ -150,20 +150,20 @@ impl<'a> From<&'a comp::humanoid::Body> for SkeletonAttr {
Tool::Sword => 0.0,
Tool::Axe => 3.0,
Tool::Hammer => 0.0,
Tool::SwordShield => 3.0,
Tool::Shield => 3.0,
Tool::Staff => 3.0,
Tool::Bow => 0.0,
Tool::Daggers => 0.0,
Tool::Dagger => 0.0,
},
weapon_y: match Tool::Hammer {
// TODO: Inventory
Tool::Sword => -1.25,
Tool::Axe => 0.0,
Tool::Hammer => -2.0,
Tool::SwordShield => 0.0,
Tool::Shield => 0.0,
Tool::Staff => 0.0,
Tool::Bow => -2.0,
Tool::Daggers => -2.0,
Tool::Dagger => -2.0,
},
}
}

View File

@ -1,11 +1,12 @@
use super::{
img_ids::{Imgs, ImgsRot},
item_imgs::{ItemImgs, ItemKind},
Event as HudEvent, Fonts, TEXT_COLOR,
};
use crate::ui::{ImageFrame, Tooltip, TooltipManager, Tooltipable};
use client::Client;
use conrod_core::{
color,
color, image,
position::Relative,
widget::{self, Button, Image, Rectangle /*, Scrollbar*/},
widget_ids, Color, Labelable, Positionable, Sizeable, Widget, WidgetCommon,
@ -33,6 +34,7 @@ widget_ids! {
pub struct Bag<'a> {
client: &'a Client,
imgs: &'a Imgs,
item_imgs: &'a ItemImgs,
fonts: &'a Fonts,
#[conrod(common_builder)]
common: widget::CommonBuilder,
@ -44,6 +46,7 @@ impl<'a> Bag<'a> {
pub fn new(
client: &'a Client,
imgs: &'a Imgs,
item_imgs: &'a ItemImgs,
fonts: &'a Fonts,
rot_imgs: &'a ImgsRot,
tooltip_manager: &'a mut TooltipManager,
@ -51,6 +54,7 @@ impl<'a> Bag<'a> {
Self {
client,
imgs,
item_imgs,
fonts,
common: widget::CommonBuilder::default(),
rot_imgs,
@ -61,6 +65,7 @@ impl<'a> Bag<'a> {
pub struct State {
ids: Ids,
img_id_cache: Vec<Option<(ItemKind, image::Id)>>,
selected_slot: Option<usize>,
}
@ -79,6 +84,7 @@ impl<'a> Widget for Bag<'a> {
fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
State {
ids: Ids::new(id_gen),
img_id_cache: Vec::new(),
selected_slot: None,
}
}
@ -111,27 +117,28 @@ impl<'a> Widget for Bag<'a> {
)
})
.title_font_size(15)
.desc_font_size(10)
.parent(ui.window)
.desc_font_size(12)
.title_text_color(TEXT_COLOR)
.desc_text_color(TEXT_COLOR);
// Bag parts
Image::new(self.imgs.bag_bot)
.w_h(61.0 * BAG_SCALE, 9.0 * BAG_SCALE)
.w_h(58.0 * BAG_SCALE, 9.0 * BAG_SCALE)
.bottom_right_with_margins_on(ui.window, 60.0, 5.0)
.set(state.ids.bag_bot, ui);
let mid_height = ((inventory.len() + 4) / 5) as f64 * 44.0;
Image::new(self.imgs.bag_mid)
.w_h(61.0 * BAG_SCALE, mid_height)
.w_h(58.0 * BAG_SCALE, mid_height)
.up_from(state.ids.bag_bot, 0.0)
.set(state.ids.bag_mid, ui);
Image::new(self.imgs.bag_top)
.w_h(61.0 * BAG_SCALE, 9.0 * BAG_SCALE)
.w_h(58.0 * BAG_SCALE, 9.0 * BAG_SCALE)
.up_from(state.ids.bag_mid, 0.0)
.set(state.ids.bag_top, ui);
// Alignment for Grid
Rectangle::fill_with([54.0 * BAG_SCALE, mid_height], color::TRANSPARENT)
Rectangle::fill_with([56.0 * BAG_SCALE, mid_height], color::TRANSPARENT)
.top_left_with_margins_on(state.ids.bag_mid, 0.0, 3.0 * BAG_SCALE)
.scroll_kids_vertically()
.set(state.ids.inv_alignment, ui);
@ -151,6 +158,12 @@ impl<'a> Widget for Bag<'a> {
.resize(inventory.len(), &mut ui.widget_id_generator());
});
}
// Expand img id cache to the number of slots
if state.img_id_cache.len() < inventory.len() {
state.update(|s| {
s.img_id_cache.resize(inventory.len(), None);
});
}
// Display inventory contents
for (i, item) in inventory.slots().iter().enumerate() {
@ -160,26 +173,30 @@ impl<'a> Widget for Bag<'a> {
let is_selected = Some(i) == state.selected_slot;
// Slot
let slot_widget = Button::image(self.imgs.inv_slot)
.top_left_with_margins_on(
state.ids.inv_alignment,
4.0 + y as f64 * (40.0 + 4.0),
4.0 + x as f64 * (40.0 + 4.0),
) // conrod uses a (y,x) format for placing...
// (the margin placement functions do this because that is the same order as "top left")
.w_h(40.0, 40.0)
.image_color(if is_selected {
color::WHITE
} else {
color::DARK_YELLOW
});
let slot_widget = Button::image(if !is_selected {
self.imgs.inv_slot
} else {
self.imgs.inv_slot_sel
})
.top_left_with_margins_on(
state.ids.inv_alignment,
0.0 + y as f64 * (40.0 + 2.0),
0.0 + x as f64 * (40.0 + 2.0),
) // conrod uses a (y,x) format for placing...
// (the margin placement functions do this because that is the same order as "top left")
.w_h(40.0, 40.0)
.image_color(if is_selected {
color::WHITE
} else {
color::DARK_YELLOW
});
let slot_widget_clicked = if let Some(item) = item {
slot_widget
.with_tooltip(
self.tooltip_manager,
&item.description(),
&item.category(),
&item.title(),
&format!("{}\n{}", item.info(), item.description()),
&item_tooltip,
)
.set(state.ids.inv_slots[i], ui)
@ -205,19 +222,29 @@ impl<'a> Widget for Bag<'a> {
state.update(|s| s.selected_slot = selected_slot);
}
// Item
if item.is_some() {
Button::image(self.imgs.flower) // TODO: Insert variable image depending on the item displayed in that slot
.w_h(28.0, 28.0) // TODO: Fix height and scale width correctly to that to avoid a stretched item image
.middle_of(state.ids.inv_slots[i]) // TODO: Items need to be assigned to a certain slot and then placed like in this example
.label("5x") // TODO: Quantity goes here...
.label_font_id(self.fonts.opensans)
.label_font_size(12)
.label_x(Relative::Scalar(10.0))
.label_y(Relative::Scalar(-10.0))
.label_color(TEXT_COLOR)
.parent(state.ids.inv_slots[i])
.graphics_for(state.ids.inv_slots[i])
.set(state.ids.items[i], ui);
if let Some(kind) = item.as_ref().map(|i| ItemKind::from(i)) {
Button::image(match &state.img_id_cache[i] {
Some((cached_kind, id)) if cached_kind == &kind => *id,
_ => {
let id = self
.item_imgs
.img_id(kind.clone())
.unwrap_or(self.imgs.not_found);
state.update(|s| s.img_id_cache[i] = Some((kind, id)));
id
}
})
.w_h(30.0, 30.0)
.middle_of(state.ids.inv_slots[i]) // TODO: Items need to be assigned to a certain slot and then placed like in this example
//.label("5x") // TODO: Quantity goes here...
//.label_font_id(self.fonts.opensans)
//.label_font_size(12)
//.label_x(Relative::Scalar(10.0))
//.label_y(Relative::Scalar(-10.0))
//.label_color(TEXT_COLOR)
//.parent(state.ids.inv_slots[i])
.graphics_for(state.ids.inv_slots[i])
.set(state.ids.items[i], ui);
}
}

View File

@ -19,6 +19,7 @@ image_ids! {
bag_contents: "voxygen.element.frames.bag",
inv_grid: "voxygen.element.frames.inv_grid",
inv_slot: "voxygen.element.buttons.inv_slot",
inv_slot_sel: "voxygen.element.buttons.inv_slot_sel",
grid_inv: "voxygen.element.buttons.grid_inv",
bag_top: "voxygen.element.bag.top",
bag_mid: "voxygen.element.bag.mid",
@ -223,6 +224,8 @@ image_ids! {
<ImageGraphic>
not_found:"voxygen.element.not_found",
help:"voxygen.element.help",
charwindow_gradient:"voxygen.element.misc_bg.charwindow",

View File

@ -0,0 +1,163 @@
use crate::ui::{Graphic, Transform, Ui};
use common::{
assets::{self, watch::ReloadIndicator, Asset},
comp::item::{Armor, Consumable, Debug, Ingredient, Item, Tool},
};
use conrod_core::image::Id;
use dot_vox::DotVoxData;
use hashbrown::HashMap;
use image::DynamicImage;
use log::{error, warn};
use serde_derive::{Deserialize, Serialize};
use std::{fs::File, io::BufReader, sync::Arc};
use vek::*;
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ItemKind {
Tool(Tool),
Armor(Armor),
Consumable(Consumable),
Ingredient(Ingredient),
Debug(Debug),
}
impl From<&Item> for ItemKind {
fn from(item: &Item) -> Self {
match item {
Item::Tool { kind, .. } => ItemKind::Tool(kind.clone()),
Item::Armor { kind, .. } => ItemKind::Armor(kind.clone()),
Item::Consumable { kind, .. } => ItemKind::Consumable(kind.clone()),
Item::Ingredient { kind } => ItemKind::Ingredient(kind.clone()),
Item::Debug(kind) => ItemKind::Debug(kind.clone()),
}
}
}
#[derive(Serialize, Deserialize)]
enum ImageSpec {
Png(String),
Vox(String),
// (specifier, offset, (axis, 2 * angle / pi), zoom)
VoxTrans(String, [f32; 3], [f32; 3], f32),
}
impl ImageSpec {
fn create_graphic(&self) -> Graphic {
match self {
ImageSpec::Png(specifier) => Graphic::Image(graceful_load_img(&specifier)),
ImageSpec::Vox(specifier) => Graphic::Voxel(
graceful_load_vox(&specifier),
Transform {
stretch: false,
..Default::default()
},
None,
),
ImageSpec::VoxTrans(specifier, offset, [rot_x, rot_y, rot_z], zoom) => Graphic::Voxel(
graceful_load_vox(&specifier),
Transform {
ori: Quaternion::rotation_x(rot_x * std::f32::consts::PI / 180.0)
.rotated_y(rot_y * std::f32::consts::PI / 180.0)
.rotated_z(rot_z * std::f32::consts::PI / 180.0),
offset: Vec3::from(*offset),
zoom: *zoom,
orth: true, // TODO: Is this what we want here? @Pfau
stretch: false,
},
None,
),
}
}
}
#[derive(Serialize, Deserialize)]
struct ItemImagesSpec(HashMap<ItemKind, ImageSpec>);
impl Asset for ItemImagesSpec {
const ENDINGS: &'static [&'static str] = &["ron"];
fn parse(buf_reader: BufReader<File>) -> Result<Self, assets::Error> {
Ok(ron::de::from_reader(buf_reader).expect("Error parsing item images spec"))
}
}
pub struct ItemImgs {
map: HashMap<ItemKind, Id>,
indicator: ReloadIndicator,
}
impl ItemImgs {
pub fn new(ui: &mut Ui) -> Self {
let mut indicator = ReloadIndicator::new();
Self {
map: assets::load_watched::<ItemImagesSpec>(
"voxygen.item_image_manifest",
&mut indicator,
)
.expect("Unable to load item image manifest")
.0
.iter()
.map(|(kind, spec)| (kind.clone(), ui.add_graphic(spec.create_graphic())))
.collect(),
indicator,
}
}
/// Checks if the manifest has been changed and reloads the images if so
/// Reuses img ids
pub fn reload_if_changed(&mut self, ui: &mut Ui) {
if self.indicator.reloaded() {
for (kind, spec) in assets::load::<ItemImagesSpec>("voxygen.item_image_manifest")
.expect("Unable to load item image manifest")
.0
.iter()
{
// Load new graphic
let graphic = spec.create_graphic();
// See if we already have an id we can use
match self.map.get(&kind) {
Some(id) => ui.replace_graphic(*id, graphic),
// Otherwise, generate new id and insert it into our Id -> ItemKind map
None => {
self.map.insert(kind.clone(), ui.add_graphic(graphic));
}
}
}
}
}
pub fn img_id(&self, item_kind: ItemKind) -> Option<Id> {
match self.map.get(&item_kind) {
Some(id) => Some(*id),
// There was no specification in the ron
None => {
warn!(
"{:?} has no specified image file (note: hot-reloading won't work here)",
item_kind
);
None
}
}
}
}
// Copied from figure/load.rs
// TODO: remove code dup?
fn graceful_load_vox(specifier: &str) -> Arc<DotVoxData> {
let full_specifier: String = ["voxygen.", specifier].concat();
match assets::load::<DotVoxData>(full_specifier.as_str()) {
Ok(dot_vox) => dot_vox,
Err(_) => {
error!(
"Could not load vox file for item images: {}",
full_specifier
);
assets::load_expect::<DotVoxData>("voxygen.voxel.not_found")
}
}
}
fn graceful_load_img(specifier: &str) -> Arc<DynamicImage> {
let full_specifier: String = ["voxygen.", specifier].concat();
match assets::load::<DynamicImage>(full_specifier.as_str()) {
Ok(img) => img,
Err(_) => {
error!(
"Could not load image file for item images: {}",
full_specifier
);
assets::load_expect::<DynamicImage>("voxygen.element.not_found")
}
}
}

View File

@ -4,6 +4,7 @@ mod character_window;
mod chat;
mod esc_menu;
mod img_ids;
mod item_imgs;
mod map;
mod minimap;
mod quest;
@ -22,6 +23,7 @@ use chat::Chat;
use chrono::NaiveTime;
use esc_menu::EscMenu;
use img_ids::Imgs;
use item_imgs::ItemImgs;
use map::Map;
use minimap::MiniMap;
use quest::Quest;
@ -370,6 +372,7 @@ pub struct Hud {
ui: Ui,
ids: Ids,
imgs: Imgs,
item_imgs: ItemImgs,
fonts: Fonts,
rot_imgs: ImgsRot,
new_messages: VecDeque<ClientEvent>,
@ -394,6 +397,8 @@ impl Hud {
let imgs = Imgs::load(&mut ui).expect("Failed to load images!");
// Load rotation images.
let rot_imgs = ImgsRot::load(&mut ui).expect("Failed to load rot images!");
// Load item images.
let item_imgs = ItemImgs::new(&mut ui);
// Load fonts.
let fonts = Fonts::load(&mut ui).expect("Failed to load fonts!");
@ -401,6 +406,7 @@ impl Hud {
ui,
imgs,
rot_imgs,
item_imgs,
fonts,
ids,
new_messages: VecDeque::new(),
@ -743,6 +749,7 @@ impl Hud {
match Bag::new(
client,
&self.imgs,
&self.item_imgs,
&self.fonts,
&self.rot_imgs,
tooltip_manager,
@ -1112,6 +1119,10 @@ impl Hud {
&mut global_state.window.renderer_mut(),
Some((view_mat, fov)),
);
// Check if item images need to be reloaded
self.item_imgs.reload_if_changed(&mut self.ui);
events
}

View File

@ -97,6 +97,10 @@ impl PlayState for CharSelectionState {
Some(comp::Item::Tool {
kind: kind,
power: 10,
stamina: 0,
strength: 0,
dexterity: 0,
intelligence: 0,
})
} else {
None

View File

@ -939,7 +939,7 @@ impl CharSelectionUi {
.w_h(70.0, 70.0)
.right_from(self.ids.sword, 2.0)
.set(self.ids.daggers, ui_widgets);
if Button::image(if let Some(Tool::Daggers) = self.character_tool {
if Button::image(if let Some(Tool::Dagger) = self.character_tool {
self.imgs.icon_border_pressed
} else {
self.imgs.icon_border

View File

@ -253,9 +253,8 @@ impl MainMenuUi {
Before you dive into the fun, please keep a few things in mind:\n\
\n\
- This is a very early alpha. Expect bugs, extremely unfinished gameplay, unpolished mechanics, and missing features. \n\
If you are a reviewer, please DO NOT review this version.\n\
\n\
- If you have constructive feedback or bug reports, you can contact us via Reddit, GitLab, or our community Discord server.\n\
-If you have constructive feedback or bug reports, you can contact us via Reddit, GitLab, or our community Discord server.\n\
\n\
- Veloren is licensed under the GPL 3 open-source licence. That means you're free to play, modify, and redistribute the game however you wish \n\
(provided derived work is also under GPL 3).

View File

@ -217,7 +217,7 @@ impl<V: RectRasterableVol<Vox = Block> + ReadVol + Debug> Meshable<TerrainPipeli
self,
pos,
offs,
&colors,
&colors, //&[[[colors[1][1][1]; 3]; 3]; 3],
|pos, norm, col, ao, light| {
TerrainVertex::new(pos, norm, col, light.min(ao))
},

View File

@ -146,7 +146,7 @@ impl Camera {
let t = self.tgt_dist + delta;
match self.mode {
CameraMode::ThirdPerson => {
if t < 2_f32 {
if t < 1_f32 {
self.set_mode(CameraMode::FirstPerson);
} else {
self.tgt_dist = t;

View File

@ -355,8 +355,8 @@ pub fn mesh_main(item: Option<&Item>) -> Mesh<FigurePipeline> {
Tool::Sword => ("weapon.sword.rusty_2h", Vec3::new(-1.5, -6.5, -4.0)),
Tool::Axe => ("weapon.axe.rusty_2h", Vec3::new(-1.5, -5.0, -4.0)),
Tool::Hammer => ("weapon.hammer.rusty_2h", Vec3::new(-2.5, -5.5, -4.0)),
Tool::Daggers => ("weapon.hammer.rusty_2h", Vec3::new(-2.5, -5.5, -4.0)),
Tool::SwordShield => ("weapon.axe.rusty_2h", Vec3::new(-2.5, -6.5, -2.0)),
Tool::Dagger => ("weapon.hammer.rusty_2h", Vec3::new(-2.5, -5.5, -4.0)),
Tool::Shield => ("weapon.axe.rusty_2h", Vec3::new(-2.5, -6.5, -2.0)),
Tool::Bow => ("weapon.bow.simple-bow", Vec3::new(-1.0, -6.0, -2.0)),
Tool::Staff => ("weapon.axe.rusty_2h", Vec3::new(-2.5, -6.5, -2.0)),
},

View File

@ -132,7 +132,8 @@ impl Scene {
}
// Zoom the camera when a zoom event occurs
Event::Zoom(delta) => {
self.camera.zoom_switch(delta * 0.3);
self.camera
.zoom_switch(delta * (0.05 + self.camera.get_distance() * 0.01));
true
}
// All other events are unhandled

View File

@ -135,6 +135,10 @@ fn sprite_config_for(kind: BlockKind) -> Option<SpriteConfig> {
variations: 1,
wind_sway: 0.0,
}),
BlockKind::Chest => Some(SpriteConfig {
variations: 4,
wind_sway: 0.0,
}),
_ => None,
}
}
@ -621,6 +625,34 @@ impl<V: RectRasterableVol> Terrain<V> {
Vec3::new(-5.0, -5.0, -5.0),
),
),
(
(BlockKind::Chest, 0),
make_model(
"voxygen.voxel.sprite.chests.chest",
Vec3::new(-7.0, -5.0, -0.0),
),
),
(
(BlockKind::Chest, 1),
make_model(
"voxygen.voxel.sprite.chests.chest_gold",
Vec3::new(-7.0, -5.0, -0.0),
),
),
(
(BlockKind::Chest, 2),
make_model(
"voxygen.voxel.sprite.chests.chest_dark",
Vec3::new(-7.0, -5.0, -0.0),
),
),
(
(BlockKind::Chest, 3),
make_model(
"voxygen.voxel.sprite.chests.chest_vines",
Vec3::new(-7.0, -5.0, -0.0),
),
),
]
.into_iter()
.collect(),

View File

@ -61,6 +61,9 @@ impl Cache {
pub fn add_graphic(&mut self, graphic: Graphic) -> GraphicId {
self.graphic_cache.add_graphic(graphic)
}
pub fn replace_graphic(&mut self, id: GraphicId, graphic: Graphic) {
self.graphic_cache.replace_graphic(id, graphic)
}
// Resizes and clears the GraphicCache
pub fn resize_graphic_cache(&mut self, renderer: &mut Renderer) -> Result<(), Error> {
let max_texture_size = renderer.max_texture_size();

View File

@ -8,10 +8,30 @@ use log::{error, warn};
use std::sync::Arc;
use vek::*;
#[derive(Clone)]
pub struct Transform {
pub ori: Quaternion<f32>,
pub offset: Vec3<f32>,
pub zoom: f32,
pub orth: bool,
pub stretch: bool,
}
impl Default for Transform {
fn default() -> Self {
Self {
ori: Quaternion::identity(),
offset: Vec3::zero(),
zoom: 1.0,
orth: true,
stretch: true,
}
}
}
#[derive(Clone)]
pub enum Graphic {
Image(Arc<DynamicImage>),
Voxel(Arc<DotVoxData>, Option<Quaternion<f32>>, Option<u8>),
Voxel(Arc<DotVoxData>, Transform, Option<u8>),
Blank,
}
@ -72,6 +92,26 @@ impl GraphicCache {
id
}
pub fn replace_graphic(&mut self, id: Id, graphic: Graphic) {
self.graphic_map.insert(id, graphic);
// Remove from caches
// Maybe make this more efficient if replace graphic is used more often
self.transfer_ready.retain(|(p, _)| p.0 != id);
let uses = self
.soft_cache
.keys()
.filter(|k| k.0 == id)
.copied()
.collect::<Vec<_>>();
for p in uses {
self.soft_cache.remove(&p);
if let Some(details) = self.cache_map.remove(&p) {
// Deallocate
self.atlas.deallocate(details.alloc_id);
}
}
}
pub fn get_graphic(&self, id: Id) -> Option<&Graphic> {
self.graphic_map.get(&id)
}
@ -129,9 +169,12 @@ impl GraphicCache {
image::FilterType::Nearest,
)
.to_rgba(),
Some(Graphic::Voxel(ref vox, ori, min_samples)) => {
renderer::draw_vox(&vox.as_ref().into(), dims, *ori, *min_samples)
}
Some(Graphic::Voxel(ref vox, trans, min_samples)) => renderer::draw_vox(
&vox.as_ref().into(),
dims,
trans.clone(),
*min_samples,
),
None => {
warn!("A graphic was requested via an id which is not in use");
return None;

View File

@ -1,3 +1,4 @@
use super::Transform;
use common::{
figure::Segment,
util::{linear_to_srgba, srgba_to_linear},
@ -61,7 +62,7 @@ impl<'a> Pipeline for Voxel {
pub fn draw_vox(
segment: &Segment,
output_size: Vec2<u16>,
ori: Option<Quaternion<f32>>,
transform: Transform,
min_samples: Option<u8>,
) -> RgbaImage {
let scale = min_samples.map_or(1.0, |s| s as f32).sqrt().ceil() as usize;
@ -71,17 +72,35 @@ pub fn draw_vox(
let (w, h, d) = segment.size().map(|e| e as f32).into_tuple();
let mvp = Mat4::<f32>::orthographic_rh_no(FrustumPlanes {
left: -1.0,
right: 1.0,
bottom: -1.0,
top: 1.0,
near: 0.0,
far: 1.0,
}) * Mat4::from(ori.unwrap_or(Quaternion::identity()))
* Mat4::rotation_x(-std::f32::consts::PI / 2.0) // TODO: remove
* Mat4::scaling_3d([2.0 / w, 2.0 / h, 2.0 / d])
let mvp = if transform.orth {
Mat4::<f32>::orthographic_rh_no(FrustumPlanes {
left: -1.0,
right: 1.0,
bottom: -1.0,
top: 1.0,
near: 0.0,
far: 1.0,
})
} else {
Mat4::<f32>::perspective_fov_rh_no(
1.1, // fov
dims[0] as f32, // width
dims[1] as f32, // height
0.0,
1.0,
)
} * Mat4::scaling_3d(
// TODO replace with camera-like parameters?
if transform.stretch {
Vec3::new(2.0 / w, 2.0 / d, 2.0 / h) // Only works with flipped models :(
} else {
let s = w.max(h).max(d);
Vec3::new(2.0 / s, 2.0 / s, 2.0 / s)
} * transform.zoom,
) * Mat4::translation_3d(transform.offset)
* Mat4::from(transform.ori)
* Mat4::translation_3d([-w / 2.0, -h / 2.0, -d / 2.0]);
Voxel { mvp }.draw::<rasterizer::Triangles<_>, _>(
&generate_mesh(segment, Vec3::from(0.0)),
&mut color,

View File

@ -1,7 +1,8 @@
use super::Graphic;
use super::{Graphic, Transform};
use common::assets::{load, Error};
use dot_vox::DotVoxData;
use image::DynamicImage;
use vek::*;
pub enum BlankGraphic {}
pub enum ImageGraphic {}
@ -31,7 +32,14 @@ pub enum VoxelMs9Graphic {}
impl<'a> GraphicCreator<'a> for VoxelGraphic {
type Specifier = &'a str;
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
Ok(Graphic::Voxel(load::<DotVoxData>(specifier)?, None, None))
Ok(Graphic::Voxel(
load::<DotVoxData>(specifier)?,
Transform {
ori: Quaternion::rotation_x(-std::f32::consts::PI / 2.0),
..Default::default()
},
None,
))
}
}
impl<'a> GraphicCreator<'a> for VoxelMsGraphic {
@ -39,7 +47,10 @@ impl<'a> GraphicCreator<'a> for VoxelMsGraphic {
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
Ok(Graphic::Voxel(
load::<DotVoxData>(specifier.0)?,
None,
Transform {
ori: Quaternion::rotation_x(-std::f32::consts::PI / 2.0),
..Default::default()
},
Some(specifier.1),
))
}
@ -49,7 +60,10 @@ impl<'a> GraphicCreator<'a> for VoxelMs4Graphic {
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
Ok(Graphic::Voxel(
load::<DotVoxData>(specifier)?,
None,
Transform {
ori: Quaternion::rotation_x(-std::f32::consts::PI / 2.0),
..Default::default()
},
Some(4),
))
}
@ -59,7 +73,10 @@ impl<'a> GraphicCreator<'a> for VoxelMs9Graphic {
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
Ok(Graphic::Voxel(
load::<DotVoxData>(specifier)?,
None,
Transform {
ori: Quaternion::rotation_x(-std::f32::consts::PI / 2.0),
..Default::default()
},
Some(9),
))
}

View File

@ -9,7 +9,7 @@ pub mod img_ids;
mod font_ids;
pub use event::Event;
pub use graphic::Graphic;
pub use graphic::{Graphic, Transform};
pub use scale::{Scale, ScaleMode};
pub use widgets::{
image_frame::ImageFrame,
@ -41,7 +41,7 @@ use conrod_core::{
Rect, UiBuilder, UiCell,
};
use graphic::Rotation;
use log::warn;
use log::{error, warn};
use std::{
fs::File,
io::{BufReader, Read},
@ -175,6 +175,17 @@ impl Ui {
}
}
pub fn replace_graphic(&mut self, id: image::Id, graphic: Graphic) {
let graphic_id = if let Some((graphic_id, _)) = self.image_map.get(&id) {
*graphic_id
} else {
error!("Failed to replace graphic the provided id is not in use");
return;
};
self.cache.replace_graphic(graphic_id, graphic);
self.image_map.replace(id, (graphic_id, Rotation::None));
}
pub fn new_font(&mut self, font: Arc<Font>) -> font::Id {
self.ui.fonts.insert(font.as_ref().0.clone())
}

View File

@ -176,10 +176,10 @@ impl<'a> BlockGen<'a> {
let wposf = wpos.map(|e| e as f64);
let (block, height) = if !only_structures {
let (_definitely_underground, height, water_height) =
let (_definitely_underground, height, on_cliff, water_height) =
if (wposf.z as f32) < alt - 64.0 * chaos {
// Shortcut warping
(true, alt, CONFIG.sea_level /*water_level*/)
(true, alt, false, CONFIG.sea_level /*water_level*/)
} else {
// Apply warping
let warp = world
@ -190,9 +190,11 @@ impl<'a> BlockGen<'a> {
.mul((chaos - 0.1).max(0.0).powf(2.0))
.mul(48.0);
let height = if (wposf.z as f32) < alt + warp - 10.0 {
let surface_height = alt + warp;
let (height, on_cliff) = if (wposf.z as f32) < alt + warp - 10.0 {
// Shortcut cliffs
alt + warp
(surface_height, false)
} else {
let turb = Vec2::new(
world.sim().gen_ctx.fast_turb_x_nz.get(wposf.div(25.0)) as f32,
@ -209,12 +211,16 @@ impl<'a> BlockGen<'a> {
0.0,
);
(alt + warp).max(cliff_height)
(
surface_height.max(cliff_height),
cliff_height > surface_height + 16.0,
)
};
(
false,
height,
on_cliff,
/*(water_level + warp).max(*/ CONFIG.sea_level, /*)*/
)
};
@ -270,6 +276,11 @@ impl<'a> BlockGen<'a> {
&& (marble * 3173.7).fract() < 0.6
&& humidity > 0.4
{
let treasures = [
BlockKind::Chest,
//BlockKind::Velorite,
];
let flowers = [
BlockKind::BlueFlower,
BlockKind::PinkFlower,
@ -288,7 +299,9 @@ impl<'a> BlockGen<'a> {
];
Some(Block::new(
if (height * 1271.0).fract() < 0.1 {
if on_cliff && (height * 1271.0).fract() < 0.015 {
treasures[(height * 731.3) as usize % treasures.len()]
} else if (height * 1271.0).fract() < 0.1 {
flowers[(height * 0.2) as usize % flowers.len()]
} else {
grasses[(height * 103.3) as usize % grasses.len()]
@ -604,6 +617,7 @@ pub fn block_from_structure(
.map(|e| e as u8),
)),
StructureBlock::Fruit => Some(Block::new(BlockKind::Apple, Rgb::new(194, 30, 37))),
StructureBlock::Chest => Some(Block::new(BlockKind::Chest, Rgb::new(0, 0, 0))),
StructureBlock::Liana => Some(Block::new(
BlockKind::Liana,
Lerp::lerp(