autodelete of summoned sprites

This commit is contained in:
flo 2023-01-15 18:28:38 +00:00 committed by Joshua Barretto
parent b127cff26c
commit 650ef9a5e2
15 changed files with 157 additions and 14 deletions

10
Cargo.lock generated
View File

@ -6177,6 +6177,15 @@ dependencies = [
"num_threads",
]
[[package]]
name = "timer-queue"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13756c29c43d836ff576221498bf4916b0d2f7ea24cd47d3531b70dc4341f038"
dependencies = [
"slab",
]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
@ -6826,6 +6835,7 @@ dependencies = [
"serde",
"specs",
"tar",
"timer-queue",
"toml",
"tracing",
"vek 0.15.8",

View File

@ -3,6 +3,7 @@ SpriteSummon(
cast_duration: 0.4,
recover_duration: 0.3,
sprite: EnsnaringWeb,
del_timeout: None,
summon_distance: (0, 10),
sparseness: 0.76,
)

View File

@ -3,6 +3,7 @@ SpriteSummon(
cast_duration: 0.4,
recover_duration: 0.3,
sprite: EnsnaringWeb,
del_timeout: None,
summon_distance: (0, 9),
sparseness: 0.8,
)

View File

@ -3,6 +3,7 @@ SpriteSummon(
cast_duration: 0.1,
recover_duration: 0.9,
sprite: SeaUrchin,
del_timeout: Some((4, 5)),
summon_distance: (5, 3.1),
sparseness: 0.2,
)

View File

@ -3,6 +3,7 @@ SpriteSummon(
cast_duration: 0.4,
recover_duration: 0.3,
sprite: EnsnaringVines,
del_timeout: None,
summon_distance: (0, 25),
sparseness: 0.67,
)

View File

@ -700,6 +700,7 @@ pub enum CharacterAbility {
cast_duration: f32,
recover_duration: f32,
sprite: SpriteKind,
del_timeout: Option<(f32, f32)>,
summon_distance: (f32, f32),
sparseness: f64,
#[serde(default)]
@ -1275,6 +1276,7 @@ impl CharacterAbility {
ref mut cast_duration,
ref mut recover_duration,
sprite: _,
del_timeout: _,
summon_distance: (ref mut inner_dist, ref mut outer_dist),
sparseness: _,
meta: _,
@ -2508,6 +2510,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
cast_duration,
recover_duration,
sprite,
del_timeout,
summon_distance,
sparseness,
meta: _,
@ -2517,6 +2520,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
cast_duration: Duration::from_secs_f32(*cast_duration),
recover_duration: Duration::from_secs_f32(*recover_duration),
sprite: *sprite,
del_timeout: *del_timeout,
summon_distance: *summon_distance,
sparseness: *sparseness,
ability_info,

View File

@ -208,6 +208,7 @@ pub enum ServerEvent {
CreateSprite {
pos: Vec3<i32>,
sprite: SpriteKind,
del_timeout: Option<(f32, f32)>,
},
TamePet {
pet_entity: EcsEntity,

View File

@ -1,4 +1,4 @@
use crate::{combat::DamageContributor, comp, uid::Uid, DamageSource};
use crate::{combat::DamageContributor, comp, uid::Uid, DamageSource, terrain::SpriteKind};
use comp::{beam, item::Reagent, poise::PoiseState, skillset::SkillGroupKind, UtteranceKind};
use hashbrown::HashSet;
use serde::{Deserialize, Serialize};
@ -97,6 +97,10 @@ pub enum Outcome {
pos: Vec3<f32>,
wielded: bool,
},
SpriteDelete {
pos: Vec3<f32>,
sprite: SpriteKind,
},
}
impl Outcome {
@ -115,6 +119,7 @@ impl Outcome {
| Outcome::PoiseChange { pos, .. }
| Outcome::GroundSlam { pos }
| Outcome::Utterance { pos, .. }
| Outcome::SpriteDelete { pos, .. }
| Outcome::Glider { pos, .. } => Some(*pos),
Outcome::BreakBlock { pos, .. } => Some(pos.map(|e| e as f32 + 0.5)),
Outcome::ExpChange { .. }

View File

@ -25,6 +25,8 @@ pub struct StaticData {
pub recover_duration: Duration,
/// What kind of sprite is created by this state
pub sprite: SpriteKind,
/// Duration until sprite-delete begins (in sec), randomization-range of sprite-delete-time (in sec)
pub del_timeout: Option<(f32, f32)>,
/// Range that sprites are created relative to the summonner
pub summon_distance: (f32, f32),
/// Chance that sprite is not created on a particular square
@ -127,6 +129,7 @@ impl CharacterBehavior for Data {
output_events.emit_server(ServerEvent::CreateSprite {
pos: Vec3::new(sprite_pos.x, sprite_pos.y, z + i),
sprite: self.static_data.sprite,
del_timeout: self.static_data.del_timeout,
});
}
}

View File

@ -35,6 +35,7 @@ tar = { version = "0.4.37", optional = true }
wasmer = { version = "2.0.0", optional = true, default-features = false, features = ["wat", "default-cranelift", "default-universal"] }
bincode = { version = "1.3.1", optional = true }
plugin-api = { package = "veloren-plugin-api", path = "../../plugin/api", optional = true }
timer-queue = "0.1.0"
# Tweak running code
#inline_tweak = { version = "1.0.8", features = ["release_tweak"] }

View File

@ -36,18 +36,20 @@ use specs::{
Component, DispatcherBuilder, Entity as EcsEntity, WorldExt,
};
use std::sync::Arc;
use timer_queue::TimerQueue;
use vek::*;
/// How much faster should an in-game day be compared to a real day?
// TODO: Don't hard-code this.
const DAY_CYCLE_FACTOR: f64 = 24.0 * 2.0;
/// At what point should we stop speeding up physics to compensate for lag? If
/// we speed physics up too fast, we'd skip important physics events like
/// collisions. This constant determines the upper limit. If delta time exceeds
/// this value, the game's physics will begin to produce time lag. Ideally, we'd
/// avoid such a situation.
const MAX_DELTA_TIME: f32 = 1.0;
/// convert seconds to milliseconds to use in TimerQueue
const SECONDS_TO_MILLISECONDS: f64 = 1000.0;
#[derive(Default)]
pub struct BlockChange {
@ -69,6 +71,29 @@ impl BlockChange {
pub fn clear(&mut self) { self.blocks.clear(); }
}
#[derive(Default)]
pub struct ScheduledBlockChange {
changes: TimerQueue<HashMap<Vec3<i32>, Block>>,
outcomes: TimerQueue<HashMap<Vec3<i32>, Block>>,
}
impl ScheduledBlockChange {
pub fn set(&mut self, pos: Vec3<i32>, block: Block, replace_time: f64) {
let timer = self.changes.insert(
(replace_time * SECONDS_TO_MILLISECONDS) as u64,
HashMap::new(),
);
self.changes.get_mut(timer).insert(pos, block);
}
pub fn outcome_set(&mut self, pos: Vec3<i32>, block: Block, replace_time: f64) {
let outcome_timer = self.outcomes.insert(
(replace_time * SECONDS_TO_MILLISECONDS) as u64,
HashMap::new(),
);
self.outcomes.get_mut(outcome_timer).insert(pos, block);
}
}
#[derive(Default)]
pub struct TerrainChanges {
pub new_chunks: HashSet<Vec2<i32>>,
@ -234,6 +259,7 @@ impl State {
ecs.insert(PlayerEntity(None));
ecs.insert(TerrainGrid::new(map_size_lg, default_chunk).unwrap());
ecs.insert(BlockChange::default());
ecs.insert(ScheduledBlockChange::default());
ecs.insert(crate::build_areas::BuildAreas::default());
ecs.insert(TerrainChanges::default());
ecs.insert(EventBus::<LocalEvent>::default());
@ -415,6 +441,23 @@ impl State {
self.ecs.write_resource::<BlockChange>().set(pos, block);
}
/// Set a block in this state's terrain (used to delete temporary summoned
/// sprites after a timeout).
pub fn schedule_set_block(
&self,
pos: Vec3<i32>,
block: Block,
sprite_block: Block,
replace_time: f64,
) {
self.ecs
.write_resource::<ScheduledBlockChange>()
.set(pos, block, replace_time);
self.ecs
.write_resource::<ScheduledBlockChange>()
.outcome_set(pos, sprite_block, replace_time);
}
/// Check if the block at given position `pos` has already been modified
/// this tick.
pub fn can_set_block(&self, pos: Vec3<i32>) -> bool {
@ -500,6 +543,28 @@ impl State {
let mut terrain = self.ecs.write_resource::<TerrainGrid>();
let mut modified_blocks =
std::mem::take(&mut self.ecs.write_resource::<BlockChange>().blocks);
let mut scheduled_changes = self.ecs.write_resource::<ScheduledBlockChange>();
let current_time: f64 = self.ecs.read_resource::<Time>().0 * SECONDS_TO_MILLISECONDS;
while let Some(changes) = scheduled_changes.changes.poll(current_time as u64) {
modified_blocks.extend(changes.iter());
}
let outcome = self.ecs.read_resource::<EventBus<Outcome>>();
while let Some(outcomes) = scheduled_changes.outcomes.poll(current_time as u64) {
for (pos, block) in outcomes.iter() {
let offset_dir = Vec3::<i32>::zero() - pos;
let offset = offset_dir
/ Vec3::new(offset_dir.x.abs(), offset_dir.y.abs(), offset_dir.z.abs());
let outcome_pos = Vec3::new(pos.x as f32, pos.y as f32, pos.z as f32)
- (Vec3::new(offset.x as f32, offset.y as f32, offset.z as f32) / 2.0);
if let Some(sprite) = block.get_sprite() {
outcome.emit_now(Outcome::SpriteDelete {
pos: outcome_pos,
sprite,
});
}
}
}
// Apply block modifications
// Only include in `TerrainChanges` if successful
modified_blocks.retain(|pos, block| {

View File

@ -23,7 +23,7 @@ use common::{
};
use common_net::sync::WorldSyncExt;
use crate::{state_ext::StateExt, Server};
use crate::{state_ext::StateExt, Server, Time};
use crate::pet::tame_pet;
use hashbrown::{HashMap, HashSet};
@ -293,16 +293,32 @@ pub fn handle_sound(server: &mut Server, sound: &Sound) {
}
}
pub fn handle_create_sprite(server: &mut Server, pos: Vec3<i32>, sprite: SpriteKind) {
pub fn handle_create_sprite(
server: &mut Server,
pos: Vec3<i32>,
sprite: SpriteKind,
del_timeout: Option<(f32, f32)>,
) {
let state = server.state_mut();
if state.can_set_block(pos) {
let block = state.terrain().get(pos).ok().copied();
if block.map_or(false, |b| (*b).is_air()) {
let new_block = state
.get_block(pos)
.unwrap_or_else(|| Block::air(SpriteKind::Empty))
.with_sprite(sprite);
server.state.set_block(pos, new_block);
let old_block = block.unwrap_or_else(|| Block::air(SpriteKind::Empty));
let new_block = old_block.with_sprite(sprite);
state.set_block(pos, new_block);
// Remove sprite after del_timeout and offset if specified
if let Some((timeout, del_offset)) = del_timeout {
use rand::Rng;
let mut rng = rand::thread_rng();
let offset = rng.gen_range(0.0..del_offset);
let current_time: f64 = state.ecs().read_resource::<Time>().0;
let replace_time = current_time + (timeout + offset) as f64;
if old_block != new_block {
server
.state
.schedule_set_block(pos, old_block, new_block, replace_time)
}
}
}
}
}

View File

@ -267,9 +267,11 @@ impl Server {
self.state.create_safezone(range, pos).build();
},
ServerEvent::Sound { sound } => handle_sound(self, &sound),
ServerEvent::CreateSprite { pos, sprite } => {
handle_create_sprite(self, pos, sprite)
},
ServerEvent::CreateSprite {
pos,
sprite,
del_timeout,
} => handle_create_sprite(self, pos, sprite, del_timeout),
ServerEvent::TamePet {
pet_entity,
owner_entity,

View File

@ -101,7 +101,7 @@ use common::{
InventoryUpdateEvent, UtteranceKind,
},
outcome::Outcome,
terrain::{BlockKind, TerrainChunk},
terrain::{BlockKind, SpriteKind, TerrainChunk},
uid::Uid,
DamageSource,
};
@ -585,6 +585,23 @@ impl SfxMgr {
audio.emit_sfx(sfx_trigger_item, *pos, Some(1.0), underwater);
}
},
Outcome::SpriteDelete { pos, sprite } => {
match sprite {
SpriteKind::SeaUrchin => {
let power = (0.6 - pos.distance(audio.listener.pos) / 5_000.0)
.max(0.0)
.powi(7);
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Explosion);
audio.emit_sfx(
sfx_trigger_item,
*pos,
Some((power.abs() / 2.5).min(0.3)),
underwater,
);
},
_ => {},
};
},
Outcome::ExpChange { .. }
| Outcome::ComboChange { .. }
| Outcome::SummonedCreature { .. } => {},

View File

@ -19,7 +19,7 @@ use common::{
resources::DeltaTime,
spiral::Spiral2d,
states::{self, utils::StageSection},
terrain::{Block, TerrainChunk, TerrainGrid},
terrain::{Block, SpriteKind, TerrainChunk, TerrainGrid},
uid::UidAllocator,
vol::{ReadVol, RectRasterableVol, SizedVol},
};
@ -74,6 +74,21 @@ impl ParticleMgr {
)
});
},
Outcome::SpriteDelete { pos, sprite } => match sprite {
SpriteKind::SeaUrchin => {
self.particles.resize_with(self.particles.len() + 10, || {
Particle::new_directed(
Duration::from_secs_f32(rng.gen_range(0.1..0.5)),
time,
ParticleMode::Steam,
*pos + Vec3::new(0.0, 0.0, rng.gen_range(0.0..1.5)),
*pos,
)
});
},
SpriteKind::EnsnaringVines => {},
_ => {},
},
Outcome::Explosion {
pos,
power,