mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'zesterer/item-merging' into 'master'
Zesterer/item merging See merge request veloren/veloren!3912
This commit is contained in:
commit
b6d8182382
@ -45,6 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Loot that drops multiple items is now distributed fairly between damage contributors.
|
- Loot that drops multiple items is now distributed fairly between damage contributors.
|
||||||
- Added accessibility settings tab.
|
- Added accessibility settings tab.
|
||||||
- Setting to enable subtitles describing sfx.
|
- Setting to enable subtitles describing sfx.
|
||||||
|
- Item drops that are spatially close and compatible will now merge with one-another to reduce performance problems.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -1158,6 +1158,37 @@ impl Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return `true` if `other` can be merged into this item. This is generally
|
||||||
|
/// only possible if the item has a compatible item ID and is stackable,
|
||||||
|
/// along with any other similarity checks.
|
||||||
|
pub fn can_merge(&self, other: &Item) -> bool {
|
||||||
|
if self.is_stackable()
|
||||||
|
&& let ItemBase::Simple(other_item_def) = &other.item_base
|
||||||
|
&& self.is_same_item_def(other_item_def)
|
||||||
|
&& u32::from(self.amount)
|
||||||
|
.checked_add(other.amount())
|
||||||
|
.filter(|&amount| amount <= self.max_amount())
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to merge `other` into this item. This is generally only possible if
|
||||||
|
/// the item has a compatible item ID and is stackable, along with any
|
||||||
|
/// other similarity checks.
|
||||||
|
pub fn try_merge(&mut self, other: Item) -> Result<(), Item> {
|
||||||
|
if self.can_merge(&other) {
|
||||||
|
self.increase_amount(other.amount())
|
||||||
|
.expect("`can_merge` succeeded but `increase_amount` did not");
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn num_slots(&self) -> u16 { self.item_base.num_slots() }
|
pub fn num_slots(&self) -> u16 { self.item_base.num_slots() }
|
||||||
|
|
||||||
/// NOTE: invariant that amount() ≤ max_amount(), 1 ≤ max_amount(),
|
/// NOTE: invariant that amount() ≤ max_amount(), 1 ≤ max_amount(),
|
||||||
|
@ -12,7 +12,7 @@ use std::sync::Arc;
|
|||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
/// Position
|
/// Position
|
||||||
#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Pos(pub Vec3<f32>);
|
pub struct Pos(pub Vec3<f32>);
|
||||||
|
|
||||||
impl Component for Pos {
|
impl Component for Pos {
|
||||||
|
@ -503,16 +503,16 @@ fn handle_drop_all(
|
|||||||
for item in item_to_place {
|
for item in item_to_place {
|
||||||
let vel = Vec3::new(rng.gen_range(-0.1..0.1), rng.gen_range(-0.1..0.1), 0.5);
|
let vel = Vec3::new(rng.gen_range(-0.1..0.1), rng.gen_range(-0.1..0.1), 0.5);
|
||||||
|
|
||||||
server
|
server.state.create_item_drop(
|
||||||
.state
|
comp::Pos(Vec3::new(
|
||||||
.create_item_drop(Default::default(), item)
|
|
||||||
.with(comp::Pos(Vec3::new(
|
|
||||||
pos.0.x + rng.gen_range(5.0..10.0),
|
pos.0.x + rng.gen_range(5.0..10.0),
|
||||||
pos.0.y + rng.gen_range(5.0..10.0),
|
pos.0.y + rng.gen_range(5.0..10.0),
|
||||||
pos.0.z + 5.0,
|
pos.0.z + 5.0,
|
||||||
)))
|
)),
|
||||||
.with(comp::Vel(vel))
|
comp::Vel(vel),
|
||||||
.build();
|
item,
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -473,20 +473,17 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, last_change: Healt
|
|||||||
|
|
||||||
let mut spawn_item = |item, loot_owner| {
|
let mut spawn_item = |item, loot_owner| {
|
||||||
let offset = item_offset_spiral.next().unwrap_or_default();
|
let offset = item_offset_spiral.next().unwrap_or_default();
|
||||||
let item_drop_entity = state
|
state.create_item_drop(
|
||||||
.create_item_drop(Pos(pos.0 + Vec3::unit_z() * 0.25 + offset), item)
|
Pos(pos.0 + Vec3::unit_z() * 0.25 + offset),
|
||||||
.maybe_with(vel)
|
vel.unwrap_or(comp::Vel(Vec3::zero())),
|
||||||
.build();
|
item,
|
||||||
if let Some(loot_owner) = loot_owner {
|
if let Some(loot_owner) = loot_owner {
|
||||||
debug!("Assigned UID {loot_owner:?} as the winner for the loot drop");
|
debug!("Assigned UID {loot_owner:?} as the winner for the loot drop");
|
||||||
if let Err(err) = state
|
Some(LootOwner::new(loot_owner))
|
||||||
.ecs()
|
} else {
|
||||||
.write_storage::<LootOwner>()
|
None
|
||||||
.insert(item_drop_entity, LootOwner::new(loot_owner))
|
},
|
||||||
{
|
);
|
||||||
error!("Failed to set loot owner on item drop: {err}");
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let msm = &MaterialStatManifest::load().read();
|
let msm = &MaterialStatManifest::load().read();
|
||||||
@ -1103,15 +1100,17 @@ pub fn handle_bonk(server: &mut Server, pos: Vec3<f32>, owner: Option<Uid>, targ
|
|||||||
for item in flatten_counted_items(&items, ability_map, msm) {
|
for item in flatten_counted_items(&items, ability_map, msm) {
|
||||||
server
|
server
|
||||||
.state
|
.state
|
||||||
.create_object(Default::default(), match block.get_sprite() {
|
.create_object(
|
||||||
// Create different containers depending on the original sprite
|
Pos(pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0)),
|
||||||
Some(SpriteKind::Apple) => comp::object::Body::Apple,
|
match block.get_sprite() {
|
||||||
Some(SpriteKind::Beehive) => comp::object::Body::Hive,
|
// Create different containers depending on the original sprite
|
||||||
Some(SpriteKind::Coconut) => comp::object::Body::Coconut,
|
Some(SpriteKind::Apple) => comp::object::Body::Apple,
|
||||||
Some(SpriteKind::Bomb) => comp::object::Body::Bomb,
|
Some(SpriteKind::Beehive) => comp::object::Body::Hive,
|
||||||
_ => comp::object::Body::Pouch,
|
Some(SpriteKind::Coconut) => comp::object::Body::Coconut,
|
||||||
})
|
Some(SpriteKind::Bomb) => comp::object::Body::Bomb,
|
||||||
.with(Pos(pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0)))
|
_ => comp::object::Body::Pouch,
|
||||||
|
},
|
||||||
|
)
|
||||||
.with(item)
|
.with(item)
|
||||||
.maybe_with(match block.get_sprite() {
|
.maybe_with(match block.get_sprite() {
|
||||||
Some(SpriteKind::Bomb) => Some(comp::Object::Bomb { owner }),
|
Some(SpriteKind::Bomb) => Some(comp::Object::Bomb { owner }),
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use specs::{world::WorldExt, Builder, Entity as EcsEntity, Join};
|
use specs::{world::WorldExt, Entity as EcsEntity, Join};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
use common::{
|
use common::{
|
||||||
@ -253,15 +253,13 @@ pub fn handle_mine_block(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for item in items {
|
for item in items {
|
||||||
let item_drop = state
|
let loot_owner = maybe_uid.map(LootOwnerKind::Player).map(LootOwner::new);
|
||||||
.create_item_drop(Default::default(), item)
|
state.create_item_drop(
|
||||||
.with(Pos(pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0)));
|
Pos(pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0)),
|
||||||
if let Some(uid) = maybe_uid {
|
comp::Vel(Vec3::zero()),
|
||||||
item_drop.with(LootOwner::new(LootOwnerKind::Player(uid)))
|
item,
|
||||||
} else {
|
loot_owner,
|
||||||
item_drop
|
);
|
||||||
}
|
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,17 +377,18 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
|||||||
drop(inventory_updates);
|
drop(inventory_updates);
|
||||||
|
|
||||||
for item in drop_items {
|
for item in drop_items {
|
||||||
state
|
state.create_item_drop(
|
||||||
.create_item_drop(Default::default(), item)
|
comp::Pos(
|
||||||
.with(comp::Pos(
|
|
||||||
Vec3::new(
|
Vec3::new(
|
||||||
sprite_pos.x as f32,
|
sprite_pos.x as f32,
|
||||||
sprite_pos.y as f32,
|
sprite_pos.y as f32,
|
||||||
sprite_pos.z as f32,
|
sprite_pos.z as f32,
|
||||||
) + Vec3::one().with_z(0.0) * 0.5,
|
) + Vec3::one().with_z(0.0) * 0.5,
|
||||||
))
|
),
|
||||||
.with(comp::Vel(Vec3::zero()))
|
comp::Vel(Vec3::zero()),
|
||||||
.build();
|
item,
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
comp::InventoryManip::Use(slot) => {
|
comp::InventoryManip::Use(slot) => {
|
||||||
@ -890,15 +891,15 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
|||||||
let items_were_crafted = if let Some(crafted_items) = crafted_items {
|
let items_were_crafted = if let Some(crafted_items) = crafted_items {
|
||||||
for item in crafted_items {
|
for item in crafted_items {
|
||||||
if let Err(item) = inventory.push(item) {
|
if let Err(item) = inventory.push(item) {
|
||||||
dropped_items.push((
|
if let Some(pos) = state.read_component_copied::<comp::Pos>(entity) {
|
||||||
state
|
dropped_items.push((
|
||||||
.read_component_copied::<comp::Pos>(entity)
|
pos,
|
||||||
.unwrap_or_default(),
|
state
|
||||||
state
|
.read_component_copied::<comp::Ori>(entity)
|
||||||
.read_component_copied::<comp::Ori>(entity)
|
.unwrap_or_default(),
|
||||||
.unwrap_or_default(),
|
item.duplicate(ability_map, &msm),
|
||||||
item.duplicate(ability_map, &msm),
|
));
|
||||||
));
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
@ -940,11 +941,12 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
state
|
state.create_item_drop(
|
||||||
.create_item_drop(Default::default(), item)
|
comp::Pos(pos.0 + *ori.look_dir() + Vec3::unit_z()),
|
||||||
.with(comp::Pos(pos.0 + *ori.look_dir() + Vec3::unit_z()))
|
comp::Vel(Vec3::zero()),
|
||||||
.with(comp::Vel(Vec3::zero()))
|
item,
|
||||||
.build();
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
@ -963,12 +965,11 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
|||||||
let uid = state.read_component_copied::<Uid>(entity);
|
let uid = state.read_component_copied::<Uid>(entity);
|
||||||
|
|
||||||
let mut new_entity = state
|
let mut new_entity = state
|
||||||
.create_object(Default::default(), match kind {
|
.create_object(comp::Pos(pos.0 + Vec3::unit_z() * 0.25), match kind {
|
||||||
item::Throwable::Bomb => comp::object::Body::Bomb,
|
item::Throwable::Bomb => comp::object::Body::Bomb,
|
||||||
item::Throwable::Firework(reagent) => comp::object::Body::for_firework(reagent),
|
item::Throwable::Firework(reagent) => comp::object::Body::for_firework(reagent),
|
||||||
item::Throwable::TrainingDummy => comp::object::Body::TrainingDummy,
|
item::Throwable::TrainingDummy => comp::object::Body::TrainingDummy,
|
||||||
})
|
})
|
||||||
.with(comp::Pos(pos.0 + Vec3::unit_z() * 0.25))
|
|
||||||
.with(comp::Vel(vel));
|
.with(comp::Vel(vel));
|
||||||
|
|
||||||
match kind {
|
match kind {
|
||||||
|
@ -19,7 +19,7 @@ use common::{
|
|||||||
self,
|
self,
|
||||||
item::{ItemKind, MaterialStatManifest},
|
item::{ItemKind, MaterialStatManifest},
|
||||||
skills::{GeneralSkill, Skill},
|
skills::{GeneralSkill, Skill},
|
||||||
ChatType, Group, Inventory, Item, Player, Poise, Presence, PresenceKind,
|
ChatType, Group, Inventory, Item, LootOwner, Player, Poise, Presence, PresenceKind,
|
||||||
},
|
},
|
||||||
effect::Effect,
|
effect::Effect,
|
||||||
link::{Link, LinkHandle},
|
link::{Link, LinkHandle},
|
||||||
@ -60,7 +60,15 @@ pub trait StateExt {
|
|||||||
) -> EcsEntityBuilder;
|
) -> EcsEntityBuilder;
|
||||||
/// Build a static object entity
|
/// Build a static object entity
|
||||||
fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder;
|
fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder;
|
||||||
fn create_item_drop(&mut self, pos: comp::Pos, item: Item) -> EcsEntityBuilder;
|
/// Create an item drop or merge the item with an existing drop, if a
|
||||||
|
/// suitable candidate exists.
|
||||||
|
fn create_item_drop(
|
||||||
|
&mut self,
|
||||||
|
pos: comp::Pos,
|
||||||
|
vel: comp::Vel,
|
||||||
|
item: Item,
|
||||||
|
loot_owner: Option<LootOwner>,
|
||||||
|
) -> Option<EcsEntity>;
|
||||||
fn create_ship<F: FnOnce(comp::ship::Body) -> comp::Collider>(
|
fn create_ship<F: FnOnce(comp::ship::Body) -> comp::Collider>(
|
||||||
&mut self,
|
&mut self,
|
||||||
pos: comp::Pos,
|
pos: comp::Pos,
|
||||||
@ -311,7 +319,53 @@ impl StateExt for State {
|
|||||||
.with(body)
|
.with(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_item_drop(&mut self, pos: comp::Pos, item: Item) -> EcsEntityBuilder {
|
fn create_item_drop(
|
||||||
|
&mut self,
|
||||||
|
pos: comp::Pos,
|
||||||
|
vel: comp::Vel,
|
||||||
|
item: Item,
|
||||||
|
loot_owner: Option<LootOwner>,
|
||||||
|
) -> Option<EcsEntity> {
|
||||||
|
{
|
||||||
|
const MAX_MERGE_DIST: f32 = 1.5;
|
||||||
|
|
||||||
|
// First, try to identify possible candidates for item merging
|
||||||
|
// We limit our search to just a few blocks and we prioritise merging with the
|
||||||
|
// closest
|
||||||
|
let positions = self.ecs().read_storage::<comp::Pos>();
|
||||||
|
let loot_owners = self.ecs().read_storage::<LootOwner>();
|
||||||
|
let mut items = self.ecs().write_storage::<Item>();
|
||||||
|
let mut nearby_items = self
|
||||||
|
.ecs()
|
||||||
|
.read_resource::<common::CachedSpatialGrid>()
|
||||||
|
.0
|
||||||
|
.in_circle_aabr(pos.0.xy(), MAX_MERGE_DIST)
|
||||||
|
.filter(|entity| items.contains(*entity))
|
||||||
|
.filter_map(|entity| {
|
||||||
|
Some((entity, positions.get(entity)?.0.distance_squared(pos.0)))
|
||||||
|
})
|
||||||
|
.filter(|(_, dist_sqrd)| *dist_sqrd < MAX_MERGE_DIST.powi(2))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
nearby_items.sort_by_key(|(_, dist_sqrd)| (dist_sqrd * 1000.0) as i32);
|
||||||
|
for (nearby, _) in nearby_items {
|
||||||
|
// Only merge if the loot owner is the same
|
||||||
|
if loot_owners.get(nearby).map(|lo| lo.owner()) == loot_owner.map(|lo| lo.owner())
|
||||||
|
&& items
|
||||||
|
.get(nearby)
|
||||||
|
.map_or(false, |nearby_item| nearby_item.can_merge(&item))
|
||||||
|
{
|
||||||
|
// Merging can occur! Perform the merge:
|
||||||
|
items
|
||||||
|
.get_mut(nearby)
|
||||||
|
.expect("we know that the item exists")
|
||||||
|
.try_merge(item)
|
||||||
|
.expect("`try_merge` should succeed because `can_merge` returned `true`");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Only if merging items fails do we give up and create a new item
|
||||||
|
}
|
||||||
|
|
||||||
let item_drop = comp::item_drop::Body::from(&item);
|
let item_drop = comp::item_drop::Body::from(&item);
|
||||||
let body = comp::Body::ItemDrop(item_drop);
|
let body = comp::Body::ItemDrop(item_drop);
|
||||||
let light_emitter = match &*item.kind() {
|
let light_emitter = match &*item.kind() {
|
||||||
@ -323,17 +377,21 @@ impl StateExt for State {
|
|||||||
}),
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
self.ecs_mut()
|
Some(
|
||||||
.create_entity_synced()
|
self.ecs_mut()
|
||||||
.with(item)
|
.create_entity_synced()
|
||||||
.with(pos)
|
.with(item)
|
||||||
.with(comp::Vel(Vec3::zero()))
|
.with(pos)
|
||||||
.with(item_drop.orientation(&mut thread_rng()))
|
.with(vel)
|
||||||
.with(item_drop.mass())
|
.with(item_drop.orientation(&mut thread_rng()))
|
||||||
.with(item_drop.density())
|
.with(item_drop.mass())
|
||||||
.with(body.collider())
|
.with(item_drop.density())
|
||||||
.with(body)
|
.with(body.collider())
|
||||||
.maybe_with(light_emitter)
|
.with(body)
|
||||||
|
.maybe_with(loot_owner)
|
||||||
|
.maybe_with(light_emitter)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_ship<F: FnOnce(comp::ship::Body) -> comp::Collider>(
|
fn create_ship<F: FnOnce(comp::ship::Body) -> comp::Collider>(
|
||||||
|
@ -56,210 +56,209 @@ impl EventMapper for BlockEventMapper {
|
|||||||
let cam_pos = camera.dependents().cam_pos + focus_off;
|
let cam_pos = camera.dependents().cam_pos + focus_off;
|
||||||
|
|
||||||
// Get the player position and chunk
|
// Get the player position and chunk
|
||||||
let player_pos = state
|
if let Some(player_pos) = state.read_component_copied::<Pos>(player_entity) {
|
||||||
.read_component_copied::<Pos>(player_entity)
|
let player_chunk = player_pos.0.xy().map2(TerrainChunk::RECT_SIZE, |e, sz| {
|
||||||
.unwrap_or_default();
|
(e.floor() as i32).div_euclid(sz as i32)
|
||||||
let player_chunk = player_pos.0.xy().map2(TerrainChunk::RECT_SIZE, |e, sz| {
|
});
|
||||||
(e.floor() as i32).div_euclid(sz as i32)
|
|
||||||
});
|
|
||||||
|
|
||||||
// For determining if underground/crickets should chirp
|
// For determining if underground/crickets should chirp
|
||||||
let (terrain_alt, temp) = match client.current_chunk() {
|
let (terrain_alt, temp) = match client.current_chunk() {
|
||||||
Some(chunk) => (chunk.meta().alt(), chunk.meta().temp()),
|
Some(chunk) => (chunk.meta().alt(), chunk.meta().temp()),
|
||||||
None => (0.0, 0.0),
|
None => (0.0, 0.0),
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BlockSounds<'a> {
|
struct BlockSounds<'a> {
|
||||||
// The function to select the blocks of interest that we should emit from
|
// The function to select the blocks of interest that we should emit from
|
||||||
blocks: fn(&'a BlocksOfInterest) -> &'a [Vec3<i32>],
|
blocks: fn(&'a BlocksOfInterest) -> &'a [Vec3<i32>],
|
||||||
// The range, in chunks, that the particles should be generated in from the player
|
// The range, in chunks, that the particles should be generated in from the player
|
||||||
range: usize,
|
range: usize,
|
||||||
// The sound of the generated particle
|
// The sound of the generated particle
|
||||||
sfx: SfxEvent,
|
sfx: SfxEvent,
|
||||||
// The volume of the sfx
|
// The volume of the sfx
|
||||||
volume: f32,
|
volume: f32,
|
||||||
// Condition that must be true to play
|
// Condition that must be true to play
|
||||||
cond: fn(&State) -> bool,
|
cond: fn(&State) -> bool,
|
||||||
}
|
|
||||||
|
|
||||||
let sounds: &[BlockSounds] = &[
|
|
||||||
BlockSounds {
|
|
||||||
blocks: |boi| &boi.leaves,
|
|
||||||
range: 1,
|
|
||||||
sfx: SfxEvent::Birdcall,
|
|
||||||
volume: 1.0,
|
|
||||||
cond: |st| st.get_day_period().is_light(),
|
|
||||||
},
|
|
||||||
BlockSounds {
|
|
||||||
blocks: |boi| &boi.leaves,
|
|
||||||
range: 1,
|
|
||||||
sfx: SfxEvent::Owl,
|
|
||||||
volume: 1.0,
|
|
||||||
cond: |st| st.get_day_period().is_dark(),
|
|
||||||
},
|
|
||||||
BlockSounds {
|
|
||||||
blocks: |boi| &boi.slow_river,
|
|
||||||
range: 1,
|
|
||||||
sfx: SfxEvent::RunningWaterSlow,
|
|
||||||
volume: 1.2,
|
|
||||||
cond: |_| true,
|
|
||||||
},
|
|
||||||
BlockSounds {
|
|
||||||
blocks: |boi| &boi.fast_river,
|
|
||||||
range: 1,
|
|
||||||
sfx: SfxEvent::RunningWaterFast,
|
|
||||||
volume: 1.5,
|
|
||||||
cond: |_| true,
|
|
||||||
},
|
|
||||||
//BlockSounds {
|
|
||||||
// blocks: |boi| &boi.embers,
|
|
||||||
// range: 1,
|
|
||||||
// sfx: SfxEvent::Embers,
|
|
||||||
// volume: 0.15,
|
|
||||||
// //volume: 0.05,
|
|
||||||
// cond: |_| true,
|
|
||||||
// //cond: |st| st.get_day_period().is_dark(),
|
|
||||||
//},
|
|
||||||
BlockSounds {
|
|
||||||
blocks: |boi| &boi.frogs,
|
|
||||||
range: 1,
|
|
||||||
sfx: SfxEvent::Frog,
|
|
||||||
volume: 0.8,
|
|
||||||
cond: |st| st.get_day_period().is_dark(),
|
|
||||||
},
|
|
||||||
//BlockSounds {
|
|
||||||
// blocks: |boi| &boi.flowers,
|
|
||||||
// range: 4,
|
|
||||||
// sfx: SfxEvent::LevelUp,
|
|
||||||
// volume: 1.0,
|
|
||||||
// cond: |st| st.get_day_period().is_dark(),
|
|
||||||
//},
|
|
||||||
BlockSounds {
|
|
||||||
blocks: |boi| &boi.cricket1,
|
|
||||||
range: 1,
|
|
||||||
sfx: SfxEvent::Cricket1,
|
|
||||||
volume: 0.33,
|
|
||||||
cond: |st| st.get_day_period().is_dark(),
|
|
||||||
},
|
|
||||||
BlockSounds {
|
|
||||||
blocks: |boi| &boi.cricket2,
|
|
||||||
range: 1,
|
|
||||||
sfx: SfxEvent::Cricket2,
|
|
||||||
volume: 0.33,
|
|
||||||
cond: |st| st.get_day_period().is_dark(),
|
|
||||||
},
|
|
||||||
BlockSounds {
|
|
||||||
blocks: |boi| &boi.cricket3,
|
|
||||||
range: 1,
|
|
||||||
sfx: SfxEvent::Cricket3,
|
|
||||||
volume: 0.33,
|
|
||||||
cond: |st| st.get_day_period().is_dark(),
|
|
||||||
},
|
|
||||||
BlockSounds {
|
|
||||||
blocks: |boi| &boi.beehives,
|
|
||||||
range: 1,
|
|
||||||
sfx: SfxEvent::Bees,
|
|
||||||
volume: 0.5,
|
|
||||||
cond: |st| st.get_day_period().is_light(),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Iterate through each kind of block of interest
|
|
||||||
for sounds in sounds.iter() {
|
|
||||||
// If the timing condition is false, continue
|
|
||||||
// or if the player is far enough underground, continue
|
|
||||||
// TODO Address bird hack properly. See TODO on line 190
|
|
||||||
if !(sounds.cond)(state)
|
|
||||||
|| player_pos.0.z < (terrain_alt - 30.0)
|
|
||||||
|| (sounds.sfx == SfxEvent::Birdcall && thread_rng().gen_bool(0.995))
|
|
||||||
|| (sounds.sfx == SfxEvent::Owl && thread_rng().gen_bool(0.998))
|
|
||||||
|| (sounds.sfx == SfxEvent::Frog && thread_rng().gen_bool(0.95))
|
|
||||||
//Crickets will not chirp below 5 Celsius
|
|
||||||
|| (sounds.sfx == SfxEvent::Cricket1 && (temp < -0.33))
|
|
||||||
|| (sounds.sfx == SfxEvent::Cricket2 && (temp < -0.33))
|
|
||||||
|| (sounds.sfx == SfxEvent::Cricket3 && (temp < -0.33))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// For chunks surrounding the player position
|
let sounds: &[BlockSounds] = &[
|
||||||
for offset in Spiral2d::new().take((sounds.range * 2 + 1).pow(2)) {
|
BlockSounds {
|
||||||
let chunk_pos = player_chunk + offset;
|
blocks: |boi| &boi.leaves,
|
||||||
|
range: 1,
|
||||||
|
sfx: SfxEvent::Birdcall,
|
||||||
|
volume: 1.0,
|
||||||
|
cond: |st| st.get_day_period().is_light(),
|
||||||
|
},
|
||||||
|
BlockSounds {
|
||||||
|
blocks: |boi| &boi.leaves,
|
||||||
|
range: 1,
|
||||||
|
sfx: SfxEvent::Owl,
|
||||||
|
volume: 1.0,
|
||||||
|
cond: |st| st.get_day_period().is_dark(),
|
||||||
|
},
|
||||||
|
BlockSounds {
|
||||||
|
blocks: |boi| &boi.slow_river,
|
||||||
|
range: 1,
|
||||||
|
sfx: SfxEvent::RunningWaterSlow,
|
||||||
|
volume: 1.2,
|
||||||
|
cond: |_| true,
|
||||||
|
},
|
||||||
|
BlockSounds {
|
||||||
|
blocks: |boi| &boi.fast_river,
|
||||||
|
range: 1,
|
||||||
|
sfx: SfxEvent::RunningWaterFast,
|
||||||
|
volume: 1.5,
|
||||||
|
cond: |_| true,
|
||||||
|
},
|
||||||
|
//BlockSounds {
|
||||||
|
// blocks: |boi| &boi.embers,
|
||||||
|
// range: 1,
|
||||||
|
// sfx: SfxEvent::Embers,
|
||||||
|
// volume: 0.15,
|
||||||
|
// //volume: 0.05,
|
||||||
|
// cond: |_| true,
|
||||||
|
// //cond: |st| st.get_day_period().is_dark(),
|
||||||
|
//},
|
||||||
|
BlockSounds {
|
||||||
|
blocks: |boi| &boi.frogs,
|
||||||
|
range: 1,
|
||||||
|
sfx: SfxEvent::Frog,
|
||||||
|
volume: 0.8,
|
||||||
|
cond: |st| st.get_day_period().is_dark(),
|
||||||
|
},
|
||||||
|
//BlockSounds {
|
||||||
|
// blocks: |boi| &boi.flowers,
|
||||||
|
// range: 4,
|
||||||
|
// sfx: SfxEvent::LevelUp,
|
||||||
|
// volume: 1.0,
|
||||||
|
// cond: |st| st.get_day_period().is_dark(),
|
||||||
|
//},
|
||||||
|
BlockSounds {
|
||||||
|
blocks: |boi| &boi.cricket1,
|
||||||
|
range: 1,
|
||||||
|
sfx: SfxEvent::Cricket1,
|
||||||
|
volume: 0.33,
|
||||||
|
cond: |st| st.get_day_period().is_dark(),
|
||||||
|
},
|
||||||
|
BlockSounds {
|
||||||
|
blocks: |boi| &boi.cricket2,
|
||||||
|
range: 1,
|
||||||
|
sfx: SfxEvent::Cricket2,
|
||||||
|
volume: 0.33,
|
||||||
|
cond: |st| st.get_day_period().is_dark(),
|
||||||
|
},
|
||||||
|
BlockSounds {
|
||||||
|
blocks: |boi| &boi.cricket3,
|
||||||
|
range: 1,
|
||||||
|
sfx: SfxEvent::Cricket3,
|
||||||
|
volume: 0.33,
|
||||||
|
cond: |st| st.get_day_period().is_dark(),
|
||||||
|
},
|
||||||
|
BlockSounds {
|
||||||
|
blocks: |boi| &boi.beehives,
|
||||||
|
range: 1,
|
||||||
|
sfx: SfxEvent::Bees,
|
||||||
|
volume: 0.5,
|
||||||
|
cond: |st| st.get_day_period().is_light(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
// Get all the blocks of interest in this chunk
|
// Iterate through each kind of block of interest
|
||||||
terrain.get(chunk_pos).map(|chunk_data| {
|
for sounds in sounds.iter() {
|
||||||
// Get the positions of the blocks of type sounds
|
// If the timing condition is false, continue
|
||||||
let blocks = (sounds.blocks)(&chunk_data.blocks_of_interest);
|
// or if the player is far enough underground, continue
|
||||||
|
// TODO Address bird hack properly. See TODO on line 190
|
||||||
|
if !(sounds.cond)(state)
|
||||||
|
|| player_pos.0.z < (terrain_alt - 30.0)
|
||||||
|
|| (sounds.sfx == SfxEvent::Birdcall && thread_rng().gen_bool(0.995))
|
||||||
|
|| (sounds.sfx == SfxEvent::Owl && thread_rng().gen_bool(0.998))
|
||||||
|
|| (sounds.sfx == SfxEvent::Frog && thread_rng().gen_bool(0.95))
|
||||||
|
//Crickets will not chirp below 5 Celsius
|
||||||
|
|| (sounds.sfx == SfxEvent::Cricket1 && (temp < -0.33))
|
||||||
|
|| (sounds.sfx == SfxEvent::Cricket2 && (temp < -0.33))
|
||||||
|
|| (sounds.sfx == SfxEvent::Cricket3 && (temp < -0.33))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let absolute_pos: Vec3<i32> =
|
// For chunks surrounding the player position
|
||||||
Vec3::from(chunk_pos * TerrainChunk::RECT_SIZE.map(|e| e as i32));
|
for offset in Spiral2d::new().take((sounds.range * 2 + 1).pow(2)) {
|
||||||
|
let chunk_pos = player_chunk + offset;
|
||||||
|
|
||||||
// Replace all RunningWater blocks with just one random one per tick
|
// Get all the blocks of interest in this chunk
|
||||||
let blocks = if sounds.sfx == SfxEvent::RunningWaterSlow
|
terrain.get(chunk_pos).map(|chunk_data| {
|
||||||
|| sounds.sfx == SfxEvent::RunningWaterFast
|
// Get the positions of the blocks of type sounds
|
||||||
{
|
let blocks = (sounds.blocks)(&chunk_data.blocks_of_interest);
|
||||||
blocks
|
|
||||||
.choose(&mut thread_rng())
|
|
||||||
.map(std::slice::from_ref)
|
|
||||||
.unwrap_or(&[])
|
|
||||||
} else {
|
|
||||||
blocks
|
|
||||||
};
|
|
||||||
|
|
||||||
// Iterate through each individual block
|
let absolute_pos: Vec3<i32> =
|
||||||
for block in blocks {
|
Vec3::from(chunk_pos * TerrainChunk::RECT_SIZE.map(|e| e as i32));
|
||||||
// TODO Address this hack properly, potentially by making a new
|
|
||||||
// block of interest type which picks fewer leaf blocks
|
// Replace all RunningWater blocks with just one random one per tick
|
||||||
// Hack to reduce the number of bird, frog, and water sounds
|
let blocks = if sounds.sfx == SfxEvent::RunningWaterSlow
|
||||||
if ((sounds.sfx == SfxEvent::Birdcall || sounds.sfx == SfxEvent::Owl)
|
|| sounds.sfx == SfxEvent::RunningWaterFast
|
||||||
&& thread_rng().gen_bool(0.9995))
|
|
||||||
|| (sounds.sfx == SfxEvent::Frog && thread_rng().gen_bool(0.75))
|
|
||||||
|| (sounds.sfx == SfxEvent::RunningWaterSlow
|
|
||||||
&& thread_rng().gen_bool(0.5))
|
|
||||||
{
|
{
|
||||||
continue;
|
blocks
|
||||||
}
|
.choose(&mut thread_rng())
|
||||||
let block_pos: Vec3<i32> = absolute_pos + block;
|
.map(std::slice::from_ref)
|
||||||
let internal_state = self.history.entry(block_pos).or_default();
|
.unwrap_or(&[])
|
||||||
|
} else {
|
||||||
|
blocks
|
||||||
|
};
|
||||||
|
|
||||||
let block_pos = block_pos.map(|x| x as f32);
|
// Iterate through each individual block
|
||||||
|
for block in blocks {
|
||||||
if Self::should_emit(
|
// TODO Address this hack properly, potentially by making a new
|
||||||
internal_state,
|
// block of interest type which picks fewer leaf blocks
|
||||||
triggers.get_key_value(&sounds.sfx),
|
// Hack to reduce the number of bird, frog, and water sounds
|
||||||
temp,
|
if ((sounds.sfx == SfxEvent::Birdcall || sounds.sfx == SfxEvent::Owl)
|
||||||
) {
|
&& thread_rng().gen_bool(0.9995))
|
||||||
// If the camera is within SFX distance
|
|| (sounds.sfx == SfxEvent::Frog && thread_rng().gen_bool(0.75))
|
||||||
if (block_pos.distance_squared(cam_pos)) < SFX_DIST_LIMIT_SQR {
|
|| (sounds.sfx == SfxEvent::RunningWaterSlow
|
||||||
let underwater = state
|
&& thread_rng().gen_bool(0.5))
|
||||||
.terrain()
|
{
|
||||||
.get(cam_pos.map(|e| e.floor() as i32))
|
continue;
|
||||||
.map(|b| b.is_liquid())
|
}
|
||||||
.unwrap_or(false);
|
let block_pos: Vec3<i32> = absolute_pos + block;
|
||||||
|
let internal_state = self.history.entry(block_pos).or_default();
|
||||||
let sfx_trigger_item = triggers.get_key_value(&sounds.sfx);
|
|
||||||
if sounds.sfx == SfxEvent::RunningWaterFast {
|
let block_pos = block_pos.map(|x| x as f32);
|
||||||
audio.emit_filtered_sfx(
|
|
||||||
sfx_trigger_item,
|
if Self::should_emit(
|
||||||
block_pos,
|
internal_state,
|
||||||
Some(sounds.volume),
|
triggers.get_key_value(&sounds.sfx),
|
||||||
Some(8000),
|
temp,
|
||||||
underwater,
|
) {
|
||||||
);
|
// If the camera is within SFX distance
|
||||||
} else {
|
if (block_pos.distance_squared(cam_pos)) < SFX_DIST_LIMIT_SQR {
|
||||||
audio.emit_sfx(
|
let underwater = state
|
||||||
sfx_trigger_item,
|
.terrain()
|
||||||
block_pos,
|
.get(cam_pos.map(|e| e.floor() as i32))
|
||||||
Some(sounds.volume),
|
.map(|b| b.is_liquid())
|
||||||
underwater,
|
.unwrap_or(false);
|
||||||
);
|
|
||||||
}
|
let sfx_trigger_item = triggers.get_key_value(&sounds.sfx);
|
||||||
|
if sounds.sfx == SfxEvent::RunningWaterFast {
|
||||||
|
audio.emit_filtered_sfx(
|
||||||
|
sfx_trigger_item,
|
||||||
|
block_pos,
|
||||||
|
Some(sounds.volume),
|
||||||
|
Some(8000),
|
||||||
|
underwater,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
audio.emit_sfx(
|
||||||
|
sfx_trigger_item,
|
||||||
|
block_pos,
|
||||||
|
Some(sounds.volume),
|
||||||
|
underwater,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
internal_state.time = Instant::now();
|
||||||
|
internal_state.event = sounds.sfx.clone();
|
||||||
}
|
}
|
||||||
internal_state.time = Instant::now();
|
|
||||||
internal_state.event = sounds.sfx.clone();
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user