Merge branch 'aweinstock/lava' into 'master'

Lava

See merge request veloren/veloren!2482
This commit is contained in:
Dominik Broński 2021-06-21 20:49:44 +00:00
commit 8356e2ffca
8 changed files with 132 additions and 48 deletions

View File

@ -64,6 +64,7 @@
cave_roof: (38, 21, 79),
dirt: (69, 48, 15),
scaffold: (195, 190, 212),
lava: (184, 39, 0),
vein: (222, 140, 39),
),
site: (

View File

@ -557,7 +557,7 @@ impl<const AVERAGE_PALETTE: bool> VoxelImageDecoding for TriPngEncoding<AVERAGE_
} else {
use BlockKind::*;
match kind {
Air | Water => Rgb { r: 0, g: 0, b: 0 },
Air | Water | Lava => Rgb { r: 0, g: 0, b: 0 },
Rock => Rgb {
r: 93,
g: 110,

View File

@ -2,18 +2,45 @@
use super::body::{object, Body};
use super::{Density, Ori, Vel};
use crate::{
consts::{AIR_DENSITY, WATER_DENSITY},
consts::{AIR_DENSITY, LAVA_DENSITY, WATER_DENSITY},
util::{Dir, Plane, Projection},
};
use serde::{Deserialize, Serialize};
use std::f32::consts::PI;
use vek::*;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum LiquidKind {
Water,
Lava,
}
impl LiquidKind {
/// If an entity is in multiple overlapping liquid blocks, which one takes
/// precedence? (should be a rare edge case, since checkerboard patterns of
/// water and lava shouldn't show up in worldgen)
pub fn merge(self, other: LiquidKind) -> LiquidKind {
use LiquidKind::{Lava, Water};
match (self, other) {
(Water, Water) => Water,
(Water, Lava) => Lava,
(Lava, _) => Lava,
}
}
}
/// Fluid medium in which the entity exists
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Fluid {
Air { vel: Vel, elevation: f32 },
Water { vel: Vel, depth: f32 },
Air {
vel: Vel,
elevation: f32,
},
Liquid {
kind: LiquidKind,
vel: Vel,
depth: f32,
},
}
impl Fluid {
@ -21,7 +48,14 @@ impl Fluid {
pub fn density(&self) -> Density {
match self {
Self::Air { .. } => Density(AIR_DENSITY),
Self::Water { .. } => Density(WATER_DENSITY),
Self::Liquid {
kind: LiquidKind::Water,
..
} => Density(WATER_DENSITY),
Self::Liquid {
kind: LiquidKind::Lava,
..
} => Density(LAVA_DENSITY),
}
}
@ -53,14 +87,14 @@ impl Fluid {
pub fn flow_vel(&self) -> Vel {
match self {
Self::Air { vel, .. } => *vel,
Self::Water { vel, .. } => *vel,
Self::Liquid { vel, .. } => *vel,
}
}
// Very simple but useful in reducing mental overhead
pub fn relative_flow(&self, vel: &Vel) -> Vel { Vel(self.flow_vel().0 - vel.0) }
pub fn is_liquid(&self) -> bool { matches!(self, Fluid::Water { .. }) }
pub fn is_liquid(&self) -> bool { matches!(self, Fluid::Liquid { .. }) }
pub fn elevation(&self) -> Option<f32> {
match self {
@ -71,7 +105,7 @@ impl Fluid {
pub fn depth(&self) -> Option<f32> {
match self {
Fluid::Water { depth, .. } => Some(*depth),
Fluid::Liquid { depth, .. } => Some(*depth),
_ => None,
}
}

View File

@ -13,6 +13,9 @@ pub const FRIC_GROUND: f32 = 0.15;
// kg/m³
pub const AIR_DENSITY: f32 = 1.225;
pub const WATER_DENSITY: f32 = 999.1026;
// LAVA_DENSITY is unsourced, estimated as "roughly three times higher" than
// water
pub const LAVA_DENSITY: f32 = 3000.0;
pub const IRON_DENSITY: f32 = 7870.0;
// pub const HUMAN_DENSITY: f32 = 1010.0; // real value
pub const HUMAN_DENSITY: f32 = 990.0; // value we use to make humanoids gently float

View File

@ -1,5 +1,8 @@
use super::SpriteKind;
use crate::{comp::tool::ToolKind, make_case_elim};
use crate::{
comp::{fluid_dynamics::LiquidKind, tool::ToolKind},
make_case_elim,
};
use enum_iterator::IntoEnumIterator;
use hashbrown::HashMap;
use lazy_static::lazy_static;
@ -33,6 +36,7 @@ make_case_elim!(
// being *very* fast).
Rock = 0x10,
WeakRock = 0x11, // Explodable
Lava = 0x12,
// 0x12 <= x < 0x20 is reserved for future rocks
Grass = 0x20, // Note: *not* the same as grass sprites
Snow = 0x21,
@ -63,6 +67,15 @@ impl BlockKind {
#[inline]
pub const fn is_liquid(&self) -> bool { self.is_fluid() && !self.is_air() }
#[inline]
pub const fn liquid_kind(&self) -> Option<LiquidKind> {
Some(match self {
BlockKind::Water => LiquidKind::Water,
BlockKind::Lava => LiquidKind::Lava,
_ => return None,
})
}
/// Determine whether the block is filled (i.e: fully solid). Right now,
/// this is the opposite of being a fluid.
#[inline]
@ -168,6 +181,9 @@ impl Block {
#[inline]
pub fn get_glow(&self) -> Option<u8> {
if matches!(self.kind, BlockKind::Lava) {
return Some(24);
}
match self.get_sprite()? {
SpriteKind::StreetLamp | SpriteKind::StreetLampTall => Some(24),
SpriteKind::Ember => Some(20),
@ -217,7 +233,7 @@ impl Block {
pub fn is_solid(&self) -> bool {
self.get_sprite()
.map(|s| s.solid_height().is_some())
.unwrap_or(true)
.unwrap_or(!matches!(self.kind, BlockKind::Lava))
}
/// Can this block be exploded? If so, what 'power' is required to do so?

View File

@ -1,8 +1,8 @@
use common::{
comp::{
fluid_dynamics::Fluid, Buff, BuffCategory, BuffChange, BuffEffect, BuffId, BuffKind,
BuffSource, Buffs, Energy, Health, HealthChange, HealthSource, Inventory, ModifierKind,
PhysicsState, Stats,
fluid_dynamics::{Fluid, LiquidKind},
Buff, BuffCategory, BuffChange, BuffData, BuffEffect, BuffId, BuffKind, BuffSource, Buffs,
Energy, Health, HealthChange, HealthSource, Inventory, ModifierKind, PhysicsState, Stats,
},
event::{EventBus, ServerEvent},
resources::DeltaTime,
@ -45,34 +45,47 @@ impl<'a> System<'a> for Sys {
// Set to false to avoid spamming server
buffs.set_event_emission(false);
stats.set_event_emission(false);
for (entity, mut buff_comp, energy, mut stat, health) in (
for (entity, mut buff_comp, energy, mut stat, health, physics_state) in (
&read_data.entities,
&mut buffs,
&read_data.energies,
&mut stats,
&read_data.healths,
read_data.physics_states.maybe(),
)
.join()
{
let in_fluid = physics_state.and_then(|p| p.in_fluid);
if matches!(
in_fluid,
Some(Fluid::Liquid {
kind: LiquidKind::Lava,
..
})
) && !buff_comp.contains(BuffKind::Burning)
{
server_emitter.emit(ServerEvent::Buff {
entity,
buff_change: BuffChange::Add(Buff::new(
BuffKind::Burning,
BuffData::new(200.0, None),
vec![BuffCategory::Natural],
BuffSource::World,
)),
});
}
let (buff_comp_kinds, buff_comp_buffs): (
&HashMap<BuffKind, Vec<BuffId>>,
&mut HashMap<BuffId, Buff>,
) = buff_comp.parts();
let mut expired_buffs = Vec::<BuffId>::new();
// For each buff kind present on entity, if the buff kind queues, only ticks
// duration of strongest buff of that kind, else it ticks durations of all buffs
// of that kind. Any buffs whose durations expire are marked expired.
for (kind, ids) in buff_comp_kinds.iter() {
// Only get the physics state component if the entity has the burning buff, as
// we don't need it for any other conditions yet
let in_fluid = if matches!(kind, BuffKind::Burning) {
read_data
.physics_states
.get(entity)
.and_then(|p| p.in_fluid)
} else {
None
};
if kind.queues() {
if let Some((Some(buff), id)) =
ids.get(0).map(|id| (buff_comp_buffs.get_mut(id), id))
@ -257,7 +270,15 @@ fn tick_buff(
}
if let Some(remaining_time) = &mut buff.time {
// Extinguish Burning buff when in water
if matches!(buff.kind, BuffKind::Burning) && matches!(in_fluid, Some(Fluid::Water { .. })) {
if matches!(buff.kind, BuffKind::Burning)
&& matches!(
in_fluid,
Some(Fluid::Liquid {
kind: LiquidKind::Water,
..
})
)
{
*remaining_time = Duration::default();
}

View File

@ -1,7 +1,7 @@
use common::{
comp::{
body::ship::figuredata::{VoxelCollider, VOXEL_COLLIDER_MANIFEST},
fluid_dynamics::{Fluid, Wings},
fluid_dynamics::{Fluid, LiquidKind, Wings},
BeamSegment, Body, CharacterState, Collider, Density, Mass, Mounting, Ori, PhysicsState,
Pos, PosVelDefer, PreviousPhysCache, Projectile, Scale, Shockwave, Stats, Sticky, Vel,
},
@ -905,13 +905,15 @@ impl<'a> PhysicsData<'a> {
.terrain
.get(pos.0.map(|e| e.floor() as i32))
.ok()
.and_then(|vox| vox.is_liquid().then_some(1.0))
.map(|depth| Fluid::Water {
depth,
vel: Vel::zero(),
.and_then(|vox| {
vox.liquid_kind().map(|kind| Fluid::Liquid {
kind,
depth: 1.0,
vel: Vel::zero(),
})
})
.or_else(|| match physics_state.in_fluid {
Some(Fluid::Water { .. }) | None => Some(Fluid::Air {
Some(Fluid::Liquid { .. }) | None => Some(Fluid::Air {
elevation: pos.0.z,
vel: Vel::default(),
}),
@ -1541,24 +1543,27 @@ fn box_voxel_collision<'a, T: BaseVol<Vox = Block> + ReadVol>(
});
// Find liquid immersion and wall collision all in one round of iteration
let mut max_liquid_z = None::<f32>;
let mut liquid = None::<(LiquidKind, f32)>;
let mut wall_dir_collisions = [false; 4];
near_iter.for_each(|(i, j, k)| {
let block_pos = player_voxel_pos + Vec3::new(i, j, k);
if let Some(block) = terrain.get(block_pos).ok().copied() {
// Check for liquid blocks
if block.is_liquid() {
if let Some(block_liquid) = block.liquid_kind() {
let liquid_aabb = Aabb {
min: block_pos.map(|e| e as f32),
// The liquid part of a liquid block always extends 1 block high.
max: block_pos.map(|e| e as f32) + Vec3::one(),
};
if player_aabb.collides_with_aabb(liquid_aabb) {
max_liquid_z = Some(match max_liquid_z {
Some(z) => z.max(liquid_aabb.max.z),
None => liquid_aabb.max.z,
});
liquid = match liquid {
Some((kind, max_liquid_z)) => Some((
kind.merge(block_liquid),
max_liquid_z.max(liquid_aabb.max.z),
)),
None => Some((block_liquid, liquid_aabb.max.z)),
};
}
}
// Check for walls
@ -1594,23 +1599,24 @@ fn box_voxel_collision<'a, T: BaseVol<Vox = Block> + ReadVol>(
physics_state.ground_vel = ground_vel;
}
physics_state.in_fluid = max_liquid_z
.map(|max_z| max_z - pos.0.z) // NOTE: assumes min_z == 0.0
.map(|depth| {
physics_state
physics_state.in_fluid = liquid
.map(|(kind, max_z)| (kind, max_z - pos.0.z)) // NOTE: assumes min_z == 0.0
.map(|(kind, depth)| {
(kind, physics_state
.in_liquid()
// This is suboptimal because it doesn't check for true depth,
// so it can cause problems for situations like swimming down
// a river and spawning or teleporting in(/to) water
.map(|old_depth| (old_depth + old_pos.z - pos.0.z).max(depth))
.unwrap_or(depth)
.unwrap_or(depth))
})
.map(|depth| Fluid::Water {
.map(|(kind, depth)| Fluid::Liquid {
kind,
depth,
vel: Vel::zero(),
})
.or_else(|| match physics_state.in_fluid {
Some(Fluid::Water { .. }) | None => Some(Fluid::Air {
Some(Fluid::Liquid { .. }) | None => Some(Fluid::Air {
elevation: pos.0.z,
vel: Vel::default(),
}),

View File

@ -34,6 +34,7 @@ pub struct Colors {
pub cave_roof: (u8, u8, u8),
pub dirt: (u8, u8, u8),
pub scaffold: (u8, u8, u8),
pub lava: (u8, u8, u8),
pub vein: (u8, u8, u8),
}
@ -210,12 +211,14 @@ pub fn apply_caves_to(canvas: &mut Canvas, rng: &mut impl Rng) {
//make pits
for z in cave_base - pit_depth..cave_base {
if pit_condition && (cave_roof - cave_base) > 10 {
let kind = if z < (cave_base - pit_depth) + (3 * pit_depth / 4) {
BlockKind::Lava
} else {
BlockKind::Air
};
canvas.set(
Vec3::new(wpos2d.x, wpos2d.y, z),
Block::new(
BlockKind::Air,
noisy_color(info.index().colors.layer.scaffold.into(), 8),
),
Block::new(kind, noisy_color(info.index().colors.layer.lava.into(), 8)),
);
}
}