diff --git a/CHANGELOG.md b/CHANGELOG.md index b9459c0951..40a27cf9fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/assets/common/abilities/ability_set_manifest.ron b/assets/common/abilities/ability_set_manifest.ron index a136ffbd82..9f3b3f8b26 100644 --- a/assets/common/abilities/ability_set_manifest.ron +++ b/assets/common/abilities/ability_set_manifest.ron @@ -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"): ( diff --git a/assets/common/abilities/custom/beastclaws/basic.ron b/assets/common/abilities/custom/beastclaws/basic.ron index 2a0a24f044..52caa48f41 100644 --- a/assets/common/abilities/custom/beastclaws/basic.ron +++ b/assets/common/abilities/custom/beastclaws/basic.ron @@ -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, diff --git a/assets/common/abilities/custom/birdlargefire/fireshockwave.ron b/assets/common/abilities/custom/birdlargefire/fireshockwave.ron index c951d349f6..07637b0099 100644 --- a/assets/common/abilities/custom/birdlargefire/fireshockwave.ron +++ b/assets/common/abilities/custom/birdlargefire/fireshockwave.ron @@ -13,4 +13,5 @@ Shockwave( requires_ground: false, move_efficiency: 0.1, damage_kind: Energy, + specifier: Fire, ) diff --git a/assets/common/abilities/custom/claygolem/shockwave.ron b/assets/common/abilities/custom/claygolem/shockwave.ron index 61c4a77289..5a88d9f395 100644 --- a/assets/common/abilities/custom/claygolem/shockwave.ron +++ b/assets/common/abilities/custom/claygolem/shockwave.ron @@ -13,4 +13,5 @@ Shockwave( requires_ground: true, move_efficiency: 0.0, damage_kind: Crushing, + specifier: Ground, ) \ No newline at end of file diff --git a/assets/common/abilities/custom/claygolem/strike.ron b/assets/common/abilities/custom/claygolem/strike.ron index 5b412456c7..08e15584bc 100644 --- a/assets/common/abilities/custom/claygolem/strike.ron +++ b/assets/common/abilities/custom/claygolem/strike.ron @@ -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, diff --git a/assets/common/abilities/custom/minotaur/cripplingstrike.ron b/assets/common/abilities/custom/minotaur/cripplingstrike.ron index 6fce13daa8..d662f33a60 100644 --- a/assets/common/abilities/custom/minotaur/cripplingstrike.ron +++ b/assets/common/abilities/custom/minotaur/cripplingstrike.ron @@ -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(( diff --git a/assets/common/abilities/custom/quadmedhoof/basic.ron b/assets/common/abilities/custom/quadmedhoof/basic.ron index ce330235d1..a7608d6b22 100644 --- a/assets/common/abilities/custom/quadmedhoof/basic.ron +++ b/assets/common/abilities/custom/quadmedhoof/basic.ron @@ -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, diff --git a/assets/common/abilities/custom/stonegolemfist/shockwave.ron b/assets/common/abilities/custom/stonegolemfist/shockwave.ron index 5d6781e2b1..0d0ced9475 100644 --- a/assets/common/abilities/custom/stonegolemfist/shockwave.ron +++ b/assets/common/abilities/custom/stonegolemfist/shockwave.ron @@ -13,4 +13,5 @@ Shockwave( requires_ground: true, move_efficiency: 0.05, damage_kind: Crushing, + specifier: Ground, ) diff --git a/assets/common/abilities/custom/tidalwarrior/bubbles.ron b/assets/common/abilities/custom/tidalwarrior/bubbles.ron new file mode 100644 index 0000000000..e53f23bd9c --- /dev/null +++ b/assets/common/abilities/custom/tidalwarrior/bubbles.ron @@ -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, +) diff --git a/assets/common/abilities/custom/tidalwarrior/pincer.ron b/assets/common/abilities/custom/tidalwarrior/pincer.ron new file mode 100644 index 0000000000..64656e7f8a --- /dev/null +++ b/assets/common/abilities/custom/tidalwarrior/pincer.ron @@ -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, +) diff --git a/assets/common/abilities/custom/tidalwarrior/scuttle.ron b/assets/common/abilities/custom/tidalwarrior/scuttle.ron new file mode 100644 index 0000000000..979ce9c032 --- /dev/null +++ b/assets/common/abilities/custom/tidalwarrior/scuttle.ron @@ -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, +) diff --git a/assets/common/abilities/custom/tidalwarrior/totem.ron b/assets/common/abilities/custom/tidalwarrior/totem.ron new file mode 100644 index 0000000000..343f0e4c2d --- /dev/null +++ b/assets/common/abilities/custom/tidalwarrior/totem.ron @@ -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, + ), +) \ No newline at end of file diff --git a/assets/common/abilities/custom/tidalwarrior/totem_wave.ron b/assets/common/abilities/custom/tidalwarrior/totem_wave.ron new file mode 100644 index 0000000000..93f076461b --- /dev/null +++ b/assets/common/abilities/custom/tidalwarrior/totem_wave.ron @@ -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, +) diff --git a/assets/common/abilities/dagger/tempbasic.ron b/assets/common/abilities/dagger/tempbasic.ron index 5f345614fc..ff5600ae76 100644 --- a/assets/common/abilities/dagger/tempbasic.ron +++ b/assets/common/abilities/dagger/tempbasic.ron @@ -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, diff --git a/assets/common/abilities/empty/basic.ron b/assets/common/abilities/empty/basic.ron index 0732fe0d93..51b68d8fd1 100644 --- a/assets/common/abilities/empty/basic.ron +++ b/assets/common/abilities/empty/basic.ron @@ -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, diff --git a/assets/common/abilities/farming/basic.ron b/assets/common/abilities/farming/basic.ron index 559e9f7e22..b77fa80aeb 100644 --- a/assets/common/abilities/farming/basic.ron +++ b/assets/common/abilities/farming/basic.ron @@ -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, diff --git a/assets/common/abilities/pick/swing.ron b/assets/common/abilities/pick/swing.ron index c2ab7d9fcc..9e4a76fd5c 100644 --- a/assets/common/abilities/pick/swing.ron +++ b/assets/common/abilities/pick/swing.ron @@ -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, diff --git a/assets/common/abilities/shield/tempbasic.ron b/assets/common/abilities/shield/tempbasic.ron index c952ce5d5c..1a6150bb7b 100644 --- a/assets/common/abilities/shield/tempbasic.ron +++ b/assets/common/abilities/shield/tempbasic.ron @@ -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, diff --git a/assets/common/abilities/staff/fireshockwave.ron b/assets/common/abilities/staff/fireshockwave.ron index c951d349f6..07637b0099 100644 --- a/assets/common/abilities/staff/fireshockwave.ron +++ b/assets/common/abilities/staff/fireshockwave.ron @@ -13,4 +13,5 @@ Shockwave( requires_ground: false, move_efficiency: 0.1, damage_kind: Energy, + specifier: Fire, ) diff --git a/assets/common/items/npc_armor/biped_large/tidal_warrior.ron b/assets/common/items/npc_armor/biped_large/tidal_warrior.ron index 72e42d6cad..2e7980bbac 100644 --- a/assets/common/items/npc_armor/biped_large/tidal_warrior.ron +++ b/assets/common/items/npc_armor/biped_large/tidal_warrior.ron @@ -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), ), )), diff --git a/assets/common/items/npc_weapons/unique/tidal_claws.ron b/assets/common/items/npc_weapons/unique/tidal_claws.ron index ac0a93a417..0d2f505bd8 100644 --- a/assets/common/items/npc_weapons/unique/tidal_claws.ron +++ b/assets/common/items/npc_weapons/unique/tidal_claws.ron @@ -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")), ) \ No newline at end of file diff --git a/assets/common/items/npc_weapons/unique/tidal_totem.ron b/assets/common/items/npc_weapons/unique/tidal_totem.ron new file mode 100644 index 0000000000..06bd8017df --- /dev/null +++ b/assets/common/items/npc_weapons/unique/tidal_totem.ron @@ -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")), +) \ No newline at end of file diff --git a/assets/server/manifests/kits.ron b/assets/server/manifests/kits.ron index e3f3ac5bce..bc2bb504e5 100644 --- a/assets/server/manifests/kits.ron +++ b/assets/server/manifests/kits.ron @@ -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), diff --git a/assets/voxygen/element/de_buffs/debuff_wet_0.png b/assets/voxygen/element/de_buffs/debuff_wet_0.png new file mode 100644 index 0000000000..439b7b4476 --- /dev/null +++ b/assets/voxygen/element/de_buffs/debuff_wet_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f574db94ea127a30aae81f02d1ab1af8251ff35ce70fa47d745cb26e4c7c0882 +size 183 diff --git a/assets/voxygen/i18n/en/buff.ron b/assets/voxygen/i18n/en/buff.ron index 7755c4e09f..1109fc4120 100644 --- a/assets/voxygen/i18n/en/buff.ron +++ b/assets/voxygen/i18n/en/buff.ron @@ -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}", diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index 12be61b909..a74f714892 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -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( diff --git a/assets/voxygen/voxel/object/sea_lantern.vox b/assets/voxygen/voxel/object/sea_lantern.vox new file mode 100644 index 0000000000..c063e37e7e --- /dev/null +++ b/assets/voxygen/voxel/object/sea_lantern.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:392b22a007200459c8578c651a9b3aa0237e2103652e69d5129abf1edce79ec5 +size 2556 diff --git a/assets/voxygen/voxel/object_manifest.ron b/assets/voxygen/voxel/object_manifest.ron index fcea90705a..00f99e525c 100644 --- a/assets/voxygen/voxel/object_manifest.ron +++ b/assets/voxygen/voxel/object_manifest.ron @@ -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"), + ) + ), }) diff --git a/common/src/cmd.rs b/common/src/cmd.rs index eed06fca75..5ec88c379d 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -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);}); diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index e616a872ac..8ab75b8928 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -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, @@ -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, diff --git a/common/src/comp/beam.rs b/common/src/comp/beam.rs index ceb78c7ad9..50e056ad1b 100644 --- a/common/src/comp/beam.rs +++ b/common/src/comp/beam.rs @@ -53,4 +53,5 @@ pub enum FrontendSpecifier { HealingBeam, Cultist, ClayGolem, + Bubbles, } diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index 82b0d3d46f..1a95d6c6d6 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -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 { diff --git a/common/src/comp/body/object.rs b/common/src/comp/body/object.rs index 6f8eb252e6..cf509fdc1c 100644 --- a/common/src/comp/body/object.rs +++ b/common/src/comp/body/object.rs @@ -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 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), } } diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index eac4ce7b77..f76965898d 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -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, diff --git a/common/src/comp/inventory/loadout_builder.rs b/common/src/comp/inventory/loadout_builder.rs index c74193f1fb..909963088a 100644 --- a/common/src/comp/inventory/loadout_builder.rs +++ b/common/src/comp/inventory/loadout_builder.rs @@ -308,6 +308,9 @@ pub fn default_main_tool(body: &Body) -> Option { 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) { diff --git a/common/src/comp/shockwave.rs b/common/src/comp/shockwave.rs index 50c1fd1412..588960de72 100644 --- a/common/src/comp/shockwave.rs +++ b/common/src/comp/shockwave.rs @@ -13,6 +13,7 @@ pub struct Properties { pub requires_ground: bool, pub duration: Duration, pub owner: Option, + pub specifier: FrontendSpecifier, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -43,3 +44,10 @@ pub struct ShockwaveHitEntities { impl Component for ShockwaveHitEntities { type Storage = IdvStorage; } + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum FrontendSpecifier { + Ground, + Fire, + Water, +} diff --git a/common/src/comp/stats.rs b/common/src/comp/stats.rs index 263107e406..fd962d702b 100644 --- a/common/src/comp/stats.rs +++ b/common/src/comp/stats.rs @@ -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; } } diff --git a/common/src/states/basic_melee.rs b/common/src/states/basic_melee.rs index b581db58f7..fefffb3948 100644 --- a/common/src/states/basic_melee.rs +++ b/common/src/states/basic_melee.rs @@ -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)) diff --git a/common/src/states/combo_melee.rs b/common/src/states/combo_melee.rs index 49d5e7c1b0..a59315043a 100644 --- a/common/src/states/combo_melee.rs +++ b/common/src/states/combo_melee.rs @@ -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 { diff --git a/common/src/states/dash_melee.rs b/common/src/states/dash_melee.rs index 94ff0db463..bb916ce4f4 100644 --- a/common/src/states/dash_melee.rs +++ b/common/src/states/dash_melee.rs @@ -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 diff --git a/common/src/states/leap_melee.rs b/common/src/states/leap_melee.rs index b6b53f357f..59c6e2a49c 100644 --- a/common/src/states/leap_melee.rs +++ b/common/src/states/leap_melee.rs @@ -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 diff --git a/common/src/states/roll.rs b/common/src/states/roll.rs index e8fa9ffb69..f6d83ab4f0 100644 --- a/common/src/states/roll.rs +++ b/common/src/states/roll.rs @@ -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 diff --git a/common/src/states/shockwave.rs b/common/src/states/shockwave.rs index 0cdd474001..09278a3698 100644 --- a/common/src/states/shockwave.rs +++ b/common/src/states/shockwave.rs @@ -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, diff --git a/common/src/states/spin_melee.rs b/common/src/states/spin_melee.rs index f6afd23e1a..2caf344353 100644 --- a/common/src/states/spin_melee.rs +++ b/common/src/states/spin_melee.rs @@ -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 diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 109a6e6d92..b95550ed1e 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -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), diff --git a/common/systems/src/buff.rs b/common/systems/src/buff.rs index 28188a41df..a04d006053 100644 --- a/common/systems/src/buff.rs +++ b/common/systems/src/buff.rs @@ -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; + }, }; } } diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index 60d6270fb8..48c36e506d 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -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 + 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 + 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; } diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index 7f26303ef6..55e27466d6 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -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, diff --git a/voxygen/anim/src/biped_large/alpha.rs b/voxygen/anim/src/biped_large/alpha.rs index c5ac772d68..b752566a0c 100644 --- a/voxygen/anim/src/biped_large/alpha.rs +++ b/voxygen/anim/src/biped_large/alpha.rs @@ -19,6 +19,7 @@ impl Animation for AlphaAnimation { f32, Option, 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); diff --git a/voxygen/anim/src/biped_large/beam.rs b/voxygen/anim/src/biped_large/beam.rs index 75633a0454..9bf1828295 100644 --- a/voxygen/anim/src/biped_large/beam.rs +++ b/voxygen/anim/src/biped_large/beam.rs @@ -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, - Option, + (Option, Option<&'a AbilitySpec>), + (Option, Option<&'a AbilitySpec>), f32, Vec3, Option, 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); + }; + }, + _ => {}, + } + } + }, _ => {}, } diff --git a/voxygen/anim/src/biped_large/dash.rs b/voxygen/anim/src/biped_large/dash.rs index f969de6df9..a949fcca92 100644 --- a/voxygen/anim/src/biped_large/dash.rs +++ b/voxygen/anim/src/biped_large/dash.rs @@ -46,7 +46,7 @@ impl Animation for DashAnimation { let lab: f32 = 0.65 * s_a.tempo; let speed = Vec2::::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); + }, _ => {}, } } diff --git a/voxygen/anim/src/biped_large/run.rs b/voxygen/anim/src/biped_large/run.rs index b39791fd20..6bbd437d13 100644 --- a/voxygen/anim/src/biped_large/run.rs +++ b/voxygen/anim/src/biped_large/run.rs @@ -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; diff --git a/voxygen/anim/src/biped_large/stunned.rs b/voxygen/anim/src/biped_large/stunned.rs index f8f0ff1e53..7ca21c4246 100644 --- a/voxygen/anim/src/biped_large/stunned.rs +++ b/voxygen/anim/src/biped_large/stunned.rs @@ -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 = diff --git a/voxygen/anim/src/biped_large/summon.rs b/voxygen/anim/src/biped_large/summon.rs index 9fa16305b5..c3a5952dcd 100644 --- a/voxygen/anim/src/biped_large/summon.rs +++ b/voxygen/anim/src/biped_large/summon.rs @@ -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, - Option, + (Option, Option<&'a AbilitySpec>), + (Option, Option<&'a AbilitySpec>), Vec3, f32, Option, @@ -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); + }, + _ => {}, + } + } + }, _ => {}, } diff --git a/voxygen/anim/src/biped_large/wield.rs b/voxygen/anim/src/biped_large/wield.rs index 6a280feb08..a619e49791 100644 --- a/voxygen/anim/src/biped_large/wield.rs +++ b/voxygen/anim/src/biped_large/wield.rs @@ -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 = diff --git a/voxygen/src/audio/sfx/event_mapper/combat/tests.rs b/voxygen/src/audio/sfx/event_mapper/combat/tests.rs index bf50ae96f6..a024d559ea 100644 --- a/voxygen/src/audio/sfx/event_mapper/combat/tests.rs +++ b/voxygen/src/audio/sfx/event_mapper/combat/tests.rs @@ -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(), diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index 3a800eeb35..d5e8eda3af 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -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); diff --git a/voxygen/src/hud/chat.rs b/voxygen/src/hud/chat.rs index e4f6c8c072..9bcd04ce3a 100644 --- a/voxygen/src/hud/chat.rs +++ b/voxygen/src/hud/chat.rs @@ -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) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index e3a9cb36ef..abe05624e7 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -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")), } } diff --git a/voxygen/src/hud/util.rs b/voxygen/src/hud/util.rs index c2eddec4f3..9199993816 100644 --- a/voxygen/src/hud/util.rs +++ b/voxygen/src/hud/util.rs @@ -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() diff --git a/voxygen/src/render/pipelines/particle.rs b/voxygen/src/render/pipelines/particle.rs index 37832a7e7d..5f3944d607 100644 --- a/voxygen/src/render/pipelines/particle.rs +++ b/voxygen/src/render/pipelines/particle.rs @@ -79,6 +79,8 @@ pub enum ParticleMode { Enraged = 26, BigShrapnel = 27, Laser = 28, + Bubbles = 29, + Water = 30, } impl ParticleMode { diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index d5b55c3033..177ba476c6 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -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, diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 0f960d56a0..5b6ae849bc 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -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::::unit_z(), *ori.look_dir()); + let m = Mat3::::rotation_from_to_3d(from, to); + self.particles.resize_with( + self.particles.len() + usize::from(beam_tick_count) / 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::().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, + )); + } + } + }, } } }