mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
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:
commit
616dd29df1
@ -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
|
||||
- Added new animals
|
||||
- Better pathfinding
|
||||
- Bombs
|
||||
- Training dummy items
|
||||
|
||||
### 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
|
||||
- Connection screen fails after 4 minutes if it can't connect to the server instead of 80 minutes
|
||||
- Rebuilt quadruped_medium animation and assets
|
||||
- Disabled destruction of most blocks by explosions
|
||||
|
||||
### Removed
|
||||
|
||||
|
7
assets/common/items/bomb.ron
Normal file
7
assets/common/items/bomb.ron
Normal file
@ -0,0 +1,7 @@
|
||||
Item(
|
||||
name: "Bomb",
|
||||
description: "Boom!\n\n<Right-Click to use>",
|
||||
kind: Throwable(
|
||||
kind: Bomb,
|
||||
),
|
||||
)
|
8
assets/common/items/bomb_pile.ron
Normal file
8
assets/common/items/bomb_pile.ron
Normal file
@ -0,0 +1,8 @@
|
||||
Item(
|
||||
name: "Bomb",
|
||||
description: "Boom!\n\n<Right-Click to use>",
|
||||
kind: Throwable(
|
||||
kind: Bomb,
|
||||
amount: 10,
|
||||
),
|
||||
)
|
7
assets/common/items/training_dummy.ron
Normal file
7
assets/common/items/training_dummy.ron
Normal 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,
|
||||
),
|
||||
)
|
@ -1,10 +1,16 @@
|
||||
[
|
||||
// All loot rates go here
|
||||
// food
|
||||
(3, "common.items.cheese"),
|
||||
(3, "common.items.apple"),
|
||||
(3, "common.items.mushroom"),
|
||||
// miscellaneous
|
||||
(0.4, "common.items.velorite"),
|
||||
(0.6, "common.items.veloritefrag"),
|
||||
(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
|
||||
(0.1, "common.items.weapons.sword.starter_sword"),
|
||||
(0.1, "common.items.weapons.sword.wood_sword"),
|
||||
|
@ -778,6 +778,15 @@
|
||||
"voxel.sprite.velorite.velorite_1",
|
||||
(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
|
||||
Ingredient(Flower): VoxTrans(
|
||||
"voxel.sprite.flowers.flower_red_2",
|
||||
|
@ -123,6 +123,10 @@ pub const ALL_OBJECTS: [Body; 54] = [
|
||||
Body::TrainingDummy,
|
||||
];
|
||||
|
||||
impl From<Body> for super::Body {
|
||||
fn from(body: Body) -> Self { super::Body::Object(body) }
|
||||
}
|
||||
|
||||
impl Body {
|
||||
pub fn to_string(&self) -> &str {
|
||||
match self {
|
||||
|
@ -28,6 +28,12 @@ pub enum Consumable {
|
||||
PotionExp,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum Throwable {
|
||||
Bomb,
|
||||
TrainingDummy,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum Utility {
|
||||
Collar,
|
||||
@ -86,6 +92,11 @@ pub enum ItemKind {
|
||||
#[serde(default = "default_amount")]
|
||||
amount: u32,
|
||||
},
|
||||
Throwable {
|
||||
kind: Throwable,
|
||||
#[serde(default = "default_amount")]
|
||||
amount: u32,
|
||||
},
|
||||
Utility {
|
||||
kind: Utility,
|
||||
#[serde(default = "default_amount")]
|
||||
@ -129,15 +140,10 @@ impl Item {
|
||||
pub fn set_amount(&mut self, give_amount: u32) -> Result<(), assets::Error> {
|
||||
use ItemKind::*;
|
||||
match self.kind {
|
||||
Consumable { ref mut amount, .. } => {
|
||||
*amount = give_amount;
|
||||
Ok(())
|
||||
},
|
||||
Utility { ref mut amount, .. } => {
|
||||
*amount = give_amount;
|
||||
Ok(())
|
||||
},
|
||||
Ingredient { ref mut amount, .. } => {
|
||||
Consumable { ref mut amount, .. }
|
||||
| Throwable { ref mut amount, .. }
|
||||
| Utility { ref mut amount, .. }
|
||||
| Ingredient { ref mut amount, .. } => {
|
||||
*amount = give_amount;
|
||||
Ok(())
|
||||
},
|
||||
|
@ -102,6 +102,37 @@ impl Inventory {
|
||||
// It didn't work
|
||||
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 {
|
||||
kind: item_kind,
|
||||
amount: new_amount,
|
||||
@ -277,6 +308,19 @@ impl Inventory {
|
||||
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 } => {
|
||||
if *amount <= 1 {
|
||||
self.remove(cell)
|
||||
|
12
common/src/comp/misc.rs
Normal file
12
common/src/comp/misc.rs
Normal 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>;
|
||||
}
|
@ -10,6 +10,7 @@ mod inputs;
|
||||
mod inventory;
|
||||
mod last;
|
||||
mod location;
|
||||
mod misc;
|
||||
mod phys;
|
||||
mod player;
|
||||
pub mod projectile;
|
||||
@ -40,6 +41,7 @@ pub use inventory::{
|
||||
};
|
||||
pub use last::Last;
|
||||
pub use location::{Waypoint, WaypointArea};
|
||||
pub use misc::Object;
|
||||
pub use phys::{Collider, ForceUpdate, Gravity, Mass, Ori, PhysicsState, Pos, Scale, Sticky, Vel};
|
||||
pub use player::Player;
|
||||
pub use projectile::Projectile;
|
||||
|
@ -78,6 +78,15 @@ pub struct PhysicsState {
|
||||
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 {
|
||||
type Storage = FlaggedStorage<Self, IDVStorage<Self>>;
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ pub enum ServerEvent {
|
||||
stats: comp::Stats,
|
||||
loadout: comp::Loadout,
|
||||
body: comp::Body,
|
||||
agent: comp::Agent,
|
||||
agent: Option<comp::Agent>,
|
||||
alignment: comp::Alignment,
|
||||
scale: comp::Scale,
|
||||
drop_item: Option<Item>,
|
||||
|
@ -12,6 +12,7 @@ pub struct EntityInfo {
|
||||
pub pos: Vec3<f32>,
|
||||
pub is_waypoint: bool, // Edge case, overrides everything else
|
||||
pub is_giant: bool,
|
||||
pub has_agency: bool,
|
||||
pub alignment: Alignment,
|
||||
pub body: Body,
|
||||
pub name: Option<String>,
|
||||
@ -28,6 +29,7 @@ impl EntityInfo {
|
||||
pos,
|
||||
is_waypoint: false,
|
||||
is_giant: false,
|
||||
has_agency: true,
|
||||
alignment: Alignment::Wild,
|
||||
body: Body::Humanoid(humanoid::Body::random()),
|
||||
name: None,
|
||||
@ -66,8 +68,13 @@ impl EntityInfo {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_name(mut self, name: String) -> Self {
|
||||
self.name = Some(name);
|
||||
pub fn with_name(mut self, name: impl Into<String>) -> Self {
|
||||
self.name = Some(name.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_agency(mut self, agency: bool) -> Self {
|
||||
self.has_agency = agency;
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,9 @@ use crate::vol::{ReadVol, Vox};
|
||||
use vek::*;
|
||||
|
||||
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,
|
||||
from: Vec3<f32>,
|
||||
to: Vec3<f32>,
|
||||
@ -14,7 +14,7 @@ pub struct Ray<'a, V: ReadVol, F: RayUntil<V::Vox>, G: RayForEach> {
|
||||
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 {
|
||||
Self {
|
||||
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 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 {
|
||||
for_each: Some(f),
|
||||
vol: self.vol,
|
||||
@ -69,12 +69,16 @@ impl<'a, V: ReadVol, F: RayUntil<V::Vox>, G: RayForEach> Ray<'a, V, F, G> {
|
||||
break;
|
||||
}
|
||||
|
||||
let vox = self.vol.get(ipos);
|
||||
|
||||
// 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))),
|
||||
Err(err) if !self.ignore_error => return (dist, Err(err)),
|
||||
_ => {},
|
||||
|
@ -122,6 +122,7 @@ impl State {
|
||||
ecs.register::<comp::Sticky>();
|
||||
ecs.register::<comp::Gravity>();
|
||||
ecs.register::<comp::CharacterState>();
|
||||
ecs.register::<comp::Object>();
|
||||
|
||||
// Register components send from clients -> server
|
||||
ecs.register::<comp::Controller>();
|
||||
|
@ -85,7 +85,7 @@ impl<'a> System<'a> for Sys {
|
||||
&orientations,
|
||||
scales.maybe(),
|
||||
agents.maybe(),
|
||||
&character_states,
|
||||
character_states.maybe(),
|
||||
&stats,
|
||||
&bodies,
|
||||
)
|
||||
@ -129,7 +129,7 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
|
||||
// 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
|
||||
{
|
||||
healthchange *= 1.0 - BLOCK_EFFICIENCY
|
||||
|
@ -96,11 +96,7 @@ impl<'a> System<'a> for Sys {
|
||||
{
|
||||
let mut physics_state = physics_states.get(entity).cloned().unwrap_or_default();
|
||||
|
||||
if sticky.is_some()
|
||||
&& (physics_state.on_ground
|
||||
|| physics_state.on_ceiling
|
||||
|| physics_state.on_wall.is_some())
|
||||
{
|
||||
if sticky.is_some() && physics_state.on_surface().is_some() {
|
||||
vel.0 = Vec3::zero();
|
||||
continue;
|
||||
}
|
||||
|
@ -33,16 +33,8 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
stats.set_event_emission(true);
|
||||
|
||||
// Mutates all stats every tick causing the server to resend this component for
|
||||
// every entity every tick
|
||||
for (entity, character_state, mut stats, mut energy) in (
|
||||
&entities,
|
||||
&character_states,
|
||||
&mut stats.restrict_mut(),
|
||||
&mut energies.restrict_mut(),
|
||||
)
|
||||
.join()
|
||||
{
|
||||
// Update stats
|
||||
for (entity, mut stats) in (&entities, &mut stats.restrict_mut()).join() {
|
||||
let (set_dead, level_up) = {
|
||||
let stat = stats.get_unchecked();
|
||||
(
|
||||
@ -74,7 +66,12 @@ impl<'a> System<'a> for Sys {
|
||||
stat.health
|
||||
.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 {
|
||||
// Accelerate recharging energy.
|
||||
CharacterState::Idle { .. }
|
||||
|
@ -9,6 +9,9 @@ pub enum BlockKind {
|
||||
Air,
|
||||
Normal,
|
||||
Dense,
|
||||
Rock,
|
||||
Grass,
|
||||
Leaves,
|
||||
Water,
|
||||
LargeCactus,
|
||||
BarrelCactus,
|
||||
@ -32,7 +35,6 @@ pub enum BlockKind {
|
||||
Velorite,
|
||||
VeloriteFrag,
|
||||
Chest,
|
||||
Leaves,
|
||||
Pumpkin,
|
||||
Welwitch,
|
||||
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>`
|
||||
pub fn get_height(&self) -> f32 {
|
||||
// Beware: the height *must* be <= `MAX_HEIGHT` or the collision system will not
|
||||
|
@ -102,7 +102,7 @@ pub trait ReadVol: BaseVol {
|
||||
&'a self,
|
||||
from: 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
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -31,7 +31,7 @@ pub fn handle_create_npc(
|
||||
stats: Stats,
|
||||
loadout: Loadout,
|
||||
body: Body,
|
||||
agent: Agent,
|
||||
agent: impl Into<Option<Agent>>,
|
||||
alignment: Alignment,
|
||||
scale: Scale,
|
||||
drop_item: Option<Item>,
|
||||
@ -39,10 +39,15 @@ pub fn handle_create_npc(
|
||||
let entity = server
|
||||
.state
|
||||
.create_npc(pos, stats, loadout, body)
|
||||
.with(agent)
|
||||
.with(scale)
|
||||
.with(alignment);
|
||||
|
||||
let entity = if let Some(agent) = agent.into() {
|
||||
entity.with(agent)
|
||||
} else {
|
||||
entity
|
||||
};
|
||||
|
||||
let entity = if let Some(drop_item) = drop_item {
|
||||
entity.with(ItemDrop(drop_item))
|
||||
} else {
|
||||
|
@ -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 (
|
||||
&ecs.read_storage::<comp::Pos>(),
|
||||
&ecs.read_storage::<comp::Ori>(),
|
||||
&ecs.read_storage::<comp::CharacterState>(),
|
||||
ecs.read_storage::<comp::CharacterState>().maybe(),
|
||||
&mut ecs.write_storage::<comp::Stats>(),
|
||||
)
|
||||
.join()
|
||||
@ -231,7 +231,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, power: f32, owner: Opti
|
||||
}
|
||||
|
||||
// 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
|
||||
{
|
||||
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>()
|
||||
.ray(pos, pos + dir * color_range)
|
||||
.until(|_| rand::random::<f32>() < 0.05)
|
||||
.for_each(|pos| touched_blocks.push(pos))
|
||||
.for_each(|_: &Block, pos| touched_blocks.push(pos))
|
||||
.cast();
|
||||
}
|
||||
|
||||
@ -292,11 +292,15 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, power: f32, owner: Opti
|
||||
)
|
||||
.normalized();
|
||||
|
||||
let _ = ecs
|
||||
.read_resource::<TerrainGrid>()
|
||||
let terrain = ecs.read_resource::<TerrainGrid>();
|
||||
let _ = terrain
|
||||
.ray(pos, pos + dir * power)
|
||||
.until(|_| rand::random::<f32>() < 0.05)
|
||||
.for_each(|pos| block_change.set(pos, Block::empty()))
|
||||
.until(|block| block.is_fluid() || rand::random::<f32>() < 0.05)
|
||||
.for_each(|block: &Block, pos| {
|
||||
if block.is_explodable() {
|
||||
block_change.set(pos, Block::empty());
|
||||
}
|
||||
})
|
||||
.cast();
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use common::{
|
||||
slot::{self, Slot},
|
||||
Pos, MAX_PICKUP_RANGE_SQR,
|
||||
},
|
||||
sync::WorldSyncExt,
|
||||
sync::{Uid, WorldSyncExt},
|
||||
terrain::block::Block,
|
||||
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) {
|
||||
let state = server.state_mut();
|
||||
let mut dropped_items = Vec::new();
|
||||
let mut thrown_items = Vec::new();
|
||||
|
||||
match manip {
|
||||
comp::InventoryManip::Pickup(uid) => {
|
||||
@ -157,6 +158,29 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
maybe_effect = Some(*effect);
|
||||
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 {
|
||||
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))
|
||||
.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 {
|
||||
|
@ -128,7 +128,7 @@ impl StateExt for State {
|
||||
.with(comp::Vel(Vec3::zero()))
|
||||
.with(comp::Ori::default())
|
||||
.with(comp::Body::Object(object))
|
||||
.with(comp::Mass(100.0))
|
||||
.with(comp::Mass(5.0))
|
||||
.with(comp::Collider::Box {
|
||||
radius: 0.4,
|
||||
z_min: 0.0,
|
||||
|
@ -1,5 +1,6 @@
|
||||
pub mod entity_sync;
|
||||
pub mod message;
|
||||
pub mod object;
|
||||
pub mod persistence;
|
||||
pub mod sentinel;
|
||||
pub mod subscription;
|
||||
@ -30,13 +31,15 @@ pub type PersistenceScheduler = SysScheduler<persistence::Sys>;
|
||||
//const SUBSCRIPTION_SYS: &str = "server_subscription_sys";
|
||||
//const TERRAIN_SYNC_SYS: &str = "server_terrain_sync_sys";
|
||||
const TERRAIN_SYS: &str = "server_terrain_sys";
|
||||
const WAYPOINT_SYS: &str = "waypoint_sys";
|
||||
const PERSISTENCE_SYS: &str = "persistence_sys";
|
||||
const WAYPOINT_SYS: &str = "server_waypoint_sys";
|
||||
const PERSISTENCE_SYS: &str = "server_persistence_sys";
|
||||
const OBJECT_SYS: &str = "server_object_sys";
|
||||
|
||||
pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
|
||||
dispatch_builder.add(terrain::Sys, TERRAIN_SYS, &[]);
|
||||
dispatch_builder.add(waypoint::Sys, WAYPOINT_SYS, &[]);
|
||||
dispatch_builder.add(persistence::Sys, PERSISTENCE_SYS, &[]);
|
||||
dispatch_builder.add(object::Sys, OBJECT_SYS, &[]);
|
||||
}
|
||||
|
||||
pub fn run_sync_systems(ecs: &mut specs::World) {
|
||||
|
48
server/src/sys/object.rs
Normal file
48
server/src/sys/object.rs
Normal 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,
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -346,8 +346,12 @@ impl<'a> System<'a> for Sys {
|
||||
stats,
|
||||
loadout,
|
||||
body,
|
||||
agent: if entity.has_agency {
|
||||
Some(comp::Agent::new(entity.pos, can_speak))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
alignment,
|
||||
agent: comp::Agent::new(entity.pos, can_speak),
|
||||
scale: comp::Scale(scale),
|
||||
drop_item: entity.loot_drop,
|
||||
})
|
||||
|
@ -4,7 +4,7 @@ use common::{
|
||||
comp::item::{
|
||||
armor::Armor,
|
||||
tool::{Tool, ToolKind},
|
||||
Consumable, Ingredient, Item, ItemKind, Lantern, LanternKind, Utility,
|
||||
Consumable, Ingredient, Item, ItemKind, Lantern, LanternKind, Throwable, Utility,
|
||||
},
|
||||
figure::Segment,
|
||||
};
|
||||
@ -24,6 +24,7 @@ pub enum ItemKey {
|
||||
Armor(Armor),
|
||||
Utility(Utility),
|
||||
Consumable(Consumable),
|
||||
Throwable(Throwable),
|
||||
Ingredient(Ingredient),
|
||||
Empty,
|
||||
}
|
||||
@ -35,6 +36,7 @@ impl From<&Item> for ItemKey {
|
||||
ItemKind::Armor { kind, .. } => ItemKey::Armor(*kind),
|
||||
ItemKind::Utility { kind, .. } => ItemKey::Utility(*kind),
|
||||
ItemKind::Consumable { kind, .. } => ItemKey::Consumable(*kind),
|
||||
ItemKind::Throwable { kind, .. } => ItemKey::Throwable(*kind),
|
||||
ItemKind::Ingredient { kind, .. } => ItemKey::Ingredient(*kind),
|
||||
}
|
||||
}
|
||||
|
@ -970,7 +970,7 @@ impl Hud {
|
||||
&pos,
|
||||
interpolated.maybe(),
|
||||
&stats,
|
||||
&energy,
|
||||
energy.maybe(),
|
||||
players.maybe(),
|
||||
scales.maybe(),
|
||||
&bodies,
|
||||
|
@ -54,7 +54,7 @@ pub struct Overhead<'a> {
|
||||
name: &'a str,
|
||||
bubble: Option<&'a SpeechBubble>,
|
||||
stats: &'a Stats,
|
||||
energy: &'a Energy,
|
||||
energy: Option<&'a Energy>,
|
||||
own_level: u32,
|
||||
settings: &'a GameplaySettings,
|
||||
pulse: f32,
|
||||
@ -71,7 +71,7 @@ impl<'a> Overhead<'a> {
|
||||
name: &'a str,
|
||||
bubble: Option<&'a SpeechBubble>,
|
||||
stats: &'a Stats,
|
||||
energy: &'a Energy,
|
||||
energy: Option<&'a Energy>,
|
||||
own_level: u32,
|
||||
settings: &'a GameplaySettings,
|
||||
pulse: f32,
|
||||
@ -297,7 +297,6 @@ impl<'a> Widget for Overhead<'a> {
|
||||
|
||||
let hp_percentage =
|
||||
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 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)
|
||||
.set(state.ids.health_bar, ui);
|
||||
|
||||
// % Mana Filling
|
||||
Rectangle::fill_with(
|
||||
[
|
||||
72.0 * (self.energy.current() as f64 / self.energy.maximum() as f64) * BARSIZE,
|
||||
MANA_BAR_HEIGHT,
|
||||
],
|
||||
MANA_COLOR,
|
||||
)
|
||||
.x_y(
|
||||
((3.5 + (energy_percentage / 100.0 * 36.5)) - 36.45) * BARSIZE,
|
||||
MANA_BAR_Y, //-32.0,
|
||||
)
|
||||
.parent(id)
|
||||
.set(state.ids.mana_bar, ui);
|
||||
if let Some(energy) = self.energy {
|
||||
let energy_factor = energy.current() as f64 / energy.maximum() as f64;
|
||||
|
||||
Rectangle::fill_with(
|
||||
[72.0 * energy_factor * BARSIZE, MANA_BAR_HEIGHT],
|
||||
MANA_COLOR,
|
||||
)
|
||||
.x_y(
|
||||
((3.5 + (energy_factor * 36.5)) - 36.45) * BARSIZE,
|
||||
MANA_BAR_Y, //-32.0,
|
||||
)
|
||||
.parent(id)
|
||||
.set(state.ids.mana_bar, ui);
|
||||
}
|
||||
|
||||
// Foreground
|
||||
Image::new(self.imgs.enemy_health)
|
||||
|
@ -42,6 +42,7 @@ impl SlotKey<Inventory, ItemImgs> for InventorySlot {
|
||||
ItemKind::Tool { .. } | ItemKind::Lantern(_) | ItemKind::Armor { .. } => None,
|
||||
ItemKind::Utility { amount, .. }
|
||||
| ItemKind::Consumable { amount, .. }
|
||||
| ItemKind::Throwable { amount, .. }
|
||||
| ItemKind::Ingredient { amount, .. } => Some(amount),
|
||||
})
|
||||
.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::Utility { amount, .. }
|
||||
| ItemKind::Consumable { amount, .. }
|
||||
| ItemKind::Throwable { amount, .. }
|
||||
| ItemKind::Ingredient { amount, .. } => Some(amount),
|
||||
})
|
||||
.filter(|amount| *amount > 1)
|
||||
|
@ -254,22 +254,19 @@ impl<'a> BlockGen<'a> {
|
||||
.map(|e| (e * 255.0) as u8);
|
||||
|
||||
// Underground
|
||||
if (wposf.z as f32) > alt - 32.0 * chaos {
|
||||
Some(Block::new(BlockKind::Normal, col))
|
||||
} else {
|
||||
Some(Block::new(BlockKind::Dense, col))
|
||||
}
|
||||
Some(Block::new(BlockKind::Normal, col))
|
||||
} else if (wposf.z as f32) < height {
|
||||
let col = Lerp::lerp(
|
||||
sub_surface_color,
|
||||
surface_color,
|
||||
(wposf.z as f32 - (height - grass_depth))
|
||||
.div(grass_depth)
|
||||
.powf(0.5),
|
||||
);
|
||||
let grass_factor = (wposf.z as f32 - (height - grass_depth))
|
||||
.div(grass_depth)
|
||||
.powf(0.5);
|
||||
let col = Lerp::lerp(sub_surface_color, surface_color, grass_factor);
|
||||
// Surface
|
||||
Some(Block::new(
|
||||
BlockKind::Normal,
|
||||
if grass_factor > 0.7 {
|
||||
BlockKind::Grass
|
||||
} else {
|
||||
BlockKind::Normal
|
||||
},
|
||||
col.map(|e| (e * 255.0) as u8),
|
||||
))
|
||||
} else if (wposf.z as f32) < height + 0.9
|
||||
@ -349,7 +346,7 @@ impl<'a> BlockGen<'a> {
|
||||
let field2 = RandomField::new(world.seed + 2);
|
||||
|
||||
Some(Block::new(
|
||||
BlockKind::Normal,
|
||||
BlockKind::Rock,
|
||||
stone_col
|
||||
- Rgb::new(
|
||||
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::GreenSludge => Some(Block::new(BlockKind::Water, Rgb::new(30, 126, 23))),
|
||||
StructureBlock::Acacia => Some(Block::new(
|
||||
BlockKind::Normal,
|
||||
BlockKind::Leaves,
|
||||
Lerp::lerp(
|
||||
Rgb::new(15.0, 126.0, 50.0),
|
||||
Rgb::new(30.0, 180.0, 10.0),
|
||||
@ -629,7 +626,7 @@ pub fn block_from_structure(
|
||||
.map(|e| e as u8),
|
||||
)),
|
||||
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)
|
||||
.map(|e| e as u8),
|
||||
)),
|
||||
|
@ -10,7 +10,7 @@ use crate::{
|
||||
use common::{
|
||||
assets,
|
||||
astar::Astar,
|
||||
comp::{self, bird_medium, humanoid, quadruped_small},
|
||||
comp::{self, bird_medium, humanoid, object, quadruped_small},
|
||||
generation::{ChunkSupplement, EntityInfo},
|
||||
path::Path,
|
||||
spiral::Spiral2d,
|
||||
@ -789,8 +789,14 @@ impl Settlement {
|
||||
&& RandomField::new(self.seed).chance(Vec3::from(wpos2d), 1.0 / (50.0 * 50.0))
|
||||
{
|
||||
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)
|
||||
.with_body(match rng.gen_range(0, 4) {
|
||||
_ if is_dummy => {
|
||||
is_human = false;
|
||||
object::Body::TrainingDummy.into()
|
||||
},
|
||||
0 => {
|
||||
let species = match rng.gen_range(0, 3) {
|
||||
0 => quadruped_small::Species::Pig,
|
||||
@ -819,7 +825,10 @@ impl Settlement {
|
||||
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
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user