Merge branch 'zesterer/fun-things' into 'master'

Fun Things

See merge request veloren/veloren!2987
This commit is contained in:
Joshua Barretto 2021-11-13 17:49:20 +00:00
commit 4b3aa7e8fa
31 changed files with 609 additions and 147 deletions

View File

@ -22,6 +22,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Stealth stat values on (some) armors
- All new dismantling interface found at your nearest dismantling staion
- Wearable headgear, including hood, crown, bandanas
- Bomb sprites (can be exploded with arrows or other explosions)
- Campfire waypoints in towns
- Arbitrary volume entities
### Changed

View File

@ -294,6 +294,11 @@
secondary: "common.abilities.empty.basic",
abilities: [],
),
Custom("Golf Club"): (
primary: "common.abilities.hammer.singlestrike",
secondary: "common.abilities.tool.golf_club.charged",
abilities: [],
),
Tool(Debug): (
primary: "common.abilities.debug.forwardboost",
secondary: "common.abilities.debug.upboost",

View File

@ -0,0 +1,17 @@
ChargedMelee(
energy_cost: 1,
energy_drain: 1.0,
initial_damage: 1.0,
scaled_damage: 0.1,
initial_poise_damage: 1,
scaled_poise_damage: 1,
initial_knockback: 30.0,
scaled_knockback: 60.0,
range: 5.0,
max_angle: 30.0,
charge_duration: 2.0,
swing_duration: 0.1,
hit_timing: 0.2,
recover_duration: 1.5,
damage_kind: Crushing,
)

View File

@ -0,0 +1,21 @@
ItemDef(
name: "Golf Club",
description: "Peasant swatter. Fiercely anti-urbanist. Climate crisis? What climate crisis?",
kind: Tool((
kind: Hammer,
hands: Two,
stats: Direct((
equip_time_secs: 0.5,
power: 0.6,
effect_power: 1.0,
speed: 1.0,
crit_chance: 0.083333336,
range: 1.0,
energy_efficiency: 1.0,
buff_strength: 1.0,
)),
)),
quality: Low,
tags: [],
ability_spec: Some(Custom("Golf Club")),
)

View File

@ -2866,6 +2866,11 @@
"voxel.weapon.tool.broom_belzeshrub_purple",
(0.0, 0.0, 0.0), (-135.0, 90.0, 0.0), 1.1,
),
// Misc
Tool("common.items.weapons.tool.golf_club"): VoxTrans(
"voxel.weapon.tool.golf_club",
(2.0, -1.0, 0.0), (-135.0, 25.0, 0.0), 1.1,
),
// Gems
Ingredient("Amethyst"): VoxTrans(
"voxel.sprite.mineral.gem.amethystgem",

View File

@ -1000,6 +1000,10 @@
vox_spec: ("weapon.sword.frost-1", (-2.0, -4.5, -7.5)),
color: None
),
"common.items.weapons.tool.golf_club": (
vox_spec: ("weapon.tool.golf_club", (-5.5, -4.0, -4.0)),
color: None
),
// Misc
"common.items.weapons.empty.empty": (
vox_spec: ("armor.empty", (-3.0, -3.5, 1.0)),

View File

@ -3411,7 +3411,7 @@ WitchWindow: Some((
(
model: "voxygen.voxel.sprite.window.witch_purple",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.5, 0.5, 0.5),
),
],
wind_sway: 0.0,
@ -3453,27 +3453,27 @@ CavernGrassBlueShort: Some((
(
model: "voxygen.voxel.sprite.cavern.grass_short-0",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.0, 0.0, 1.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_short-1",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.0, 0.0, 1.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_short-2",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.0, 0.0, 1.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_short-3",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.0, 0.0, 1.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_short-4",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.0, 0.0, 1.0),
),
],
wind_sway: 0.0,
@ -3484,22 +3484,22 @@ CavernGrassBlueMedium: Some((
(
model: "voxygen.voxel.sprite.cavern.grass_med-0",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.0, 0.0, 1.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_med-1",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.0, 0.0, 1.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_med-2",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.0, 0.0, 1.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_med-3",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.0, 0.0, 1.0),
),
],
wind_sway: 0.0,
@ -3510,42 +3510,42 @@ CavernGrassBlueLong: Some((
(
model: "voxygen.voxel.sprite.cavern.grass_long-0",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.0, 0.0, 1.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_long-1",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.0, 0.0, 1.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_long-2",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.0, 0.0, 1.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_long-3",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.0, 0.0, 1.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_long-4",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.0, 0.0, 1.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_long-5",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.0, 0.0, 1.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_long-6",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.0, 0.0, 1.0),
),
(
model: "voxygen.voxel.sprite.cavern.grass_long-7",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.0, 0.0, 1.0),
),
],
wind_sway: 0.0,
@ -3556,27 +3556,27 @@ CavernLillypadBlue: Some((
(
model: "voxygen.voxel.sprite.cavern.lillypad-0",
offset: (-5.5, -5.5, -1.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.5, 0.5, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.lillypad-1",
offset: (-5.5, -5.5, -1.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.5, 0.5, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.lillypad-2",
offset: (-5.5, -5.5, -1.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.5, 0.5, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.lillypad-3",
offset: (-5.5, -5.5, -1.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.5, 0.5, 0.0),
),
(
model: "voxygen.voxel.sprite.cavern.lillypad-4",
offset: (-5.5, -5.5, -1.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.5, 0.5, 0.0),
),
],
wind_sway: 0.0,
@ -3587,22 +3587,22 @@ CavernMycelBlue: Some((
(
model: "voxygen.voxel.sprite.cavern.mycel-0",
offset: (-0.5, -0.5, -21.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.0, 0.0, 1.0),
),
(
model: "voxygen.voxel.sprite.cavern.mycel-1",
offset: (-0.5, -0.5, -31.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.0, 0.0, 1.0),
),
(
model: "voxygen.voxel.sprite.cavern.mycel-2",
offset: (-0.5, -0.5, -14.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.0, 0.0, 1.0),
),
(
model: "voxygen.voxel.sprite.cavern.mycel-3",
offset: (-0.5, -0.5, -40.0),
lod_axes: (0.0, 0.0, 0.0),
lod_axes: (0.0, 0.0, 1.0),
),
],
wind_sway: 0.1,
@ -3658,4 +3658,14 @@ LillyPads: Some((
],
wind_sway: 0.6,
)),
Bomb: Some((
variations: [
(
model: "voxygen.voxel.object.bomb",
offset: (-5.5, -5.5, 0.0),
lod_axes: (0.5, 0.5, 0.5),
),
],
wind_sway: 0.0,
)),
)

BIN
assets/voxygen/voxel/weapon/tool/golf_club.vox (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -64,7 +64,7 @@ csv = { version = "1.1.3", optional = true }
structopt = { version = "0.3.13", optional = true }
# graphviz exporters
petgraph = { version = "0.5.1", optional = true }
# K-d trees used for RRT pathfinding
# K-d trees used for RRT pathfinding
kiddo = { version = "0.1", optional = true }
# Data structures

View File

@ -107,6 +107,7 @@ pub enum ChatCommand {
Whitelist,
Wiring,
World,
MakeVolume,
}
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
@ -638,6 +639,7 @@ impl ChatCommand {
"Send messages to everyone on the server",
None,
),
ChatCommand::MakeVolume => cmd(vec![], "Create a volume (experimental)", Some(Admin)),
}
}
@ -709,6 +711,7 @@ impl ChatCommand {
ChatCommand::Wiring => "wiring",
ChatCommand::Whitelist => "whitelist",
ChatCommand::World => "world",
ChatCommand::MakeVolume => "make_volume",
}
}

View File

@ -859,6 +859,7 @@ impl Body {
ship::Body::AirBalloon => [0.0, 0.0, 5.0],
ship::Body::SailBoat => [-2.0, -5.0, 4.0],
ship::Body::Galleon => [-2.0, -5.0, 4.0],
ship::Body::Volume => [0.0, 0.0, 0.0],
},
_ => [0.0, 0.0, 0.0],
}

View File

@ -1,11 +1,13 @@
use crate::{
comp::{Density, Mass},
comp::{Collider, Density, Mass},
consts::{AIR_DENSITY, WATER_DENSITY},
make_case_elim,
terrain::{Block, BlockKind, SpriteKind},
};
use rand::prelude::SliceRandom;
use serde::{Deserialize, Serialize};
use vek::Vec3;
use std::sync::Arc;
use vek::*;
pub const ALL_BODIES: [Body; 4] = [
Body::DefaultAirship,
@ -23,6 +25,7 @@ make_case_elim!(
AirBalloon = 1,
SailBoat = 2,
Galleon = 3,
Volume = 4,
}
);
@ -38,18 +41,21 @@ impl Body {
pub fn random_with(rng: &mut impl rand::Rng) -> Self { *(&ALL_BODIES).choose(rng).unwrap() }
pub fn manifest_entry(&self) -> &'static str {
/// Return the structure manifest that this ship uses. `None` means that it
/// should be derived from the collider.
pub fn manifest_entry(&self) -> Option<&'static str> {
match self {
Body::DefaultAirship => "airship_human.structure",
Body::AirBalloon => "air_balloon.structure",
Body::SailBoat => "sail_boat.structure",
Body::Galleon => "galleon.structure",
Body::DefaultAirship => Some("airship_human.structure"),
Body::AirBalloon => Some("air_balloon.structure"),
Body::SailBoat => Some("sail_boat.structure"),
Body::Galleon => Some("galleon.structure"),
Body::Volume => None,
}
}
pub fn dimensions(&self) -> Vec3<f32> {
match self {
Body::DefaultAirship => Vec3::new(25.0, 50.0, 40.0),
Body::DefaultAirship | Body::Volume => Vec3::new(25.0, 50.0, 40.0),
Body::AirBalloon => Vec3::new(25.0, 50.0, 40.0),
Body::SailBoat => Vec3::new(13.0, 31.0, 3.0),
Body::Galleon => Vec3::new(13.0, 32.0, 3.0),
@ -58,7 +64,7 @@ impl Body {
fn balloon_vol(&self) -> f32 {
match self {
Body::DefaultAirship | Body::AirBalloon => {
Body::DefaultAirship | Body::AirBalloon | Body::Volume => {
let spheroid_vol = |equat_d: f32, polar_d: f32| -> f32 {
(std::f32::consts::PI / 6.0) * equat_d.powi(2) * polar_d
};
@ -84,18 +90,39 @@ impl Body {
pub fn density(&self) -> Density {
match self {
Body::DefaultAirship | Body::AirBalloon => Density(AIR_DENSITY),
Body::DefaultAirship | Body::AirBalloon | Body::Volume => Density(AIR_DENSITY),
_ => Density(AIR_DENSITY * 0.8 + WATER_DENSITY * 0.2), // Most boats should be buoyant
}
}
pub fn mass(&self) -> Mass { Mass((self.hull_vol() + self.balloon_vol()) * self.density().0) }
pub fn can_fly(&self) -> bool { matches!(self, Body::DefaultAirship | Body::AirBalloon) }
pub fn can_fly(&self) -> bool {
matches!(self, Body::DefaultAirship | Body::AirBalloon | Body::Volume)
}
pub fn has_water_thrust(&self) -> bool {
!self.can_fly() // TODO: Differentiate this more carefully
}
pub fn make_collider(&self) -> Collider {
match self.manifest_entry() {
Some(manifest_entry) => Collider::Voxel {
id: manifest_entry.to_string(),
},
None => {
use rand::prelude::*;
let sz = Vec3::broadcast(11);
Collider::Volume(Arc::new(figuredata::VoxelCollider::from_fn(sz, |_pos| {
if thread_rng().gen_bool(0.25) {
Block::new(BlockKind::Rock, Rgb::new(255, 0, 0))
} else {
Block::air(SpriteKind::Empty)
}
})))
},
}
}
}
/// Terrain is 11.0 scale relative to small-scale voxels,
@ -117,7 +144,7 @@ pub mod figuredata {
};
use hashbrown::HashMap;
use lazy_static::lazy_static;
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use vek::Vec3;
#[derive(Deserialize)]
@ -148,10 +175,25 @@ pub mod figuredata {
pub colliders: HashMap<String, VoxelCollider>,
}
#[derive(Clone)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct VoxelCollider {
pub dyna: Dyna<Block, (), ColumnAccess>,
pub(super) dyna: Dyna<Block, (), ColumnAccess>,
pub translation: Vec3<f32>,
/// This value should be incremented every time the volume is mutated
/// and can be used to keep track of volume changes.
pub mut_count: usize,
}
impl VoxelCollider {
pub fn from_fn<F: FnMut(Vec3<i32>) -> Block>(sz: Vec3<u32>, f: F) -> Self {
Self {
dyna: Dyna::from_fn(sz, (), f),
translation: -sz.map(|e| e as f32) / 2.0,
mut_count: 0,
}
}
pub fn volume(&self) -> &Dyna<Block, (), ColumnAccess> { &self.dyna }
}
impl assets::Compound for ShipSpec {
@ -180,6 +222,7 @@ pub mod figuredata {
let collider = VoxelCollider {
dyna,
translation: Vec3::from(bone.offset) + Vec3::from(bone.phys_offset),
mut_count: 0,
};
colliders.insert(bone.central.0.clone(), collider);
}
@ -201,13 +244,15 @@ pub mod figuredata {
#[test]
fn test_ship_manifest_entries() {
for body in super::ALL_BODIES {
assert!(
VOXEL_COLLIDER_MANIFEST
.read()
.colliders
.get(body.manifest_entry())
.is_some()
);
if let Some(entry) = body.manifest_entry() {
assert!(
VOXEL_COLLIDER_MANIFEST
.read()
.colliders
.get(entry)
.is_some()
);
}
}
}
}

View File

@ -1,9 +1,12 @@
use super::{Fluid, Ori};
use crate::{consts::WATER_DENSITY, terrain::Block, uid::Uid};
use crate::{
comp::body::ship::figuredata::VoxelCollider, consts::WATER_DENSITY, terrain::Block, uid::Uid,
};
use hashbrown::HashSet;
use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage, NullStorage};
use specs_idvs::IdvStorage;
use std::sync::Arc;
use vek::*;
/// Position
@ -103,13 +106,16 @@ impl Component for Density {
}
// Collider
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Collider {
/// A volume based on an existing voxel asset.
// TODO: pass the map from ids -> voxel data to get_radius
// and get_z_limits to compute a bounding cylinder.
Voxel {
id: String,
},
/// A mutable volume.
Volume(Arc<VoxelCollider>),
/// Capsule prism with line segment from p0 to p1
CapsulePrism {
p0: Vec2<f32>,
@ -122,9 +128,11 @@ pub enum Collider {
}
impl Collider {
pub fn is_voxel(&self) -> bool { matches!(self, Collider::Voxel { .. } | Collider::Volume(_)) }
pub fn bounding_radius(&self) -> f32 {
match self {
Collider::Voxel { .. } => 1.0,
Collider::Voxel { .. } | Collider::Volume(_) => 1.0,
Collider::CapsulePrism { radius, p0, p1, .. } => {
let a = p0.distance(*p1);
a / 2.0 + *radius
@ -140,7 +148,7 @@ impl Collider {
pub fn get_z_limits(&self, modifier: f32) -> (f32, f32) {
match self {
Collider::Voxel { .. } => (0.0, 1.0),
Collider::Voxel { .. } | Collider::Volume(_) => (0.0, 1.0),
Collider::CapsulePrism { z_min, z_max, .. } => (*z_min * modifier, *z_max * modifier),
Collider::Point => (0.0, 0.0),
}

View File

@ -156,7 +156,8 @@ impl From<SpriteKind> for Option<SpriteInteractKind> {
| SpriteKind::VialEmpty
| SpriteKind::Bowl
| SpriteKind::PotionMinor
| SpriteKind::Seashells => Some(SpriteInteractKind::Collectible),
| SpriteKind::Seashells
| SpriteKind::Bomb => Some(SpriteInteractKind::Collectible),
// Collectible checked in addition to container for case that sprite requires a tool to
// collect and cannot be collected by hand, yet still meets the container check
_ if sprite_kind.is_container() && sprite_kind.is_collectible() => {

View File

@ -279,7 +279,9 @@ impl Block {
#[inline]
pub fn is_bonkable(&self) -> bool {
match self.get_sprite() {
Some(SpriteKind::Apple | SpriteKind::Beehive | SpriteKind::Coconut) => self.is_solid(),
Some(
SpriteKind::Apple | SpriteKind::Beehive | SpriteKind::Coconut | SpriteKind::Bomb,
) => self.is_solid(),
_ => false,
}
}

View File

@ -187,6 +187,7 @@ make_case_elim!(
LillyPads = 0xA0,
JungleLeafyPlant = 0xA1,
JungleRedGrass = 0xA2,
Bomb = 0xA3,
}
);
@ -261,7 +262,8 @@ impl SpriteKind {
| SpriteKind::Window3
| SpriteKind::Window4
| SpriteKind::DropGate
| SpriteKind::WitchWindow => 1.0,
| SpriteKind::WitchWindow
| SpriteKind::Bomb => 1.0,
// TODO: Figure out if this should be solid or not.
SpriteKind::Shelf => 1.0,
SpriteKind::Lantern => 0.9,
@ -335,6 +337,7 @@ impl SpriteKind {
SpriteKind::RoundCactus => item("common.items.crafting_ing.cactus"),
SpriteKind::ShortFlatCactus => item("common.items.crafting_ing.cactus"),
SpriteKind::MedFlatCactus => item("common.items.crafting_ing.cactus"),
SpriteKind::Bomb => item("common.items.utility.bomb"),
SpriteKind::DungeonChest0 => table("common.loot_tables.dungeon.tier-0.chest"),
SpriteKind::DungeonChest1 => table("common.loot_tables.dungeon.tier-1.chest"),
SpriteKind::DungeonChest2 => table("common.loot_tables.dungeon.tier-2.chest"),

View File

@ -129,6 +129,19 @@ impl<V: Clone, M, A: Access> Dyna<V, M, A> {
}
}
/// Same as [`Dyna::filled`], but with the voxel determined by the function
/// `f`.
pub fn from_fn<F: FnMut(Vec3<i32>) -> V>(sz: Vec3<u32>, meta: M, mut f: F) -> Self {
Self {
vox: (0..sz.product() as usize)
.map(|idx| f(A::pos(idx, sz)))
.collect(),
meta,
sz,
_phantom: std::marker::PhantomData,
}
}
/// Get a reference to the internal metadata.
pub fn metadata(&self) -> &M { &self.meta }
@ -138,6 +151,8 @@ impl<V: Clone, M, A: Access> Dyna<V, M, A> {
pub trait Access {
fn idx(pos: Vec3<i32>, sz: Vec3<u32>) -> usize;
/// `idx` must be in range, permitted to panic otherwise.
fn pos(idx: usize, sz: Vec3<u32>) -> Vec3<i32>;
}
#[derive(Copy, Clone, Debug)]
@ -147,4 +162,11 @@ impl Access for ColumnAccess {
fn idx(pos: Vec3<i32>, sz: Vec3<u32>) -> usize {
(pos.x * sz.y as i32 * sz.z as i32 + pos.y * sz.z as i32 + pos.z) as usize
}
fn pos(idx: usize, sz: Vec3<u32>) -> Vec3<i32> {
let z = idx as u32 % sz.z;
let y = (idx as u32 / sz.z) % sz.y;
let x = idx as u32 / (sz.y * sz.z);
Vec3::new(x, y, z).map(|e| e as i32)
}
}

View File

@ -225,7 +225,7 @@ impl<'a> PhysicsData<'a> {
let neighborhood_radius = match collider {
Collider::CapsulePrism { radius, .. } => radius * scale,
Collider::Voxel { .. } | Collider::Point => flat_radius,
Collider::Voxel { .. } | Collider::Volume(_) | Collider::Point => flat_radius,
};
phys_cache.neighborhood_radius = neighborhood_radius;
@ -265,7 +265,7 @@ impl<'a> PhysicsData<'a> {
Some((p0, p1))
}
},
Collider::Voxel { .. } | Collider::Point => None,
Collider::Voxel { .. } | Collider::Volume(_) | Collider::Point => None,
};
phys_cache.origins = origins;
phys_cache.ori = ori;
@ -508,6 +508,9 @@ impl<'a> PhysicsData<'a> {
ref read,
ref write,
} = self;
let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
// NOTE: i32 places certain constraints on how far out collision works
// NOTE: uses the radius of the entity and their current position rather than
// the radius of their bounding sphere for the current frame of movement
@ -527,13 +530,14 @@ impl<'a> PhysicsData<'a> {
)
.join()
{
let voxel_id = match collider {
Collider::Voxel { id } => id,
_ => continue,
let vol = match collider {
Collider::Voxel { id } => voxel_colliders_manifest.colliders.get(&*id),
Collider::Volume(vol) => Some(&**vol),
_ => None,
};
if let Some(voxel_collider) = VOXEL_COLLIDER_MANIFEST.read().colliders.get(&*voxel_id) {
let sphere = voxel_collider_bounding_sphere(voxel_collider, pos, ori);
if let Some(vol) = vol {
let sphere = voxel_collider_bounding_sphere(vol, pos, ori);
let radius = sphere.radius.ceil() as u32;
let pos_2d = sphere.center.xy().map(|e| e as i32);
const POS_TRUNCATION_ERROR: u32 = 1;
@ -729,7 +733,7 @@ impl<'a> PhysicsData<'a> {
!&read.mountings,
)
.par_join()
.filter(|tuple| matches!(tuple.3, Collider::Voxel { .. }) == terrain_like_entities)
.filter(|tuple| tuple.3.is_voxel() == terrain_like_entities)
.map_init(
|| {
prof_span!(guard, "physics e<>t rayon job");
@ -760,7 +764,7 @@ impl<'a> PhysicsData<'a> {
let old_ori = *ori;
let mut ori = *ori;
let scale = if let Collider::Voxel { .. } = collider {
let scale = if collider.is_voxel() {
scale.map(|s| s.0).unwrap_or(1.0)
} else {
// TODO: Use scale & actual proportions when pathfinding is good
@ -806,7 +810,7 @@ impl<'a> PhysicsData<'a> {
character_state.map_or(false, |cs| matches!(cs, CharacterState::Climb(_)));
match &collider {
Collider::Voxel { .. } => {
Collider::Voxel { .. } | Collider::Volume(_) => {
// For now, treat entities with voxel colliders
// as their bounding cylinders for the purposes of
// colliding them with terrain.
@ -1003,6 +1007,8 @@ impl<'a> PhysicsData<'a> {
let query_center = path_sphere.center.xy();
let query_radius = path_sphere.radius;
let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
voxel_collider_spatial_grid
.in_circle_aabr(query_center, query_radius)
.filter_map(|entity| {
@ -1029,10 +1035,12 @@ impl<'a> PhysicsData<'a> {
return;
}
let voxel_id = if let Collider::Voxel { id } = collider_other {
id
} else {
return;
let voxel_collider = match collider_other {
Collider::Voxel { id } => {
voxel_colliders_manifest.colliders.get(id)
},
Collider::Volume(vol) => Some(&**vol),
_ => None,
};
// use bounding cylinder regardless of our collider
@ -1045,9 +1053,7 @@ impl<'a> PhysicsData<'a> {
let z_min = 0.0;
let z_max = z_max.clamped(1.2, 1.95) * scale;
if let Some(voxel_collider) =
VOXEL_COLLIDER_MANIFEST.read().colliders.get(voxel_id)
{
if let Some(voxel_collider) = voxel_collider {
// TODO: cache/precompute sphere?
let voxel_sphere = voxel_collider_bounding_sphere(
voxel_collider,
@ -1112,7 +1118,7 @@ impl<'a> PhysicsData<'a> {
let cylinder = (radius, z_min, z_max);
box_voxel_collision(
cylinder,
&voxel_collider.dyna,
&voxel_collider.volume(),
entity,
&mut cpos,
transform_to.mul_point(tgt_pos - wpos),
@ -1226,7 +1232,7 @@ impl<'a> PhysicsData<'a> {
&read.colliders,
)
.join()
.filter(|tuple| matches!(tuple.5, Collider::Voxel { .. }) == terrain_like_entities)
.filter(|tuple| tuple.5.is_voxel() == terrain_like_entities)
{
if let Some(new_pos) = pos_vel_ori_defer.pos.take() {
*pos = new_pos;
@ -1725,8 +1731,8 @@ fn voxel_collider_bounding_sphere(
) -> Sphere<f32, f32> {
let origin_offset = voxel_collider.translation;
use common::vol::SizedVol;
let lower_bound = voxel_collider.dyna.lower_bound().map(|e| e as f32);
let upper_bound = voxel_collider.dyna.upper_bound().map(|e| e as f32);
let lower_bound = voxel_collider.volume().lower_bound().map(|e| e as f32);
let upper_bound = voxel_collider.volume().upper_bound().map(|e| e as f32);
let center = (lower_bound + upper_bound) / 2.0;
// Compute vector from the origin (where pos value corresponds to) and the model
// center
@ -1845,8 +1851,8 @@ fn resolve_e2e_collision(
&& (!is_sticky || is_mid_air)
&& diff.magnitude_squared() > 0.0
&& !is_projectile
&& !matches!(collider_other, Collider::Voxel { .. })
&& !matches!(collider, Collider::Voxel { .. })
&& !collider_other.is_voxel()
&& !collider.is_voxel()
{
const ELASTIC_FORCE_COEFFICIENT: f32 = 400.0;
let mass_coefficient = mass_other.0 / (mass.0 + mass_other.0);

View File

@ -37,7 +37,7 @@ use common::{
resources::{BattleMode, PlayerPhysicsSettings, Time, TimeOfDay},
terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize},
uid::Uid,
vol::RectVolSize,
vol::{ReadVol, RectVolSize},
Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
};
use common_net::{
@ -50,7 +50,7 @@ use hashbrown::{HashMap, HashSet};
use humantime::Duration as HumanDuration;
use rand::Rng;
use specs::{storage::StorageEntry, Builder, Entity as EcsEntity, Join, WorldExt};
use std::str::FromStr;
use std::{str::FromStr, sync::Arc};
use vek::*;
use wiring::{Circuit, Wire, WiringAction, WiringActionEffect, WiringElement};
use world::util::Sampler;
@ -176,6 +176,7 @@ fn do_command(
ChatCommand::Wiring => handle_spawn_wiring,
ChatCommand::Whitelist => handle_whitelist,
ChatCommand::World => handle_world,
ChatCommand::MakeVolume => handle_make_volume,
};
handler(server, client, target, args, cmd)
@ -1269,7 +1270,7 @@ fn handle_spawn_airship(
let ship = comp::ship::Body::random();
let mut builder = server
.state
.create_ship(pos, ship, true)
.create_ship(pos, ship, |ship| ship.make_collider(), true)
.with(LightEmitter {
col: Rgb::new(1.0, 0.65, 0.2),
strength: 2.0,
@ -1293,6 +1294,46 @@ fn handle_spawn_airship(
Ok(())
}
fn handle_make_volume(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
_args: Vec<String>,
_action: &ChatCommand,
) -> CmdResult<()> {
use comp::body::ship::figuredata::VoxelCollider;
//let () = parse_args!(args);
let pos = position(server, target, "target")?;
let ship = comp::ship::Body::Volume;
let sz = Vec3::new(15, 15, 15);
let collider = {
let terrain = server.state().terrain();
comp::Collider::Volume(Arc::new(VoxelCollider::from_fn(sz, |rpos| {
terrain
.get(pos.0.map(|e| e.floor() as i32) + rpos - sz.map(|e| e as i32) / 2)
.ok()
.copied()
.unwrap_or_else(Block::empty)
})))
};
server
.state
.create_ship(
comp::Pos(pos.0 + Vec3::unit_z() * 50.0),
ship,
move |_| collider,
true,
)
.build();
server.notify_client(
client,
ServerGeneral::server_msg(ChatType::CommandInfo, "Created a volume"),
);
Ok(())
}
fn handle_spawn_campfire(
server: &mut Server,
client: EcsEntity,

View File

@ -156,7 +156,9 @@ pub fn handle_create_ship(
agent: Option<Agent>,
rtsim_entity: Option<RtSimEntity>,
) {
let mut entity = server.state.create_ship(pos, ship, mountable);
let mut entity = server
.state
.create_ship(pos, ship, |ship| ship.make_collider(), mountable);
if let Some(mut agent) = agent {
let (kp, ki, kd) = pid_coefficients(&Body::Ship(ship));
fn pure_z(sp: Vec3<f32>, pv: Vec3<f32>) -> f32 { (sp - pv).z }

View File

@ -526,7 +526,8 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
});
let explosion_volume = 2.5 * explosion.radius;
server_eventbus.emit_now(ServerEvent::Sound {
let mut emitter = server_eventbus.emitter();
emitter.emit(ServerEvent::Sound {
sound: Sound::new(SoundKind::Explosion, pos, explosion_volume, time.0),
});
@ -659,6 +660,14 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
block_change.set(block_pos, Block::new(block.kind(), color));
}
}
if block.is_bonkable() {
emitter.emit(ServerEvent::Bonk {
pos: block_pos.map(|e| e as f32 + 0.5),
owner,
target: None,
});
}
}
}
@ -804,7 +813,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
attack_options,
strength,
combat::AttackSource::Explosion,
|e| server_eventbus.emit_now(e),
|e| emitter.emit(e),
|o| outcomes.push(o),
);
}
@ -866,7 +875,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
}
}
pub fn handle_bonk(server: &mut Server, pos: Vec3<f32>, _owner: Option<Uid>, target: Option<Uid>) {
pub fn handle_bonk(server: &mut Server, pos: Vec3<f32>, owner: Option<Uid>, target: Option<Uid>) {
let ecs = &server.state.ecs();
let terrain = ecs.read_resource::<TerrainGrid>();
let mut block_change = ecs.write_resource::<BlockChange>();
@ -891,10 +900,15 @@ pub fn handle_bonk(server: &mut Server, pos: Vec3<f32>, _owner: Option<Uid>, tar
Some(SpriteKind::Apple) => comp::object::Body::Apple,
Some(SpriteKind::Beehive) => comp::object::Body::Hive,
Some(SpriteKind::Coconut) => comp::object::Body::Coconut,
Some(SpriteKind::Bomb) => comp::object::Body::Bomb,
_ => comp::object::Body::Pouch,
})
.with(comp::Pos(pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0)))
.with(item)
.maybe_with(match block.get_sprite() {
Some(SpriteKind::Bomb) => Some(comp::Object::Bomb { owner }),
_ => None,
})
.build();
}
};

View File

@ -51,10 +51,11 @@ pub trait StateExt {
) -> EcsEntityBuilder;
/// Build a static object entity
fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder;
fn create_ship(
fn create_ship<F: FnOnce(comp::ship::Body) -> comp::Collider>(
&mut self,
pos: comp::Pos,
ship: comp::ship::Body,
make_collider: F,
mountable: bool,
) -> EcsEntityBuilder;
/// Build a projectile
@ -200,9 +201,7 @@ impl StateExt for State {
.with(body.mass())
.with(body.density())
.with(match body {
comp::Body::Ship(ship) => comp::Collider::Voxel {
id: ship.manifest_entry().to_string(),
},
comp::Body::Ship(ship) => ship.make_collider(),
_ => capsule(&body),
})
.with(comp::Controller::default())
@ -243,10 +242,11 @@ impl StateExt for State {
.with(body)
}
fn create_ship(
fn create_ship<F: FnOnce(comp::ship::Body) -> comp::Collider>(
&mut self,
pos: comp::Pos,
ship: comp::ship::Body,
make_collider: F,
mountable: bool,
) -> EcsEntityBuilder {
let body = comp::Body::Ship(ship);
@ -258,9 +258,7 @@ impl StateExt for State {
.with(comp::Ori::default())
.with(body.mass())
.with(body.density())
.with(comp::Collider::Voxel {
id: ship.manifest_entry().to_string(),
})
.with(make_collider(ship))
.with(body)
.with(comp::Scale(comp::ship::AIRSHIP_SCALE))
.with(comp::Controller::default())

View File

@ -81,7 +81,7 @@ pub struct FigureBoneData(pub MatRaw, pub MatRaw);
pub const MAX_BONE_COUNT: usize = 16;
fn make_bone(mat: Mat4<f32>) -> FigureBoneData {
pub fn make_bone(mat: Mat4<f32>) -> FigureBoneData {
let normal = mat.map_cols(Vec4::normalized);
FigureBoneData(mat.into_col_arrays(), normal.into_col_arrays())
}

View File

@ -95,24 +95,28 @@ impl<'a> From<&'a Body> for SkeletonAttr {
AirBalloon => (0.0, 0.0, 0.0),
SailBoat => (0.0, 0.0, 0.0),
Galleon => (0.0, 0.0, 0.0),
Volume => (0.0, 0.0, 0.0),
},
bone1: match body {
DefaultAirship => (-13.0, -25.0, 10.0),
AirBalloon => (0.0, 0.0, 0.0),
SailBoat => (0.0, 0.0, 0.0),
Galleon => (0.0, 0.0, 0.0),
Volume => (0.0, 0.0, 0.0),
},
bone2: match body {
DefaultAirship => (13.0, -25.0, 10.0),
AirBalloon => (0.0, 0.0, 0.0),
SailBoat => (0.0, 0.0, 0.0),
Galleon => (0.0, 0.0, 0.0),
Volume => (0.0, 0.0, 0.0),
},
bone3: match body {
DefaultAirship => (0.0, -27.5, 8.5),
AirBalloon => (0.0, -9.0, 8.0),
SailBoat => (0.0, 0.0, 0.0),
Galleon => (0.0, 0.0, 0.0),
Volume => (0.0, 0.0, 0.0),
},
}
}

View File

@ -6,7 +6,6 @@ use crate::{
};
use anim::Skeleton;
use common::{
assets::AssetHandle,
comp::{
inventory::{
slot::{ArmorSlot, EquipSlot},
@ -287,7 +286,7 @@ where
Skel::Body: BodySpec,
{
models: HashMap<FigureKey<Skel::Body>, ((FigureModelEntryFuture<LOD_COUNT>, Skel::Attr), u64)>,
manifests: AssetHandle<<Skel::Body as BodySpec>::Spec>,
manifests: <Skel::Body as BodySpec>::Manifests,
}
impl<Skel: Skeleton> FigureModelCache<Skel>
@ -345,6 +344,7 @@ where
col_lights: &mut super::FigureColLights,
body: Skel::Body,
inventory: Option<&Inventory>,
extra: <Skel::Body as BodySpec>::Extra,
tick: u64,
camera_mode: CameraMode,
character_state: Option<&CharacterState>,
@ -407,13 +407,12 @@ where
Entry::Vacant(v) => {
let key = v.key().clone();
let slot = Arc::new(atomic::AtomicCell::new(None));
let manifests = self.manifests;
let manifests = self.manifests.clone();
let slot_ = Arc::clone(&slot);
slow_jobs.spawn("FIGURE_MESHING", move || {
// First, load all the base vertex data.
let manifests = &*manifests.read();
let meshes = <Skel::Body as BodySpec>::bone_meshes(&key, manifests);
let meshes = <Skel::Body as BodySpec>::bone_meshes(&key, &manifests, extra);
// Then, set up meshing context.
let mut greedy = FigureModel::make_greedy();
@ -562,7 +561,7 @@ where
{
// Check for reloaded manifests
// TODO: maybe do this in a different function, maintain?
if self.manifests.reloaded() {
if <Skel::Body as BodySpec>::is_reloaded(&mut self.manifests) {
col_lights.atlas.clear();
self.models.clear();
}

View File

@ -87,9 +87,16 @@ fn recolor_grey(rgb: Rgb<u8>, color: Rgb<u8>) -> Rgb<u8> {
/// A set of reloadable specifications for a Body.
pub trait BodySpec: Sized {
type Spec;
/// Cloned on each cache invalidation. If this type is expensive to clone,
/// place it behind an [`Arc`].
type Manifests: Send + Sync + Clone;
type Extra: Send + Sync;
/// Initialize all the specifications for this Body.
fn load_spec() -> Result<AssetHandle<Self::Spec>, assets::Error>;
fn load_spec() -> Result<Self::Manifests, assets::Error>;
/// Determine whether the cache's manifest was reloaded
fn is_reloaded(manifests: &mut Self::Manifests) -> bool;
/// Mesh bones using the given spec, character state, and mesh generation
/// function.
@ -100,7 +107,8 @@ pub trait BodySpec: Sized {
/// in which case this strategy might change.
fn bone_meshes(
key: &FigureKey<Self>,
spec: &Self::Spec,
manifests: &Self::Manifests,
extra: Self::Extra,
) -> [Option<BoneMeshes>; anim::MAX_BONE_COUNT];
}
@ -125,16 +133,22 @@ macro_rules! make_vox_spec {
impl BodySpec for $body {
type Spec = $Spec;
type Manifests = AssetHandle<Self::Spec>;
type Extra = ();
#[allow(unused_variables)]
fn load_spec() -> Result<AssetHandle<Self::Spec>, assets::Error> {
fn load_spec() -> Result<Self::Manifests, assets::Error> {
Self::Spec::load("")
}
fn is_reloaded(manifests: &mut Self::Manifests) -> bool { manifests.reloaded() }
fn bone_meshes(
$self_pat: &FigureKey<Self>,
$spec_pat: &Self::Spec,
manifests: &Self::Manifests,
_: Self::Extra,
) -> [Option<BoneMeshes>; anim::MAX_BONE_COUNT] {
let $spec_pat = &*manifests.read();
$bone_meshes
}
}
@ -4528,15 +4542,21 @@ fn mesh_ship_bone<K: fmt::Debug + Eq + Hash, V, F: Fn(&V) -> &ShipCentralSubSpec
}
impl BodySpec for ship::Body {
type Extra = ();
type Manifests = AssetHandle<Self::Spec>;
type Spec = ShipSpec;
#[allow(unused_variables)]
fn load_spec() -> Result<AssetHandle<Self::Spec>, assets::Error> { Self::Spec::load("") }
fn load_spec() -> Result<Self::Manifests, assets::Error> { Self::Spec::load("") }
fn is_reloaded(manifests: &mut Self::Manifests) -> bool { manifests.reloaded() }
fn bone_meshes(
FigureKey { body, .. }: &FigureKey<Self>,
spec: &Self::Spec,
manifests: &Self::Manifests,
_: Self::Extra,
) -> [Option<BoneMeshes>; anim::MAX_BONE_COUNT] {
let spec = &*manifests.read();
let map = &(spec.central.read().0).0;
[
Some(mesh_ship_bone(map, body, |spec| &spec.bone0)),

View File

@ -1,8 +1,10 @@
mod cache;
pub mod load;
mod volume;
pub use cache::FigureModelCache;
pub use load::load_mesh; // TODO: Don't make this public.
pub use volume::VolumeKey;
use crate::{
ecs::comp::Interpolated,
@ -30,7 +32,7 @@ use common::{
comp::{
inventory::slot::EquipSlot,
item::{Hands, ItemKind, ToolKind},
Body, CharacterState, Controller, Health, Inventory, Item, Last, LightAnimation,
Body, CharacterState, Collider, Controller, Health, Inventory, Item, Last, LightAnimation,
LightEmitter, Mounting, Ori, PhysicsState, PoiseState, Pos, Scale, Vel,
},
resources::DeltaTime,
@ -50,6 +52,7 @@ use core::{
use guillotiere::AtlasAllocator;
use hashbrown::HashMap;
use specs::{saveload::MarkerAllocator, Entity as EcsEntity, Join, LazyUpdate, WorldExt};
use std::sync::Arc;
use treeculler::{BVol, BoundingSphere};
use vek::*;
@ -113,6 +116,7 @@ struct FigureMgrStates {
golem_states: HashMap<EcsEntity, FigureState<GolemSkeleton>>,
object_states: HashMap<EcsEntity, FigureState<ObjectSkeleton>>,
ship_states: HashMap<EcsEntity, FigureState<ShipSkeleton>>,
volume_states: HashMap<EcsEntity, FigureState<VolumeKey>>,
}
impl FigureMgrStates {
@ -133,6 +137,7 @@ impl FigureMgrStates {
golem_states: HashMap::new(),
object_states: HashMap::new(),
ship_states: HashMap::new(),
volume_states: HashMap::new(),
}
}
@ -193,7 +198,13 @@ impl FigureMgrStates {
.map(DerefMut::deref_mut),
Body::Golem(_) => self.golem_states.get_mut(entity).map(DerefMut::deref_mut),
Body::Object(_) => self.object_states.get_mut(entity).map(DerefMut::deref_mut),
Body::Ship(_) => self.ship_states.get_mut(entity).map(DerefMut::deref_mut),
Body::Ship(ship) => {
if ship.manifest_entry().is_some() {
self.ship_states.get_mut(entity).map(DerefMut::deref_mut)
} else {
self.volume_states.get_mut(entity).map(DerefMut::deref_mut)
}
},
}
}
@ -217,7 +228,13 @@ impl FigureMgrStates {
Body::BipedSmall(_) => self.biped_small_states.remove(entity).map(|e| e.meta),
Body::Golem(_) => self.golem_states.remove(entity).map(|e| e.meta),
Body::Object(_) => self.object_states.remove(entity).map(|e| e.meta),
Body::Ship(_) => self.ship_states.remove(entity).map(|e| e.meta),
Body::Ship(ship) => {
if ship.manifest_entry().is_some() {
self.ship_states.remove(entity).map(|e| e.meta)
} else {
self.volume_states.remove(entity).map(|e| e.meta)
}
},
}
}
@ -238,6 +255,7 @@ impl FigureMgrStates {
self.golem_states.retain(|k, v| f(k, &mut *v));
self.object_states.retain(|k, v| f(k, &mut *v));
self.ship_states.retain(|k, v| f(k, &mut *v));
self.volume_states.retain(|k, v| f(k, &mut *v));
}
fn count(&self) -> usize {
@ -257,6 +275,7 @@ impl FigureMgrStates {
+ self.golem_states.len()
+ self.object_states.len()
+ self.ship_states.len()
+ self.volume_states.len()
}
fn count_visible(&self) -> usize {
@ -330,6 +349,11 @@ impl FigureMgrStates {
.filter(|(_, c)| c.visible())
.count()
+ self.ship_states.iter().filter(|(_, c)| c.visible()).count()
+ self
.volume_states
.iter()
.filter(|(_, c)| c.visible())
.count()
}
}
@ -350,6 +374,7 @@ pub struct FigureMgr {
object_model_cache: FigureModelCache<ObjectSkeleton>,
ship_model_cache: FigureModelCache<ShipSkeleton>,
golem_model_cache: FigureModelCache<GolemSkeleton>,
volume_model_cache: FigureModelCache<VolumeKey>,
states: FigureMgrStates,
}
@ -372,6 +397,7 @@ impl FigureMgr {
object_model_cache: FigureModelCache::new(),
ship_model_cache: FigureModelCache::new(),
golem_model_cache: FigureModelCache::new(),
volume_model_cache: FigureModelCache::new(),
states: FigureMgrStates::default(),
}
}
@ -404,6 +430,7 @@ impl FigureMgr {
self.object_model_cache.clean(&mut self.col_lights, tick);
self.ship_model_cache.clean(&mut self.col_lights, tick);
self.golem_model_cache.clean(&mut self.col_lights, tick);
self.volume_model_cache.clean(&mut self.col_lights, tick);
}
pub fn update_lighting(&mut self, scene_data: &SceneData) {
@ -600,6 +627,7 @@ impl FigureMgr {
item,
light_emitter,
mountings,
collider,
),
) in (
&ecs.entities(),
@ -617,6 +645,7 @@ impl FigureMgr {
ecs.read_storage::<Item>().maybe(),
ecs.read_storage::<LightEmitter>().maybe(),
ecs.read_storage::<Mounting>().maybe(),
ecs.read_storage::<Collider>().maybe(),
)
.join()
.enumerate()
@ -794,6 +823,7 @@ impl FigureMgr {
&mut self.col_lights,
body,
inventory,
(),
tick,
player_camera_mode,
player_character_state,
@ -1670,6 +1700,7 @@ impl FigureMgr {
&mut self.col_lights,
body,
inventory,
(),
tick,
player_camera_mode,
player_character_state,
@ -1859,6 +1890,7 @@ impl FigureMgr {
&mut self.col_lights,
body,
inventory,
(),
tick,
player_camera_mode,
player_character_state,
@ -2173,6 +2205,7 @@ impl FigureMgr {
&mut self.col_lights,
body,
inventory,
(),
tick,
player_camera_mode,
player_character_state,
@ -2519,6 +2552,7 @@ impl FigureMgr {
&mut self.col_lights,
body,
inventory,
(),
tick,
player_camera_mode,
player_character_state,
@ -2620,6 +2654,7 @@ impl FigureMgr {
&mut self.col_lights,
body,
inventory,
(),
tick,
player_camera_mode,
player_character_state,
@ -2700,6 +2735,7 @@ impl FigureMgr {
&mut self.col_lights,
body,
inventory,
(),
tick,
player_camera_mode,
player_character_state,
@ -3096,6 +3132,7 @@ impl FigureMgr {
&mut self.col_lights,
body,
inventory,
(),
tick,
player_camera_mode,
player_character_state,
@ -3180,6 +3217,7 @@ impl FigureMgr {
&mut self.col_lights,
body,
inventory,
(),
tick,
player_camera_mode,
player_character_state,
@ -3356,6 +3394,7 @@ impl FigureMgr {
&mut self.col_lights,
body,
inventory,
(),
tick,
player_camera_mode,
player_character_state,
@ -3676,6 +3715,7 @@ impl FigureMgr {
&mut self.col_lights,
body,
inventory,
(),
tick,
player_camera_mode,
player_character_state,
@ -3756,6 +3796,7 @@ impl FigureMgr {
&mut self.col_lights,
body,
inventory,
(),
tick,
player_camera_mode,
player_character_state,
@ -4375,6 +4416,7 @@ impl FigureMgr {
&mut self.col_lights,
body,
inventory,
(),
tick,
player_camera_mode,
player_character_state,
@ -4613,6 +4655,7 @@ impl FigureMgr {
&mut self.col_lights,
body,
inventory,
(),
tick,
player_camera_mode,
player_character_state,
@ -4733,16 +4776,57 @@ impl FigureMgr {
);
},
Body::Ship(body) => {
let (model, skeleton_attr) = self.ship_model_cache.get_or_create_model(
renderer,
&mut self.col_lights,
body,
inventory,
tick,
player_camera_mode,
player_character_state,
&slow_jobs,
);
let (model, skeleton_attr) = if let Some(Collider::Volume(vol)) = collider {
let vk = VolumeKey {
entity,
mut_count: vol.mut_count,
};
let (model, _skeleton_attr) = self.volume_model_cache.get_or_create_model(
renderer,
&mut self.col_lights,
vk,
inventory,
Arc::clone(vol),
tick,
player_camera_mode,
player_character_state,
&slow_jobs,
);
let state = self
.states
.volume_states
.entry(entity)
.or_insert_with(|| FigureState::new(renderer, vk, vk));
state.update(
renderer,
&mut update_buf,
&common_params,
state_animation_rate,
model,
vk,
);
break;
} else if body.manifest_entry().is_some() {
self.ship_model_cache.get_or_create_model(
renderer,
&mut self.col_lights,
body,
inventory,
(),
tick,
player_camera_mode,
player_character_state,
&slow_jobs,
)
} else {
// No way to determine model (this is okay, we might just not have received
// the `Collider` for the entity yet. Wait until the
// next tick.
break;
};
let state = self.states.ship_states.entry(entity).or_insert_with(|| {
FigureState::new(renderer, ShipSkeleton::default(), body)
@ -4847,11 +4931,12 @@ impl FigureMgr {
ecs.read_storage::<Health>().maybe(),
ecs.read_storage::<Inventory>().maybe(),
ecs.read_storage::<Scale>().maybe(),
ecs.read_storage::<Collider>().maybe(),
)
.join()
// Don't render dead entities
.filter(|(_, _, _, _, health, _, _)| health.map_or(true, |h| !h.is_dead))
.for_each(|(entity, pos, _, body, _, inventory, scale)| {
.filter(|(_, _, _, _, health, _, _, _)| health.map_or(true, |h| !h.is_dead))
.for_each(|(entity, pos, _, body, _, inventory, scale, collider)| {
if let Some((bound, model, _)) = self.get_model_for_render(
tick,
camera,
@ -4862,6 +4947,10 @@ impl FigureMgr {
false,
pos.0,
figure_lod_render_distance * scale.map_or(1.0, |s| s.0),
match collider {
Some(Collider::Volume(vol)) => vol.mut_count,
_ => 0,
},
|state| state.can_shadow_sun(),
) {
drawer.draw(model, bound);
@ -4884,19 +4973,20 @@ impl FigureMgr {
let character_state_storage = state.read_storage::<common::comp::CharacterState>();
let character_state = character_state_storage.get(player_entity);
for (entity, pos, body, _, inventory, scale) in (
for (entity, pos, body, _, inventory, scale, collider) in (
&ecs.entities(),
&ecs.read_storage::<Pos>(),
&ecs.read_storage::<Body>(),
ecs.read_storage::<Health>().maybe(),
ecs.read_storage::<Inventory>().maybe(),
ecs.read_storage::<Scale>().maybe()
ecs.read_storage::<Scale>().maybe(),
ecs.read_storage::<Collider>().maybe(),
)
.join()
// Don't render dead entities
.filter(|(_, _, _, health, _, _)| health.map_or(true, |h| !h.is_dead))
.filter(|(_, _, _, health, _, _, _)| health.map_or(true, |h| !h.is_dead))
// Don't render player
.filter(|(entity, _, _, _, _, _)| *entity != player_entity)
.filter(|(entity, _, _, _, _, _, _)| *entity != player_entity)
{
if let Some((bound, model, col_lights)) = self.get_model_for_render(
tick,
@ -4908,6 +4998,10 @@ impl FigureMgr {
false,
pos.0,
figure_lod_render_distance * scale.map_or(1.0, |s| s.0),
match collider {
Some(Collider::Volume(vol)) => vol.mut_count,
_ => 0,
},
|state| state.visible(),
) {
drawer.draw(model, bound, col_lights);
@ -4953,6 +5047,7 @@ impl FigureMgr {
true,
pos.0,
figure_lod_render_distance,
0,
|state| state.visible(),
) {
drawer.draw(model, bound, col_lights);
@ -4980,6 +5075,7 @@ impl FigureMgr {
is_player: bool,
pos: vek::Vec3<f32>,
figure_lod_render_distance: f32,
mut_count: usize,
filter_state: impl Fn(&FigureStateMeta) -> bool,
) -> Option<FigureModelRef> {
let body = *body;
@ -5010,6 +5106,7 @@ impl FigureMgr {
object_model_cache,
ship_model_cache,
golem_model_cache,
volume_model_cache,
states:
FigureMgrStates {
character_states,
@ -5027,6 +5124,7 @@ impl FigureMgr {
golem_states,
object_states,
ship_states,
volume_states,
},
} = self;
let col_lights = &*col_lights_;
@ -5255,22 +5353,43 @@ impl FigureMgr {
),
)
}),
Body::Ship(body) => ship_states
.get(&entity)
.filter(|state| filter_state(*state))
.map(move |state| {
(
state.bound(),
ship_model_cache.get_model(
col_lights,
body,
inventory,
tick,
player_camera_mode,
character_state,
),
)
}),
Body::Ship(body) => {
if body.manifest_entry().is_some() {
ship_states
.get(&entity)
.filter(|state| filter_state(*state))
.map(move |state| {
(
state.bound(),
ship_model_cache.get_model(
col_lights,
body,
inventory,
tick,
player_camera_mode,
character_state,
),
)
})
} else {
volume_states
.get(&entity)
.filter(|state| filter_state(*state))
.map(move |state| {
(
state.bound(),
volume_model_cache.get_model(
col_lights,
VolumeKey { entity, mut_count },
inventory,
tick,
player_camera_mode,
character_state,
),
)
})
}
},
} {
let model_entry = model_entry?;

View File

@ -0,0 +1,99 @@
use super::{
cache::FigureKey,
load::{BodySpec, BoneMeshes},
EcsEntity,
};
use common::{
assets,
comp::ship::figuredata::VoxelCollider,
figure::{Cell, Segment},
vol::ReadVol,
};
use std::{convert::TryFrom, sync::Arc};
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct VolumeKey {
pub entity: EcsEntity,
pub mut_count: usize,
}
impl<'a> From<&'a Self> for VolumeKey {
fn from(this: &Self) -> Self { *this }
}
impl anim::Skeleton for VolumeKey {
type Attr = Self;
type Body = Self;
const BONE_COUNT: usize = 4;
//#[cfg(feature = "use-dyn-lib")]
// TODO
fn compute_matrices_inner(
&self,
base_mat: anim::vek::Mat4<f32>,
buf: &mut [anim::FigureBoneData; anim::MAX_BONE_COUNT],
_: Self::Body,
) -> anim::Offsets {
let scale_mat = anim::vek::Mat4::scaling_3d(1.0 / 11.0);
let bone = base_mat * scale_mat;
*(<&mut [_; Self::BONE_COUNT]>::try_from(&mut buf[0..Self::BONE_COUNT]).unwrap()) = [
anim::make_bone(bone),
anim::make_bone(bone),
anim::make_bone(bone),
anim::make_bone(bone),
];
anim::Offsets {
lantern: None,
mount_bone: anim::vek::Transform::default(),
}
}
}
impl BodySpec for VolumeKey {
type Extra = Arc<VoxelCollider>;
type Manifests = ();
type Spec = ();
fn load_spec() -> Result<Self::Manifests, assets::Error> { Ok(()) }
fn is_reloaded(_: &mut Self::Manifests) -> bool { false }
fn bone_meshes(
_: &FigureKey<Self>,
_: &Self::Manifests,
collider: Self::Extra,
) -> [Option<BoneMeshes>; anim::MAX_BONE_COUNT] {
println!("Generating segment...");
[
Some((
Segment::from_fn(collider.volume().sz, (), |pos| {
match collider.volume().get(pos).unwrap().get_color() {
Some(col) => Cell::new(col, false, false, false),
None => Cell::Empty,
}
}),
-collider.volume().sz.map(|e| e as f32) / 2.0,
)),
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
]
}
}

View File

@ -1198,7 +1198,9 @@ impl Scene {
let hb_ori = [ori.x, ori.y, ori.z, ori.w];
self.debug.set_context(*shape_id, hb_pos, color, hb_ori);
},
comp::Collider::Voxel { .. } | comp::Collider::Point => {
comp::Collider::Voxel { .. }
| comp::Collider::Volume(_)
| comp::Collider::Point => {
// ignore terrain-like or point-hitboxes
},
}

View File

@ -320,6 +320,7 @@ impl Scene {
&mut self.col_lights,
body,
inventory,
(),
scene_data.tick,
CameraMode::default(),
None,

View File

@ -137,10 +137,10 @@ impl Civs {
SiteKind::Tree => (12i32, 8.0),
};
let (raise, raise_dist): (f32, i32) = match &site.kind {
SiteKind::Settlement => (10.0, 6),
SiteKind::Castle => (0.0, 6),
_ => (0.0, 0),
let (raise, raise_dist, make_waypoint): (f32, i32, bool) = match &site.kind {
SiteKind::Settlement => (10.0, 6, true),
SiteKind::Castle => (0.0, 6, true),
_ => (0.0, 0, false),
};
// Flatten ground
@ -173,6 +173,10 @@ impl Civs {
chunk.basement += diff;
chunk.rockiness = 0.0;
chunk.surface_veg *= 1.0 - factor * rng.gen_range(0.25..0.9);
if make_waypoint && offs == Vec2::zero() {
chunk.contains_waypoint = true;
}
});
}
}