Merge branch 'sam/yeti' into 'master'

Yeti Rework

See merge request veloren/veloren!2359
This commit is contained in:
Samuel Keiffer 2021-06-05 23:15:20 +00:00
commit 85b8d4e7c0
28 changed files with 675 additions and 100 deletions

View File

@ -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

View File

@ -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",

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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")),
)

View File

@ -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(

View File

@ -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"),
)
),
})

BIN
assets/voxygen/voxel/weapon/projectile/snowball.vox (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -54,4 +54,5 @@ pub enum FrontendSpecifier {
Cultist,
ClayGolem,
Bubbles,
Frost,
}

View File

@ -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 {

View File

@ -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<Body> 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),
}
}

View File

@ -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
}

View File

@ -50,4 +50,5 @@ pub enum FrontendSpecifier {
Ground,
Fire,
Water,
IceSpikes,
}

View File

@ -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 {

View File

@ -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(

View File

@ -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,

View File

@ -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 {

View File

@ -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);
},
_ => {},
}
}

View File

@ -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,
);
},
_ => {},
}
}

View File

@ -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<ToolKind>, Option<&'a AbilitySpec>),
(Option<ToolKind>, Option<&'a AbilitySpec>),
f32,
f32,
Option<StageSection>,
);
impl Animation for ShockwaveAnimation {
type Dependency<'a> = (
Option<ToolKind>,
Option<ToolKind>,
f32,
f32,
Option<StageSection>,
);
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
}

View File

@ -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);
},
_ => {},
}
}

View File

@ -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);
},
_ => {},
}
}

View File

@ -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);

View File

@ -81,6 +81,7 @@ pub enum ParticleMode {
Laser = 28,
Bubbles = 29,
Water = 30,
IceSpikes = 31,
}
impl ParticleMode {

View File

@ -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(

View File

@ -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::<f32>::unit_z(), *ori.look_dir());
let m = Mat3::<f32>::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,
));
}
}
}
},
}
}
}