Merge branch 'zesterer/bombs' into 'master'

Added bombs, training dummies, throwable items, more block kinds, weaker explosions

See merge request veloren/veloren!1155
This commit is contained in:
Joshua Barretto 2020-07-05 16:45:19 +00:00
commit 616dd29df1
34 changed files with 367 additions and 104 deletions

View File

@ -38,6 +38,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- New quadruped_low body for reptile-likes - New quadruped_low body for reptile-likes
- Added new animals - Added new animals
- Better pathfinding - Better pathfinding
- Bombs
- Training dummy items
### Changed ### Changed
@ -57,6 +59,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Switch to a new network backend that will allow several improvements in the future - Switch to a new network backend that will allow several improvements in the future
- Connection screen fails after 4 minutes if it can't connect to the server instead of 80 minutes - Connection screen fails after 4 minutes if it can't connect to the server instead of 80 minutes
- Rebuilt quadruped_medium animation and assets - Rebuilt quadruped_medium animation and assets
- Disabled destruction of most blocks by explosions
### Removed ### Removed

View File

@ -0,0 +1,7 @@
Item(
name: "Bomb",
description: "Boom!\n\n<Right-Click to use>",
kind: Throwable(
kind: Bomb,
),
)

View File

@ -0,0 +1,8 @@
Item(
name: "Bomb",
description: "Boom!\n\n<Right-Click to use>",
kind: Throwable(
kind: Bomb,
amount: 10,
),
)

View File

@ -0,0 +1,7 @@
Item(
name: "Training Dummy",
description: "His name is William. Fire at will.\n\n<Right-Click to use>",
kind: Throwable(
kind: TrainingDummy,
),
)

View File

@ -1,10 +1,16 @@
[ [
// All loot rates go here // All loot rates go here
// food
(3, "common.items.cheese"),
(3, "common.items.apple"),
(3, "common.items.mushroom"),
// miscellaneous // miscellaneous
(0.4, "common.items.velorite"), (0.4, "common.items.velorite"),
(0.6, "common.items.veloritefrag"), (0.6, "common.items.veloritefrag"),
(1.5, "common.items.potion_minor"), (1.5, "common.items.potion_minor"),
(1, "common.items.collar"), (0.5, "common.items.collar"),
(0.5, "common.items.bomb_pile"),
(1, "common.items.bomb"),
// swords // swords
(0.1, "common.items.weapons.sword.starter_sword"), (0.1, "common.items.weapons.sword.starter_sword"),
(0.1, "common.items.weapons.sword.wood_sword"), (0.1, "common.items.weapons.sword.wood_sword"),

View File

@ -200,7 +200,7 @@
Tool(Staff(Sceptre)): VoxTrans( Tool(Staff(Sceptre)): VoxTrans(
"voxel.weapon.staff.wood-nature", "voxel.weapon.staff.wood-nature",
(1.0, -1.0, 0.0), (-310., 90.0, 0.0), 1.2, (1.0, -1.0, 0.0), (-310., 90.0, 0.0), 1.2,
), ),
// Shields // Shields
Tool(Shield(BasicShield)): VoxTrans( Tool(Shield(BasicShield)): VoxTrans(
"voxel.weapon.shield.wood-0", "voxel.weapon.shield.wood-0",
@ -219,11 +219,11 @@
Lantern(Red0): Png( Lantern(Red0): Png(
"element.icons.lantern_red-0", "element.icons.lantern_red-0",
), ),
// Farming Equipment // Farming Equipment
Tool(Farming(Broom)): VoxTrans( Tool(Farming(Broom)): VoxTrans(
"voxel.weapon.tool.broom-0", "voxel.weapon.tool.broom-0",
(0.0, 0.0, 0.0), (-135.0, 90.0, 0.0), 1.1, (0.0, 0.0, 0.0), (-135.0, 90.0, 0.0), 1.1,
), ),
Tool(Farming(Hoe0)): VoxTrans( Tool(Farming(Hoe0)): VoxTrans(
"voxel.weapon.tool.hoe_green", "voxel.weapon.tool.hoe_green",
(0.0, 0.0, 0.0), (130.0, 35.0, 180.0), 1.0, (0.0, 0.0, 0.0), (130.0, 35.0, 180.0), 1.0,
@ -231,7 +231,7 @@
Tool(Farming(Hoe1)): VoxTrans( Tool(Farming(Hoe1)): VoxTrans(
"voxel.weapon.tool.hoe_blue", "voxel.weapon.tool.hoe_blue",
(0.0, 0.0, 0.0), (130.0, 35.0, 180.0), 1.0, (0.0, 0.0, 0.0), (130.0, 35.0, 180.0), 1.0,
), ),
Tool(Farming(Pitchfork)): VoxTrans( Tool(Farming(Pitchfork)): VoxTrans(
"voxel.weapon.tool.pitchfork-0", "voxel.weapon.tool.pitchfork-0",
(0.0, 0.0, 0.0), (-135.0, 90.0, 0.0), 1.1, (0.0, 0.0, 0.0), (-135.0, 90.0, 0.0), 1.1,
@ -255,7 +255,7 @@
Tool(Farming(Shovel0)): VoxTrans( Tool(Farming(Shovel0)): VoxTrans(
"voxel.weapon.tool.shovel_green", "voxel.weapon.tool.shovel_green",
(0.0, 0.0, 0.0), (-135.0, 90.0, 0.0), 1.2, (0.0, 0.0, 0.0), (-135.0, 90.0, 0.0), 1.2,
), ),
Tool(Farming(Shovel1)): VoxTrans( Tool(Farming(Shovel1)): VoxTrans(
"voxel.weapon.tool.shovel_gold", "voxel.weapon.tool.shovel_gold",
(0.0, 0.0, 0.0), (-135.0, 90.0, 0.0), 1.2, (0.0, 0.0, 0.0), (-135.0, 90.0, 0.0), 1.2,
@ -264,7 +264,7 @@
// Other // Other
Utility(Collar): Png( Utility(Collar): Png(
"element.icons.collar", "element.icons.collar",
), ),
// Armor // Armor
// Starter Parts // Starter Parts
Armor(Foot(Sandal0)): VoxTrans( Armor(Foot(Sandal0)): VoxTrans(
@ -311,7 +311,7 @@
Armor(Pants(CultistBlue)): VoxTrans( Armor(Pants(CultistBlue)): VoxTrans(
"voxel.armor.pants.cultist", "voxel.armor.pants.cultist",
(0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.2, (0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.2,
), ),
Armor(Hand(CultistBlue)): VoxTrans( Armor(Hand(CultistBlue)): VoxTrans(
"voxel.armor.hand.cultist_right", "voxel.armor.hand.cultist_right",
(0.0, -1.0, 0.0), (-90.0, 135.0, 0.0), 1.0, (0.0, -1.0, 0.0), (-90.0, 135.0, 0.0), 1.0,
@ -680,7 +680,7 @@
Armor(Shoulder(LeafyShoulder)): VoxTrans( Armor(Shoulder(LeafyShoulder)): VoxTrans(
"voxel.armor.shoulder.twigsleaves_shoulder_right", "voxel.armor.shoulder.twigsleaves_shoulder_right",
(0.0, 0.0, 0.0), (-90.0, 130.0, 0.0), 1.2, (0.0, 0.0, 0.0), (-90.0, 130.0, 0.0), 1.2,
), ),
//TwigsFlowers Set //TwigsFlowers Set
Armor(Chest(Twigsflowers)): VoxTrans( Armor(Chest(Twigsflowers)): VoxTrans(
"voxel.armor.chest.twigsflowers_chest", "voxel.armor.chest.twigsflowers_chest",
@ -710,7 +710,7 @@
Armor(Pants(Hunting)): VoxTrans( Armor(Pants(Hunting)): VoxTrans(
"voxel.armor.pants.grayscale", "voxel.armor.pants.grayscale",
(0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.2, (0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.2,
), ),
// Backs // Backs
Armor(Back(Short0)): VoxTrans( Armor(Back(Short0)): VoxTrans(
"voxel.armor.back.short-0", "voxel.armor.back.short-0",
@ -722,15 +722,15 @@
), ),
// Rings // Rings
Armor(Ring(Ring0)): Png( Armor(Ring(Ring0)): Png(
"element.icons.ring-0", "element.icons.ring-0",
), ),
// Necks // Necks
Armor(Neck(Neck0)): Png( Armor(Neck(Neck0)): Png(
"element.icons.neck-0", "element.icons.neck-0",
), ),
// Tabards // Tabards
Armor(Tabard(Admin)): Png( Armor(Tabard(Admin)): Png(
"element.icons.tabard_admin", "element.icons.tabard_admin",
), ),
// Heads // Heads
Armor(Head(Leather0)): VoxTrans( Armor(Head(Leather0)): VoxTrans(
@ -746,7 +746,7 @@
VoxTrans( VoxTrans(
"element.icons.item_apple", "element.icons.item_apple",
(0.0, 0.0, 0.0), (-90.0, 90.0, 0.0), 1.0, (0.0, 0.0, 0.0), (-90.0, 90.0, 0.0), 1.0,
), ),
Consumable(Coconut): Png( Consumable(Coconut): Png(
"element.icons.item_coconut", "element.icons.item_coconut",
), ),
@ -761,7 +761,7 @@
Consumable(Cheese): VoxTrans( Consumable(Cheese): VoxTrans(
"element.icons.item_cheese", "element.icons.item_cheese",
(0.0, 0.0, 0.0), (-90.0, 90.0, 0.0), 0.9, (0.0, 0.0, 0.0), (-90.0, 90.0, 0.0), 0.9,
), ),
Consumable(Potion): VoxTrans( Consumable(Potion): VoxTrans(
"voxel.object.potion_red", "voxel.object.potion_red",
(0.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 1.0, (0.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 1.0,
@ -778,6 +778,15 @@
"voxel.sprite.velorite.velorite_1", "voxel.sprite.velorite.velorite_1",
(0.0, -1.0, 0.0), (-50.0, 40.0, 20.0), 0.8, (0.0, -1.0, 0.0), (-50.0, 40.0, 20.0), 0.8,
), ),
// Throwables
Throwable(Bomb): VoxTrans(
"voxel.object.bomb",
(0.0, -1.0, 0.0), (-50.0, 40.0, 20.0), 0.8,
),
Throwable(TrainingDummy): VoxTrans(
"voxel.object.training_dummy",
(0.0, -1.0, 0.0), (-50.0, 40.0, 20.0), 0.8,
),
// Ingredients // Ingredients
Ingredient(Flower): VoxTrans( Ingredient(Flower): VoxTrans(
"voxel.sprite.flowers.flower_red_2", "voxel.sprite.flowers.flower_red_2",
@ -791,5 +800,5 @@
Tool(Debug(Boost)): VoxTrans( Tool(Debug(Boost)): VoxTrans(
"voxel.weapon.tool.broom_belzeshrub_purple", "voxel.weapon.tool.broom_belzeshrub_purple",
(0.0, 0.0, 0.0), (-135.0, 90.0, 0.0), 1.1, (0.0, 0.0, 0.0), (-135.0, 90.0, 0.0), 1.1,
), ),
}) })

View File

@ -123,6 +123,10 @@ pub const ALL_OBJECTS: [Body; 54] = [
Body::TrainingDummy, Body::TrainingDummy,
]; ];
impl From<Body> for super::Body {
fn from(body: Body) -> Self { super::Body::Object(body) }
}
impl Body { impl Body {
pub fn to_string(&self) -> &str { pub fn to_string(&self) -> &str {
match self { match self {

View File

@ -28,6 +28,12 @@ pub enum Consumable {
PotionExp, PotionExp,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Throwable {
Bomb,
TrainingDummy,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Utility { pub enum Utility {
Collar, Collar,
@ -86,6 +92,11 @@ pub enum ItemKind {
#[serde(default = "default_amount")] #[serde(default = "default_amount")]
amount: u32, amount: u32,
}, },
Throwable {
kind: Throwable,
#[serde(default = "default_amount")]
amount: u32,
},
Utility { Utility {
kind: Utility, kind: Utility,
#[serde(default = "default_amount")] #[serde(default = "default_amount")]
@ -129,15 +140,10 @@ impl Item {
pub fn set_amount(&mut self, give_amount: u32) -> Result<(), assets::Error> { pub fn set_amount(&mut self, give_amount: u32) -> Result<(), assets::Error> {
use ItemKind::*; use ItemKind::*;
match self.kind { match self.kind {
Consumable { ref mut amount, .. } => { Consumable { ref mut amount, .. }
*amount = give_amount; | Throwable { ref mut amount, .. }
Ok(()) | Utility { ref mut amount, .. }
}, | Ingredient { ref mut amount, .. } => {
Utility { ref mut amount, .. } => {
*amount = give_amount;
Ok(())
},
Ingredient { ref mut amount, .. } => {
*amount = give_amount; *amount = give_amount;
Ok(()) Ok(())
}, },

View File

@ -102,6 +102,37 @@ impl Inventory {
// It didn't work // It didn't work
self.add_to_first_empty(item) self.add_to_first_empty(item)
}, },
ItemKind::Throwable {
kind: item_kind,
amount: new_amount,
..
} => {
for slot in &mut self.slots {
if slot
.as_ref()
.map(|s| s.name() == item.name())
.unwrap_or(false)
&& slot
.as_ref()
.map(|s| s.description() == item.description())
.unwrap_or(false)
{
if let Some(Item {
kind: ItemKind::Throwable { kind, amount, .. },
..
}) = slot
{
if item_kind == *kind {
*amount += new_amount;
self.recount_items();
return None;
}
}
}
}
// It didn't work
self.add_to_first_empty(item)
},
ItemKind::Ingredient { ItemKind::Ingredient {
kind: item_kind, kind: item_kind,
amount: new_amount, amount: new_amount,
@ -277,6 +308,19 @@ impl Inventory {
Some(return_item) Some(return_item)
} }
}, },
ItemKind::Throwable { kind, amount } => {
if *amount <= 1 {
self.remove(cell)
} else {
*amount -= 1;
return_item.kind = ItemKind::Throwable {
kind: *kind,
amount: 1,
};
self.recount_items();
Some(return_item)
}
},
ItemKind::Ingredient { kind, amount } => { ItemKind::Ingredient { kind, amount } => {
if *amount <= 1 { if *amount <= 1 {
self.remove(cell) self.remove(cell)

12
common/src/comp/misc.rs Normal file
View File

@ -0,0 +1,12 @@
use crate::sync::Uid;
use specs::Component;
use specs_idvs::IDVStorage;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Object {
Bomb { owner: Option<Uid> },
}
impl Component for Object {
type Storage = IDVStorage<Self>;
}

View File

@ -10,6 +10,7 @@ mod inputs;
mod inventory; mod inventory;
mod last; mod last;
mod location; mod location;
mod misc;
mod phys; mod phys;
mod player; mod player;
pub mod projectile; pub mod projectile;
@ -40,6 +41,7 @@ pub use inventory::{
}; };
pub use last::Last; pub use last::Last;
pub use location::{Waypoint, WaypointArea}; pub use location::{Waypoint, WaypointArea};
pub use misc::Object;
pub use phys::{Collider, ForceUpdate, Gravity, Mass, Ori, PhysicsState, Pos, Scale, Sticky, Vel}; pub use phys::{Collider, ForceUpdate, Gravity, Mass, Ori, PhysicsState, Pos, Scale, Sticky, Vel};
pub use player::Player; pub use player::Player;
pub use projectile::Projectile; pub use projectile::Projectile;

View File

@ -78,6 +78,15 @@ pub struct PhysicsState {
pub in_fluid: bool, pub in_fluid: bool,
} }
impl PhysicsState {
pub fn on_surface(&self) -> Option<Vec3<f32>> {
self.on_ground
.then_some(-Vec3::unit_z())
.or_else(|| self.on_ceiling.then_some(Vec3::unit_z()))
.or(self.on_wall)
}
}
impl Component for PhysicsState { impl Component for PhysicsState {
type Storage = FlaggedStorage<Self, IDVStorage<Self>>; type Storage = FlaggedStorage<Self, IDVStorage<Self>>;
} }

View File

@ -70,7 +70,7 @@ pub enum ServerEvent {
stats: comp::Stats, stats: comp::Stats,
loadout: comp::Loadout, loadout: comp::Loadout,
body: comp::Body, body: comp::Body,
agent: comp::Agent, agent: Option<comp::Agent>,
alignment: comp::Alignment, alignment: comp::Alignment,
scale: comp::Scale, scale: comp::Scale,
drop_item: Option<Item>, drop_item: Option<Item>,

View File

@ -12,6 +12,7 @@ pub struct EntityInfo {
pub pos: Vec3<f32>, pub pos: Vec3<f32>,
pub is_waypoint: bool, // Edge case, overrides everything else pub is_waypoint: bool, // Edge case, overrides everything else
pub is_giant: bool, pub is_giant: bool,
pub has_agency: bool,
pub alignment: Alignment, pub alignment: Alignment,
pub body: Body, pub body: Body,
pub name: Option<String>, pub name: Option<String>,
@ -28,6 +29,7 @@ impl EntityInfo {
pos, pos,
is_waypoint: false, is_waypoint: false,
is_giant: false, is_giant: false,
has_agency: true,
alignment: Alignment::Wild, alignment: Alignment::Wild,
body: Body::Humanoid(humanoid::Body::random()), body: Body::Humanoid(humanoid::Body::random()),
name: None, name: None,
@ -66,8 +68,13 @@ impl EntityInfo {
self self
} }
pub fn with_name(mut self, name: String) -> Self { pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name); self.name = Some(name.into());
self
}
pub fn with_agency(mut self, agency: bool) -> Self {
self.has_agency = agency;
self self
} }

View File

@ -2,9 +2,9 @@ use crate::vol::{ReadVol, Vox};
use vek::*; use vek::*;
pub trait RayUntil<V: Vox> = FnMut(&V) -> bool; pub trait RayUntil<V: Vox> = FnMut(&V) -> bool;
pub trait RayForEach = FnMut(Vec3<i32>); pub trait RayForEach<V: Vox> = FnMut(&V, Vec3<i32>);
pub struct Ray<'a, V: ReadVol, F: RayUntil<V::Vox>, G: RayForEach> { pub struct Ray<'a, V: ReadVol, F: RayUntil<V::Vox>, G: RayForEach<V::Vox>> {
vol: &'a V, vol: &'a V,
from: Vec3<f32>, from: Vec3<f32>,
to: Vec3<f32>, to: Vec3<f32>,
@ -14,7 +14,7 @@ pub struct Ray<'a, V: ReadVol, F: RayUntil<V::Vox>, G: RayForEach> {
ignore_error: bool, ignore_error: bool,
} }
impl<'a, V: ReadVol, F: RayUntil<V::Vox>, G: RayForEach> Ray<'a, V, F, G> { impl<'a, V: ReadVol, F: RayUntil<V::Vox>, G: RayForEach<V::Vox>> Ray<'a, V, F, G> {
pub fn new(vol: &'a V, from: Vec3<f32>, to: Vec3<f32>, until: F) -> Self { pub fn new(vol: &'a V, from: Vec3<f32>, to: Vec3<f32>, until: F) -> Self {
Self { Self {
vol, vol,
@ -29,7 +29,7 @@ impl<'a, V: ReadVol, F: RayUntil<V::Vox>, G: RayForEach> Ray<'a, V, F, G> {
pub fn until(self, f: F) -> Ray<'a, V, F, G> { Ray { until: f, ..self } } pub fn until(self, f: F) -> Ray<'a, V, F, G> { Ray { until: f, ..self } }
pub fn for_each<H: RayForEach>(self, f: H) -> Ray<'a, V, F, H> { pub fn for_each<H: RayForEach<V::Vox>>(self, f: H) -> Ray<'a, V, F, H> {
Ray { Ray {
for_each: Some(f), for_each: Some(f),
vol: self.vol, vol: self.vol,
@ -69,12 +69,16 @@ impl<'a, V: ReadVol, F: RayUntil<V::Vox>, G: RayForEach> Ray<'a, V, F, G> {
break; break;
} }
let vox = self.vol.get(ipos);
// for_each // for_each
if let Some(g) = &mut self.for_each { if let Some(g) = &mut self.for_each {
g(ipos); if let Ok(vox) = vox {
g(vox, ipos);
}
} }
match self.vol.get(ipos).map(|vox| (vox, (self.until)(vox))) { match vox.map(|vox| (vox, (self.until)(vox))) {
Ok((vox, true)) => return (dist, Ok(Some(vox))), Ok((vox, true)) => return (dist, Ok(Some(vox))),
Err(err) if !self.ignore_error => return (dist, Err(err)), Err(err) if !self.ignore_error => return (dist, Err(err)),
_ => {}, _ => {},

View File

@ -122,6 +122,7 @@ impl State {
ecs.register::<comp::Sticky>(); ecs.register::<comp::Sticky>();
ecs.register::<comp::Gravity>(); ecs.register::<comp::Gravity>();
ecs.register::<comp::CharacterState>(); ecs.register::<comp::CharacterState>();
ecs.register::<comp::Object>();
// Register components send from clients -> server // Register components send from clients -> server
ecs.register::<comp::Controller>(); ecs.register::<comp::Controller>();

View File

@ -85,7 +85,7 @@ impl<'a> System<'a> for Sys {
&orientations, &orientations,
scales.maybe(), scales.maybe(),
agents.maybe(), agents.maybe(),
&character_states, character_states.maybe(),
&stats, &stats,
&bodies, &bodies,
) )
@ -129,7 +129,7 @@ impl<'a> System<'a> for Sys {
} }
// Block // Block
if character_b.is_block() if character_b.map(|c_b| c_b.is_block()).unwrap_or(false)
&& ori_b.0.angle_between(pos.0 - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0 && ori_b.0.angle_between(pos.0 - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0
{ {
healthchange *= 1.0 - BLOCK_EFFICIENCY healthchange *= 1.0 - BLOCK_EFFICIENCY

View File

@ -96,11 +96,7 @@ impl<'a> System<'a> for Sys {
{ {
let mut physics_state = physics_states.get(entity).cloned().unwrap_or_default(); let mut physics_state = physics_states.get(entity).cloned().unwrap_or_default();
if sticky.is_some() if sticky.is_some() && physics_state.on_surface().is_some() {
&& (physics_state.on_ground
|| physics_state.on_ceiling
|| physics_state.on_wall.is_some())
{
vel.0 = Vec3::zero(); vel.0 = Vec3::zero();
continue; continue;
} }

View File

@ -33,16 +33,8 @@ impl<'a> System<'a> for Sys {
} }
stats.set_event_emission(true); stats.set_event_emission(true);
// Mutates all stats every tick causing the server to resend this component for // Update stats
// every entity every tick for (entity, mut stats) in (&entities, &mut stats.restrict_mut()).join() {
for (entity, character_state, mut stats, mut energy) in (
&entities,
&character_states,
&mut stats.restrict_mut(),
&mut energies.restrict_mut(),
)
.join()
{
let (set_dead, level_up) = { let (set_dead, level_up) = {
let stat = stats.get_unchecked(); let stat = stats.get_unchecked();
( (
@ -74,7 +66,12 @@ impl<'a> System<'a> for Sys {
stat.health stat.health
.set_to(stat.health.maximum(), HealthSource::LevelUp); .set_to(stat.health.maximum(), HealthSource::LevelUp);
} }
}
// Update energies
for (character_state, mut energy) in
(&character_states, &mut energies.restrict_mut()).join()
{
match character_state { match character_state {
// Accelerate recharging energy. // Accelerate recharging energy.
CharacterState::Idle { .. } CharacterState::Idle { .. }

View File

@ -9,6 +9,9 @@ pub enum BlockKind {
Air, Air,
Normal, Normal,
Dense, Dense,
Rock,
Grass,
Leaves,
Water, Water,
LargeCactus, LargeCactus,
BarrelCactus, BarrelCactus,
@ -32,7 +35,6 @@ pub enum BlockKind {
Velorite, Velorite,
VeloriteFrag, VeloriteFrag,
Chest, Chest,
Leaves,
Pumpkin, Pumpkin,
Welwitch, Welwitch,
LingonBerry, LingonBerry,
@ -325,6 +327,13 @@ impl BlockKind {
} }
} }
pub fn is_explodable(&self) -> bool {
match self {
BlockKind::Leaves | BlockKind::Grass | BlockKind::Rock => true,
_ => false,
}
}
// TODO: Integrate this into `is_solid` by returning an `Option<f32>` // TODO: Integrate this into `is_solid` by returning an `Option<f32>`
pub fn get_height(&self) -> f32 { pub fn get_height(&self) -> f32 {
// Beware: the height *must* be <= `MAX_HEIGHT` or the collision system will not // Beware: the height *must* be <= `MAX_HEIGHT` or the collision system will not

View File

@ -102,7 +102,7 @@ pub trait ReadVol: BaseVol {
&'a self, &'a self,
from: Vec3<f32>, from: Vec3<f32>,
to: Vec3<f32>, to: Vec3<f32>,
) -> Ray<'a, Self, fn(&Self::Vox) -> bool, fn(Vec3<i32>)> ) -> Ray<'a, Self, fn(&Self::Vox) -> bool, fn(&Self::Vox, Vec3<i32>)>
where where
Self: Sized, Self: Sized,
{ {

View File

@ -31,7 +31,7 @@ pub fn handle_create_npc(
stats: Stats, stats: Stats,
loadout: Loadout, loadout: Loadout,
body: Body, body: Body,
agent: Agent, agent: impl Into<Option<Agent>>,
alignment: Alignment, alignment: Alignment,
scale: Scale, scale: Scale,
drop_item: Option<Item>, drop_item: Option<Item>,
@ -39,10 +39,15 @@ pub fn handle_create_npc(
let entity = server let entity = server
.state .state
.create_npc(pos, stats, loadout, body) .create_npc(pos, stats, loadout, body)
.with(agent)
.with(scale) .with(scale)
.with(alignment); .with(alignment);
let entity = if let Some(agent) = agent.into() {
entity.with(agent)
} else {
entity
};
let entity = if let Some(drop_item) = drop_item { let entity = if let Some(drop_item) = drop_item {
entity.with(ItemDrop(drop_item)) entity.with(ItemDrop(drop_item))
} else { } else {

View File

@ -211,7 +211,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, power: f32, owner: Opti
for (pos_b, ori_b, character_b, stats_b) in ( for (pos_b, ori_b, character_b, stats_b) in (
&ecs.read_storage::<comp::Pos>(), &ecs.read_storage::<comp::Pos>(),
&ecs.read_storage::<comp::Ori>(), &ecs.read_storage::<comp::Ori>(),
&ecs.read_storage::<comp::CharacterState>(), ecs.read_storage::<comp::CharacterState>().maybe(),
&mut ecs.write_storage::<comp::Stats>(), &mut ecs.write_storage::<comp::Stats>(),
) )
.join() .join()
@ -231,7 +231,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, power: f32, owner: Opti
} }
// Block // Block
if character_b.is_block() if character_b.map(|c_b| c_b.is_block()).unwrap_or(false)
&& ori_b.0.angle_between(pos - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0 && ori_b.0.angle_between(pos - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0
{ {
dmg = (dmg as f32 * (1.0 - BLOCK_EFFICIENCY)) as u32 dmg = (dmg as f32 * (1.0 - BLOCK_EFFICIENCY)) as u32
@ -261,7 +261,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, power: f32, owner: Opti
.read_resource::<TerrainGrid>() .read_resource::<TerrainGrid>()
.ray(pos, pos + dir * color_range) .ray(pos, pos + dir * color_range)
.until(|_| rand::random::<f32>() < 0.05) .until(|_| rand::random::<f32>() < 0.05)
.for_each(|pos| touched_blocks.push(pos)) .for_each(|_: &Block, pos| touched_blocks.push(pos))
.cast(); .cast();
} }
@ -292,11 +292,15 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, power: f32, owner: Opti
) )
.normalized(); .normalized();
let _ = ecs let terrain = ecs.read_resource::<TerrainGrid>();
.read_resource::<TerrainGrid>() let _ = terrain
.ray(pos, pos + dir * power) .ray(pos, pos + dir * power)
.until(|_| rand::random::<f32>() < 0.05) .until(|block| block.is_fluid() || rand::random::<f32>() < 0.05)
.for_each(|pos| block_change.set(pos, Block::empty())) .for_each(|block: &Block, pos| {
if block.is_explodable() {
block_change.set(pos, Block::empty());
}
})
.cast(); .cast();
} }
} }

View File

@ -5,7 +5,7 @@ use common::{
slot::{self, Slot}, slot::{self, Slot},
Pos, MAX_PICKUP_RANGE_SQR, Pos, MAX_PICKUP_RANGE_SQR,
}, },
sync::WorldSyncExt, sync::{Uid, WorldSyncExt},
terrain::block::Block, terrain::block::Block,
vol::{ReadVol, Vox}, vol::{ReadVol, Vox},
}; };
@ -33,6 +33,7 @@ pub fn snuff_lantern(storage: &mut WriteStorage<comp::LightEmitter>, entity: Ecs
pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::InventoryManip) { pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::InventoryManip) {
let state = server.state_mut(); let state = server.state_mut();
let mut dropped_items = Vec::new(); let mut dropped_items = Vec::new();
let mut thrown_items = Vec::new();
match manip { match manip {
comp::InventoryManip::Pickup(uid) => { comp::InventoryManip::Pickup(uid) => {
@ -157,6 +158,29 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
maybe_effect = Some(*effect); maybe_effect = Some(*effect);
Some(comp::InventoryUpdateEvent::Consumed(*kind)) Some(comp::InventoryUpdateEvent::Consumed(*kind))
}, },
ItemKind::Throwable { kind, .. } => {
if let Some(pos) =
state.ecs().read_storage::<comp::Pos>().get(entity)
{
thrown_items.push((
*pos,
state
.ecs()
.read_storage::<comp::Vel>()
.get(entity)
.copied()
.unwrap_or_default(),
state
.ecs()
.read_storage::<comp::Ori>()
.get(entity)
.copied()
.unwrap_or_default(),
*kind,
));
}
Some(comp::InventoryUpdateEvent::Used)
},
ItemKind::Utility { ItemKind::Utility {
kind: comp::item::Utility::Collar, kind: comp::item::Utility::Collar,
.. ..
@ -318,6 +342,38 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
.with(comp::Vel(vel)) .with(comp::Vel(vel))
.build(); .build();
} }
// Throw items
for (pos, vel, ori, kind) in thrown_items {
let vel = vel.0
+ *ori.0 * 20.0
+ Vec3::unit_z() * 15.0
+ Vec3::<f32>::zero().map(|_| rand::thread_rng().gen::<f32>() - 0.5) * 4.0;
let uid = state.read_component_cloned::<Uid>(entity);
let mut new_entity = state
.create_object(Default::default(), match kind {
item::Throwable::Bomb => comp::object::Body::Bomb,
item::Throwable::TrainingDummy => comp::object::Body::TrainingDummy,
})
.with(comp::Pos(pos.0 + Vec3::unit_z() * 0.25))
.with(comp::Vel(vel));
match kind {
item::Throwable::Bomb => {
new_entity = new_entity.with(comp::Object::Bomb { owner: uid });
},
item::Throwable::TrainingDummy => {
new_entity = new_entity.with(comp::Stats::new(
"Training Dummy".to_string(),
comp::object::Body::TrainingDummy.into(),
));
},
};
new_entity.build();
}
} }
fn within_pickup_range(player_position: Option<&Pos>, item_position: Option<&Pos>) -> bool { fn within_pickup_range(player_position: Option<&Pos>, item_position: Option<&Pos>) -> bool {

View File

@ -128,7 +128,7 @@ impl StateExt for State {
.with(comp::Vel(Vec3::zero())) .with(comp::Vel(Vec3::zero()))
.with(comp::Ori::default()) .with(comp::Ori::default())
.with(comp::Body::Object(object)) .with(comp::Body::Object(object))
.with(comp::Mass(100.0)) .with(comp::Mass(5.0))
.with(comp::Collider::Box { .with(comp::Collider::Box {
radius: 0.4, radius: 0.4,
z_min: 0.0, z_min: 0.0,

View File

@ -1,5 +1,6 @@
pub mod entity_sync; pub mod entity_sync;
pub mod message; pub mod message;
pub mod object;
pub mod persistence; pub mod persistence;
pub mod sentinel; pub mod sentinel;
pub mod subscription; pub mod subscription;
@ -30,13 +31,15 @@ pub type PersistenceScheduler = SysScheduler<persistence::Sys>;
//const SUBSCRIPTION_SYS: &str = "server_subscription_sys"; //const SUBSCRIPTION_SYS: &str = "server_subscription_sys";
//const TERRAIN_SYNC_SYS: &str = "server_terrain_sync_sys"; //const TERRAIN_SYNC_SYS: &str = "server_terrain_sync_sys";
const TERRAIN_SYS: &str = "server_terrain_sys"; const TERRAIN_SYS: &str = "server_terrain_sys";
const WAYPOINT_SYS: &str = "waypoint_sys"; const WAYPOINT_SYS: &str = "server_waypoint_sys";
const PERSISTENCE_SYS: &str = "persistence_sys"; const PERSISTENCE_SYS: &str = "server_persistence_sys";
const OBJECT_SYS: &str = "server_object_sys";
pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) { pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
dispatch_builder.add(terrain::Sys, TERRAIN_SYS, &[]); dispatch_builder.add(terrain::Sys, TERRAIN_SYS, &[]);
dispatch_builder.add(waypoint::Sys, WAYPOINT_SYS, &[]); dispatch_builder.add(waypoint::Sys, WAYPOINT_SYS, &[]);
dispatch_builder.add(persistence::Sys, PERSISTENCE_SYS, &[]); dispatch_builder.add(persistence::Sys, PERSISTENCE_SYS, &[]);
dispatch_builder.add(object::Sys, OBJECT_SYS, &[]);
} }
pub fn run_sync_systems(ecs: &mut specs::World) { pub fn run_sync_systems(ecs: &mut specs::World) {

48
server/src/sys/object.rs Normal file
View File

@ -0,0 +1,48 @@
use common::{
comp::{HealthSource, Object, PhysicsState, Pos},
event::{EventBus, ServerEvent},
state::DeltaTime,
};
use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage};
/// This system is responsible for handling misc object behaviours
pub struct Sys;
impl<'a> System<'a> for Sys {
#[allow(clippy::type_complexity)]
type SystemData = (
Entities<'a>,
Read<'a, DeltaTime>,
Read<'a, EventBus<ServerEvent>>,
ReadStorage<'a, Pos>,
ReadStorage<'a, PhysicsState>,
WriteStorage<'a, Object>,
);
fn run(
&mut self,
(entities, _dt, server_bus, positions, physics_states, mut objects): Self::SystemData,
) {
let mut server_emitter = server_bus.emitter();
// Objects
for (entity, pos, physics, object) in
(&entities, &positions, &physics_states, &mut objects).join()
{
match object {
Object::Bomb { owner } => {
if physics.on_surface().is_some() {
server_emitter.emit(ServerEvent::Destroy {
entity,
cause: HealthSource::Suicide,
});
server_emitter.emit(ServerEvent::Explosion {
pos: pos.0,
power: 4.0,
owner: *owner,
});
}
},
}
}
}
}

View File

@ -346,8 +346,12 @@ impl<'a> System<'a> for Sys {
stats, stats,
loadout, loadout,
body, body,
agent: if entity.has_agency {
Some(comp::Agent::new(entity.pos, can_speak))
} else {
None
},
alignment, alignment,
agent: comp::Agent::new(entity.pos, can_speak),
scale: comp::Scale(scale), scale: comp::Scale(scale),
drop_item: entity.loot_drop, drop_item: entity.loot_drop,
}) })

View File

@ -4,7 +4,7 @@ use common::{
comp::item::{ comp::item::{
armor::Armor, armor::Armor,
tool::{Tool, ToolKind}, tool::{Tool, ToolKind},
Consumable, Ingredient, Item, ItemKind, Lantern, LanternKind, Utility, Consumable, Ingredient, Item, ItemKind, Lantern, LanternKind, Throwable, Utility,
}, },
figure::Segment, figure::Segment,
}; };
@ -24,6 +24,7 @@ pub enum ItemKey {
Armor(Armor), Armor(Armor),
Utility(Utility), Utility(Utility),
Consumable(Consumable), Consumable(Consumable),
Throwable(Throwable),
Ingredient(Ingredient), Ingredient(Ingredient),
Empty, Empty,
} }
@ -35,6 +36,7 @@ impl From<&Item> for ItemKey {
ItemKind::Armor { kind, .. } => ItemKey::Armor(*kind), ItemKind::Armor { kind, .. } => ItemKey::Armor(*kind),
ItemKind::Utility { kind, .. } => ItemKey::Utility(*kind), ItemKind::Utility { kind, .. } => ItemKey::Utility(*kind),
ItemKind::Consumable { kind, .. } => ItemKey::Consumable(*kind), ItemKind::Consumable { kind, .. } => ItemKey::Consumable(*kind),
ItemKind::Throwable { kind, .. } => ItemKey::Throwable(*kind),
ItemKind::Ingredient { kind, .. } => ItemKey::Ingredient(*kind), ItemKind::Ingredient { kind, .. } => ItemKey::Ingredient(*kind),
} }
} }

View File

@ -970,7 +970,7 @@ impl Hud {
&pos, &pos,
interpolated.maybe(), interpolated.maybe(),
&stats, &stats,
&energy, energy.maybe(),
players.maybe(), players.maybe(),
scales.maybe(), scales.maybe(),
&bodies, &bodies,

View File

@ -54,7 +54,7 @@ pub struct Overhead<'a> {
name: &'a str, name: &'a str,
bubble: Option<&'a SpeechBubble>, bubble: Option<&'a SpeechBubble>,
stats: &'a Stats, stats: &'a Stats,
energy: &'a Energy, energy: Option<&'a Energy>,
own_level: u32, own_level: u32,
settings: &'a GameplaySettings, settings: &'a GameplaySettings,
pulse: f32, pulse: f32,
@ -71,7 +71,7 @@ impl<'a> Overhead<'a> {
name: &'a str, name: &'a str,
bubble: Option<&'a SpeechBubble>, bubble: Option<&'a SpeechBubble>,
stats: &'a Stats, stats: &'a Stats,
energy: &'a Energy, energy: Option<&'a Energy>,
own_level: u32, own_level: u32,
settings: &'a GameplaySettings, settings: &'a GameplaySettings,
pulse: f32, pulse: f32,
@ -297,7 +297,6 @@ impl<'a> Widget for Overhead<'a> {
let hp_percentage = let hp_percentage =
self.stats.health.current() as f64 / self.stats.health.maximum() as f64 * 100.0; self.stats.health.current() as f64 / self.stats.health.maximum() as f64 * 100.0;
let energy_percentage = self.energy.current() as f64 / self.energy.maximum() as f64 * 100.0;
let hp_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 1.0; //Animation timer let hp_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 1.0; //Animation timer
let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani); let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani);
@ -325,20 +324,22 @@ impl<'a> Widget for Overhead<'a> {
})) }))
.parent(id) .parent(id)
.set(state.ids.health_bar, ui); .set(state.ids.health_bar, ui);
// % Mana Filling // % Mana Filling
Rectangle::fill_with( if let Some(energy) = self.energy {
[ let energy_factor = energy.current() as f64 / energy.maximum() as f64;
72.0 * (self.energy.current() as f64 / self.energy.maximum() as f64) * BARSIZE,
MANA_BAR_HEIGHT, Rectangle::fill_with(
], [72.0 * energy_factor * BARSIZE, MANA_BAR_HEIGHT],
MANA_COLOR, MANA_COLOR,
) )
.x_y( .x_y(
((3.5 + (energy_percentage / 100.0 * 36.5)) - 36.45) * BARSIZE, ((3.5 + (energy_factor * 36.5)) - 36.45) * BARSIZE,
MANA_BAR_Y, //-32.0, MANA_BAR_Y, //-32.0,
) )
.parent(id) .parent(id)
.set(state.ids.mana_bar, ui); .set(state.ids.mana_bar, ui);
}
// Foreground // Foreground
Image::new(self.imgs.enemy_health) Image::new(self.imgs.enemy_health)

View File

@ -42,6 +42,7 @@ impl SlotKey<Inventory, ItemImgs> for InventorySlot {
ItemKind::Tool { .. } | ItemKind::Lantern(_) | ItemKind::Armor { .. } => None, ItemKind::Tool { .. } | ItemKind::Lantern(_) | ItemKind::Armor { .. } => None,
ItemKind::Utility { amount, .. } ItemKind::Utility { amount, .. }
| ItemKind::Consumable { amount, .. } | ItemKind::Consumable { amount, .. }
| ItemKind::Throwable { amount, .. }
| ItemKind::Ingredient { amount, .. } => Some(amount), | ItemKind::Ingredient { amount, .. } => Some(amount),
}) })
.filter(|amount| *amount > 1) .filter(|amount| *amount > 1)
@ -139,6 +140,7 @@ impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
ItemKind::Tool { .. } | ItemKind::Lantern(_) | ItemKind::Armor { .. } => None, ItemKind::Tool { .. } | ItemKind::Lantern(_) | ItemKind::Armor { .. } => None,
ItemKind::Utility { amount, .. } ItemKind::Utility { amount, .. }
| ItemKind::Consumable { amount, .. } | ItemKind::Consumable { amount, .. }
| ItemKind::Throwable { amount, .. }
| ItemKind::Ingredient { amount, .. } => Some(amount), | ItemKind::Ingredient { amount, .. } => Some(amount),
}) })
.filter(|amount| *amount > 1) .filter(|amount| *amount > 1)

View File

@ -254,22 +254,19 @@ impl<'a> BlockGen<'a> {
.map(|e| (e * 255.0) as u8); .map(|e| (e * 255.0) as u8);
// Underground // Underground
if (wposf.z as f32) > alt - 32.0 * chaos { Some(Block::new(BlockKind::Normal, col))
Some(Block::new(BlockKind::Normal, col))
} else {
Some(Block::new(BlockKind::Dense, col))
}
} else if (wposf.z as f32) < height { } else if (wposf.z as f32) < height {
let col = Lerp::lerp( let grass_factor = (wposf.z as f32 - (height - grass_depth))
sub_surface_color, .div(grass_depth)
surface_color, .powf(0.5);
(wposf.z as f32 - (height - grass_depth)) let col = Lerp::lerp(sub_surface_color, surface_color, grass_factor);
.div(grass_depth)
.powf(0.5),
);
// Surface // Surface
Some(Block::new( Some(Block::new(
BlockKind::Normal, if grass_factor > 0.7 {
BlockKind::Grass
} else {
BlockKind::Normal
},
col.map(|e| (e * 255.0) as u8), col.map(|e| (e * 255.0) as u8),
)) ))
} else if (wposf.z as f32) < height + 0.9 } else if (wposf.z as f32) < height + 0.9
@ -349,7 +346,7 @@ impl<'a> BlockGen<'a> {
let field2 = RandomField::new(world.seed + 2); let field2 = RandomField::new(world.seed + 2);
Some(Block::new( Some(Block::new(
BlockKind::Normal, BlockKind::Rock,
stone_col stone_col
- Rgb::new( - Rgb::new(
field0.get(wpos) as u8 % 16, field0.get(wpos) as u8 % 16,
@ -596,7 +593,7 @@ pub fn block_from_structure(
StructureBlock::Water => Some(Block::new(BlockKind::Water, Rgb::new(100, 150, 255))), StructureBlock::Water => Some(Block::new(BlockKind::Water, Rgb::new(100, 150, 255))),
StructureBlock::GreenSludge => Some(Block::new(BlockKind::Water, Rgb::new(30, 126, 23))), StructureBlock::GreenSludge => Some(Block::new(BlockKind::Water, Rgb::new(30, 126, 23))),
StructureBlock::Acacia => Some(Block::new( StructureBlock::Acacia => Some(Block::new(
BlockKind::Normal, BlockKind::Leaves,
Lerp::lerp( Lerp::lerp(
Rgb::new(15.0, 126.0, 50.0), Rgb::new(15.0, 126.0, 50.0),
Rgb::new(30.0, 180.0, 10.0), Rgb::new(30.0, 180.0, 10.0),
@ -629,7 +626,7 @@ pub fn block_from_structure(
.map(|e| e as u8), .map(|e| e as u8),
)), )),
StructureBlock::Mangrove => Some(Block::new( StructureBlock::Mangrove => Some(Block::new(
BlockKind::Normal, BlockKind::Leaves,
Lerp::lerp(Rgb::new(32.0, 56.0, 22.0), Rgb::new(57.0, 69.0, 27.0), lerp) Lerp::lerp(Rgb::new(32.0, 56.0, 22.0), Rgb::new(57.0, 69.0, 27.0), lerp)
.map(|e| e as u8), .map(|e| e as u8),
)), )),

View File

@ -10,7 +10,7 @@ use crate::{
use common::{ use common::{
assets, assets,
astar::Astar, astar::Astar,
comp::{self, bird_medium, humanoid, quadruped_small}, comp::{self, bird_medium, humanoid, object, quadruped_small},
generation::{ChunkSupplement, EntityInfo}, generation::{ChunkSupplement, EntityInfo},
path::Path, path::Path,
spiral::Spiral2d, spiral::Spiral2d,
@ -789,8 +789,14 @@ impl Settlement {
&& RandomField::new(self.seed).chance(Vec3::from(wpos2d), 1.0 / (50.0 * 50.0)) && RandomField::new(self.seed).chance(Vec3::from(wpos2d), 1.0 / (50.0 * 50.0))
{ {
let is_human: bool; let is_human: bool;
let is_dummy =
RandomField::new(self.seed + 1).chance(Vec3::from(wpos2d), 1.0 / 15.0);
let entity = EntityInfo::at(entity_wpos) let entity = EntityInfo::at(entity_wpos)
.with_body(match rng.gen_range(0, 4) { .with_body(match rng.gen_range(0, 4) {
_ if is_dummy => {
is_human = false;
object::Body::TrainingDummy.into()
},
0 => { 0 => {
let species = match rng.gen_range(0, 3) { let species = match rng.gen_range(0, 3) {
0 => quadruped_small::Species::Pig, 0 => quadruped_small::Species::Pig,
@ -819,7 +825,10 @@ impl Settlement {
comp::Body::Humanoid(humanoid::Body::random()) comp::Body::Humanoid(humanoid::Body::random())
}, },
}) })
.with_alignment(if is_human { .with_agency(!is_dummy)
.with_alignment(if is_dummy {
comp::Alignment::Wild
} else if is_human {
comp::Alignment::Npc comp::Alignment::Npc
} else { } else {
comp::Alignment::Tame comp::Alignment::Tame
@ -838,7 +847,8 @@ impl Settlement {
}, },
)) ))
}) })
.with_automatic_name(); .do_if(is_dummy, |e| e.with_name("Training Dummy"))
.do_if(!is_dummy, |e| e.with_automatic_name());
supplement.add_entity(entity); supplement.add_entity(entity);
} }