Slightly functional sprite summon.

This commit is contained in:
Sam 2021-06-19 13:53:23 -05:00
parent 15a2fbc555
commit ed503236d6
14 changed files with 271 additions and 20 deletions

View File

@ -1,15 +1,7 @@
BasicRanged(
energy_cost: 0,
buildup_duration: 0.5,
recover_duration: 0.8,
projectile: ExplodingPumpkin(
damage: 500.0,
knockback: 25.0,
radius: 10.0,
),
projectile_body: Object(Pumpkin),
projectile_light: None,
projectile_speed: 30.0,
num_projectiles: 1,
projectile_spread: 0.0,
)
SpriteSummon(
buildup_duration: 0.3,
cast_duration: 1.0,
recover_duration: 0.2,
sprite: EnsnaringVines,
summon_distance: (0, 25),
)

BIN
assets/voxygen/voxel/sprite/misc/ensnaring_vines.vox (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -3247,4 +3247,14 @@ CookingPot: Some((
],
wind_sway: 0.0,
)),
EnsnaringVines: Some((
variations: [
(
model: "voxygen.voxel.sprite.misc.ensnaring_vines",
offset: (-5.0, -6.5, 0.0),
lod_axes: (0.0, 0.0, 0.0),
),
],
wind_sway: 0.0,
)),
)

View File

@ -10,6 +10,7 @@ use crate::{
utils::{AbilityInfo, StageSection},
*,
},
terrain::SpriteKind,
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
@ -294,6 +295,13 @@ pub enum CharacterAbility {
buff_duration: Option<f32>,
energy_cost: f32,
},
SpriteSummon {
buildup_duration: f32,
cast_duration: f32,
recover_duration: f32,
sprite: SpriteKind,
summon_distance: (f32, f32),
},
}
impl Default for CharacterAbility {
@ -365,7 +373,8 @@ impl CharacterAbility {
| CharacterAbility::Boost { .. }
| CharacterAbility::BasicBeam { .. }
| CharacterAbility::Blink { .. }
| CharacterAbility::BasicSummon { .. } => true,
| CharacterAbility::BasicSummon { .. }
| CharacterAbility::SpriteSummon { .. } => true,
}
}
@ -627,6 +636,17 @@ impl CharacterAbility {
*cast_duration /= speed;
*recover_duration /= speed;
},
SpriteSummon {
ref mut buildup_duration,
ref mut cast_duration,
ref mut recover_duration,
..
} => {
// TODO: Figure out how/if power should affect this
*buildup_duration /= speed;
*cast_duration /= speed;
*recover_duration /= speed;
},
}
self
}
@ -655,7 +675,11 @@ impl CharacterAbility {
0
}
},
Boost { .. } | ComboMelee { .. } | Blink { .. } | BasicSummon { .. } => 0,
Boost { .. }
| ComboMelee { .. }
| Blink { .. }
| BasicSummon { .. }
| SpriteSummon { .. } => 0,
}
}
@ -1781,6 +1805,25 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState {
timer: Duration::default(),
stage_section: StageSection::Buildup,
}),
CharacterAbility::SpriteSummon {
buildup_duration,
cast_duration,
recover_duration,
sprite,
summon_distance,
} => CharacterState::SpriteSummon(sprite_summon::Data {
static_data: sprite_summon::StaticData {
buildup_duration: Duration::from_secs_f32(*buildup_duration),
cast_duration: Duration::from_secs_f32(*cast_duration),
recover_duration: Duration::from_secs_f32(*recover_duration),
sprite: *sprite,
summon_distance: *summon_distance,
ability_info,
},
timer: Duration::default(),
stage_section: StageSection::Buildup,
achieved_radius: 0,
}),
}
}
}

View File

@ -103,6 +103,8 @@ pub enum CharacterState {
BasicSummon(basic_summon::Data),
/// Inserts a buff on the caster
SelfBuff(self_buff::Data),
/// Creates sprites around the caster
SpriteSummon(sprite_summon::Data),
}
impl CharacterState {
@ -125,6 +127,9 @@ impl CharacterState {
| CharacterState::BasicAura(_)
| CharacterState::HealingBeam(_)
| CharacterState::SelfBuff(_)
| CharacterState::Blink(_)
| CharacterState::BasicSummon(_)
| CharacterState::SpriteSummon(_)
)
}
@ -149,6 +154,9 @@ impl CharacterState {
| CharacterState::BasicAura(_)
| CharacterState::HealingBeam(_)
| CharacterState::SelfBuff(_)
| CharacterState::Blink(_)
| CharacterState::BasicSummon(_)
| CharacterState::SpriteSummon(_)
)
}

View File

@ -9,6 +9,7 @@ use crate::{
},
outcome::Outcome,
rtsim::RtSimEntity,
terrain::SpriteKind,
trade::{TradeAction, TradeId},
uid::Uid,
util::Dir,
@ -181,6 +182,10 @@ pub enum ServerEvent {
Sound {
sound: Sound,
},
CreateSprite {
pos: Vec3<i32>,
sprite: SpriteKind,
},
}
pub struct EventBus<E> {

View File

@ -10,12 +10,22 @@ pub struct Spiral2d {
impl Spiral2d {
#[allow(clippy::new_without_default)] // TODO: Pending review in #587
/// Creates a new spiral starting at the origin
pub fn new() -> Self { Self { layer: 0, i: 0 } }
/// Creates an iterator over points in a spiral starting at the origin and
/// going out to some radius
pub fn radius(self, radius: i32) -> impl Iterator<Item = Vec2<i32>> {
self.take((radius * 2 + 1).pow(2) as usize)
.filter(move |pos| pos.magnitude_squared() < (radius + 1).pow(2))
}
/// Creates an iterator over points in the edge of a circle of some radius
pub fn edge_radius(self, radius: i32) -> impl Iterator<Item = Vec2<i32>> {
self.take((radius * 2 + 1).pow(2) as usize)
.filter(move |pos| pos.magnitude_squared() < (radius + 1).pow(2))
.filter(move |pos| pos.magnitude_squared() >= radius.pow(2))
}
}
impl Iterator for Spiral2d {

View File

@ -26,6 +26,7 @@ pub mod shockwave;
pub mod sit;
pub mod sneak;
pub mod spin_melee;
pub mod sprite_summon;
pub mod stunned;
pub mod talk;
pub mod utils;

View File

@ -0,0 +1,156 @@
use crate::{
comp::{CharacterState, StateUpdate},
event::ServerEvent,
spiral::Spiral2d,
states::{
behavior::{CharacterBehavior, JoinData},
utils::*,
},
terrain::{Block, SpriteKind},
vol::ReadVol,
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use vek::*;
/// Separated out to condense update portions of character state
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct StaticData {
/// How long the state builds up for
pub buildup_duration: Duration,
/// How long the state is casting for
pub cast_duration: Duration,
/// How long the state recovers for
pub recover_duration: Duration,
/// What kind of sprite is created by this state
pub sprite: SpriteKind,
/// Range that sprites are created relative to the summonner
pub summon_distance: (f32, f32),
/// Miscellaneous information about the ability
pub ability_info: AbilityInfo,
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Data {
/// Struct containing data that does not change over the course of the
/// character state
pub static_data: StaticData,
/// Timer for each stage
pub timer: Duration,
/// What section the character stage is in
pub stage_section: StageSection,
/// What radius of sprites have already been summoned
pub achieved_radius: i32,
}
impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data);
match self.stage_section {
StageSection::Buildup => {
if self.timer < self.static_data.buildup_duration {
// Build up
update.character = CharacterState::SpriteSummon(Data {
timer: tick_attack_or_default(data, self.timer, None),
..*self
});
} else {
// Transitions to recover section of stage
update.character = CharacterState::SpriteSummon(Data {
timer: Duration::default(),
stage_section: StageSection::Cast,
..*self
});
}
},
StageSection::Cast => {
if self.timer < self.static_data.cast_duration {
let timer_frac =
self.timer.as_secs_f32() / self.static_data.cast_duration.as_secs_f32();
// Determines distance from summoner sprites should be created. Goes outward
// with time.
let summon_distance = timer_frac
* (self.static_data.summon_distance.1 - self.static_data.summon_distance.0)
+ self.static_data.summon_distance.0;
let summon_distance = summon_distance.round() as i32;
// Only summons sprites if summon distance is greater than achieved radius
if summon_distance > self.achieved_radius {
// Creates a spiral iterator for the newly achieved radius
let spiral = Spiral2d::new().edge_radius(summon_distance);
for point in spiral {
// The coordinates of where the sprite is created
let sprite_pos = Vec3::new(
data.pos.0.x.floor() as i32 + point.x,
data.pos.0.y.floor() as i32 + point.y,
data.pos.0.z.floor() as i32,
);
// Check for collision in z up to 25 blocks up or down
let obstacle_z = data
.terrain
.ray(
sprite_pos.map(|x| x as f32 + 0.5) + Vec3::unit_z() * 25.0,
sprite_pos.map(|x| x as f32 + 0.5) - Vec3::unit_z() * 25.0,
)
.until(|b| {
Block::is_solid(b)
&& !matches!(
b.get_sprite(),
Some(SpriteKind::EnsnaringVines)
)
})
.cast()
.0;
// z height relative to caster
let z = sprite_pos.z + (25.5 - obstacle_z).ceil() as i32;
// Location sprite will be created
let sprite_pos = Vec3::new(sprite_pos.x as i32, sprite_pos.y as i32, z);
// Send server event to create sprite
update.server_events.push_front(ServerEvent::CreateSprite {
pos: sprite_pos,
sprite: self.static_data.sprite,
});
}
}
update.character = CharacterState::SpriteSummon(Data {
timer: tick_attack_or_default(data, self.timer, None),
achieved_radius: summon_distance,
..*self
});
} else {
// Transitions to recover section of stage
update.character = CharacterState::SpriteSummon(Data {
timer: Duration::default(),
stage_section: StageSection::Recover,
..*self
});
}
},
StageSection::Recover => {
if self.timer < self.static_data.recover_duration {
// Recovery
update.character = CharacterState::SpriteSummon(Data {
timer: tick_attack_or_default(data, self.timer, None),
..*self
});
} else {
// Done
update.character = CharacterState::Wielding;
}
},
_ => {
// If it somehow ends up in an incorrect stage section
update.character = CharacterState::Wielding;
},
}
update
}
}

View File

@ -173,6 +173,7 @@ make_case_elim!(
CrystalLow = 0x92,
CeilingMushroom = 0x93,
Orb = 0x94,
EnsnaringVines = 0x95,
}
);
@ -256,6 +257,7 @@ impl SpriteKind {
| SpriteKind::Tin
| SpriteKind::Silver
| SpriteKind::Gold => 0.6,
SpriteKind::EnsnaringVines => 0.1,
_ => return None,
})
}

View File

@ -331,6 +331,7 @@ impl<'a> System<'a> for Sys {
CharacterState::Blink(data) => data.handle_event(&j, action),
CharacterState::BasicSummon(data) => data.handle_event(&j, action),
CharacterState::SelfBuff(data) => data.handle_event(&j, action),
CharacterState::SpriteSummon(data) => data.handle_event(&j, action),
};
local_emitter.append(&mut state_update.local_events);
server_emitter.append(&mut state_update.server_events);
@ -386,6 +387,7 @@ impl<'a> System<'a> for Sys {
CharacterState::Blink(data) => data.behavior(&j),
CharacterState::BasicSummon(data) => data.behavior(&j),
CharacterState::SelfBuff(data) => data.behavior(&j),
CharacterState::SpriteSummon(data) => data.behavior(&j),
};
local_emitter.append(&mut state_update.local_events);

View File

@ -279,7 +279,8 @@ impl<'a> System<'a> for Sys {
| CharacterState::HealingBeam { .. }
| CharacterState::Blink { .. }
| CharacterState::BasicSummon { .. }
| CharacterState::SelfBuff { .. } => {
| CharacterState::SelfBuff { .. }
| CharacterState::SpriteSummon { .. } => {
if energy.get_unchecked().regen_rate != 0.0 {
energy.get_mut_unchecked().regen_rate = 0.0
}

View File

@ -16,6 +16,7 @@ use common::{
},
consts::{MAX_MOUNT_RANGE, SOUND_TRAVEL_DIST_PER_VOLUME},
outcome::Outcome,
terrain::{Block, SpriteKind},
uid::Uid,
vol::ReadVol,
};
@ -418,3 +419,17 @@ pub fn handle_sound(server: &mut Server, sound: &Sound) {
ecs.write_resource::<Vec<Outcome>>().push(outcome);
}
}
pub fn handle_create_sprite(server: &mut Server, pos: Vec3<i32>, sprite: SpriteKind) {
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);
}
}
}

View File

@ -13,8 +13,8 @@ use entity_manipulation::{
use group_manip::handle_group;
use information::handle_site_info;
use interaction::{
handle_lantern, handle_mine_block, handle_mount, handle_npc_interaction, handle_possess,
handle_sound, handle_unmount,
handle_create_sprite, handle_lantern, handle_mine_block, handle_mount, handle_npc_interaction,
handle_possess, handle_sound, handle_unmount,
};
use inventory_manip::handle_inventory;
use invite::{handle_invite, handle_invite_response};
@ -221,6 +221,9 @@ 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)
},
}
}