Merge branch 'zesterer/toggle-light-sprites' into 'master'

Added the ability to toggle light sources on and off

See merge request veloren/veloren!4267
This commit is contained in:
Joshua Barretto 2024-01-22 09:39:31 +00:00
commit 92a8bc2806
21 changed files with 296 additions and 165 deletions

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -40,6 +40,9 @@ pub enum InventoryAction {
Use(Slot),
Sort,
Collect(Vec3<i32>),
// TODO: Not actually inventory-related: refactor to allow sprite interaction without
// inventory manipulation!
ToggleSpriteLight(VolumePos, bool),
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -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;

View File

@ -338,6 +338,11 @@ pub enum ServerEvent {
entity: EcsEntity,
portal: EcsEntity,
},
ToggleSpriteLight {
entity: EcsEntity,
pos: Vec3<i32>,
enable: bool,
},
}
pub struct EventBus<E> {

View File

@ -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<SpriteKind> for Option<SpriteInteractKind> {
@ -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),
),
}
}
}

View File

@ -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,
});
}
},
}
}

View File

@ -156,50 +156,44 @@ impl Block {
/* Constructors */
// TODO: Rename to `filled`
#[inline]
pub const fn new(kind: BlockKind, color: Rgb<u8>) -> 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<u8>) -> 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<u8> {
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::<sprite::LightEnabled>()
.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> {
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::<BlockKind>() == 1);
const _: () = assert!(core::mem::size_of::<Block>() == 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())),
);
}
}
}
}

View File

@ -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(),
)

View File

@ -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<f32> {

View File

@ -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<E> {
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<Self, Self::Error>;
fn into_bits(self) -> u16;
}

View File

@ -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::<IdMaps>();
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<i32>,
enable: bool,
) {
let state = server.state_mut();
// TODO: Implement toggling lights on volume entities
if let Some(entity_pos) = state.ecs().read_storage::<Pos>().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
}
}
}

View File

@ -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),
}
}

View File

@ -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

View File

@ -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<Block> instead of just assuming air.
let mut flat = vec![AIR; (w * h * d) as usize];

View File

@ -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() {

View File

@ -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,
}
})

View File

@ -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) => {

View File

@ -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,

View File

@ -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);