diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e8e8612d9..b7e3ed8ddb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Potion of Agility - A way for servers to specify must-accept rules for players - A flag argument type for commands +- The ability to turn lamp-like sprites on and off ### Changed diff --git a/assets/voxygen/i18n/en/hud/misc.ftl b/assets/voxygen/i18n/en/hud/misc.ftl index e1307e867e..8b72eb73dd 100644 --- a/assets/voxygen/i18n/en/hud/misc.ftl +++ b/assets/voxygen/i18n/en/hud/misc.ftl @@ -40,6 +40,7 @@ hud-zoom_lock_indicator-remind = Zoom locked hud-zoom_lock_indicator-enable = Camera zoom locked hud-zoom_lock_indicator-disable = Camera zoom unlocked hud-activate = Activate +hud-deactivate = Deactivate hud-collect = Collect hud-pick_up = Pick up hud-open = Open diff --git a/client/src/lib.rs b/client/src/lib.rs index 383a3f537e..6dad7944a1 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1419,6 +1419,12 @@ impl Client { self.send_msg(ClientGeneral::ControlEvent(ControlEvent::DisableLantern)); } + pub fn toggle_sprite_light(&mut self, pos: VolumePos, enable: bool) { + self.control_action(ControlAction::InventoryAction( + InventoryAction::ToggleSpriteLight(pos, enable), + )); + } + pub fn remove_buff(&mut self, buff_id: BuffKind) { self.send_msg(ClientGeneral::ControlEvent(ControlEvent::RemoveBuff( buff_id, diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index fe84c1f642..285ca705e1 100644 --- a/common/src/comp/controller.rs +++ b/common/src/comp/controller.rs @@ -40,6 +40,9 @@ pub enum InventoryAction { Use(Slot), Sort, Collect(Vec3), + // TODO: Not actually inventory-related: refactor to allow sprite interaction without + // inventory manipulation! + ToggleSpriteLight(VolumePos, bool), } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] diff --git a/common/src/consts.rs b/common/src/consts.rs index ea0017acdd..c03aa987f3 100644 --- a/common/src/consts.rs +++ b/common/src/consts.rs @@ -4,6 +4,7 @@ pub const MAX_MOUNT_RANGE: f32 = 5.0; pub const MAX_SPRITE_MOUNT_RANGE: f32 = 2.0; pub const MAX_TRADE_RANGE: f32 = 20.0; pub const MAX_NPCINTERACT_RANGE: f32 = 30.0; +pub const MAX_INTERACT_RANGE: f32 = 5.0; pub const GRAVITY: f32 = 25.0; pub const FRIC_GROUND: f32 = 0.15; diff --git a/common/src/event.rs b/common/src/event.rs index 3280d41a55..01d02d298c 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -338,6 +338,11 @@ pub enum ServerEvent { entity: EcsEntity, portal: EcsEntity, }, + ToggleSpriteLight { + entity: EcsEntity, + pos: Vec3, + enable: bool, + }, } pub struct EventBus { diff --git a/common/src/states/sprite_interact.rs b/common/src/states/sprite_interact.rs index dd6af8388f..9acee5caec 100644 --- a/common/src/states/sprite_interact.rs +++ b/common/src/states/sprite_interact.rs @@ -117,8 +117,19 @@ impl CharacterBehavior for Data { sprite_pos: self.static_data.sprite_pos, required_item: inv_slot, }; - output_events - .emit_server(ServerEvent::InventoryManip(data.entity, inv_manip)); + + match self.static_data.sprite_kind { + SpriteInteractKind::ToggleLight(enable) => { + output_events.emit_server(ServerEvent::ToggleSpriteLight { + entity: data.entity, + pos: self.static_data.sprite_pos, + enable, + }) + }, + _ => output_events + .emit_server(ServerEvent::InventoryManip(data.entity, inv_manip)), + } + if matches!(self.static_data.sprite_kind, SpriteInteractKind::Unlock) { output_events.emit_local(LocalEvent::CreateOutcome( Outcome::SpriteUnlocked { @@ -157,6 +168,7 @@ pub enum SpriteInteractKind { Collectible, Unlock, Fallback, + ToggleLight(bool), } impl From for Option { @@ -233,6 +245,11 @@ impl SpriteInteractKind { Duration::from_secs_f32(1.0), Duration::from_secs_f32(0.3), ), + Self::ToggleLight(_) => ( + Duration::from_secs_f32(0.1), + Duration::from_secs_f32(0.2), + Duration::from_secs_f32(0.1), + ), } } } diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 90b8b410e0..67958d22b4 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -20,6 +20,7 @@ use crate::{ }, consts::{FRIC_GROUND, GRAVITY, MAX_PICKUP_RANGE}, event::{LocalEvent, ServerEvent}, + mounting::Volume, outcome::Outcome, states::{behavior::JoinData, utils::CharacterState::Idle, *}, terrain::{Block, TerrainGrid, UnlockKind}, @@ -1140,6 +1141,29 @@ pub fn handle_manipulate_loadout( let inv_manip = InventoryManip::Use(slot); output_events.emit_server(ServerEvent::InventoryManip(data.entity, inv_manip)); }, + InventoryAction::ToggleSpriteLight(pos, enable) => { + if matches!(pos.kind, Volume::Terrain) { + let sprite_interact = sprite_interact::SpriteInteractKind::ToggleLight(enable); + + let (buildup_duration, use_duration, recover_duration) = + sprite_interact.durations(); + + update.character = CharacterState::SpriteInteract(sprite_interact::Data { + static_data: sprite_interact::StaticData { + buildup_duration, + use_duration, + recover_duration, + sprite_pos: pos.pos, + sprite_kind: sprite_interact, + was_wielded: data.character.is_wield(), + was_sneak: data.character.is_stealthy(), + required_item: None, + }, + timer: Duration::default(), + stage_section: StageSection::Buildup, + }); + } + }, } } diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index 154510a4bb..e04c195396 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -156,50 +156,44 @@ impl Block { /* Constructors */ - // TODO: Rename to `filled` #[inline] - pub const fn new(kind: BlockKind, color: Rgb) -> Self { - // TODO: we should probably assert this, overwriting the data fields with a - // colour is bad news - /* - #[cfg(debug_assertions)] - assert!(kind.is_filled()); - */ + pub(super) const fn from_raw(kind: BlockKind, data: [u8; 3]) -> Self { Self { kind, data } } - Self { - kind, - // Colours are only valid for non-fluids - data: if kind.is_filled() { - [color.r, color.g, color.b] - } else { - [0; 3] - }, + // TODO: Rename to `filled`, make caller guarantees stronger + #[inline] + #[track_caller] + pub const fn new(kind: BlockKind, color: Rgb) -> Self { + if kind.is_filled() { + Self::from_raw(kind, [color.r, color.g, color.b]) + } else { + // Works because `SpriteKind::Empty` has no attributes + let data = (SpriteKind::Empty as u32).to_be_bytes(); + Self::from_raw(kind, [data[1], data[2], data[3]]) } } // Only valid if `block_kind` is unfilled, so this is just a private utility // method #[inline] - const fn unfilled(kind: BlockKind, sprite: SpriteKind) -> Self { + pub fn unfilled(kind: BlockKind, sprite: SpriteKind) -> Self { #[cfg(debug_assertions)] assert!(!kind.is_filled()); - let sprite_bytes = (sprite as u32).to_be_bytes(); - - Self { - kind, - data: [sprite_bytes[1], sprite_bytes[2], sprite_bytes[3]], - } + Self::from_raw(kind, sprite.to_initial_bytes()) } #[inline] - pub const fn air(sprite: SpriteKind) -> Self { Self::unfilled(BlockKind::Air, sprite) } + pub fn air(sprite: SpriteKind) -> Self { Self::unfilled(BlockKind::Air, sprite) } #[inline] - pub const fn empty() -> Self { Self::air(SpriteKind::Empty) } + pub const fn empty() -> Self { + // Works because `SpriteKind::Empty` has no attributes + let data = (SpriteKind::Empty as u32).to_be_bytes(); + Self::from_raw(BlockKind::Air, [data[1], data[2], data[3]]) + } #[inline] - pub const fn water(sprite: SpriteKind) -> Self { Self::unfilled(BlockKind::Water, sprite) } + pub fn water(sprite: SpriteKind) -> Self { Self::unfilled(BlockKind::Water, sprite) } /* Sprite decoding */ @@ -258,6 +252,9 @@ impl Block { } } + #[inline(always)] + pub(super) const fn data(&self) -> [u8; 3] { self.data } + #[inline(always)] pub(super) const fn with_data(mut self, data: [u8; 3]) -> Self { self.data = data; @@ -377,13 +374,13 @@ impl Block { #[inline] pub fn get_glow(&self) -> Option { - match self.kind() { - BlockKind::Lava => Some(24), - BlockKind::GlowingRock | BlockKind::GlowingWeakRock => Some(10), - BlockKind::GlowingMushroom => Some(20), + let glow_level = match self.kind() { + BlockKind::Lava => 24, + BlockKind::GlowingRock | BlockKind::GlowingWeakRock => 10, + BlockKind::GlowingMushroom => 20, _ => match self.get_sprite()? { - SpriteKind::StreetLamp | SpriteKind::StreetLampTall => Some(24), - SpriteKind::Ember | SpriteKind::FireBlock => Some(20), + SpriteKind::StreetLamp | SpriteKind::StreetLampTall => 24, + SpriteKind::Ember | SpriteKind::FireBlock => 20, SpriteKind::WallLamp | SpriteKind::WallLampSmall | SpriteKind::WallSconce @@ -391,8 +388,8 @@ impl Block { | SpriteKind::ChristmasOrnament | SpriteKind::CliffDecorBlock | SpriteKind::Orb - | SpriteKind::Candle => Some(16), - SpriteKind::DiamondLight => Some(30), + | SpriteKind::Candle => 16, + SpriteKind::DiamondLight => 30, SpriteKind::Velorite | SpriteKind::VeloriteFrag | SpriteKind::CavernGrassBlueShort @@ -400,12 +397,12 @@ impl Block { | SpriteKind::CavernGrassBlueLong | SpriteKind::CavernLillypadBlue | SpriteKind::CavernMycelBlue - | SpriteKind::CeilingMushroom => Some(6), + | SpriteKind::CeilingMushroom => 6, SpriteKind::CaveMushroom | SpriteKind::CookingPot | SpriteKind::CrystalHigh - | SpriteKind::CrystalLow => Some(10), - SpriteKind::SewerMushroom => Some(16), + | SpriteKind::CrystalLow => 10, + SpriteKind::SewerMushroom => 16, SpriteKind::Amethyst | SpriteKind::Ruby | SpriteKind::Sapphire @@ -417,14 +414,23 @@ impl Block { | SpriteKind::DiamondSmall | SpriteKind::RubySmall | SpriteKind::EmeraldSmall - | SpriteKind::SapphireSmall => Some(3), - SpriteKind::Lantern => Some(24), - SpriteKind::SeashellLantern | SpriteKind::GlowIceCrystal => Some(16), - SpriteKind::SeaDecorEmblem => Some(12), - SpriteKind::SeaDecorBlock | SpriteKind::HaniwaKeyDoor => Some(10), - SpriteKind::Mine => Some(2), - _ => None, + | SpriteKind::SapphireSmall => 3, + SpriteKind::Lantern => 24, + SpriteKind::SeashellLantern | SpriteKind::GlowIceCrystal => 16, + SpriteKind::SeaDecorEmblem => 12, + SpriteKind::SeaDecorBlock | SpriteKind::HaniwaKeyDoor => 10, + SpriteKind::Mine => 2, + _ => return None, }, + }; + + if self + .get_attr::() + .map_or(true, |l| l.0) + { + Some(glow_level) + } else { + None } } @@ -625,6 +631,11 @@ impl Block { } } + /// Apply a light toggle to this block, if possible + pub fn with_toggle_light(self, enable: bool) -> Option { + self.with_attr(sprite::LightEnabled(enable)).ok() + } + #[inline] pub fn kind(&self) -> BlockKind { self.kind } @@ -648,7 +659,7 @@ impl Block { #[must_use] pub fn into_vacant(self) -> Self { if self.is_fluid() { - Block::new(self.kind(), Rgb::zero()) + Block::unfilled(self.kind(), SpriteKind::Empty) } else { // FIXME: Figure out if there's some sensible way to determine what medium to // replace a filled block with if it's removed. @@ -675,24 +686,3 @@ impl Block { const _: () = assert!(core::mem::size_of::() == 1); const _: () = assert!(core::mem::size_of::() == 4); - -#[cfg(test)] -mod tests { - use super::*; - use strum::IntoEnumIterator; - - #[test] - fn convert_u32() { - for bk in BlockKind::iter() { - let block = Block::new(bk, Rgb::new(165, 90, 204)); // Pretty unique bit patterns - if bk.is_filled() { - assert_eq!(Block::from_u32(block.to_u32()), Some(block)); - } else { - assert_eq!( - Block::from_u32(block.to_u32()), - Some(Block::new(bk, Rgb::zero())), - ); - } - } - } -} diff --git a/common/src/terrain/mod.rs b/common/src/terrain/mod.rs index 93ec4833f2..5e6e0fd8e6 100644 --- a/common/src/terrain/mod.rs +++ b/common/src/terrain/mod.rs @@ -286,7 +286,7 @@ impl TerrainChunk { pub fn water(sea_level: i32) -> TerrainChunk { TerrainChunk::new( sea_level, - Block::new(BlockKind::Water, Rgb::zero()), + Block::water(SpriteKind::Empty), Block::air(SpriteKind::Empty), TerrainChunkMeta::void(), ) diff --git a/common/src/terrain/sprite.rs b/common/src/terrain/sprite.rs index 337f82ea97..1e574af545 100644 --- a/common/src/terrain/sprite.rs +++ b/common/src/terrain/sprite.rs @@ -120,18 +120,12 @@ sprites! { DungeonChest4 = 0x35, DungeonChest5 = 0x36, CoralChest = 0x37, - HaniwaUrn = 0x38, + HaniwaUrn = 0x38, CommonLockedChest = 0x39, ChestBuried = 0x3A, Crate = 0x3B, Barrel = 0x3C, CrateBlock = 0x3D, - // Standalone lights - Lantern = 0x40, - StreetLamp = 0x41, - StreetLampTall = 0x42, - SeashellLantern = 0x43, - FireBowlGround = 0x44, // Wall HangingBasket = 0x50, HangingSign = 0x51, @@ -355,21 +349,42 @@ sprites! { SeaDecorPillar = 0x1E, MagicalSeal = 0x1F, }, + Lamp = 7 has Ori, LightEnabled { + // Standalone lights + Lantern = 0, + StreetLamp = 1, + StreetLampTall = 2, + SeashellLantern = 3, + FireBowlGround = 4, + }, } attributes! { Ori { bits: 4, err: Infallible, from: |bits| Ok(Self(bits as u8)), into: |Ori(x)| x as u16 }, Growth { bits: 4, err: Infallible, from: |bits| Ok(Self(bits as u8)), into: |Growth(x)| x as u16 }, + LightEnabled { bits: 1, err: Infallible, from: |bits| Ok(Self(bits == 1)), into: |LightEnabled(x)| x as u16 }, } -// The orientation of the sprite, 0..8 -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +// The orientation of the sprite, 0..16 +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] pub struct Ori(pub u8); // The growth of the plant, 0..16 #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Growth(pub u8); +impl Default for Growth { + fn default() -> Self { Self(15) } +} + +// Whether a light has been toggled on or off. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct LightEnabled(pub bool); + +impl Default for LightEnabled { + fn default() -> Self { Self(true) } +} + impl SpriteKind { #[inline] pub fn solid_height(&self) -> Option { diff --git a/common/src/terrain/sprite/magic.rs b/common/src/terrain/sprite/magic.rs index 91729d97b5..113f281ca7 100644 --- a/common/src/terrain/sprite/magic.rs +++ b/common/src/terrain/sprite/magic.rs @@ -158,6 +158,15 @@ macro_rules! sprites { },)* } } + + #[inline] pub(super) fn to_initial_bytes(self) -> [u8; 3] { + let sprite_bytes = (self as u32).to_be_bytes(); + let block = Block::from_raw(super::BlockKind::Air, [sprite_bytes[1], sprite_bytes[2], sprite_bytes[3]]); + match self.category() { + $(Category::$category_name => block$($(.with_attr($attr::default()).unwrap())*)?,)* + } + .data() + } } }; } @@ -170,12 +179,14 @@ pub enum AttributeError { Attribute(E), } -pub trait Attribute: Sized { - /// The unique index assigned to this attribute, used to index offset arrays +pub trait Attribute: Default + Sized { + /// The unique index assigned to this attribute, used to index offset + /// arrays. const INDEX: usize; - /// The number of bits required to represent this attribute + /// The number of bits required to represent this attribute. const BITS: u8; - type Error; + /// The error that might occur when decoding the attribute from bits. + type Error: core::fmt::Debug; fn from_bits(bits: u16) -> Result; fn into_bits(self) -> u16; } diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs index d843534409..c314c97b7c 100755 --- a/server/src/events/interaction.rs +++ b/server/src/events/interaction.rs @@ -15,7 +15,7 @@ use common::{ Inventory, LootOwner, Pos, SkillGroupKind, }, consts::{ - MAX_MOUNT_RANGE, MAX_NPCINTERACT_RANGE, MAX_SPRITE_MOUNT_RANGE, + MAX_INTERACT_RANGE, MAX_MOUNT_RANGE, MAX_NPCINTERACT_RANGE, MAX_SPRITE_MOUNT_RANGE, SOUND_TRAVEL_DIST_PER_VOLUME, }, event::EventBus, @@ -194,7 +194,7 @@ pub fn handle_mount_volume(server: &mut Server, rider: EcsEntity, volume_pos: Vo block, rider, }).is_ok(); - #[cfg(feature = "worldgen")] + #[cfg(feature = "worldgen")] if _link_successful { let uid_allocator = state.ecs().read_resource::(); if let Some(rider_entity) = uid_allocator.uid_entity(rider) @@ -469,3 +469,27 @@ pub fn handle_tame_pet(server: &mut Server, pet_entity: EcsEntity, owner_entity: // showing taming success? tame_pet(server.state.ecs(), pet_entity, owner_entity); } + +pub fn handle_toggle_sprite_light( + server: &mut Server, + entity: EcsEntity, + pos: Vec3, + enable: bool, +) { + let state = server.state_mut(); + // TODO: Implement toggling lights on volume entities + if let Some(entity_pos) = state.ecs().read_storage::().get(entity) + && entity_pos.0.distance_squared(pos.as_()) < MAX_INTERACT_RANGE.powi(2) + && state.can_set_block(pos) + { + if let Some(new_block) = state + .terrain() + .get(pos) + .ok() + .and_then(|block| block.with_toggle_light(enable)) + { + state.set_block(pos, new_block); + // TODO: Emit outcome + } + } +} diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index 1aa20f50b1..c0b471f899 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -26,7 +26,7 @@ use group_manip::handle_group; use information::handle_site_info; use interaction::{ handle_create_sprite, handle_lantern, handle_mine_block, handle_mount, handle_npc_interaction, - handle_set_pet_stay, handle_sound, handle_unmount, + handle_set_pet_stay, handle_sound, handle_toggle_sprite_light, handle_unmount, }; use inventory_manip::handle_inventory; use invite::{handle_invite, handle_invite_response}; @@ -305,6 +305,11 @@ impl Server { ServerEvent::StartTeleporting { entity, portal } => { handle_start_teleporting(self, entity, portal) }, + ServerEvent::ToggleSpriteLight { + entity, + pos, + enable, + } => handle_toggle_sprite_light(self, entity, pos, enable), } } diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 893e3e4700..94151b2ccc 100755 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -2161,6 +2161,11 @@ impl Hud { Some(GameInput::Interact), i18n.get_msg("hud-read").to_string(), )], + // TODO: change to turn on/turn off? + BlockInteraction::LightToggle(enable) => vec![( + Some(GameInput::Interact), + i18n.get_msg(if *enable { "hud-activate" } else { "hud-deactivate" }).to_string(), + )], }; // This is only done once per frame, so it's not a performance issue diff --git a/voxygen/src/mesh/terrain.rs b/voxygen/src/mesh/terrain.rs index c571227102..5c715f2d61 100644 --- a/voxygen/src/mesh/terrain.rs +++ b/voxygen/src/mesh/terrain.rs @@ -294,7 +294,7 @@ pub fn generate_mesh<'a>( let d = d + 2; let flat = { let mut volume = vol.cached(); - const AIR: Block = Block::air(common::terrain::sprite::SpriteKind::Empty); + const AIR: Block = Block::empty(); // TODO: Once we can manage it sensibly, consider using something like // Option instead of just assuming air. let mut flat = vec![AIR; (w * h * d) as usize]; diff --git a/voxygen/src/scene/terrain/watcher.rs b/voxygen/src/scene/terrain/watcher.rs index 0109c06715..9c0ba54f01 100644 --- a/voxygen/src/scene/terrain/watcher.rs +++ b/voxygen/src/scene/terrain/watcher.rs @@ -1,6 +1,6 @@ use crate::hud::CraftingTab; use common::{ - terrain::{Block, BlockKind, SpriteKind}, + terrain::{sprite, Block, BlockKind, SpriteKind}, vol::ReadVol, }; use common_base::span; @@ -16,6 +16,7 @@ pub enum Interaction { Craft(CraftingTab), Mount, Read, + LightToggle(bool), } pub enum FireplaceType { @@ -138,80 +139,96 @@ impl BlocksOfInterest { } }, BlockKind::Snow | BlockKind::Ice if rng.gen_range(0..16) == 0 => snow.push(pos), - _ => match block.get_sprite() { - Some(SpriteKind::Ember) => { - fires.push(pos); - smokers.push(SmokerProperties::new(pos, FireplaceType::House)); - }, - // Offset positions to account for block height. - // TODO: Is this a good idea? - Some(SpriteKind::StreetLamp) => fire_bowls.push(pos + Vec3::unit_z() * 2), - Some(SpriteKind::FireBowlGround) => fire_bowls.push(pos + Vec3::unit_z()), - Some(SpriteKind::StreetLampTall) => fire_bowls.push(pos + Vec3::unit_z() * 4), - Some(SpriteKind::WallSconce) => fire_bowls.push(pos + Vec3::unit_z()), - Some(SpriteKind::Beehive) => beehives.push(pos), - Some(SpriteKind::CrystalHigh) => fireflies.push(pos), - Some(SpriteKind::Reed) => { - reeds.push(pos); - fireflies.push(pos); - if rng.gen_range(0..12) == 0 { - frogs.push(pos); + _ => { + if let Some(sprite) = block.get_sprite() { + if sprite.category() == sprite::Category::Lamp { + if let Ok(sprite::LightEnabled(enabled)) = block.get_attr() { + interactables.push((pos, Interaction::LightToggle(!enabled))); + } } - }, - Some(SpriteKind::CaveMushroom) => fireflies.push(pos), - Some(SpriteKind::PinkFlower) => flowers.push(pos), - Some(SpriteKind::PurpleFlower) => flowers.push(pos), - Some(SpriteKind::RedFlower) => flowers.push(pos), - Some(SpriteKind::WhiteFlower) => flowers.push(pos), - Some(SpriteKind::YellowFlower) => flowers.push(pos), - Some(SpriteKind::Sunflower) => flowers.push(pos), - Some(SpriteKind::CraftingBench) => { - interactables.push((pos, Interaction::Craft(CraftingTab::All))) - }, - Some(SpriteKind::SmokeDummy) => { - smokers.push(SmokerProperties::new(pos, FireplaceType::Workshop)); - }, - Some(SpriteKind::Forge) => interactables - .push((pos, Interaction::Craft(CraftingTab::ProcessedMaterial))), - Some(SpriteKind::TanningRack) => interactables - .push((pos, Interaction::Craft(CraftingTab::ProcessedMaterial))), - Some(SpriteKind::SpinningWheel) => { - interactables.push((pos, Interaction::Craft(CraftingTab::All))) - }, - Some(SpriteKind::Loom) => { - interactables.push((pos, Interaction::Craft(CraftingTab::All))) - }, - Some(SpriteKind::Cauldron) => { - fires.push(pos); - interactables.push((pos, Interaction::Craft(CraftingTab::Potion))) - }, - Some(SpriteKind::Anvil) => { - interactables.push((pos, Interaction::Craft(CraftingTab::Weapon))) - }, - Some(SpriteKind::CookingPot) => { - fires.push(pos); - interactables.push((pos, Interaction::Craft(CraftingTab::Food))) - }, - Some(SpriteKind::DismantlingBench) => { - fires.push(pos); - interactables.push((pos, Interaction::Craft(CraftingTab::Dismantle))) - }, - Some(SpriteKind::RepairBench) => { - interactables.push((pos, Interaction::Craft(CraftingTab::All))) - }, - Some(SpriteKind::OneWayWall) => one_way_walls.push(( - pos, - Vec2::unit_y() - .rotated_z( - std::f32::consts::PI * 0.25 * block.get_ori().unwrap_or(0) as f32, - ) - .with_z(0.0), - )), - Some(SpriteKind::Sign | SpriteKind::HangingSign) => { - interactables.push((pos, Interaction::Read)) - }, - _ if block.is_mountable() => interactables.push((pos, Interaction::Mount)), - _ => {}, + + if block.is_mountable() { + interactables.push((pos, Interaction::Mount)); + } + + match sprite { + SpriteKind::Ember => { + fires.push(pos); + smokers.push(SmokerProperties::new(pos, FireplaceType::House)); + }, + // Offset positions to account for block height. + // TODO: Is this a good idea? + SpriteKind::StreetLamp => fire_bowls.push(pos + Vec3::unit_z() * 2), + SpriteKind::FireBowlGround => fire_bowls.push(pos + Vec3::unit_z()), + SpriteKind::StreetLampTall => fire_bowls.push(pos + Vec3::unit_z() * 4), + SpriteKind::WallSconce => fire_bowls.push(pos + Vec3::unit_z()), + SpriteKind::Beehive => beehives.push(pos), + SpriteKind::CrystalHigh => fireflies.push(pos), + SpriteKind::Reed => { + reeds.push(pos); + fireflies.push(pos); + if rng.gen_range(0..12) == 0 { + frogs.push(pos); + } + }, + SpriteKind::CaveMushroom => fireflies.push(pos), + SpriteKind::PinkFlower => flowers.push(pos), + SpriteKind::PurpleFlower => flowers.push(pos), + SpriteKind::RedFlower => flowers.push(pos), + SpriteKind::WhiteFlower => flowers.push(pos), + SpriteKind::YellowFlower => flowers.push(pos), + SpriteKind::Sunflower => flowers.push(pos), + SpriteKind::CraftingBench => { + interactables.push((pos, Interaction::Craft(CraftingTab::All))) + }, + SpriteKind::SmokeDummy => { + smokers.push(SmokerProperties::new(pos, FireplaceType::Workshop)); + }, + SpriteKind::Forge => interactables + .push((pos, Interaction::Craft(CraftingTab::ProcessedMaterial))), + SpriteKind::TanningRack => interactables + .push((pos, Interaction::Craft(CraftingTab::ProcessedMaterial))), + SpriteKind::SpinningWheel => { + interactables.push((pos, Interaction::Craft(CraftingTab::All))) + }, + SpriteKind::Loom => { + interactables.push((pos, Interaction::Craft(CraftingTab::All))) + }, + SpriteKind::Cauldron => { + fires.push(pos); + interactables.push((pos, Interaction::Craft(CraftingTab::Potion))) + }, + SpriteKind::Anvil => { + interactables.push((pos, Interaction::Craft(CraftingTab::Weapon))) + }, + SpriteKind::CookingPot => { + fires.push(pos); + interactables.push((pos, Interaction::Craft(CraftingTab::Food))) + }, + SpriteKind::DismantlingBench => { + fires.push(pos); + interactables + .push((pos, Interaction::Craft(CraftingTab::Dismantle))) + }, + SpriteKind::RepairBench => { + interactables.push((pos, Interaction::Craft(CraftingTab::All))) + }, + SpriteKind::OneWayWall => one_way_walls.push(( + pos, + Vec2::unit_y() + .rotated_z( + std::f32::consts::PI + * 0.25 + * block.get_ori().unwrap_or(0) as f32, + ) + .with_z(0.0), + )), + SpriteKind::Sign | SpriteKind::HangingSign => { + interactables.push((pos, Interaction::Read)) + }, + _ => {}, + } + } }, } if block.collectible_id().is_some() { diff --git a/voxygen/src/session/interactable.rs b/voxygen/src/session/interactable.rs index 76e639e982..a2e0a5a9ca 100644 --- a/voxygen/src/session/interactable.rs +++ b/voxygen/src/session/interactable.rs @@ -10,7 +10,7 @@ use client::Client; use common::{ comp, comp::{ship::figuredata::VOXEL_COLLIDER_MANIFEST, tool::ToolKind, Collider, Content}, - consts::{MAX_PICKUP_RANGE, MAX_SPRITE_MOUNT_RANGE, TELEPORTER_RADIUS}, + consts::{MAX_INTERACT_RANGE, MAX_PICKUP_RANGE, MAX_SPRITE_MOUNT_RANGE, TELEPORTER_RADIUS}, link::Is, mounting::{Mount, Rider, VolumePos, VolumeRider}, terrain::{Block, TerrainGrid, UnlockKind}, @@ -36,6 +36,7 @@ pub enum BlockInteraction { Mine(ToolKind), Mount, Read(Content), + LightToggle(bool), } #[derive(Clone, Debug)] @@ -105,6 +106,7 @@ impl Interactable { }, Interaction::Craft(tab) => BlockInteraction::Craft(tab), Interaction::Mount => BlockInteraction::Mount, + Interaction::LightToggle(enable) => BlockInteraction::LightToggle(enable), }; Some(Self::Block(block, volume_pos, block_interaction)) } @@ -341,8 +343,9 @@ pub(super) fn select_interactable( .filter(|(wpos, volume_pos, interaction)| { match interaction { Interaction::Mount => !is_volume_rider.contains(player_entity) - && wpos.distance_squared(player_pos) < MAX_SPRITE_MOUNT_RANGE * MAX_SPRITE_MOUNT_RANGE - && !is_volume_rider.join().any(|is_volume_rider| is_volume_rider.pos == *volume_pos), + && wpos.distance_squared(player_pos) < MAX_SPRITE_MOUNT_RANGE.powi(2) + && !is_volume_rider.join().any(|is_volume_rider| is_volume_rider.pos == *volume_pos), + Interaction::LightToggle(_) => wpos.distance_squared(player_pos) < MAX_INTERACT_RANGE.powi(2), _ => true, } }) diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index ee23990583..1ec6afb16e 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -1042,6 +1042,9 @@ impl PlayState for SessionState { // currently supported common::mounting::Volume::Entity(_) => {}, }, + BlockInteraction::LightToggle(enable) => { + client.toggle_sprite_light(*pos, *enable); + }, } }, Interactable::Entity(entity) => { diff --git a/world/src/layer/mod.rs b/world/src/layer/mod.rs index 9798dde111..171cdf396e 100644 --- a/world/src/layer/mod.rs +++ b/world/src/layer/mod.rs @@ -47,7 +47,7 @@ pub struct Colors { pub vein: (u8, u8, u8), } -const EMPTY_AIR: Block = Block::air(SpriteKind::Empty); +const EMPTY_AIR: Block = Block::empty(); pub struct PathLocals { pub riverless_alt: f32, diff --git a/world/src/site/settlement/building/archetype/keep.rs b/world/src/site/settlement/building/archetype/keep.rs index 530dd288fe..486f807daa 100644 --- a/world/src/site/settlement/building/archetype/keep.rs +++ b/world/src/site/settlement/building/archetype/keep.rs @@ -167,7 +167,7 @@ impl Archetype for Keep { make_block(colors.pole.0, colors.pole.1, colors.pole.2).with_priority(important_layer); let flag = make_block(flag_color.0, flag_color.1, flag_color.2).with_priority(important_layer); - const AIR: Block = Block::air(SpriteKind::Empty); + const AIR: Block = Block::empty(); const EMPTY: BlockMask = BlockMask::nothing(); let internal = BlockMask::new(AIR, internal_layer);