Merge branch 'sam/tidal-warrior' into 'master'

Tidal Warrior Rework

See merge request veloren/veloren!2340
This commit is contained in:
Samuel Keiffer 2021-06-04 03:52:49 +00:00
commit e984ebc446
64 changed files with 898 additions and 260 deletions

View File

@ -119,6 +119,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Reworked sprite rendering to vastly reduce the CPU work. Large sprite view distances are now much more performant.
- 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
### Removed

View File

@ -108,9 +108,17 @@
secondary: "common.abilities.custom.wendigomagic.singlestrike",
abilities: [],
),
Custom("Tidal Claws"): (
primary: "common.abilities.staff.flamethrower",
secondary: "common.abilities.custom.wendigomagic.singlestrike",
Custom("Tidal Warrior"): (
primary: "common.abilities.custom.tidalwarrior.pincer",
secondary: "common.abilities.custom.tidalwarrior.scuttle",
abilities: [
(None, "common.abilities.custom.tidalwarrior.bubbles"),
(None, "common.abilities.custom.tidalwarrior.totem"),
],
),
Custom("Tidal Totem"): (
primary: "common.abilities.custom.tidalwarrior.totem_wave",
secondary: "common.abilities.custom.tidalwarrior.totem_wave",
abilities: [],
),
Custom("Quad Med Quick"): (

View File

@ -3,7 +3,7 @@ BasicMelee(
buildup_duration: 0.25,
swing_duration: 0.1,
recover_duration: 0.25,
knockback: 25.0,
knockback: ( strength: 25.0, direction: Away),
base_damage: 200,
base_poise_damage: 40,
range: 5.0,

View File

@ -13,4 +13,5 @@ Shockwave(
requires_ground: false,
move_efficiency: 0.1,
damage_kind: Energy,
specifier: Fire,
)

View File

@ -13,4 +13,5 @@ Shockwave(
requires_ground: true,
move_efficiency: 0.0,
damage_kind: Crushing,
specifier: Ground,
)

View File

@ -5,7 +5,7 @@ BasicMelee(
recover_duration: 0.5,
base_damage: 200,
base_poise_damage: 50,
knockback: 10.0,
knockback: ( strength: 10.0, direction: Away),
range: 4.0,
max_angle: 45.0,
damage_effect: None,

View File

@ -5,7 +5,7 @@ BasicMelee(
recover_duration: 0.6,
base_damage: 150.0,
base_poise_damage: 60.0,
knockback: 15.0,
knockback: ( strength: 15.0, direction: Away),
range: 5.0,
max_angle: 60.0,
damage_effect: Some(Buff((

View File

@ -5,7 +5,7 @@ BasicMelee(
recover_duration: 0.35,
base_damage: 100,
base_poise_damage: 28,
knockback: 25.0,
knockback: ( strength: 25.0, direction: Away),
range: 1.2,
max_angle: 50.0,
damage_effect: None,

View File

@ -13,4 +13,5 @@ Shockwave(
requires_ground: true,
move_efficiency: 0.05,
damage_kind: Crushing,
specifier: Ground,
)

View File

@ -0,0 +1,19 @@
BasicBeam(
buildup_duration: 0.5,
recover_duration: 0.5,
beam_duration: 2.5,
damage: 100,
tick_rate: 2.0,
range: 25.0,
max_angle: 15.0,
damage_effect: Some(Buff((
kind: Wet,
dur_secs: 15.0,
strength: Value(4.5),
chance: 1.0,
))),
energy_regen: 0,
energy_drain: 0,
orientation_behavior: Normal,
specifier: Bubbles,
)

View File

@ -0,0 +1,13 @@
BasicMelee(
energy_cost: 0,
buildup_duration: 0.3,
swing_duration: 0.1,
recover_duration: 0.6,
base_damage: 50.0,
base_poise_damage: 0.0,
knockback: ( strength: 100.0, direction: Towards),
range: 5.0,
max_angle: 60.0,
damage_effect: None,
damage_kind: Crushing,
)

View File

@ -0,0 +1,20 @@
DashMelee(
energy_cost: 0,
base_damage: 50,
scaled_damage: 250,
base_poise_damage: 10,
scaled_poise_damage: 40,
base_knockback: 10.0,
scaled_knockback: 30.0,
range: 5.0,
angle: 90.0,
energy_drain: 0,
forward_speed: 10.0,
buildup_duration: 0.4,
charge_duration: 2.0,
swing_duration: 0.1,
recover_duration: 0.5,
charge_through: true,
is_interruptible: false,
damage_kind: Crushing,
)

View File

@ -0,0 +1,13 @@
BasicSummon(
buildup_duration: 0.5,
cast_duration: 1.0,
recover_duration: 0.5,
summon_amount: 1,
summon_info: (
body: Object(SeaLantern),
scale: None,
health_scaling: 0,
loadout_config: None,
skillset_config: None,
),
)

View File

@ -0,0 +1,17 @@
Shockwave(
energy_cost: 0,
buildup_duration: 1.4,
swing_duration: 0.1,
recover_duration: 0.5,
damage: 10,
poise_damage: 0,
knockback: ( strength: 100.0, direction: Up),
shockwave_angle: 360.0,
shockwave_vertical_angle: 30.0,
shockwave_speed: 10.0,
shockwave_duration: 5.0,
requires_ground: true,
move_efficiency: 0.0,
damage_kind: Crushing,
specifier: Water,
)

View File

@ -5,7 +5,7 @@ BasicMelee(
recover_duration: 0.3,
base_damage: 50,
base_poise_damage: 0,
knockback: 0.0,
knockback: ( strength: 0.0, direction: Away),
range: 3.5,
max_angle: 20.0,
damage_effect: None,

View File

@ -5,7 +5,7 @@ BasicMelee(
recover_duration: 0.9,
base_damage: 20,
base_poise_damage: 0,
knockback: 0.0,
knockback: ( strength: 0.0, direction: Away),
range: 3.5,
max_angle: 15.0,
damage_effect: None,

View File

@ -5,7 +5,7 @@ BasicMelee(
recover_duration: 0.15,
base_damage: 50,
base_poise_damage: 0,
knockback: 0.0,
knockback: ( strength: 0.0, direction: Away),
range: 3.5,
max_angle: 20.0,
damage_effect: None,

View File

@ -5,7 +5,7 @@ BasicMelee(
recover_duration: 0.15,
base_damage: 50,
base_poise_damage: 0,
knockback: 0.0,
knockback: ( strength: 0.0, direction: Away),
range: 3.5,
max_angle: 20.0,
damage_effect: None,

View File

@ -5,7 +5,7 @@ BasicMelee(
recover_duration: 0.3,
base_damage: 40,
base_poise_damage: 0,
knockback: 0.0,
knockback: ( strength: 0.0, direction: Away),
range: 3.0,
max_angle: 120.0,
damage_effect: None,

View File

@ -13,4 +13,5 @@ Shockwave(
requires_ground: false,
move_efficiency: 0.1,
damage_kind: Energy,
specifier: Fire,
)

View File

@ -4,7 +4,7 @@ ItemDef(
kind: Armor((
kind: Chest("Tidal Warrior"),
stats: (
protection: Normal(0.0),
protection: Normal(20.0),
poise_resilience: Normal(0.0),
),
)),

View File

@ -5,7 +5,7 @@ ItemDef(
kind: Natural,
hands: Two,
stats: Direct((
equip_time_secs: 0.5,
equip_time_secs: 0.0,
power: 1.0,
poise_strength: 1.0,
speed: 1.0,
@ -15,5 +15,5 @@ ItemDef(
)),
quality: Low,
tags: [],
ability_spec: Some(Custom("Tidal Claws")),
ability_spec: Some(Custom("Tidal Warrior")),
)

View File

@ -0,0 +1,19 @@
ItemDef(
name: "Tidal Totem",
description: "Yeet",
kind: Tool((
kind: Natural,
hands: Two,
stats: Direct((
equip_time_secs: 0.01,
power: 1.0,
poise_strength: 1.0,
speed: 1.0,
crit_chance: 0.0625,
crit_mult: 1.9142857,
)),
)),
quality: Low,
tags: [],
ability_spec: Some(Custom("Tidal Totem")),
)

View File

@ -40,9 +40,9 @@
("common.items.armor.ferocious.hand",1),
("common.items.armor.ferocious.foot",1),
("common.items.armor.ferocious.shoulder",1),
("common.items.armor.ferocious.belt",1),
("common.items.armor.ferocious.back",1),
("common.items.weapons.sword.bloodsteel-1",1),
("common.items.armor.ferocious.belt",1),
("common.items.armor.ferocious.back",1),
("common.items.weapons.sword.bloodsteel-1",1),
],
"consumables": [
("common.items.consumable.potion_minor", 100),
@ -80,6 +80,36 @@
("common.items.weapons.sceptre.coralline_cane", 1),
("common.items.consumable.potion_med", 100),
],
"tier-2": [
("common.items.armor.twigs.belt", 1),
("common.items.armor.twigs.chest", 1),
("common.items.armor.twigs.foot", 1),
("common.items.armor.twigs.hand", 1),
("common.items.armor.twigs.pants", 1),
("common.items.armor.twigs.shoulder", 1),
("common.items.weapons.sword.iron-0", 1),
("common.items.weapons.axe.iron_axe-0", 1),
("common.items.weapons.hammer.iron_hammer-0", 1),
("common.items.weapons.bow.hardwood-0", 1),
("common.items.weapons.staff.heated_arm", 1),
("common.items.weapons.sceptre.druids_arbor", 1),
("common.items.consumable.potion_med", 100),
],
"tier-1": [
("common.items.armor.agile.belt", 1),
("common.items.armor.agile.chest", 1),
("common.items.armor.agile.foot", 1),
("common.items.armor.agile.hand", 1),
("common.items.armor.agile.pants", 1),
("common.items.armor.agile.shoulder", 1),
("common.items.weapons.sword.bronze-0", 1),
("common.items.weapons.axe.bronze_axe-0", 1),
("common.items.weapons.hammer.bronze_hammer-0", 1),
("common.items.weapons.bow.bone-0", 1),
("common.items.weapons.staff.bone_staff", 1),
("common.items.weapons.sceptre.divine_gohei", 1),
("common.items.consumable.potion_minor", 100),
],
"tier-0": [
("common.items.armor.cloth_purple.belt", 1),
("common.items.armor.cloth_purple.chest", 1),

BIN
assets/voxygen/element/de_buffs/debuff_wet_0.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -32,6 +32,8 @@
"buff.desc.crippled": "Your movement is crippled as your legs are heavily injured.",
"buff.title.frozen": "Frozen",
"buff.desc.frozen": "Your movements and attacks are slowed.",
"buff.title.wet": "Wet",
"buff.desc.wet": "The ground rejects your feet, making it hard to stop.",
// Buffs stats
"buff.stat.health": "Restores {str_total} Health",
"buff.stat.increase_max_stamina": "Raises Maximum Stamina by {strength}",

View File

@ -67,6 +67,8 @@ const int BLOOD = 25;
const int ENRAGED = 26;
const int BIG_SHRAPNEL = 27;
const int LASER = 28;
const int BUBBLES = 29;
const int WATER = 30;
// meters per second squared (acceleration)
const float earth_gravity = 9.807;
@ -181,7 +183,7 @@ void main() {
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3 + lifetime * 0.5)
);
break;
case FIRE:
case FIRE:
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
attr = Attr(
linear_motion(
@ -489,6 +491,28 @@ void main() {
spin_in_axis(perp_axis, asin(inst_dir.z / length(inst_dir)) + PI / 2.0)
);
break;
case BUBBLES:
f_reflect = 0.0; // Magic water doesn't reflect light, it emits it
float blue_color = 1.5 + 0.2 * rand3 + 1.5 * max(floor(rand4 + 0.3), 0.0);
float size = 8.0 * (1 - slow_start(0.1)) * slow_end(0.15);
attr = Attr(
(inst_dir * slow_end(1.5)) + vec3(rand0, rand1, rand2) * (percent() + 2) * 0.1,
vec3(size),
vec4(0.5 * blue_color, 0.75 * blue_color, blue_color, 1),
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
);
break;
case WATER:
f_reflect = 0.0; // Magic water doesn't reflect light, it emits it
blue_color = 1.25 + 0.2 * rand3 + 1.75 * max(floor(rand4 + 0.15), 0.0);
size = 8.0 * (1 - slow_start(0.1)) * slow_end(0.15);
attr = Attr(
(inst_dir * slow_end(0.2)) + vec3(rand0, rand1, rand2) * 0.5,
vec3(size),
vec4(0.5 * blue_color, 0.9 * blue_color, blue_color, 1),
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 5 + 3 * rand9)
);
break;
default:
attr = Attr(
linear_motion(

BIN
assets/voxygen/voxel/object/sea_lantern.vox (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -709,4 +709,14 @@
central: ("object.haniwa_sentry.bone1"),
)
),
SeaLantern: (
bone0: (
offset: (-4.5, -4.5, 0.0),
central: ("object.sea_lantern"),
),
bone1: (
offset: (0.0, 0.0, 0.0),
central: ("armor.empty"),
)
),
})

View File

@ -271,6 +271,7 @@ lazy_static! {
BuffKind::Frenzied => "frenzied",
BuffKind::Crippled => "crippled",
BuffKind::Frozen => "frozen",
BuffKind::Wet => "wet",
};
let mut buff_parser = HashMap::new();
BuffKind::iter().for_each(|kind| {buff_parser.insert(string_from_buff(kind).to_string(), kind);});

View File

@ -2,7 +2,7 @@ use crate::{
assets::{self, Asset},
combat::{self, CombatEffect, DamageKind, Knockback},
comp::{
aura, beam, buff, inventory::item::tool::ToolKind, projectile::ProjectileConstructor,
self, aura, beam, buff, inventory::item::tool::ToolKind, projectile::ProjectileConstructor,
skills, Body, CharacterState, EnergySource, LightEmitter, StateUpdate,
},
states::{
@ -65,7 +65,7 @@ pub enum CharacterAbility {
recover_duration: f32,
base_damage: f32,
base_poise_damage: f32,
knockback: f32,
knockback: Knockback,
range: f32,
max_angle: f32,
damage_effect: Option<CombatEffect>,
@ -233,6 +233,7 @@ pub enum CharacterAbility {
requires_ground: bool,
move_efficiency: f32,
damage_kind: DamageKind,
specifier: comp::shockwave::FrontendSpecifier,
},
BasicBeam {
buildup_duration: f32,
@ -301,7 +302,10 @@ impl Default for CharacterAbility {
recover_duration: 0.5,
base_damage: 10.0,
base_poise_damage: 0.0,
knockback: 0.0,
knockback: Knockback {
strength: 0.0,
direction: combat::KnockbackDir::Away,
},
range: 3.5,
max_angle: 15.0,
damage_effect: None,
@ -1593,6 +1597,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState {
requires_ground,
move_efficiency,
damage_kind,
specifier,
} => CharacterState::Shockwave(shockwave::Data {
static_data: shockwave::StaticData {
buildup_duration: Duration::from_secs_f32(*buildup_duration),
@ -1609,6 +1614,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState {
move_efficiency: *move_efficiency,
ability_info,
damage_kind: *damage_kind,
specifier: *specifier,
},
timer: Duration::default(),
stage_section: StageSection::Buildup,

View File

@ -53,4 +53,5 @@ pub enum FrontendSpecifier {
HealingBeam,
Cultist,
ClayGolem,
Bubbles,
}

View File

@ -470,7 +470,7 @@ impl Body {
biped_large::Species::Troll => 2400,
biped_large::Species::Dullahan => 3000,
biped_large::Species::Mindflayer => 12500,
biped_large::Species::Tidalwarrior => 2500,
biped_large::Species::Tidalwarrior => 16000,
biped_large::Species::Yeti => 4000,
biped_large::Species::Minotaur => 30000,
biped_large::Species::Harvester => 3000,
@ -491,6 +491,7 @@ impl Body {
object::Body::TrainingDummy => 10000,
object::Body::Crossbow => 800,
object::Body::HaniwaSentry => 600,
object::Body::SeaLantern => 1000,
_ => 10000,
},
Body::Golem(golem) => match golem.species {
@ -585,12 +586,12 @@ impl Body {
biped_large::Species::Wendigo => 80,
biped_large::Species::Troll => 60,
biped_large::Species::Dullahan => 120,
biped_large::Species::Tidalwarrior => 90,
biped_large::Species::Yeti => 80,
biped_large::Species::Harvester => 80,
// Boss enemies have their health set, not adjusted by level.
biped_large::Species::Mindflayer => 0,
biped_large::Species::Minotaur => 0,
biped_large::Species::Tidalwarrior => 0,
_ => 100,
},
Body::BipedSmall(_) => 10,
@ -648,6 +649,7 @@ impl Body {
Body::BipedLarge(b) => match b.species {
biped_large::Species::Mindflayer => 4.8,
biped_large::Species::Minotaur => 3.2,
biped_large::Species::Tidalwarrior => 2.25,
_ => 1.0,
},
Body::Golem(g) => match g.species {

View File

@ -83,6 +83,7 @@ make_case_elim!(
SilverOre = 68,
ClayRocket = 69,
HaniwaSentry = 70,
SeaLantern = 71,
}
);
@ -93,7 +94,7 @@ impl Body {
}
}
pub const ALL_OBJECTS: [Body; 71] = [
pub const ALL_OBJECTS: [Body; 72] = [
Body::Arrow,
Body::Bomb,
Body::Scarecrow,
@ -165,6 +166,7 @@ pub const ALL_OBJECTS: [Body; 71] = [
Body::GoldOre,
Body::ClayRocket,
Body::HaniwaSentry,
Body::SeaLantern,
];
impl From<Body> for super::Body {
@ -245,6 +247,7 @@ impl Body {
Body::GoldOre => "gold_ore",
Body::ClayRocket => "clay_rocket",
Body::HaniwaSentry => "haniwa_sentry",
Body::SeaLantern => "sea_lantern",
}
}
@ -336,6 +339,7 @@ impl Body {
Body::GoldOre => 1000.0,
Body::ClayRocket => 50.0,
Body::HaniwaSentry => 300.0,
Body::SeaLantern => 1000.0,
};
Mass(m)
@ -349,6 +353,7 @@ impl Body {
Body::BoltFire => Vec3::new(0.1, 0.1, 0.1),
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),
_ => Vec3::broadcast(0.5),
}
}

View File

@ -67,6 +67,10 @@ pub enum BuffKind {
/// speed, 1.0 is 33% speed. Movement speed debuff is scaled to be slightly
/// smaller than attack speed debuff.
Frozen,
/// Makes you wet and causes you to have reduced friction on the ground.
/// Strength scales the friction you ignore non-linearly. 0.5 is 50% ground
/// friction, 1.0 is 33% ground friction.
Wet,
}
#[cfg(not(target_arch = "wasm32"))]
@ -88,6 +92,7 @@ impl BuffKind {
BuffKind::Crippled => false,
BuffKind::Frenzied => true,
BuffKind::Frozen => false,
BuffKind::Wet => false,
}
}
@ -156,6 +161,8 @@ pub enum BuffEffect {
MovementSpeed(f32),
/// Modifies attack speed of target
AttackSpeed(f32),
/// Modifies ground friction of target
GroundFriction(f32),
}
/// Actual de/buff.
@ -316,6 +323,10 @@ impl Buff {
],
data.duration,
),
BuffKind::Wet => (
vec![BuffEffect::GroundFriction(1.0 - nn_scaling(data.strength))],
data.duration,
),
};
Buff {
kind,

View File

@ -308,6 +308,9 @@ pub fn default_main_tool(body: &Body) -> Option<Item> {
object::Body::HaniwaSentry => Some(Item::new_from_asset_expect(
"common.items.npc_weapons.unique.haniwa_sentry",
)),
object::Body::SeaLantern => Some(Item::new_from_asset_expect(
"common.items.npc_weapons.unique.tidal_totem",
)),
_ => None,
},
Body::BipedSmall(biped_small) => match (biped_small.species, biped_small.body_type) {

View File

@ -13,6 +13,7 @@ pub struct Properties {
pub requires_ground: bool,
pub duration: Duration,
pub owner: Option<Uid>,
pub specifier: FrontendSpecifier,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
@ -43,3 +44,10 @@ pub struct ShockwaveHitEntities {
impl Component for ShockwaveHitEntities {
type Storage = IdvStorage<Self>;
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum FrontendSpecifier {
Ground,
Fire,
Water,
}

View File

@ -27,6 +27,7 @@ pub struct Stats {
pub max_health_modifier: f32,
pub move_speed_modifier: f32,
pub attack_speed_modifier: f32,
pub friction_modifier: f32,
}
impl Stats {
@ -37,6 +38,7 @@ impl Stats {
max_health_modifier: 1.0,
move_speed_modifier: 1.0,
attack_speed_modifier: 1.0,
friction_modifier: 1.0,
}
}
@ -49,6 +51,7 @@ impl Stats {
max_health_modifier: 1.0,
move_speed_modifier: 1.0,
attack_speed_modifier: 1.0,
friction_modifier: 1.0,
}
}
@ -58,6 +61,7 @@ impl Stats {
self.max_health_modifier = 1.0;
self.move_speed_modifier = 1.0;
self.attack_speed_modifier = 1.0;
self.friction_modifier = 1.0;
}
}

View File

@ -1,11 +1,13 @@
use crate::{
combat::{Attack, AttackDamage, AttackEffect, CombatBuff, CombatEffect, CombatRequirement},
combat::{
Attack, AttackDamage, AttackEffect, CombatBuff, CombatEffect, CombatRequirement, Damage,
DamageKind, DamageSource, GroupTarget, Knockback,
},
comp::{tool::ToolKind, CharacterState, Melee, StateUpdate},
states::{
behavior::{CharacterBehavior, JoinData},
utils::*,
},
Damage, DamageKind, DamageSource, GroupTarget, Knockback, KnockbackDir,
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
@ -24,7 +26,7 @@ pub struct StaticData {
/// Base poise reduction
pub base_poise_damage: f32,
/// Knockback
pub knockback: f32,
pub knockback: Knockback,
/// Max range
pub range: f32,
/// Max angle (45.0 will give you a 90.0 angle window)
@ -91,10 +93,7 @@ impl CharacterBehavior for Data {
.with_requirement(CombatRequirement::AnyDamage);
let knockback = AttackEffect::new(
Some(GroupTarget::OutOfGroup),
CombatEffect::Knockback(Knockback {
strength: self.static_data.knockback,
direction: KnockbackDir::Away,
}),
CombatEffect::Knockback(self.static_data.knockback),
)
.with_requirement(CombatRequirement::AnyDamage);
let energy = AttackEffect::new(None, CombatEffect::EnergyReward(50.0))

View File

@ -235,14 +235,9 @@ impl CharacterBehavior for Data {
handle_orientation(data, &mut update, 0.4 * self.static_data.ori_modifier);
// Forward movement
handle_forced_movement(
data,
&mut update,
ForcedMovement::Forward {
strength: self.static_data.stage_data[stage_index].forward_movement,
},
0.3,
);
handle_forced_movement(data, &mut update, ForcedMovement::Forward {
strength: self.static_data.stage_data[stage_index].forward_movement,
});
// Swings
update.character = CharacterState::ComboMelee(Data {

View File

@ -106,14 +106,9 @@ impl CharacterBehavior for Data {
.min(1.0);
handle_orientation(data, &mut update, 0.6);
handle_forced_movement(
data,
&mut update,
ForcedMovement::Forward {
strength: self.static_data.forward_speed * charge_frac.sqrt(),
},
0.1,
);
handle_forced_movement(data, &mut update, ForcedMovement::Forward {
strength: self.static_data.forward_speed * charge_frac.sqrt(),
});
// This logic basically just decides if a charge should end, and prevents the
// character state spamming attacks while checking if it has hit something

View File

@ -86,17 +86,12 @@ impl CharacterBehavior for Data {
let progress = 1.0
- self.timer.as_secs_f32()
/ self.static_data.movement_duration.as_secs_f32();
handle_forced_movement(
data,
&mut update,
ForcedMovement::Leap {
vertical: self.static_data.vertical_leap_strength,
forward: self.static_data.forward_leap_strength,
progress,
direction: MovementDirection::Look,
},
0.15,
);
handle_forced_movement(data, &mut update, ForcedMovement::Leap {
vertical: self.static_data.vertical_leap_strength,
forward: self.static_data.forward_leap_strength,
progress,
direction: MovementDirection::Look,
});
// Increment duration
// If we were to set a timeout for state, this would be

View File

@ -78,19 +78,14 @@ impl CharacterBehavior for Data {
},
StageSection::Movement => {
// Update velocity
handle_forced_movement(
data,
&mut update,
ForcedMovement::Forward {
strength: self.static_data.roll_strength
* ((1.0
- self.timer.as_secs_f32()
/ self.static_data.movement_duration.as_secs_f32())
/ 2.0
+ 0.5),
},
0.0,
);
handle_forced_movement(data, &mut update, ForcedMovement::Forward {
strength: self.static_data.roll_strength
* ((1.0
- self.timer.as_secs_f32()
/ self.static_data.movement_duration.as_secs_f32())
/ 2.0
+ 0.5),
});
if self.timer < self.static_data.movement_duration {
// Movement

View File

@ -44,6 +44,8 @@ pub struct StaticData {
pub ability_info: AbilityInfo,
/// What kind of damage the attack does
pub damage_kind: DamageKind,
/// Used to specify the shockwave to the frontend
pub specifier: shockwave::FrontendSpecifier,
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -108,6 +110,7 @@ impl CharacterBehavior for Data {
attack,
requires_ground: self.static_data.requires_ground,
owner: Some(*data.uid),
specifier: self.static_data.specifier,
};
update.server_events.push_front(ServerEvent::Shockwave {
properties,

View File

@ -167,14 +167,9 @@ impl CharacterBehavior for Data {
self.static_data.movement_behavior,
MovementBehavior::ForwardGround
) {
handle_forced_movement(
data,
&mut update,
ForcedMovement::Forward {
strength: self.static_data.forward_speed,
},
0.1,
);
handle_forced_movement(data, &mut update, ForcedMovement::Forward {
strength: self.static_data.forward_speed,
});
}
// Swings

View File

@ -243,7 +243,7 @@ pub fn handle_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
/// Updates components to move player as if theyre on ground or in air
#[allow(clippy::assign_op_pattern)] // TODO: Pending review in #587
fn basic_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
let efficiency = efficiency * data.stats.move_speed_modifier;
let efficiency = efficiency * data.stats.move_speed_modifier * data.stats.friction_modifier;
let accel = if data.physics.on_ground {
data.body.base_accel()
@ -263,20 +263,15 @@ fn basic_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
}
/// Handles forced movement
pub fn handle_forced_movement(
data: &JoinData,
update: &mut StateUpdate,
movement: ForcedMovement,
efficiency: f32,
) {
let efficiency = efficiency * data.stats.move_speed_modifier;
pub fn handle_forced_movement(data: &JoinData, update: &mut StateUpdate, movement: ForcedMovement) {
match movement {
ForcedMovement::Forward { strength } => {
let strength = strength * data.stats.move_speed_modifier * data.stats.friction_modifier;
if let Some(accel) = data.physics.on_ground.then_some(data.body.base_accel()) {
update.vel.0 += Vec2::broadcast(data.dt.0)
* accel
* (data.inputs.move_dir * efficiency + Vec2::from(update.ori) * strength);
* (data.inputs.move_dir + Vec2::from(update.ori))
* strength;
}
},
ForcedMovement::Leap {
@ -328,7 +323,7 @@ pub fn handle_orientation(data: &JoinData, update: &mut StateUpdate, efficiency:
/// Updates components to move player as if theyre swimming
fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32, submersion: f32) -> bool {
let efficiency = efficiency * data.stats.move_speed_modifier;
let efficiency = efficiency * data.stats.move_speed_modifier * data.stats.friction_modifier;
if let Some(force) = data.body.swim_thrust() {
let force = efficiency * force;
let mut water_accel = force / data.mass.0;
@ -366,7 +361,7 @@ fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32, submers
/// Updates components to move entity as if it's flying
pub fn fly_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) -> bool {
let efficiency = efficiency * data.stats.move_speed_modifier;
let efficiency = efficiency * data.stats.move_speed_modifier * data.stats.friction_modifier;
let glider = match data.character {
CharacterState::Glide(data) => Some(data),

View File

@ -214,6 +214,9 @@ impl<'a> System<'a> for Sys {
BuffEffect::AttackSpeed(speed) => {
stat.attack_speed_modifier *= *speed;
},
BuffEffect::GroundFriction(gf) => {
stat.friction_modifier *= *gf;
},
};
}
}

View File

@ -3,7 +3,7 @@ use common::{
body::ship::figuredata::{VoxelCollider, VOXEL_COLLIDER_MANIFEST},
fluid_dynamics::{Fluid, Wings},
BeamSegment, Body, CharacterState, Collider, Density, Mass, Mounting, Ori, PhysicsState,
Pos, PosVelDefer, PreviousPhysCache, Projectile, Scale, Shockwave, Sticky, Vel,
Pos, PosVelDefer, PreviousPhysCache, Projectile, Scale, Shockwave, Stats, Sticky, Vel,
},
consts::{AIR_DENSITY, FRIC_GROUND, GRAVITY},
event::{EventBus, ServerEvent},
@ -124,6 +124,7 @@ pub struct PhysicsRead<'a> {
bodies: ReadStorage<'a, Body>,
character_states: ReadStorage<'a, CharacterState>,
densities: ReadStorage<'a, Density>,
stats: ReadStorage<'a, Stats>,
}
#[derive(SystemData)]
@ -779,6 +780,7 @@ impl<'a> PhysicsData<'a> {
block_snap,
climbing,
|entity, vel| land_on_ground = Some((entity, vel)),
read,
);
tgt_pos = cpos.0;
},
@ -808,6 +810,7 @@ impl<'a> PhysicsData<'a> {
block_snap,
climbing,
|entity, vel| land_on_ground = Some((entity, vel)),
read,
);
// Sticky things shouldn't move when on a surface
@ -1048,6 +1051,7 @@ impl<'a> PhysicsData<'a> {
land_on_ground =
Some((entity, Vel(ori_from.mul_direction(vel.0))));
},
read,
);
cpos.0 = transform_from.mul_point(cpos.0) + wpos;
@ -1242,6 +1246,7 @@ fn box_voxel_collision<'a, T: BaseVol<Vox = Block> + ReadVol>(
block_snap: bool,
climbing: bool,
mut land_on_ground: impl FnMut(Entity, Vel),
read: &PhysicsRead,
) {
let (radius, z_min, z_max) = cylinder;
@ -1567,8 +1572,9 @@ fn box_voxel_collision<'a, T: BaseVol<Vox = Block> + ReadVol>(
}
}
physics_state.on_wall = on_wall;
let fric_mod = read.stats.get(entity).map_or(1.0, |s| s.friction_modifier);
if physics_state.on_ground || (physics_state.on_wall.is_some() && climbing) {
vel.0 *= (1.0 - FRIC_GROUND.min(1.0)).powf(dt.0 * 60.0);
vel.0 *= (1.0 - FRIC_GROUND.min(1.0) * fric_mod).powf(dt.0 * 60.0);
physics_state.ground_vel = ground_vel;
}

View File

@ -87,6 +87,10 @@ struct AttackData {
angle: f32,
}
impl AttackData {
fn in_min_range(&self) -> bool { self.dist_sqrd < self.min_attack_dist.powi(2) }
}
#[derive(Eq, PartialEq)]
pub enum Tactic {
Melee,
@ -108,11 +112,13 @@ pub enum Tactic {
Turret,
FixedTurret,
RotatingTurret,
RadialTurret,
Mindflayer,
BirdLargeBreathe,
BirdLargeFire,
Minotaur,
ClayGolem,
TidalWarrior,
}
#[derive(SystemData)]
@ -1601,6 +1607,8 @@ impl<'a> AgentData<'a> {
"Mindflayer" => Tactic::Mindflayer,
"Minotaur" => Tactic::Minotaur,
"Clay Golem" => Tactic::ClayGolem,
"Tidal Warrior" => Tactic::TidalWarrior,
"Tidal Totem" => Tactic::RadialTurret,
_ => Tactic::Melee,
},
AbilitySpec::Tool(tool_kind) => tool_tactic(*tool_kind),
@ -1843,6 +1851,20 @@ impl<'a> AgentData<'a> {
&tgt_data,
&read_data,
),
Tactic::TidalWarrior => self.handle_tidal_warrior_attack(
agent,
controller,
&attack_data,
&tgt_data,
&read_data,
),
Tactic::RadialTurret => self.handle_radial_turret_attack(
agent,
controller,
&attack_data,
&tgt_data,
&read_data,
),
}
}
@ -1854,7 +1876,7 @@ impl<'a> AgentData<'a> {
tgt_data: &TargetData,
read_data: &ReadData,
) {
if attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) && attack_data.angle < 45.0 {
if attack_data.in_min_range() && attack_data.angle < 45.0 {
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
@ -1883,7 +1905,7 @@ impl<'a> AgentData<'a> {
tgt_data: &TargetData,
read_data: &ReadData,
) {
if attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) && attack_data.angle < 45.0 {
if attack_data.in_min_range() && attack_data.angle < 45.0 {
controller.inputs.move_dir = Vec2::zero();
if agent.action_state.timer > 6.0 {
controller
@ -1932,7 +1954,7 @@ impl<'a> AgentData<'a> {
tgt_data: &TargetData,
read_data: &ReadData,
) {
if attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) && attack_data.angle < 45.0 {
if attack_data.in_min_range() && attack_data.angle < 45.0 {
controller.inputs.move_dir = Vec2::zero();
if agent.action_state.timer > 4.0 {
controller
@ -2004,7 +2026,7 @@ impl<'a> AgentData<'a> {
tgt_data: &TargetData,
read_data: &ReadData,
) {
if attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) && attack_data.angle < 45.0 {
if attack_data.in_min_range() && attack_data.angle < 45.0 {
controller.inputs.move_dir = Vec2::zero();
if self
.skill_set
@ -2226,9 +2248,7 @@ impl<'a> AgentData<'a> {
tgt_data: &TargetData,
read_data: &ReadData,
) {
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
&& attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2)
{
if self.body.map(|b| b.is_humanoid()).unwrap_or(false) && attack_data.in_min_range() {
controller
.actions
.push(ControlAction::basic_input(InputKind::Roll));
@ -2325,7 +2345,7 @@ impl<'a> AgentData<'a> {
tgt_data: &TargetData,
read_data: &ReadData,
) {
if attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) && attack_data.angle < 90.0 {
if attack_data.in_min_range() && attack_data.angle < 90.0 {
controller.inputs.move_dir = Vec2::zero();
controller
.actions
@ -2371,8 +2391,7 @@ impl<'a> AgentData<'a> {
radius: u32,
circle_time: u32,
) {
if attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) && thread_rng().gen_bool(0.5)
{
if attack_data.in_min_range() && thread_rng().gen_bool(0.5) {
controller.inputs.move_dir = Vec2::zero();
controller
.actions
@ -2679,7 +2698,7 @@ impl<'a> AgentData<'a> {
tgt_data: &TargetData,
read_data: &ReadData,
) {
if attack_data.angle < 90.0 && attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) {
if attack_data.angle < 90.0 && attack_data.in_min_range() {
controller.inputs.move_dir = Vec2::zero();
if agent.action_state.timer < 2.0 {
controller
@ -2762,7 +2781,7 @@ impl<'a> AgentData<'a> {
tgt_data: &TargetData,
read_data: &ReadData,
) {
if attack_data.angle < 90.0 && attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) {
if attack_data.angle < 90.0 && attack_data.in_min_range() {
controller.inputs.move_dir = Vec2::zero();
controller
.actions
@ -2850,6 +2869,26 @@ impl<'a> AgentData<'a> {
}
}
fn handle_radial_turret_attack(
&self,
_agent: &mut Agent,
controller: &mut Controller,
attack_data: &AttackData,
tgt_data: &TargetData,
read_data: &ReadData,
) {
if can_see_tgt(
&*read_data.terrain,
self.pos,
tgt_data.pos,
attack_data.dist_sqrd,
) {
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
}
}
fn handle_mindflayer_attack(
&self,
agent: &mut Agent,
@ -3190,7 +3229,7 @@ impl<'a> AgentData<'a> {
agent.action_state.timer += read_data.dt.0;
} else if agent.action_state.timer < 6.0
&& attack_data.angle < 90.0
&& attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2)
&& attack_data.in_min_range()
{
// Triplestrike
controller
@ -3355,6 +3394,88 @@ impl<'a> AgentData<'a> {
self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
}
fn handle_tidal_warrior_attack(
&self,
agent: &mut Agent,
controller: &mut Controller,
attack_data: &AttackData,
tgt_data: &TargetData,
read_data: &ReadData,
) {
const SCUTTLE_RANGE: f32 = 40.0;
const BUBBLE_RANGE: f32 = 20.0;
const MINION_SUMMON_THRESHOLD: f32 = 0.20;
let health_fraction = self.health.map_or(0.5, |h| h.fraction());
// Sets counter at start of combat, using `condition` to keep track of whether
// it was already intitialized
if !agent.action_state.condition {
agent.action_state.counter = 1.0 - MINION_SUMMON_THRESHOLD;
agent.action_state.condition = true;
}
if agent.action_state.counter > health_fraction {
// Summon minions at particular thresholds of health
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(1)));
if matches!(self.char_state, CharacterState::BasicSummon(c) if matches!(c.stage_section, StageSection::Recover))
{
agent.action_state.counter -= MINION_SUMMON_THRESHOLD;
}
} else if attack_data.dist_sqrd < SCUTTLE_RANGE.powi(2) {
if matches!(self.char_state, CharacterState::DashMelee(c) if !matches!(c.stage_section, StageSection::Recover))
{
// Keep scuttling if already in dash melee and not in recover
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
} else if attack_data.dist_sqrd < BUBBLE_RANGE.powi(2) {
if matches!(self.char_state, CharacterState::BasicBeam(c) if !matches!(c.stage_section, StageSection::Recover) && c.timer < Duration::from_secs(10))
{
// Keep shooting bubbles at them if already in basic beam and not in recover and
// have not been bubbling too long
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
} else if attack_data.in_min_range() && attack_data.angle < 60.0 {
// Pincer them if they're in range and angle
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
} else if attack_data.angle < 30.0
&& can_see_tgt(
&*read_data.terrain,
self.pos,
tgt_data.pos,
attack_data.dist_sqrd,
)
{
// Start bubbling them if not close enough to do something else and in angle and
// can see target
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
}
} else if attack_data.angle < 90.0
&& can_see_tgt(
&*read_data.terrain,
self.pos,
tgt_data.pos,
attack_data.dist_sqrd,
)
{
// Start scuttling if not close enough to do something else and in angle and can
// see target
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
}
}
// 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

@ -19,6 +19,7 @@ impl Animation for AlphaAnimation {
f32,
Option<StageSection>,
f32,
f32,
);
type Skeleton = BipedLargeSkeleton;
@ -33,9 +34,10 @@ impl Animation for AlphaAnimation {
(active_tool_kind, active_tool_spec),
_second_tool,
velocity,
_global_time,
global_time,
stage_section,
acc_vel,
timer,
): Self::Dependency<'a>,
anim_time: f32,
rate: &mut f32,
@ -63,6 +65,9 @@ impl Animation for AlphaAnimation {
let pullback = 1.0 - move3;
let move1 = move1base * pullback;
let move2 = move2base * pullback;
let subtract = global_time - timer;
let check = subtract - subtract.trunc();
let mirror = (check - 0.5).signum();
next.second.position = Vec3::new(0.0, 0.0, 0.0);
next.second.orientation = Quaternion::rotation_x(0.0);
next.shoulder_l.position = Vec3::new(
@ -196,40 +201,69 @@ impl Animation for AlphaAnimation {
* Quaternion::rotation_z(move1 * -0.5 + move2 * 0.6);
next.head.orientation = Quaternion::rotation_x(move1 * 0.3);
},
"Tidal Claws" => {
next.torso.position = Vec3::new(0.0, 0.0, move1 * -0.3);
next.upper_torso.orientation =
Quaternion::rotation_x(move1 * -0.5 + move2 * -0.4);
next.lower_torso.orientation =
Quaternion::rotation_x(move1 * 0.5 + move2 * 0.4);
"Tidal Warrior" => {
if mirror > 0.0 {
next.head.orientation = Quaternion::rotation_z(move1 * 0.75);
next.upper_torso.orientation =
Quaternion::rotation_x(move1 * 0.2 + move2 * 0.7)
* Quaternion::rotation_z(move1 * -1.0 + move2 * 1.3);
next.lower_torso.orientation =
Quaternion::rotation_x(move1 * 0.2 + move2 * -0.7)
* Quaternion::rotation_y(move1 * -0.5 + move2 * 0.7)
* Quaternion::rotation_z(move1 * 1.0 + move2 * -1.2);
next.shoulder_l.orientation = Quaternion::rotation_x(
move1 * 0.4 + 0.4 * speednorm + (footrotl * -0.2) * speednorm,
);
next.shoulder_r.orientation = Quaternion::rotation_x(
move1 * 0.4 + 0.4 * speednorm + (footrotl * -0.2) * speednorm,
);
next.shoulder_l.orientation =
Quaternion::rotation_x(move1 * 0.3 + move2 * 0.8)
* Quaternion::rotation_y(move1 * -0.3 + move2 * -0.5);
next.hand_l.position = Vec3::new(
-14.0 + move1 * -2.0 + move2 * 4.0,
2.0 + move2 * 4.0,
-4.0 + move2 * 3.0,
);
next.control_l.position = Vec3::new(
-14.0 + move2 * 9.0,
12.0 + move1 * 6.0,
-12.0 + move1 * 9.0,
);
next.control_r.position = Vec3::new(
14.0 + move2 * -9.0,
12.0 + move1 * 6.0,
-12.0 + move1 * 9.0,
);
next.hand_l.orientation =
Quaternion::rotation_x(PI / 3.0 + move2 * 1.5)
* Quaternion::rotation_y(move2 * 0.5)
* Quaternion::rotation_z(
-0.35 + move1 * -0.5 + move2 * 1.0,
);
next.hand_r.position = Vec3::new(14.0, 2.0, -4.0);
next.control_l.orientation =
Quaternion::rotation_x(PI / 3.0 + move1 * 0.5)
* Quaternion::rotation_y(-0.15)
* Quaternion::rotation_z(move1 * 0.5 + move2 * -0.6);
next.control_r.orientation =
Quaternion::rotation_x(PI / 3.0 + move1 * 0.5)
* Quaternion::rotation_y(0.15)
* Quaternion::rotation_z(move1 * -0.5 + move2 * 0.6);
next.head.orientation = Quaternion::rotation_x(move1 * 0.3);
next.hand_r.orientation =
Quaternion::rotation_x(PI / 3.0) * Quaternion::rotation_z(0.35);
next.shoulder_r.orientation = Quaternion::rotation_x(0.0);
} else {
next.head.orientation = Quaternion::rotation_z(move1 * -0.75);
next.upper_torso.orientation =
Quaternion::rotation_x(move1 * 0.2 + move2 * 0.7)
* Quaternion::rotation_z(move1 * 1.0 + move2 * -1.3);
next.lower_torso.orientation =
Quaternion::rotation_x(move1 * 0.2 + move2 * -0.7)
* Quaternion::rotation_y(move1 * 0.5 + move2 * -0.7)
* Quaternion::rotation_z(move1 * -1.0 + move2 * 1.2);
next.shoulder_r.orientation =
Quaternion::rotation_x(move1 * 0.3 + move2 * 0.8)
* Quaternion::rotation_y(move1 * 0.3 + move2 * 0.5);
next.hand_r.position = Vec3::new(
14.0 + move1 * 2.0 + move2 * -4.0,
2.0 + move2 * 4.0,
-4.0 + move2 * 3.0,
);
next.hand_r.orientation =
Quaternion::rotation_x(PI / 3.0 + move2 * 1.5)
* Quaternion::rotation_y(move2 * -0.5)
* Quaternion::rotation_z(0.35 + move1 * 0.5 + move2 * -1.0);
next.hand_l.position = Vec3::new(-14.0, 2.0, -4.0);
next.hand_l.orientation = Quaternion::rotation_x(PI / 3.0)
* Quaternion::rotation_z(-0.35);
next.shoulder_l.orientation = Quaternion::rotation_x(0.0);
};
next.torso.position = Vec3::new(0.0, move2 * -2.2, move2 * -1.0);
},
"Minotaur" => {
next.control_l.position = Vec3::new(0.0, 4.0, 5.0);

View File

@ -2,19 +2,24 @@ use super::{
super::{vek::*, Animation},
BipedLargeSkeleton, SkeletonAttr,
};
use common::{comp::item::ToolKind, states::utils::StageSection};
use common::{
comp::item::{AbilitySpec, ToolKind},
states::utils::StageSection,
};
use std::f32::consts::PI;
pub struct BeamAnimation;
impl Animation for BeamAnimation {
#[allow(clippy::type_complexity)]
type Dependency<'a> = (
Option<ToolKind>,
Option<ToolKind>,
(Option<ToolKind>, Option<&'a AbilitySpec>),
(Option<ToolKind>, Option<&'a AbilitySpec>),
f32,
Vec3<f32>,
Option<StageSection>,
f32,
f32,
);
type Skeleton = BipedLargeSkeleton;
@ -25,7 +30,15 @@ impl Animation for BeamAnimation {
#[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, acc_vel): Self::Dependency<'a>,
(
(active_tool_kind, active_tool_spec),
_second_tool_kind,
global_time,
velocity,
stage_section,
acc_vel,
timer,
): Self::Dependency<'a>,
anim_time: f32,
rate: &mut f32,
s_a: &SkeletonAttr,
@ -46,7 +59,9 @@ impl Animation for BeamAnimation {
let footrotr = ((1.0 / (0.5 + (0.5) * ((acc_vel * lab + PI * 0.4).sin()).powi(2))).sqrt())
* ((acc_vel * lab + PI * 0.4).sin())
* speednorm;
let subtract = global_time - timer;
let check = subtract - subtract.trunc();
let mirror = (check - 0.5).signum();
next.jaw.position = Vec3::new(0.0, s_a.jaw.0, s_a.jaw.1);
next.jaw.orientation = Quaternion::rotation_x(0.0);
@ -58,26 +73,26 @@ 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,
),
Some(StageSection::Cast) => (
1.0,
(anim_time * 15.0 + PI).sin(),
anim_time.powf(0.25),
0.0,
),
Some(StageSection::Recover) => (1.0, 1.0, 1.0, anim_time),
_ => (0.0, 0.0, 0.0, 0.0),
};
let pullback = 1.0 - move3;
let move1 = move1base * pullback;
match active_tool_kind {
Some(ToolKind::Sceptre) | Some(ToolKind::Staff) => {
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,
),
Some(StageSection::Cast) => (
1.0,
(anim_time * 15.0 + PI).sin(),
anim_time.powf(0.25),
0.0,
),
Some(StageSection::Recover) => (1.0, 1.0, 1.0, anim_time),
_ => (0.0, 0.0, 0.0, 0.0),
};
let pullback = 1.0 - move3;
let move1 = move1base * pullback;
next.control_l.position = Vec3::new(-1.0, 3.0, 12.0);
next.control_r.position =
Vec3::new(1.0 + move1 * 5.0, 2.0 + move1 * 1.0, 2.0 + move1 * 14.0);
@ -116,6 +131,56 @@ impl Animation for BeamAnimation {
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 {
match spec.as_str() {
"Tidal Warrior" => {
if mirror > 0.0 {
next.head.orientation = Quaternion::rotation_z(move1 * -0.6);
next.upper_torso.orientation = Quaternion::rotation_z(move1 * 0.6);
next.lower_torso.orientation = Quaternion::rotation_z(move1 * -0.6);
next.shoulder_l.orientation = Quaternion::rotation_z(move1 * 0.3);
next.hand_l.position = Vec3::new(-14.0 + move1 * 3.0, 2.0, -4.0);
next.hand_l.orientation =
Quaternion::rotation_x(PI / 3.0 + move2shake * -0.07)
* Quaternion::rotation_y(move1 * -0.5)
* Quaternion::rotation_z(-0.35 + move2shake * 0.07);
next.hand_r.position = Vec3::new(14.0 + move1 - 3.0, 2.0, -4.0);
next.hand_r.orientation =
Quaternion::rotation_x(PI / 3.0 + move2shake * 0.07)
* Quaternion::rotation_y(move1 * -0.5)
* Quaternion::rotation_z(0.35 - move2shake * 0.07);
next.shoulder_r.orientation = Quaternion::rotation_z(move1 * -0.3);
} else {
next.head.orientation = Quaternion::rotation_z(move1 * 0.6);
next.upper_torso.orientation = Quaternion::rotation_z(move1 * -0.6);
next.lower_torso.orientation = Quaternion::rotation_z(move1 * 0.6);
next.shoulder_l.orientation = Quaternion::rotation_z(move1 * -0.3);
next.hand_l.position = Vec3::new(-14.0 + move1 * 3.0, 2.0, -4.0);
next.hand_l.orientation =
Quaternion::rotation_x(PI / 3.0 + move2shake * 0.07)
* Quaternion::rotation_y(move1 * 0.5)
* Quaternion::rotation_z(-0.35 + move2shake * 0.07);
next.hand_r.position = Vec3::new(14.0 + move1 - 3.0, 2.0, -4.0);
next.hand_r.orientation =
Quaternion::rotation_x(PI / 3.0 + move2shake * -0.07)
* Quaternion::rotation_y(move1 * 0.5)
* Quaternion::rotation_z(0.35 - move2shake * -0.07);
next.shoulder_r.orientation = Quaternion::rotation_z(move1 * 0.3);
};
},
_ => {},
}
}
},
_ => {},
}

View File

@ -46,7 +46,7 @@ impl Animation for DashAnimation {
let lab: f32 = 0.65 * s_a.tempo;
let speed = Vec2::<f32>::from(velocity).magnitude();
let speednorm = (speed / 12.0).powf(0.4);
let speednorm = (speed.min(16.0) / 12.0).powf(0.4);
let foothoril = (acc_vel * lab + PI * 1.45).sin() * speednorm;
let foothorir = (acc_vel * lab + PI * (0.45)).sin() * speednorm;
let footrotl = ((1.0 / (0.5 + (0.5) * ((acc_vel * lab + PI * 1.4).sin()).powi(2))).sqrt())
@ -64,12 +64,18 @@ impl Animation for DashAnimation {
next.second.orientation = Quaternion::rotation_x(0.0);
next.hand_l.orientation = Quaternion::rotation_x(0.0);
next.hand_r.orientation = Quaternion::rotation_x(0.0);
let (move1base, move2base, move3base, move4) = match stage_section {
Some(StageSection::Buildup) => (anim_time.powf(0.25), 0.0, 0.0, 0.0),
Some(StageSection::Charge) => (1.0, (anim_time.powf(4.0)).min(1.0), 0.0, 0.0),
Some(StageSection::Swing) => (1.0, 1.0, anim_time.powf(4.0), 0.0),
Some(StageSection::Recover) => (1.1, 1.0, 1.0, anim_time.powf(4.0)),
_ => (0.0, 0.0, 0.0, 0.0),
let (move1base, motion, move2base, move3base, move4) = match stage_section {
Some(StageSection::Buildup) => (anim_time.powf(0.25), 0.0, 0.0, 0.0, 0.0),
Some(StageSection::Charge) => (
1.0,
(acc_vel * lab).sin(),
(anim_time.powf(4.0)).min(1.0),
0.0,
0.0,
),
Some(StageSection::Swing) => (1.0, 1.0, 1.0, anim_time.powf(4.0), 0.0),
Some(StageSection::Recover) => (1.1, 1.0, 1.0, 1.0, anim_time.powf(4.0)),
_ => (0.0, 0.0, 0.0, 0.0, 0.0),
};
let pullback = 1.0 - move4;
let move1 = move1base * pullback;
@ -189,6 +195,32 @@ impl Animation for DashAnimation {
next.shoulder_r.orientation = Quaternion::rotation_x(-0.3);
},
"Tidal Warrior" => {
next.head.orientation =
Quaternion::rotation_x(0.0) * Quaternion::rotation_z(move1 * -0.3);
next.upper_torso.orientation = Quaternion::rotation_x(move1 * -0.1)
* Quaternion::rotation_z(move1 * 1.57);
next.lower_torso.orientation = Quaternion::rotation_x(move1 * 0.1)
* Quaternion::rotation_x(move1 * -0.1)
* Quaternion::rotation_z(move1 * -0.2);
next.hand_l.position = Vec3::new(-14.0, 2.0 + motion * 1.5, -4.0);
next.hand_l.orientation =
Quaternion::rotation_x(PI / 3.0 + move1 * 1.0)
* Quaternion::rotation_y(0.0)
* Quaternion::rotation_z(-0.35 + motion * -0.6);
next.hand_r.position = Vec3::new(14.0, 2.0 + motion * -1.5, -4.0);
next.hand_r.orientation =
Quaternion::rotation_x(PI / 3.0 + move1 * 1.0)
* Quaternion::rotation_y(0.0)
* Quaternion::rotation_z(0.35 + motion * 0.6);
next.shoulder_l.orientation = Quaternion::rotation_x(move1 * 0.8);
next.shoulder_r.orientation = Quaternion::rotation_x(move1 * 0.8);
},
_ => {},
}
}

View File

@ -48,8 +48,8 @@ impl Animation for RunAnimation {
*rate = 1.0;
let lab: f32 = 0.65 * s_a.tempo;
let speednorm = (speed / 12.0).powf(0.6);
let speednormlow = (speed / 12.0).powf(4.0);
let speednorm = (speed.min(16.0) / 12.0).powf(0.6);
let speednormlow = (speed.min(16.0) / 12.0).powf(4.0);
let footvertl = (acc_vel * lab + PI * -0.2).sin() * speednorm;
let footvertr = (acc_vel * lab + PI * -1.2).sin() * speednorm;
@ -119,16 +119,17 @@ impl Animation for RunAnimation {
let foothoril = (acc_vel * lab + PI * 1.45).sin() * speednorm;
let foothorir = (acc_vel * lab + PI * (0.45)).sin() * speednorm;
let footstrafel = (acc_vel * lab + PI * 1.45).sin();
let footstrafer = (acc_vel * lab + PI * (0.95)).sin();
let footvertsl = (acc_vel * lab).sin();
let footvertsr = (acc_vel * lab + PI * 0.5).sin();
let footstrafel = (acc_vel * lab + PI * 1.45).sin() * speednorm;
let footstrafer = (acc_vel * lab + PI * (0.95)).sin() * speednorm;
let footvertsl = (acc_vel * lab).sin() * speednorm;
let footvertsr = (acc_vel * lab + PI * 0.5).sin() * speednorm;
let direction = velocity.y * -0.098 * orientation.y + velocity.x * -0.098 * orientation.x;
let side =
(velocity.x * -0.098 * orientation.y + velocity.y * 0.098 * orientation.x) * -1.0;
let side = ((velocity.x * -0.098 * orientation.y + velocity.y * 0.098 * orientation.x)
* -1.0)
.min(1.0)
.max(-1.0);
let sideabs = side.abs();
let x_tilt = avg_vel.z.atan2(avg_vel.xy().magnitude());
next.jaw.scale = Vec3::one() * 1.02;

View File

@ -212,14 +212,19 @@ impl Animation for StunnedAnimation {
next.control_r.orientation =
Quaternion::rotation_x(PI / 3.0) * Quaternion::rotation_y(0.15);
},
"Tidal Claws" => {
next.control_l.position = Vec3::new(-14.0, 12.0, -12.0);
next.control_r.position = Vec3::new(14.0, 12.0, -12.0);
"Tidal Warrior" => {
next.head.orientation = Quaternion::rotation_x(movement1 * -2.0);
next.upper_torso.orientation =
Quaternion::rotation_z(movement1 * 1.0);
next.lower_torso.orientation =
Quaternion::rotation_z(movement1 * -1.0);
next.hand_l.position = Vec3::new(-14.0, 2.0, -4.0);
next.hand_r.position = Vec3::new(14.0, 2.0, -4.0);
next.control_l.orientation = Quaternion::rotation_x(PI / 3.0)
* Quaternion::rotation_y(-0.15);
next.control_r.orientation =
Quaternion::rotation_x(PI / 3.0) * Quaternion::rotation_y(0.15);
next.hand_l.orientation = Quaternion::rotation_x(PI / 3.0)
* Quaternion::rotation_z(-0.35);
next.hand_r.orientation =
Quaternion::rotation_x(PI / 3.0) * Quaternion::rotation_z(0.35);
},
"Beast Claws" => {
next.shoulder_l.position =

View File

@ -2,15 +2,19 @@ use super::{
super::{vek::*, Animation},
BipedLargeSkeleton, SkeletonAttr,
};
use common::{comp::item::ToolKind, states::utils::StageSection};
use common::{
comp::item::{AbilitySpec, ToolKind},
states::utils::StageSection,
};
use std::f32::consts::PI;
pub struct SummonAnimation;
impl Animation for SummonAnimation {
#[allow(clippy::type_complexity)]
type Dependency<'a> = (
Option<ToolKind>,
Option<ToolKind>,
(Option<ToolKind>, Option<&'a AbilitySpec>),
(Option<ToolKind>, Option<&'a AbilitySpec>),
Vec3<f32>,
f32,
Option<StageSection>,
@ -25,7 +29,14 @@ impl Animation for SummonAnimation {
#[allow(clippy::approx_constant)] // TODO: Pending review in #587
fn update_skeleton_inner<'a>(
skeleton: &Self::Skeleton,
(active_tool_kind, _second_tool_kind, velocity, _global_time, stage_section, acc_vel): Self::Dependency<'a>,
(
(active_tool_kind, active_tool_spec),
_second_tool_kind,
velocity,
_global_time,
stage_section,
acc_vel,
): Self::Dependency<'a>,
anim_time: f32,
rate: &mut f32,
s_a: &SkeletonAttr,
@ -55,21 +66,6 @@ impl Animation for SummonAnimation {
let move1 = move1base * pullback;
let move2 = move2base * pullback;
next.shoulder_l.position = Vec3::new(
-s_a.shoulder.0,
s_a.shoulder.1,
s_a.shoulder.2 - foothorir * 1.0,
);
next.shoulder_l.orientation =
Quaternion::rotation_x(move1 * 0.8 + 0.6 * speednorm + (footrotr * -0.2) * speednorm);
next.shoulder_r.position = Vec3::new(
s_a.shoulder.0,
s_a.shoulder.1,
s_a.shoulder.2 - foothoril * 1.0,
);
next.shoulder_r.orientation =
Quaternion::rotation_x(move1 * 0.8 + 0.6 * speednorm + (footrotl * -0.2) * speednorm);
next.torso.orientation = Quaternion::rotation_z(0.0);
next.main.position = Vec3::new(0.0, 0.0, 0.0);
@ -84,6 +80,23 @@ impl Animation for SummonAnimation {
#[allow(clippy::single_match)]
match active_tool_kind {
Some(ToolKind::Staff) => {
next.shoulder_l.position = Vec3::new(
-s_a.shoulder.0,
s_a.shoulder.1,
s_a.shoulder.2 - foothorir * 1.0,
);
next.shoulder_l.orientation = Quaternion::rotation_x(
move1 * 0.8 + 0.6 * speednorm + (footrotr * -0.2) * speednorm,
);
next.shoulder_r.position = Vec3::new(
s_a.shoulder.0,
s_a.shoulder.1,
s_a.shoulder.2 - foothoril * 1.0,
);
next.shoulder_r.orientation = Quaternion::rotation_x(
move1 * 0.8 + 0.6 * speednorm + (footrotl * -0.2) * speednorm,
);
next.head.orientation = Quaternion::rotation_x(0.0);
next.control_l.position = Vec3::new(-1.0, 3.0, 12.0);
next.control_r.position = Vec3::new(
@ -108,7 +121,77 @@ impl Animation for SummonAnimation {
next.control.orientation = Quaternion::rotation_x(-0.2 + move1 * 1.0)
* Quaternion::rotation_y(-0.1 + move2 * -0.8);
},
Some(ToolKind::Natural) => {
if let Some(AbilitySpec::Custom(spec)) = active_tool_spec {
match spec.as_str() {
"Tidal Warrior" => {
let (move1base, move2base, move3) = match stage_section {
Some(StageSection::Buildup) => ((anim_time.powi(2)), 0.0, 0.0),
Some(StageSection::Cast) => (1.0, (anim_time * 30.0).sin(), 0.0),
Some(StageSection::Recover) => (1.0, 1.0, anim_time),
_ => (0.0, 0.0, 0.0),
};
let pullback = 1.0 - move3;
let move1 = move1base * pullback;
let move2 = move2base * pullback;
next.torso.position = Vec3::new(0.0, 0.0 + move1 * 1.0, move1 * -4.0);
next.upper_torso.position =
Vec3::new(0.0, s_a.upper_torso.0, s_a.upper_torso.1);
next.lower_torso.position =
Vec3::new(0.0, s_a.lower_torso.0, s_a.lower_torso.1);
next.head.position =
Vec3::new(0.0, s_a.head.0 + move1 * -8.0, s_a.head.1 + move1 * 6.0);
next.shoulder_l.orientation = Quaternion::rotation_x(move1 * 2.5)
* Quaternion::rotation_y(move1 * 0.4 + move2 * 0.05);
next.shoulder_r.orientation = Quaternion::rotation_x(move1 * 2.5)
* Quaternion::rotation_y(move1 * -0.4 + move2 * -0.05);
next.head.orientation = Quaternion::rotation_x(move1 * 1.4)
* Quaternion::rotation_y(move2 * 0.02);
next.upper_torso.orientation = Quaternion::rotation_x(move1 * -1.5)
* Quaternion::rotation_y(move2 * -0.02);
next.lower_torso.orientation = Quaternion::rotation_x(move1 * 0.2)
* Quaternion::rotation_y(move2 * 0.02);
next.hand_l.position = Vec3::new(
-14.0 + move1 * -5.0,
2.0 + move1 * -2.0,
-4.0 + move1 * 12.0,
);
next.hand_r.position = Vec3::new(
14.0 + move1 * 5.0,
2.0 + move1 * -2.0,
-4.0 + move1 * 12.0,
);
next.hand_l.orientation =
Quaternion::rotation_x(PI / 3.0 + move1 * 1.5)
* Quaternion::rotation_y(-move1 * 0.7 + move2 * 0.2)
* Quaternion::rotation_z(-0.35);
next.hand_r.orientation =
Quaternion::rotation_x(PI / 3.0 + move1 * 1.5)
* Quaternion::rotation_y(move1 * 0.7 + move2 * 0.2)
* Quaternion::rotation_z(0.35);
next.leg_l.position = Vec3::new(-s_a.leg.0, s_a.leg.1, s_a.leg.2);
next.leg_l.orientation =
Quaternion::rotation_z(0.0) * Quaternion::rotation_x(move1 * -0.8);
next.leg_r.position = Vec3::new(s_a.leg.0, s_a.leg.1, s_a.leg.2);
next.foot_l.position =
Vec3::new(-s_a.foot.0, s_a.foot.1 + move1 * -3.0, s_a.foot.2);
next.foot_r.position =
Vec3::new(s_a.foot.0, s_a.foot.1 + move1 * -3.0, s_a.foot.2);
next.leg_r.orientation =
Quaternion::rotation_z(0.0) * Quaternion::rotation_x(move1 * -0.8);
next.foot_l.orientation =
Quaternion::rotation_z(0.0) * Quaternion::rotation_x(move1 * 0.8);
next.foot_r.orientation =
Quaternion::rotation_z(0.0) * Quaternion::rotation_x(move1 * 0.8);
},
_ => {},
}
}
},
_ => {},
}

View File

@ -251,14 +251,14 @@ impl Animation for WieldAnimation {
next.control_r.orientation =
Quaternion::rotation_x(PI / 3.0) * Quaternion::rotation_y(0.15);
},
"Tidal Claws" => {
next.control_l.position = Vec3::new(-14.0, 12.0, -12.0);
next.control_r.position = Vec3::new(14.0, 12.0, -12.0);
"Tidal Warrior" => {
next.hand_l.position = Vec3::new(-14.0, 2.0, -4.0);
next.hand_r.position = Vec3::new(14.0, 2.0, -4.0);
next.control_l.orientation = Quaternion::rotation_x(PI / 3.0)
* Quaternion::rotation_y(-0.15);
next.control_r.orientation =
Quaternion::rotation_x(PI / 3.0) * Quaternion::rotation_y(0.15);
next.hand_l.orientation = Quaternion::rotation_x(PI / 3.0)
* Quaternion::rotation_z(-0.35);
next.hand_r.orientation =
Quaternion::rotation_x(PI / 3.0) * Quaternion::rotation_z(0.35);
},
"Beast Claws" => {
next.shoulder_l.position =

View File

@ -1,7 +1,7 @@
use super::*;
use crate::audio::sfx::SfxEvent;
use common::{
combat::DamageKind,
combat::{self, DamageKind},
comp::{
inventory::loadout_builder::LoadoutBuilder, item::tool::ToolKind, CharacterAbilityType,
CharacterState, InputKind, Item,
@ -76,7 +76,10 @@ fn maps_basic_melee() {
recover_duration: Duration::default(),
base_damage: 10.0,
base_poise_damage: 10.0,
knockback: 0.0,
knockback: combat::Knockback {
strength: 0.0,
direction: combat::KnockbackDir::Away,
},
range: 1.0,
max_angle: 1.0,
ability_info: empty_ability_info(),

View File

@ -403,7 +403,7 @@ impl SfxMgr {
audio.emit_sfx(sfx_trigger_item, *pos, None, false);
}
},
beam::FrontendSpecifier::ClayGolem => {},
beam::FrontendSpecifier::ClayGolem | beam::FrontendSpecifier::Bubbles => {},
},
Outcome::BreakBlock { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::BreakBlock);

View File

@ -756,6 +756,10 @@ fn insert_killing_buff(buff: BuffKind, localized_strings: &Localization, templat
tracing::error!("Player was killed by a positive buff!");
localized_strings.get("hud.outcome.mysterious")
},
BuffKind::Wet => {
tracing::error!("Player was killed by a debuff that doesn't do damage!");
localized_strings.get("hud.outcome.mysterious")
},
};
template.replace("{died_of_buff}", buff_outcome)

View File

@ -3777,6 +3777,8 @@ pub fn get_buff_image(buff: BuffKind, imgs: &Imgs) -> conrod_core::image::Id {
BuffKind::Burning { .. } => imgs.debuff_burning_0,
BuffKind::Crippled { .. } => imgs.debuff_crippled_0,
BuffKind::Frozen { .. } => imgs.debuff_frozen_0,
// TODO: Get icon for this before merging. Anyone doing code review open a comment here.
BuffKind::Wet { .. } => imgs.debuff_burning_0,
}
}
@ -3798,6 +3800,7 @@ pub fn get_buff_title(buff: BuffKind, localized_strings: &Localization) -> &str
BuffKind::Burning { .. } => localized_strings.get("buff.title.burn"),
BuffKind::Crippled { .. } => localized_strings.get("buff.title.crippled"),
BuffKind::Frozen { .. } => localized_strings.get("buff.title.frozen"),
BuffKind::Wet { .. } => localized_strings.get("buff.title.wet"),
}
}
@ -3831,6 +3834,7 @@ pub fn get_buff_desc(buff: BuffKind, data: BuffData, localized_strings: &Localiz
BuffKind::Burning { .. } => Cow::Borrowed(localized_strings.get("buff.desc.burn")),
BuffKind::Crippled { .. } => Cow::Borrowed(localized_strings.get("buff.desc.crippled")),
BuffKind::Frozen { .. } => Cow::Borrowed(localized_strings.get("buff.desc.frozen")),
BuffKind::Wet { .. } => Cow::Borrowed(localized_strings.get("buff.desc.wet")),
}
}

View File

@ -121,7 +121,8 @@ pub fn consumable_desc(effects: &[Effect], i18n: &Localization) -> String {
| BuffKind::ProtectingWard
| BuffKind::Crippled
| BuffKind::Frenzied
| BuffKind::Frozen => continue,
| BuffKind::Frozen
| BuffKind::Wet => continue,
};
write!(&mut description, "{}", buff_desc).unwrap();
@ -144,7 +145,8 @@ pub fn consumable_desc(effects: &[Effect], i18n: &Localization) -> String {
| BuffKind::ProtectingWard
| BuffKind::Crippled
| BuffKind::Frenzied
| BuffKind::Frozen => continue,
| BuffKind::Frozen
| BuffKind::Wet => continue,
}
} else if let BuffKind::Saturation | BuffKind::Regeneration = buff.kind {
i18n.get("buff.text.every_second").to_string()

View File

@ -79,6 +79,8 @@ pub enum ParticleMode {
Enraged = 26,
BigShrapnel = 27,
Laser = 28,
Bubbles = 29,
Water = 30,
}
impl ParticleMode {

View File

@ -3807,6 +3807,7 @@ impl FigureMgr {
time,
Some(s.stage_section),
state.acc_vel,
state.state_time,
),
stage_progress,
&mut state_animation_rate,
@ -4005,6 +4006,7 @@ impl FigureMgr {
time,
Some(s.stage_section),
state.acc_vel,
state.state_time,
),
stage_progress,
&mut state_animation_rate,
@ -4090,8 +4092,8 @@ impl FigureMgr {
anim::biped_large::SummonAnimation::update_skeleton(
&target_base,
(
active_tool_kind,
second_tool_kind,
(active_tool_kind, active_tool_spec),
(second_tool_kind, second_tool_spec),
rel_vel,
time,
Some(s.stage_section),
@ -4185,12 +4187,13 @@ impl FigureMgr {
anim::biped_large::BeamAnimation::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,
Some(s.stage_section),
state.acc_vel,
state.state_time,
),
stage_progress,
&mut state_animation_rate,

View File

@ -9,8 +9,8 @@ use crate::{
use common::{
assets::{AssetExt, DotVoxAsset},
comp::{
self, aura, beam, body, buff, item::Reagent, object, BeamSegment, Body, CharacterState,
Ori, Pos, Shockwave, Vel,
self, aura, beam, body, buff, item::Reagent, object, shockwave, BeamSegment, Body,
CharacterState, Ori, Pos, Shockwave, Vel,
},
figure::Segment,
outcome::Outcome,
@ -830,6 +830,31 @@ impl ParticleMgr {
)
})
},
beam::FrontendSpecifier::Bubbles => {
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) / 15,
|| {
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::Bubbles,
pos.0,
pos.0 + random_ori * range,
)
},
);
},
}
}
}
@ -1093,6 +1118,7 @@ impl ParticleMgr {
let state = scene_data.state;
let ecs = state.ecs();
let time = state.get_time();
let dt = scene_data.state.ecs().fetch::<DeltaTime>().0;
for (_entity, pos, ori, shockwave) in (
&ecs.entities(),
@ -1102,9 +1128,10 @@ impl ParticleMgr {
)
.join()
{
let elapsed = time - shockwave.creation.unwrap_or_default();
let elapsed = time - shockwave.creation.unwrap_or(time);
let speed = shockwave.properties.speed;
let distance = shockwave.properties.speed * elapsed as f32;
let distance = speed * elapsed as f32;
let radians = shockwave.properties.angle.to_radians();
@ -1112,55 +1139,99 @@ impl ParticleMgr {
let theta = ori_vec.y.atan2(ori_vec.x);
let dtheta = radians / distance;
let heartbeats = self.scheduler.heartbeats(Duration::from_millis(2));
// Number of particles derived from arc length (for new particles at least, old
// can be converted later)
let arc_length = distance * radians;
for heartbeat in 0..heartbeats {
if shockwave.properties.requires_ground {
// 1 / 3 the size of terrain voxel
let scale = 1.0 / 3.0;
use shockwave::FrontendSpecifier;
match shockwave.properties.specifier {
FrontendSpecifier::Ground => {
let heartbeats = self.scheduler.heartbeats(Duration::from_millis(2));
for heartbeat in 0..heartbeats {
// 1 / 3 the size of terrain voxel
let scale = 1.0 / 3.0;
let scaled_speed = shockwave.properties.speed * scale;
let scaled_speed = speed * scale;
let sub_tick_interpolation = scaled_speed * 1000.0 * heartbeat as f32;
let sub_tick_interpolation = scaled_speed * 1000.0 * heartbeat as f32;
let distance =
shockwave.properties.speed * (elapsed as f32 - sub_tick_interpolation);
let distance = speed * (elapsed as f32 - sub_tick_interpolation);
let particle_count_factor = radians / (3.0 * scale);
let new_particle_count = distance * particle_count_factor;
self.particles.reserve(new_particle_count as usize);
let particle_count_factor = radians / (3.0 * scale);
let new_particle_count = distance * particle_count_factor;
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;
for d in 0..(new_particle_count as i32) {
let arc_position =
theta - radians / 2.0 + dtheta * d as f32 / particle_count_factor;
let position = pos.0
+ distance * Vec3::new(arc_position.cos(), arc_position.sin(), 0.0);
let position = pos.0
+ distance * Vec3::new(arc_position.cos(), arc_position.sin(), 0.0);
let position_snapped = ((position / scale).floor() + 0.5) * scale;
let position_snapped = ((position / scale).floor() + 0.5) * scale;
self.particles.push(Particle::new(
Duration::from_millis(250),
time,
ParticleMode::GroundShockwave,
position_snapped,
));
self.particles.push(Particle::new(
Duration::from_millis(250),
time,
ParticleMode::GroundShockwave,
position_snapped,
));
}
}
} else {
for d in 0..3 * distance as i32 {
let arc_position = theta - radians / 2.0 + dtheta * d as f32 / 3.0;
},
FrontendSpecifier::Fire => {
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 position = pos.0
+ distance * Vec3::new(arc_position.cos(), arc_position.sin(), 0.0);
let position = pos.0
+ distance * Vec3::new(arc_position.cos(), arc_position.sin(), 0.0);
self.particles.push(Particle::new(
Duration::from_secs_f32((distance + 10.0) / 50.0),
time,
ParticleMode::FireShockwave,
position,
));
self.particles.push(Particle::new(
Duration::from_secs_f32((distance + 10.0) / 50.0),
time,
ParticleMode::FireShockwave,
position,
));
}
}
}
},
FrontendSpecifier::Water => {
// 4 particles 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
let heartbeats = self
.scheduler
.heartbeats(Duration::from_secs_f32(1.0 / speed));
// Reserves capacity for new particles
let new_particle_count = particles_per_length * heartbeats as usize;
self.particles.reserve(new_particle_count);
for i in 0..particles_per_length {
let angle = 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 distance = distance + speed * dt;
let pos1 = pos.0 + distance * direction - Vec3::unit_z();
let pos2 = pos1 + (Vec3::unit_z() + direction) * 3.0;
let time = time + dt as f64;
self.particles.push(Particle::new_directed(
Duration::from_secs_f32(0.5),
time,
ParticleMode::Water,
pos1,
pos2,
));
}
}
},
}
}
}