diff --git a/CHANGELOG.md b/CHANGELOG.md index b2b5d68eb9..29e66c3154 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -120,6 +120,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Optimized rendering of quads (most of the graphics in the game) using an index buffer, decreasing the number of vertices that need to be processed by 33%. - Moved the rest of screenshot work into the background. Screenshoting no longer induces large pauses. - Reworked tidal warrior to have unique attacks +- Reworked yeti to have unique attacks ### Removed diff --git a/assets/common/abilities/ability_set_manifest.ron b/assets/common/abilities/ability_set_manifest.ron index 9f3b3f8b26..d9e401f986 100644 --- a/assets/common/abilities/ability_set_manifest.ron +++ b/assets/common/abilities/ability_set_manifest.ron @@ -237,6 +237,14 @@ (None, "common.abilities.custom.claygolem.rocket"), ], ), + Custom("Yeti"): ( + primary: "common.abilities.custom.yeti.strike", + secondary: "common.abilities.custom.yeti.icespikes", + abilities: [ + (None, "common.abilities.custom.yeti.frostbreath"), + (None, "common.abilities.custom.yeti.snowball"), + ], + ), Custom("Bird Large Breathe"): ( primary: "common.abilities.custom.birdlargebreathe.firebomb", secondary: "common.abilities.custom.birdlargebreathe.triplestrike", diff --git a/assets/common/abilities/custom/yeti/frostbreath.ron b/assets/common/abilities/custom/yeti/frostbreath.ron new file mode 100644 index 0000000000..826f59bbcb --- /dev/null +++ b/assets/common/abilities/custom/yeti/frostbreath.ron @@ -0,0 +1,19 @@ +BasicBeam( + buildup_duration: 0.4, + recover_duration: 0.25, + beam_duration: 0.25, + damage: 50, + tick_rate: 1.0, + range: 15.0, + max_angle: 30.0, + damage_effect: Some(Buff(( + kind: Frozen, + dur_secs: 3.0, + strength: Value(1.5), + chance: 1.0, + ))), + energy_regen: 0, + energy_drain: 0, + orientation_behavior: Normal, + specifier: Frost, +) \ No newline at end of file diff --git a/assets/common/abilities/custom/yeti/icespikes.ron b/assets/common/abilities/custom/yeti/icespikes.ron new file mode 100644 index 0000000000..3b99b3af93 --- /dev/null +++ b/assets/common/abilities/custom/yeti/icespikes.ron @@ -0,0 +1,17 @@ +Shockwave( + energy_cost: 0, + buildup_duration: 0.6, + swing_duration: 0.12, + recover_duration: 0.3, + damage: 100, + poise_damage: 10, + knockback: (strength: 25.0, direction: Up), + shockwave_angle: 90.0, + shockwave_vertical_angle: 15.0, + shockwave_speed: 50.0, + shockwave_duration: 0.5, + requires_ground: true, + move_efficiency: 0.5, + damage_kind: Piercing, + specifier: IceSpikes, +) \ No newline at end of file diff --git a/assets/common/abilities/custom/yeti/snowball.ron b/assets/common/abilities/custom/yeti/snowball.ron new file mode 100644 index 0000000000..7cd3348c8e --- /dev/null +++ b/assets/common/abilities/custom/yeti/snowball.ron @@ -0,0 +1,13 @@ +BasicRanged( + energy_cost: 0, + buildup_duration: 0.5, + recover_duration: 0.4, + projectile: Snowball( + damage: 200.0, + radius: 5.0, + ), + projectile_body: Object(Snowball), + projectile_speed: 60.0, + num_projectiles: 1, + projectile_spread: 0.0, +) diff --git a/assets/common/abilities/custom/yeti/strike.ron b/assets/common/abilities/custom/yeti/strike.ron new file mode 100644 index 0000000000..1624a3c6e2 --- /dev/null +++ b/assets/common/abilities/custom/yeti/strike.ron @@ -0,0 +1,13 @@ +BasicMelee( + energy_cost: 0, + buildup_duration: 0.6, + swing_duration: 0.1, + recover_duration: 0.5, + base_damage: 100, + base_poise_damage: 50, + knockback: ( strength: 40.0, direction: Away), + range: 4.0, + max_angle: 20.0, + damage_effect: None, + damage_kind: Crushing, +) diff --git a/assets/common/items/npc_weapons/hammer/yeti_hammer.ron b/assets/common/items/npc_weapons/hammer/yeti_hammer.ron index d906f684c9..602926451b 100644 --- a/assets/common/items/npc_weapons/hammer/yeti_hammer.ron +++ b/assets/common/items/npc_weapons/hammer/yeti_hammer.ron @@ -2,11 +2,11 @@ ItemDef( name: "Yeti Hammer", description: "Placeholder", kind: Tool(( - kind: Hammer, + kind: Natural, hands: Two, stats: Direct(( - equip_time_secs: 0.0, - power: 2.0, + equip_time_secs: 0.5, + power: 1.0, poise_strength: 1.0, speed: 1.0, crit_chance: 0.046875, @@ -15,5 +15,5 @@ ItemDef( )), quality: Low, tags: [], - ability_spec: Some(Custom("Hammer Simple")), + ability_spec: Some(Custom("Yeti")), ) \ No newline at end of file diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index a74f714892..4bad57d31e 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -69,6 +69,7 @@ const int BIG_SHRAPNEL = 27; const int LASER = 28; const int BUBBLES = 29; const int WATER = 30; +const int ICE_SPIKES = 31; // meters per second squared (acceleration) const float earth_gravity = 9.807; @@ -144,6 +145,12 @@ vec3 perp_axis2(vec3 axis1, vec3 axis2) { return normalize(vec3(axis1.y * axis2.z - axis1.z * axis2.y, axis1.z * axis2.x - axis1.x * axis2.z, axis1.x * axis2.y - axis1.y * axis2.x)); } +// Line is the axis of the spiral, it goes from the start position to the end position +// Radius is the distance from the axis the particle is +// Time function is some value that ideally goes from 0 to 1. When it is 0, it is as +// the point (0, 0, 0), when it is 1, it is at the point provided by the coordinates of line +// Frequency increases the frequency of rotation +// Offset is an offset to the angle of the rotation vec3 spiral_motion(vec3 line, float radius, float time_function, float frequency, float offset) { vec3 axis2 = perp_axis1(line); vec3 axis3 = perp_axis2(line, axis2); @@ -420,10 +427,11 @@ void main() { break; case ICE: f_reflect = 0.0; // Ice doesn't reflect to look like magic + float ice_color = 1.9 + rand5 * 0.3; attr = Attr( inst_dir * ((rand0+1.0)/2 + 0.4) * slow_end(2.0) + 0.3 * grav_vel(earth_gravity), - vec3((3 * (1 - slow_start(0.1)))), - vec4(0.2, 1.6 + rand5 * 0.3 - 0.4 * percent(), 3, 1), + vec3((5 * (1 - slow_start(.1)))), + vec4(0.8 * ice_color, 0.9 * ice_color, ice_color, 1), spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9) ); break; @@ -513,6 +521,16 @@ void main() { spin_in_axis(vec3(rand6, rand7, rand8), percent() * 5 + 3 * rand9) ); break; + case ICE_SPIKES: + f_reflect = 0.0; // Ice doesn't reflect to look like magic + ice_color = 1.7 + rand5 * 0.2; + attr = Attr( + vec3(0.0), + vec3(11.0, 11.0, 11.0 * length(inst_dir) * 2.0 * (0.5 - abs(0.5 - slow_end(0.5)))) / 3, + vec4(0.8 * ice_color, 0.9 * ice_color, ice_color, 1), + spin_in_axis(vec3(1,0,0),0) + ); + break; default: attr = Attr( linear_motion( diff --git a/assets/voxygen/voxel/object_manifest.ron b/assets/voxygen/voxel/object_manifest.ron index 00f99e525c..cb43158072 100644 --- a/assets/voxygen/voxel/object_manifest.ron +++ b/assets/voxygen/voxel/object_manifest.ron @@ -719,4 +719,14 @@ central: ("armor.empty"), ) ), + Snowball: ( + bone0: ( + offset: (-12.5, -12.5, 0.0), + central: ("weapon.projectile.snowball"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), + ) + ), }) diff --git a/assets/voxygen/voxel/weapon/projectile/snowball.vox b/assets/voxygen/voxel/weapon/projectile/snowball.vox new file mode 100644 index 0000000000..a1f2a3b875 --- /dev/null +++ b/assets/voxygen/voxel/weapon/projectile/snowball.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6de3e25adcf704d7024d358642ce8e37ef01fe88577ff06c886cf8836067300 +size 33964 diff --git a/common/src/comp/beam.rs b/common/src/comp/beam.rs index 50e056ad1b..9111c35453 100644 --- a/common/src/comp/beam.rs +++ b/common/src/comp/beam.rs @@ -54,4 +54,5 @@ pub enum FrontendSpecifier { Cultist, ClayGolem, Bubbles, + Frost, } diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index 1a95d6c6d6..73724f5f7d 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -471,7 +471,7 @@ impl Body { biped_large::Species::Dullahan => 3000, biped_large::Species::Mindflayer => 12500, biped_large::Species::Tidalwarrior => 16000, - biped_large::Species::Yeti => 4000, + biped_large::Species::Yeti => 12000, biped_large::Species::Minotaur => 30000, biped_large::Species::Harvester => 3000, biped_large::Species::Blueoni => 2400, @@ -586,7 +586,7 @@ impl Body { biped_large::Species::Wendigo => 80, biped_large::Species::Troll => 60, biped_large::Species::Dullahan => 120, - biped_large::Species::Yeti => 80, + biped_large::Species::Yeti => 0, biped_large::Species::Harvester => 80, // Boss enemies have their health set, not adjusted by level. biped_large::Species::Mindflayer => 0, @@ -650,6 +650,7 @@ impl Body { biped_large::Species::Mindflayer => 4.8, biped_large::Species::Minotaur => 3.2, biped_large::Species::Tidalwarrior => 2.25, + biped_large::Species::Yeti => 2.0, _ => 1.0, }, Body::Golem(g) => match g.species { diff --git a/common/src/comp/body/object.rs b/common/src/comp/body/object.rs index cf509fdc1c..97f2d203a5 100644 --- a/common/src/comp/body/object.rs +++ b/common/src/comp/body/object.rs @@ -84,6 +84,7 @@ make_case_elim!( ClayRocket = 69, HaniwaSentry = 70, SeaLantern = 71, + Snowball = 72, } ); @@ -94,7 +95,7 @@ impl Body { } } -pub const ALL_OBJECTS: [Body; 72] = [ +pub const ALL_OBJECTS: [Body; 73] = [ Body::Arrow, Body::Bomb, Body::Scarecrow, @@ -167,6 +168,7 @@ pub const ALL_OBJECTS: [Body; 72] = [ Body::ClayRocket, Body::HaniwaSentry, Body::SeaLantern, + Body::Snowball, ]; impl From for super::Body { @@ -248,6 +250,7 @@ impl Body { Body::ClayRocket => "clay_rocket", Body::HaniwaSentry => "haniwa_sentry", Body::SeaLantern => "sea_lantern", + Body::Snowball => "snowball", } } @@ -270,6 +273,7 @@ impl Body { Body::Crate => 300.0, // let's say it's a lot of wood and maybe some contents Body::Scarecrow => 900.0, Body::TrainingDummy => 2000.0, + Body::Snowball => 0.9 * WATER_DENSITY, // let them sink _ => 1.1 * WATER_DENSITY, }; @@ -340,6 +344,7 @@ impl Body { Body::ClayRocket => 50.0, Body::HaniwaSentry => 300.0, Body::SeaLantern => 1000.0, + Body::Snowball => 7360.0, // 2.5 m diamter }; Mass(m) @@ -354,6 +359,7 @@ impl Body { Body::Crossbow => Vec3::new(3.0, 3.0, 1.5), Body::HaniwaSentry => Vec3::new(0.8, 0.8, 1.4), Body::SeaLantern => Vec3::new(0.5, 0.5, 1.0), + Body::Snowball => Vec3::broadcast(2.5), _ => Vec3::broadcast(0.5), } } diff --git a/common/src/comp/projectile.rs b/common/src/comp/projectile.rs index a50bbc3788..a3e716e1be 100644 --- a/common/src/comp/projectile.rs +++ b/common/src/comp/projectile.rs @@ -32,6 +32,10 @@ pub struct Projectile { /// Whether projectile collides with entities in the same group as its /// owner pub ignore_group: bool, + /// Whether the projectile is sticky + pub is_sticky: bool, + /// Whether the projectile should use a point collider + pub is_point: bool, } impl Component for Projectile { @@ -64,6 +68,10 @@ pub enum ProjectileConstructor { radius: f32, knockback: f32, }, + Snowball { + damage: f32, + radius: f32, + }, } impl ProjectileConstructor { @@ -113,6 +121,8 @@ impl ProjectileConstructor { time_left: Duration::from_secs(15), owner, ignore_group: true, + is_sticky: true, + is_point: true, } }, Fireball { @@ -149,6 +159,8 @@ impl ProjectileConstructor { time_left: Duration::from_secs(10), owner, ignore_group: true, + is_sticky: true, + is_point: true, } }, Frostball { damage, radius } => { @@ -167,7 +179,7 @@ impl ProjectileConstructor { let explosion = Explosion { effects: vec![RadiusEffect::Attack(attack)], radius, - reagent: Some(Reagent::Blue), + reagent: Some(Reagent::White), }; Projectile { hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish], @@ -175,6 +187,8 @@ impl ProjectileConstructor { time_left: Duration::from_secs(10), owner, ignore_group: true, + is_sticky: true, + is_point: true, } }, NecroticSphere { damage, radius } => { @@ -201,6 +215,8 @@ impl ProjectileConstructor { time_left: Duration::from_secs(10), owner, ignore_group: true, + is_sticky: true, + is_point: true, } }, Possess => Projectile { @@ -209,6 +225,8 @@ impl ProjectileConstructor { time_left: Duration::from_secs(10), owner, ignore_group: false, + is_sticky: true, + is_point: true, }, ClayRocket { damage, @@ -249,6 +267,35 @@ impl ProjectileConstructor { time_left: Duration::from_secs(10), owner, ignore_group: true, + is_sticky: true, + is_point: true, + } + }, + Snowball { damage, radius } => { + let damage = AttackDamage::new( + Damage { + source: DamageSource::Explosion, + kind: DamageKind::Energy, + value: damage, + }, + Some(GroupTarget::OutOfGroup), + ); + let attack = Attack::default() + .with_damage(damage) + .with_crit(crit_chance, crit_mult); + let explosion = Explosion { + effects: vec![RadiusEffect::Attack(attack)], + radius, + reagent: Some(Reagent::White), + }; + Projectile { + hit_solid: vec![], + hit_entity: vec![Effect::Explode(explosion), Effect::Vanish], + time_left: Duration::from_secs(120), + owner, + ignore_group: true, + is_sticky: false, + is_point: false, } }, } @@ -300,6 +347,13 @@ impl ProjectileConstructor { *damage *= power; *radius *= range; }, + Snowball { + ref mut damage, + ref mut radius, + } => { + *damage *= power; + *radius *= range; + }, } self } diff --git a/common/src/comp/shockwave.rs b/common/src/comp/shockwave.rs index 588960de72..df6b666c6a 100644 --- a/common/src/comp/shockwave.rs +++ b/common/src/comp/shockwave.rs @@ -50,4 +50,5 @@ pub enum FrontendSpecifier { Ground, Fire, Water, + IceSpikes, } diff --git a/common/src/states/roll.rs b/common/src/states/roll.rs index f6d83ab4f0..8acf61ef7f 100644 --- a/common/src/states/roll.rs +++ b/common/src/states/roll.rs @@ -84,7 +84,7 @@ impl CharacterBehavior for Data { - self.timer.as_secs_f32() / self.static_data.movement_duration.as_secs_f32()) / 2.0 - + 0.5), + + 0.25), }); if self.timer < self.static_data.movement_duration { diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index c1a57cd7f9..a929d8224d 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -285,17 +285,29 @@ impl StateExt for State { body: comp::Body, projectile: comp::Projectile, ) -> EcsEntityBuilder { - self.ecs_mut() + let mut projectile_base = self + .ecs_mut() .create_entity_synced() .with(pos) .with(vel) .with(comp::Ori::from_unnormalized_vec(vel.0).unwrap_or_default()) .with(body.mass()) - .with(body.density()) - .with(comp::Collider::Point) - .with(body) - .with(projectile) - .with(comp::Sticky) + .with(body.density()); + + if projectile.is_sticky { + projectile_base = projectile_base.with(comp::Sticky) + } + if projectile.is_point { + projectile_base = projectile_base.with(comp::Collider::Point) + } else { + projectile_base = projectile_base.with(comp::Collider::Box { + radius: body.radius(), + z_min: 0.0, + z_max: body.height(), + }) + } + + projectile_base.with(projectile).with(body) } fn create_shockwave( diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index 55e27466d6..152e49030b 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -119,6 +119,7 @@ pub enum Tactic { Minotaur, ClayGolem, TidalWarrior, + Yeti, } #[derive(SystemData)] @@ -1609,6 +1610,7 @@ impl<'a> AgentData<'a> { "Clay Golem" => Tactic::ClayGolem, "Tidal Warrior" => Tactic::TidalWarrior, "Tidal Totem" => Tactic::RadialTurret, + "Yeti" => Tactic::Yeti, _ => Tactic::Melee, }, AbilitySpec::Tool(tool_kind) => tool_tactic(*tool_kind), @@ -1697,6 +1699,18 @@ impl<'a> AgentData<'a> { ), ) }, + Tactic::Yeti if matches!(self.char_state, CharacterState::BasicRanged(_)) => { + const SNOWBALL_SPEED: f32 = 60.0; + aim_projectile( + SNOWBALL_SPEED, + Vec3::new(self.pos.0.x, self.pos.0.y, self.pos.0.z + eye_offset), + Vec3::new( + tgt_data.pos.0.x, + tgt_data.pos.0.y, + tgt_data.pos.0.z + tgt_eye_offset, + ), + ) + }, _ => Dir::from_unnormalized( Vec3::new( tgt_data.pos.0.x, @@ -1865,6 +1879,9 @@ impl<'a> AgentData<'a> { &tgt_data, &read_data, ), + Tactic::Yeti => { + self.handle_yeti_attack(agent, controller, &attack_data, &tgt_data, &read_data) + }, } } @@ -3476,6 +3493,65 @@ impl<'a> AgentData<'a> { self.path_toward_target(agent, controller, tgt_data, read_data, false, None); } + fn handle_yeti_attack( + &self, + agent: &mut Agent, + controller: &mut Controller, + attack_data: &AttackData, + tgt_data: &TargetData, + read_data: &ReadData, + ) { + const ICE_SPIKES_RANGE: f32 = 20.0; + const ICE_BREATH_RANGE: f32 = 15.0; + const ICE_BREATH_TIMER: f32 = 10.0; + const SNOWBALL_MAX_RANGE: f32 = 50.0; + + agent.action_state.counter += read_data.dt.0; + + if attack_data.dist_sqrd < ICE_BREATH_RANGE.powi(2) && attack_data.angle < 60.0 { + if matches!(self.char_state, CharacterState::BasicBeam(c) if c.timer < Duration::from_secs(1)) + { + // Keep using ice breath until a second has passed + controller + .actions + .push(ControlAction::basic_input(InputKind::Ability(0))); + } else if agent.action_state.counter > ICE_BREATH_TIMER { + // Use ice breath if timer has gone for long enough + controller + .actions + .push(ControlAction::basic_input(InputKind::Ability(0))); + + if matches!(self.char_state, CharacterState::BasicBeam(_)) { + // Resets action counter when using beam + agent.action_state.counter = 0.0; + } + } else if attack_data.in_min_range() { + // Basic attack if on top of them + controller + .actions + .push(ControlAction::basic_input(InputKind::Primary)); + } else { + // Use ice spikes if too far for other abilities + controller + .actions + .push(ControlAction::basic_input(InputKind::Secondary)); + } + } else if attack_data.dist_sqrd < ICE_SPIKES_RANGE.powi(2) && attack_data.angle < 60.0 { + // Use ice spikes if in range + controller + .actions + .push(ControlAction::basic_input(InputKind::Secondary)); + } else if attack_data.dist_sqrd < SNOWBALL_MAX_RANGE.powi(2) && attack_data.angle < 60.0 { + // Otherwise, chuck all the snowballs + controller + .actions + .push(ControlAction::basic_input(InputKind::Ability(1))); + } + + // Always attempt to path towards target + self.path_toward_target(agent, controller, tgt_data, read_data, false, None); + } + fn follow( &self, agent: &mut Agent, diff --git a/server/src/sys/object.rs b/server/src/sys/object.rs index b7ca5ce30c..f5255db813 100644 --- a/server/src/sys/object.rs +++ b/server/src/sys/object.rs @@ -132,6 +132,8 @@ impl<'a> System<'a> for Sys { time_left: Duration::from_secs(60), owner: *owner, ignore_group: true, + is_sticky: true, + is_point: true, }, speed, object: Some(Object::Firework { diff --git a/voxygen/anim/src/biped_large/alpha.rs b/voxygen/anim/src/biped_large/alpha.rs index b752566a0c..1e52049cc7 100644 --- a/voxygen/anim/src/biped_large/alpha.rs +++ b/voxygen/anim/src/biped_large/alpha.rs @@ -297,6 +297,35 @@ impl Animation for AlphaAnimation { next.head.orientation = Quaternion::rotation_x(move1 * -0.6 + move2 * 0.4) }, + "Yeti" => { + next.control_l.position = Vec3::new(-1.0, 2.0, 12.0 + move2 * -10.0); + next.control_r.position = Vec3::new(1.0, 2.0, -2.0); + + next.control.position = Vec3::new( + 4.0 + move1 * -12.0 + move2 * 20.0, + (s_a.grip.0 / 1.0) + move1 * -3.0 + move2 * 5.0, + (-s_a.grip.0 / 0.8) + move1 * -2.0 + move2 * 8.0, + ); + next.head.orientation = Quaternion::rotation_x(move1 * -0.25) + * Quaternion::rotation_z(move1 * -0.2 + move2 * 0.6); + next.upper_torso.orientation = + Quaternion::rotation_z(move1 * 0.2 + move2 * -0.4); + next.lower_torso.orientation = + Quaternion::rotation_z(move1 * -0.2 + move2 * 0.2); + + next.control_l.orientation = + Quaternion::rotation_x(PI / 2.0 + move2 * 0.8) + * Quaternion::rotation_y(-0.0); + next.control_r.orientation = + Quaternion::rotation_x(PI / 2.0 + 0.2 + move2 * 0.8) + * Quaternion::rotation_y(0.0) + * Quaternion::rotation_z(0.0); + + next.control.orientation = + Quaternion::rotation_x(-1.0 + move1 * -0.5 + move2 * -0.3) + * Quaternion::rotation_y(-1.8 + move1 * -0.8 + move2 * 3.0) + * Quaternion::rotation_z(move1 * -0.8 + move2 * -0.8); + }, _ => {}, } } diff --git a/voxygen/anim/src/biped_large/beam.rs b/voxygen/anim/src/biped_large/beam.rs index 9bf1828295..62f8e01481 100644 --- a/voxygen/anim/src/biped_large/beam.rs +++ b/voxygen/anim/src/biped_large/beam.rs @@ -73,17 +73,12 @@ impl Animation for BeamAnimation { next.hand_l.orientation = Quaternion::rotation_x(0.0); next.hand_r.orientation = Quaternion::rotation_x(0.0); - let (move1base, move2shake, _move2base, move3) = match stage_section { - Some(StageSection::Buildup) => ( - (anim_time.powf(0.25)).min(1.0), - (anim_time * 15.0 + PI).sin(), - (anim_time * 10.0 + PI).sin(), - 0.0, - ), + let (move1base, move2shake, move2base, move3) = match stage_section { + Some(StageSection::Buildup) => ((anim_time.powf(0.25)).min(1.0), 0.0, 0.0, 0.0), Some(StageSection::Cast) => ( 1.0, (anim_time * 15.0 + PI).sin(), - anim_time.powf(0.25), + (anim_time.powf(0.1)).min(1.0), 0.0, ), Some(StageSection::Recover) => (1.0, 1.0, 1.0, anim_time), @@ -91,6 +86,7 @@ impl Animation for BeamAnimation { }; let pullback = 1.0 - move3; let move1 = move1base * pullback; + let move2 = move2base * pullback; match active_tool_kind { Some(ToolKind::Sceptre) | Some(ToolKind::Staff) => { next.control_l.position = Vec3::new(-1.0, 3.0, 12.0); @@ -128,8 +124,6 @@ impl Animation for BeamAnimation { ); next.shoulder_r.orientation = Quaternion::rotation_x(move1 * 0.2 + 0.3 + 0.6 * speednorm + (footrotl * -0.2)); - next.torso.orientation = Quaternion::rotation_x(move1 * -0.1); - next.torso.position = Vec3::new(0.0, 0.0, move1 * 1.0); }, Some(ToolKind::Natural) => { if let Some(AbilitySpec::Custom(spec)) = active_tool_spec { @@ -177,6 +171,50 @@ impl Animation for BeamAnimation { next.shoulder_r.orientation = Quaternion::rotation_z(move1 * 0.3); }; }, + "Yeti" => { + next.second.scale = Vec3::one() * 0.0; + + next.head.orientation = Quaternion::rotation_x( + move1 * 0.5 + move2 * -0.5 + move2shake * -0.02, + ); + next.jaw.position = Vec3::new(0.0, s_a.jaw.0, s_a.jaw.1); + next.jaw.orientation = + Quaternion::rotation_x(move2 * -0.5 + move2shake * -0.1); + next.control_l.position = Vec3::new(-0.5, 4.0, 1.0); + next.control_r.position = Vec3::new(-0.5, 4.0, 1.0); + next.control_l.orientation = Quaternion::rotation_x(1.57); + next.control_r.orientation = Quaternion::rotation_x(1.57); + + next.weapon_l.position = Vec3::new(-12.0, -1.0, -15.0); + next.weapon_r.position = Vec3::new(12.0, -1.0, -15.0); + + next.weapon_l.orientation = Quaternion::rotation_x(-1.57 - 0.1); + next.weapon_r.orientation = Quaternion::rotation_x(-1.57 - 0.1); + + next.arm_control_r.orientation = + Quaternion::rotation_x(move1 * 1.1 + move2 * -1.6) + * Quaternion::rotation_y(move1 * 1.4 + move2 * -1.8); + + next.shoulder_l.orientation = + Quaternion::rotation_x(move1 * 1.4 + move2 * -1.8); + + next.shoulder_r.orientation = + Quaternion::rotation_x(move1 * 1.4 + move2 * -1.8); + + next.upper_torso.position = Vec3::new( + 0.0, + s_a.upper_torso.0, + s_a.upper_torso.1 + move1 * -1.9 + move2 * 1.2, + ); + next.upper_torso.orientation = Quaternion::rotation_x( + move1 * 0.8 + move2 * -1.1 + move2shake * -0.02, + ); + next.lower_torso.position = + Vec3::new(0.0, s_a.lower_torso.0, s_a.lower_torso.1); + next.lower_torso.orientation = Quaternion::rotation_x( + move1 * -0.8 + move2 * 1.1 + move2shake * 0.02, + ); + }, _ => {}, } } diff --git a/voxygen/anim/src/biped_large/shockwave.rs b/voxygen/anim/src/biped_large/shockwave.rs index e2ba47be04..81d373014a 100644 --- a/voxygen/anim/src/biped_large/shockwave.rs +++ b/voxygen/anim/src/biped_large/shockwave.rs @@ -2,18 +2,22 @@ use super::{ super::{vek::*, Animation}, BipedLargeSkeleton, SkeletonAttr, }; -use common::{comp::item::ToolKind, states::utils::StageSection}; +use common::{ + comp::item::{AbilitySpec, ToolKind}, + states::utils::StageSection, +}; pub struct ShockwaveAnimation; +type ShockwaveAnimationDependency<'a> = ( + (Option, Option<&'a AbilitySpec>), + (Option, Option<&'a AbilitySpec>), + f32, + f32, + Option, +); impl Animation for ShockwaveAnimation { - type Dependency<'a> = ( - Option, - Option, - f32, - f32, - Option, - ); + type Dependency<'a> = ShockwaveAnimationDependency<'a>; type Skeleton = BipedLargeSkeleton; #[cfg(feature = "use-dyn-lib")] @@ -23,7 +27,13 @@ impl Animation for ShockwaveAnimation { #[allow(clippy::single_match)] // TODO: Pending review in #587 fn update_skeleton_inner<'a>( skeleton: &Self::Skeleton, - (_active_tool_kind, _second_tool_kind, _global_time, velocity, stage_section): Self::Dependency<'a>, + ( + (active_tool_kind, active_tool_spec), + _second_tool_kind, + _global_time, + velocity, + stage_section, + ): Self::Dependency<'a>, anim_time: f32, rate: &mut f32, s_a: &SkeletonAttr, @@ -31,71 +41,162 @@ impl Animation for ShockwaveAnimation { *rate = 1.0; let mut next = (*skeleton).clone(); - let (movement1, movement2, movement3) = match stage_section { - Some(StageSection::Buildup) => (anim_time, 0.0, 0.0), - Some(StageSection::Swing) => (1.0, anim_time, 0.0), - Some(StageSection::Recover) => (1.0, 1.0, anim_time), - _ => (0.0, 0.0, 0.0), + let (move1, move1pow, move2, move3) = match stage_section { + Some(StageSection::Buildup) => (anim_time, anim_time.powf(0.25) as f32, 0.0, 0.0), + Some(StageSection::Swing) => (1.0, 1.0, anim_time, 0.0), + Some(StageSection::Recover) => (1.0, 1.0, 1.0, anim_time), + _ => (0.0, 0.0, 0.0, 0.0), }; - next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1); + let pullback = 1.0 - move3; + let move1pow = move1pow * pullback; + let move2 = move2 * pullback; - next.hand_l.position = Vec3::new(s_a.sthl.0, s_a.sthl.1, s_a.sthl.2); - next.hand_l.orientation = - Quaternion::rotation_x(s_a.sthl.3) * Quaternion::rotation_y(s_a.sthl.4); - next.hand_r.position = Vec3::new(s_a.sthr.0, s_a.sthr.1, s_a.sthr.2); - next.hand_r.orientation = - Quaternion::rotation_x(s_a.sthr.3) * Quaternion::rotation_y(s_a.sthr.4); next.main.position = Vec3::new(0.0, 0.0, 0.0); - next.main.orientation = Quaternion::rotation_y(0.0); + next.main.orientation = Quaternion::rotation_x(0.0); - next.control.position = Vec3::new(s_a.stc.0, s_a.stc.1, s_a.stc.2); - next.control.orientation = - Quaternion::rotation_x(s_a.stc.3) * Quaternion::rotation_y(s_a.stc.4); + next.hand_l.position = Vec3::new(0.0, 0.0, s_a.grip.0); + next.hand_r.position = Vec3::new(0.0, 0.0, s_a.grip.0); - let twist = movement1 * 0.8; + next.hand_l.orientation = Quaternion::rotation_x(0.0); + next.hand_r.orientation = Quaternion::rotation_x(0.0); - next.control.position = Vec3::new( - s_a.stc.0 + movement1 * 5.0 + movement3 * -5.0, - s_a.stc.1 + movement1 * 13.0 + movement3 * -3.0, - s_a.stc.2 + movement1 * 10.0 + movement2 * -2.0 + movement3 * -8.0, - ); - next.control.orientation = Quaternion::rotation_x( - s_a.stc.3 + movement1 * 0.8 + movement2 * 0.3 + movement3 * -1.1, - ) * Quaternion::rotation_y( - s_a.stc.4 + movement1 * -0.15 + movement2 * 0.3 + movement3 * -0.45, - ) * Quaternion::rotation_z(movement1 * 0.8 + movement2 * -0.8); + match active_tool_kind { + Some(ToolKind::Sceptre) => { + next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1); - next.head.orientation = Quaternion::rotation_x(movement1 * 0.4 + movement3 * -0.4) - * Quaternion::rotation_z(twist * 0.2 + movement2 * -0.8 + movement3 * 0.6); + next.hand_l.position = Vec3::new(s_a.sthl.0, s_a.sthl.1, s_a.sthl.2); + next.hand_l.orientation = + Quaternion::rotation_x(s_a.sthl.3) * Quaternion::rotation_y(s_a.sthl.4); + next.hand_r.position = Vec3::new(s_a.sthr.0, s_a.sthr.1, s_a.sthr.2); + next.hand_r.orientation = + Quaternion::rotation_x(s_a.sthr.3) * Quaternion::rotation_y(s_a.sthr.4); + next.main.position = Vec3::new(0.0, 0.0, 0.0); + next.main.orientation = Quaternion::rotation_y(0.0); - next.upper_torso.position = Vec3::new( - 0.0, - s_a.upper_torso.0, - s_a.upper_torso.1 + movement1 * 2.0 + movement2 * -4.0 + movement3 * 2.0, - ); - next.upper_torso.orientation = Quaternion::rotation_x(movement2 * -0.8 + movement3 * 0.8) - * Quaternion::rotation_z(twist * -0.2 + movement2 * -0.1 + movement3 * 0.3); + next.control.position = Vec3::new(s_a.stc.0, s_a.stc.1, s_a.stc.2); + next.control.orientation = + Quaternion::rotation_x(s_a.stc.3) * Quaternion::rotation_y(s_a.stc.4); - next.lower_torso.orientation = Quaternion::rotation_x(movement2 * 0.3 + movement3 * -0.3) - * Quaternion::rotation_z(twist + movement2 * -0.8); + let twist = move1 * 0.8; - if velocity < 0.5 { - next.foot_l.position = Vec3::new( - -s_a.foot.0, - s_a.foot.1 + movement1 * -7.0 + movement2 * 7.0, - s_a.foot.2, - ); - next.foot_l.orientation = Quaternion::rotation_x(movement1 * -0.8 + movement2 * 0.8) - * Quaternion::rotation_z(movement1 * 0.3 + movement2 * -0.3); + next.control.position = Vec3::new( + s_a.stc.0 + move1 * 5.0 + move3 * -5.0, + s_a.stc.1 + move1 * 13.0 + move3 * -3.0, + s_a.stc.2 + move1 * 10.0 + move2 * -2.0 + move3 * -8.0, + ); + next.control.orientation = + Quaternion::rotation_x(s_a.stc.3 + move1 * 0.8 + move2 * 0.3 + move3 * -1.1) + * Quaternion::rotation_y( + s_a.stc.4 + move1 * -0.15 + move2 * 0.3 + move3 * -0.45, + ) + * Quaternion::rotation_z(move1 * 0.8 + move2 * -0.8); - next.foot_r.position = Vec3::new( - s_a.foot.0, - s_a.foot.1 + movement1 * 5.0 + movement2 * -5.0, - s_a.foot.2, - ); - next.foot_r.orientation = Quaternion::rotation_y(movement1 * -0.3 + movement2 * 0.3) - * Quaternion::rotation_z(movement1 * 0.4 + movement2 * -0.4); + next.head.orientation = Quaternion::rotation_x(move1 * 0.4 + move3 * -0.4) + * Quaternion::rotation_z(twist * 0.2 + move2 * -0.8 + move3 * 0.6); + + next.upper_torso.position = Vec3::new( + 0.0, + s_a.upper_torso.0, + s_a.upper_torso.1 + move1 * 2.0 + move2 * -4.0 + move3 * 2.0, + ); + next.upper_torso.orientation = Quaternion::rotation_x(move2 * -0.8 + move3 * 0.8) + * Quaternion::rotation_z(twist * -0.2 + move2 * -0.1 + move3 * 0.3); + + next.lower_torso.orientation = Quaternion::rotation_x(move2 * 0.3 + move3 * -0.3) + * Quaternion::rotation_z(twist + move2 * -0.8); + + if velocity < 0.5 { + next.foot_l.position = Vec3::new( + -s_a.foot.0, + s_a.foot.1 + move1 * -7.0 + move2 * 7.0, + s_a.foot.2, + ); + next.foot_l.orientation = Quaternion::rotation_x(move1 * -0.8 + move2 * 0.8) + * Quaternion::rotation_z(move1 * 0.3 + move2 * -0.3); + + next.foot_r.position = Vec3::new( + s_a.foot.0, + s_a.foot.1 + move1 * 5.0 + move2 * -5.0, + s_a.foot.2, + ); + next.foot_r.orientation = Quaternion::rotation_y(move1 * -0.3 + move2 * 0.3) + * Quaternion::rotation_z(move1 * 0.4 + move2 * -0.4); + } + }, + Some(ToolKind::Natural) => { + if let Some(AbilitySpec::Custom(spec)) = active_tool_spec { + match spec.as_str() { + "Yeti" => { + next.second.scale = Vec3::one() * 0.0; + + next.head.orientation = + Quaternion::rotation_x(move1pow * 0.8 + move2 * -1.2); + next.jaw.position = Vec3::new(0.0, s_a.jaw.0, s_a.jaw.1); + next.jaw.orientation = Quaternion::rotation_x(move2 * -0.3); + next.control_l.position = Vec3::new(-0.5, 4.0, 1.0); + next.control_r.position = Vec3::new(-0.5, 4.0, 1.0); + next.control_l.orientation = Quaternion::rotation_x(1.57); + next.control_r.orientation = Quaternion::rotation_x(1.57); + next.weapon_l.position = + Vec3::new(-12.0 + (move1pow * 20.0).min(10.0), -1.0, -15.0); + next.weapon_r.position = + Vec3::new(12.0 + (move1pow * -20.0).max(-10.0), -1.0, -15.0); + + next.weapon_l.orientation = Quaternion::rotation_x(-1.57 - 0.1) + * Quaternion::rotation_z(move1pow * -1.0); + next.weapon_r.orientation = Quaternion::rotation_x(-1.57 - 0.1) + * Quaternion::rotation_z(move1pow * 1.0); + + next.shoulder_l.orientation = + Quaternion::rotation_x(-0.3 + move1pow * 2.8 + move2 * -2.8); + + next.shoulder_r.orientation = + Quaternion::rotation_x(-0.3 + move1pow * 2.8 + move2 * -2.8); + + next.control.orientation = + Quaternion::rotation_x(move1pow * 2.5 + move2 * -2.0); + + let twist = move1 * 0.6 + move3 * -0.6; + next.upper_torso.position = + Vec3::new(0.0, s_a.upper_torso.0, s_a.upper_torso.1); + next.upper_torso.orientation = + Quaternion::rotation_x(move1pow * 0.8 + move2 * -1.1) + * Quaternion::rotation_z( + twist * -0.2 + move1 * -0.1 + move2 * 0.3, + ); + + next.lower_torso.orientation = + Quaternion::rotation_x(move1pow * -0.8 + move2 * 1.1) + * Quaternion::rotation_z(twist); + + next.foot_l.position = Vec3::new( + -s_a.foot.0, + s_a.foot.1 + move1pow * -7.0 + move2 * 7.0, + s_a.foot.2, + ); + next.foot_l.orientation = + Quaternion::rotation_x(move1pow * -0.8 + move2 * 0.8) + * Quaternion::rotation_z(move1pow * 0.3 + move2 * -0.3); + + next.foot_r.position = Vec3::new( + s_a.foot.0, + s_a.foot.1 + move1pow * 5.0 + move2 * -5.0, + s_a.foot.2, + ); + next.foot_r.orientation = + Quaternion::rotation_y(move1pow * -0.3 + move2 * 0.3) + * Quaternion::rotation_z(move1pow * 0.4 + move2 * -0.4); + + next.main.orientation = + Quaternion::rotation_y(move1 * 0.4 + move2 * -0.6) + * Quaternion::rotation_x(move2 * -0.4); + }, + _ => {}, + } + } + }, + _ => {}, } next } diff --git a/voxygen/anim/src/biped_large/shoot.rs b/voxygen/anim/src/biped_large/shoot.rs index 42d9717950..206d2ad2bd 100644 --- a/voxygen/anim/src/biped_large/shoot.rs +++ b/voxygen/anim/src/biped_large/shoot.rs @@ -219,6 +219,49 @@ impl Animation for ShootAnimation { * Quaternion::rotation_z(move1 * -0.5); next.head.orientation = Quaternion::rotation_x(move1 * -0.3); }, + "Yeti" => { + let (move1, move2, move3) = match stage_section { + Some(StageSection::Buildup) => (anim_time.powf(0.25), 0.0, 0.0), + Some(StageSection::Swing) => (1.0, anim_time, 0.0), + Some(StageSection::Recover) => (1.0, 1.0, anim_time.powi(4)), + _ => (0.0, 0.0, 0.0), + }; + next.second.scale = Vec3::one() * 0.0; + + next.head.orientation = Quaternion::rotation_x(move1 * 0.4); + next.jaw.position = Vec3::new(0.0, s_a.jaw.0, s_a.jaw.1); + next.jaw.orientation = Quaternion::rotation_x(move2 * -0.3); + next.control_l.position = Vec3::new(-0.5, 4.0, 1.0); + next.control_r.position = Vec3::new(-0.5, 4.0, 1.0); + next.control_l.orientation = Quaternion::rotation_x(1.57); + next.control_r.orientation = Quaternion::rotation_x(1.57); + next.weapon_l.position = Vec3::new(-12.0, -1.0, -15.0); + next.weapon_r.position = Vec3::new(12.0, -1.0, -15.0); + + next.weapon_l.orientation = Quaternion::rotation_x(-1.57 - 0.1); + next.weapon_r.orientation = Quaternion::rotation_x(-1.57 - 0.1); + + let twist = move1 * 0.8 + move3 * -0.8; + next.upper_torso.position = + Vec3::new(0.0, s_a.upper_torso.0, s_a.upper_torso.1); + next.upper_torso.orientation = + Quaternion::rotation_x(move1 * 0.8 + move2 * -1.1) + * Quaternion::rotation_z( + twist * -0.2 + move1 * -0.1 + move2 * 0.3, + ); + + next.lower_torso.orientation = + Quaternion::rotation_x(move1 * -0.8 + move2 * 1.1) + * Quaternion::rotation_z(twist); + + next.arm_control_r.orientation = + Quaternion::rotation_x(move1 * PI / 2.0) + * Quaternion::rotation_y(move1 * -PI / 2.0 + move2 * 2.5); + //* Quaternion::rotation_y(move1 * -PI/2.0) + //* Quaternion::rotation_z(move1 * -PI/2.0); + next.arm_control_r.position = + Vec3::new(0.0, move1 * 10.0 + move2 * -10.0, 0.0); + }, _ => {}, } } diff --git a/voxygen/anim/src/biped_large/wield.rs b/voxygen/anim/src/biped_large/wield.rs index a619e49791..282be8dfb3 100644 --- a/voxygen/anim/src/biped_large/wield.rs +++ b/voxygen/anim/src/biped_large/wield.rs @@ -450,6 +450,25 @@ impl Animation for WieldAnimation { next.shoulder_r.orientation = Quaternion::rotation_x(-0.3); }, + "Yeti" => { + next.second.scale = Vec3::one() * 0.0; + next.control_l.position = Vec3::new(-0.5, 4.0, 1.0); + next.control_r.position = Vec3::new(-0.5, 4.0, 1.0); + next.weapon_l.position = Vec3::new(-12.0, -1.0, -15.0); + next.weapon_r.position = Vec3::new(12.0, -1.0, -15.0); + + next.weapon_l.orientation = Quaternion::rotation_x(-1.57 - 0.1); + next.weapon_r.orientation = Quaternion::rotation_x(-1.57 - 0.1); + + next.control_l.orientation = Quaternion::rotation_x(1.57); + next.control_r.orientation = Quaternion::rotation_x(1.57); + + next.control.orientation = + Quaternion::rotation_x(0.0) * Quaternion::rotation_y(0.0); + next.shoulder_l.orientation = Quaternion::rotation_x(-0.3); + + next.shoulder_r.orientation = Quaternion::rotation_x(-0.3); + }, _ => {}, } } diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index d5e8eda3af..f0ab9e4a55 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -403,7 +403,9 @@ impl SfxMgr { audio.emit_sfx(sfx_trigger_item, *pos, None, false); } }, - beam::FrontendSpecifier::ClayGolem | beam::FrontendSpecifier::Bubbles => {}, + beam::FrontendSpecifier::ClayGolem + | beam::FrontendSpecifier::Bubbles + | beam::FrontendSpecifier::Frost => {}, }, Outcome::BreakBlock { pos, .. } => { let sfx_trigger_item = triggers.get_key_value(&SfxEvent::BreakBlock); diff --git a/voxygen/src/render/pipelines/particle.rs b/voxygen/src/render/pipelines/particle.rs index 5f3944d607..e067f906f3 100644 --- a/voxygen/src/render/pipelines/particle.rs +++ b/voxygen/src/render/pipelines/particle.rs @@ -81,6 +81,7 @@ pub enum ParticleMode { Laser = 28, Bubbles = 29, Water = 30, + IceSpikes = 31, } impl ParticleMode { diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 177ba476c6..167bbf2571 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -4161,8 +4161,8 @@ impl FigureMgr { anim::biped_large::ShockwaveAnimation::update_skeleton( &target_base, ( - active_tool_kind, - second_tool_kind, + (active_tool_kind, active_tool_spec), + (second_tool_kind, second_tool_spec), time, rel_vel.magnitude(), Some(s.stage_section), @@ -4775,7 +4775,7 @@ impl FigureMgr { .join() // Don't render dead entities .filter(|(_, _, _, health, _, _)| health.map_or(true, |h| !h.is_dead)) - // Don't render player + // Don't render player .filter(|(entity, _, _, _, _, _)| *entity != player_entity) { if let Some((bound, model, col_lights)) = self.get_model_for_render( diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 5b6ae849bc..0bb3297d83 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -100,7 +100,7 @@ impl ParticleMgr { }, ); }, - Some(Reagent::Blue) => { + Some(Reagent::White) => { self.particles.resize_with( self.particles.len() + (75.0 * power.abs()) as usize, || { @@ -855,6 +855,31 @@ impl ParticleMgr { }, ); }, + beam::FrontendSpecifier::Frost => { + let mut rng = thread_rng(); + let (from, to) = (Vec3::::unit_z(), *ori.look_dir()); + let m = Mat3::::rotation_from_to_3d(from, to); + self.particles.resize_with( + self.particles.len() + usize::from(beam_tick_count) / 4, + || { + let phi: f32 = rng.gen_range(0.0..beam.properties.angle); + let theta: f32 = rng.gen_range(0.0..2.0 * PI); + let offset_z = Vec3::new( + phi.sin() * theta.cos(), + phi.sin() * theta.sin(), + phi.cos(), + ); + let random_ori = offset_z * m * Vec3::new(-1.0, -1.0, 1.0); + Particle::new_directed( + beam.properties.duration, + time, + ParticleMode::Ice, + pos.0, + pos.0 + random_ori * range, + ) + }, + ); + }, } } } @@ -1131,12 +1156,14 @@ impl ParticleMgr { let elapsed = time - shockwave.creation.unwrap_or(time); let speed = shockwave.properties.speed; + let percent = elapsed as f32 / shockwave.properties.duration.as_secs_f32(); + let distance = speed * elapsed as f32; let radians = shockwave.properties.angle.to_radians(); let ori_vec = ori.look_vec(); - let theta = ori_vec.y.atan2(ori_vec.x); + let theta = ori_vec.y.atan2(ori_vec.x) - radians / 2.0; let dtheta = radians / distance; // Number of particles derived from arc length (for new particles at least, old @@ -1162,8 +1189,7 @@ impl ParticleMgr { self.particles.reserve(new_particle_count as usize); for d in 0..(new_particle_count as i32) { - let arc_position = - theta - radians / 2.0 + dtheta * d as f32 / particle_count_factor; + let arc_position = theta + dtheta * d as f32 / particle_count_factor; let position = pos.0 + distance * Vec3::new(arc_position.cos(), arc_position.sin(), 0.0); @@ -1183,7 +1209,7 @@ impl ParticleMgr { let heartbeats = self.scheduler.heartbeats(Duration::from_millis(2)); for _ in 0..heartbeats { for d in 0..3 * distance as i32 { - let arc_position = theta - radians / 2.0 + dtheta * d as f32 / 3.0; + let arc_position = theta + dtheta * d as f32 / 3.0; let position = pos.0 + distance * Vec3::new(arc_position.cos(), arc_position.sin(), 0.0); @@ -1198,8 +1224,8 @@ impl ParticleMgr { } }, FrontendSpecifier::Water => { - // 4 particles per unit length of arc - let particles_per_length = (arc_length) as usize; + // 1 particle per unit length of arc + let particles_per_length = arc_length as usize; let dtheta = radians / particles_per_length as f32; // Scales number of desired heartbeats from speed - thicker arc = higher speed = // lower duration = more particles @@ -1232,6 +1258,67 @@ impl ParticleMgr { } } }, + FrontendSpecifier::IceSpikes => { + // 1 / 3 the size of terrain voxel + let scale = 1.0 / 3.0; + let scaled_distance = distance / scale; + let scaled_speed = speed / scale; + + // 1 particle per scaled unit length of arc + let particles_per_length = (0.25 * arc_length / scale) as usize; + let dtheta = radians / particles_per_length as f32; + // Scales number of desired heartbeats from speed - thicker arc = higher speed = + // lower duration = more particles + let heartbeats = self + .scheduler + .heartbeats(Duration::from_secs_f32(3.0 / scaled_speed)); + + // Reserves capacity for new particles + let new_particle_count = particles_per_length * heartbeats as usize; + self.particles.reserve(new_particle_count); + + // Used to make taller the further out spikes are + let height_scale = 0.5 + 1.5 * percent; + + for i in 0..particles_per_length { + let angle = theta + dtheta * i as f32; + let direction = Vec3::new(angle.cos(), angle.sin(), 0.0); + for j in 0..heartbeats { + // Sub tick dt + let dt = (j as f32 / heartbeats as f32) * dt; + let scaled_distance = scaled_distance + scaled_speed * dt; + let pos1 = pos.0 + (scaled_distance * direction).floor() * scale; + let time = time + dt as f64; + + let get_positions = |a| { + let pos1 = match a { + 2 => pos1 + Vec3::unit_x() * scale, + 3 => pos1 - Vec3::unit_x() * scale, + 4 => pos1 + Vec3::unit_y() * scale, + 5 => pos1 - Vec3::unit_y() * scale, + _ => pos1, + }; + let pos2 = if a == 1 { + pos1 + Vec3::unit_z() * 5.0 * height_scale + } else { + pos1 + Vec3::unit_z() * 1.0 * height_scale + }; + (pos1, pos2) + }; + + for a in 1..=5 { + let (pos1, pos2) = get_positions(a); + self.particles.push(Particle::new_directed( + Duration::from_secs_f32(0.5), + time, + ParticleMode::IceSpikes, + pos1, + pos2, + )); + } + } + } + }, } } }