diff --git a/assets/common/abilities/ability_set_manifest.ron b/assets/common/abilities/ability_set_manifest.ron index 80a5fb06ed..030c47f3a3 100644 --- a/assets/common/abilities/ability_set_manifest.ron +++ b/assets/common/abilities/ability_set_manifest.ron @@ -476,6 +476,24 @@ Simple(None, "common.abilities.custom.minotaur.frenzy"), ], ), + Custom("Dullahan"): ( + primary: Simple(None, "common.abilities.custom.dullahan.melee"), + secondary: Simple(None, "common.abilities.custom.dullahan.fierce_darts"), + abilities: [ + Simple(None, "common.abilities.custom.dullahan.knife_rain"), + Simple(None, "common.abilities.custom.dullahan.dash"), + ], + ), + Custom("Cyclops"): ( + primary: Simple(None, "common.abilities.custom.cyclops.doublestrike"), + secondary: Simple(None, "common.abilities.custom.cyclops.optic_blast"), + abilities: [ + Simple(None, "common.abilities.custom.cyclops.hammer_shockwave"), + Simple(None, "common.abilities.custom.cyclops.dash"), + Simple(None, "common.abilities.custom.cyclops.reinforce"), + + ], + ), Custom("Clay Golem"): ( primary: Simple(None, "common.abilities.custom.claygolem.strike"), secondary: Simple(None, "common.abilities.custom.claygolem.laser"), diff --git a/assets/common/abilities/custom/cyclops/dash.ron b/assets/common/abilities/custom/cyclops/dash.ron new file mode 100644 index 0000000000..95d3d8f853 --- /dev/null +++ b/assets/common/abilities/custom/cyclops/dash.ron @@ -0,0 +1,28 @@ +DashMelee( + energy_cost: 0, + melee_constructor: ( + kind: Bash( + damage: 28.0, + poise: 20.0, + knockback: 2.0, + energy_regen: 0.0, + ), + scaled: Some(Bash( + damage: 36.0, + poise: 60.0, + knockback: 5.0, + energy_regen: 0.0, + )), + range: 6.0, + angle: 90.0, + multi_target: Some(Normal), + ), + energy_drain: 0, + forward_speed: 9.0, + buildup_duration: 0.8, + charge_duration: 2.0, + swing_duration: 0.1, + recover_duration: 0.8, + ori_modifier: 0.1, + charge_through: false, +) diff --git a/assets/common/abilities/custom/cyclops/doublestrike.ron b/assets/common/abilities/custom/cyclops/doublestrike.ron new file mode 100644 index 0000000000..0fa7b32a60 --- /dev/null +++ b/assets/common/abilities/custom/cyclops/doublestrike.ron @@ -0,0 +1,49 @@ +ComboMelee( + stage_data: [ + ( + stage: 1, + base_damage: 32.0, + damage_increase: 0.0, + base_poise_damage: 20, + poise_damage_increase: 0.0, + knockback: 5.0, + range: 6, + angle: 90.0, + base_buildup_duration: 0.5, + base_swing_duration: 0.4, + hit_timing: 0.4, + base_recover_duration: 0.4, + forward_movement: 0.3, + damage_kind: Crushing, + ), + ( + stage: 2, + base_damage: 36.0, + damage_increase: 0.0, + base_poise_damage: 40.0, + poise_damage_increase: 0.0, + knockback: 10.0, + range: 8, + angle: 45.0, + base_buildup_duration: 0.6, + base_swing_duration: 0.6, + hit_timing: 0.3, + base_recover_duration: 1.2, + forward_movement: 0.2, + damage_kind: Crushing, + damage_effect: Some(Buff(( + kind: Crippled, + dur_secs: 3.0, + strength: DamageFraction(0.1), + chance: 1.0, + ))), + ), + ], + initial_energy_gain: 0, + max_energy_gain: 0, + energy_increase: 0, + speed_increase: 0.0, + max_speed_increase: 0.0, + scales_from_combo: 0, + ori_modifier: 0.65, +) diff --git a/assets/common/abilities/custom/cyclops/hammer_shockwave.ron b/assets/common/abilities/custom/cyclops/hammer_shockwave.ron new file mode 100644 index 0000000000..68492262ac --- /dev/null +++ b/assets/common/abilities/custom/cyclops/hammer_shockwave.ron @@ -0,0 +1,18 @@ +Shockwave( + energy_cost: 0, + buildup_duration: 1.2, + swing_duration: 0.12, + recover_duration: 0.8, + damage: 45.0, + poise_damage: 60, + knockback: (strength: 10.0, direction: TowardsUp), + shockwave_angle: 360.0, + shockwave_vertical_angle: 360.0, + shockwave_speed: 40.0, + shockwave_duration: 0.4, + requires_ground: true, + move_efficiency: 0.0, + damage_kind: Piercing, + specifier: Ground, + ori_rate: 1.0, +) diff --git a/assets/common/abilities/custom/cyclops/optic_blast.ron b/assets/common/abilities/custom/cyclops/optic_blast.ron new file mode 100644 index 0000000000..e49a8b0f8a --- /dev/null +++ b/assets/common/abilities/custom/cyclops/optic_blast.ron @@ -0,0 +1,16 @@ +BasicRanged( + energy_cost: 0.0, + buildup_duration: 1.6, + recover_duration: 1.2, + projectile: LaserBeam( + damage: 48.0, + radius: 8.0, + knockback: 5.0, + energy_regen: 20.0, + min_falloff: 0.0, + ), + projectile_body: Object(LaserBeam), + projectile_speed: 100.0, + num_projectiles: 1, + projectile_spread: 0, +) diff --git a/assets/common/abilities/custom/cyclops/reinforce.ron b/assets/common/abilities/custom/cyclops/reinforce.ron new file mode 100644 index 0000000000..110363697e --- /dev/null +++ b/assets/common/abilities/custom/cyclops/reinforce.ron @@ -0,0 +1,9 @@ +SelfBuff( + buildup_duration: 0.4, + cast_duration: 0.8, + recover_duration: 0.3, + buff_kind: ProtectingWard, + buff_strength: 2.0, + buff_duration: Some(300.0), + energy_cost: 0, +) \ No newline at end of file diff --git a/assets/common/abilities/custom/dullahan/dash.ron b/assets/common/abilities/custom/dullahan/dash.ron new file mode 100644 index 0000000000..fbbc225ef7 --- /dev/null +++ b/assets/common/abilities/custom/dullahan/dash.ron @@ -0,0 +1,28 @@ +DashMelee( + energy_cost: 0, + melee_constructor: ( + kind: Bash( + damage: 28.5, + poise: 30.0, + knockback: 2.0, + energy_regen: 0.0, + ), + scaled: Some(Bash( + damage: 36.0, + poise: 38.6, + knockback: 3.0, + energy_regen: 0.0, + )), + range: 5.0, + angle: 90.0, + multi_target: Some(Normal), + ), + energy_drain: 0, + forward_speed: 8.0, + buildup_duration: 0.6, + charge_duration: 2.0, + swing_duration: 0.1, + recover_duration: 1.2, + ori_modifier: 0.1, + charge_through: false, +) diff --git a/assets/common/abilities/custom/dullahan/fierce_darts.ron b/assets/common/abilities/custom/dullahan/fierce_darts.ron new file mode 100644 index 0000000000..64a9b1f906 --- /dev/null +++ b/assets/common/abilities/custom/dullahan/fierce_darts.ron @@ -0,0 +1,15 @@ +BasicRanged( + energy_cost: 0.0, + buildup_duration: 0.55, + recover_duration: 0.45, + projectile: Knife( + damage: 31.0, + knockback: 5.0, + energy_regen: 20.0, + min_falloff: 0.0, + ), + projectile_body: Object(SpectralSwordLarge), + projectile_speed: 120.0, + num_projectiles: 3, + projectile_spread: 0.075, +) diff --git a/assets/common/abilities/custom/dullahan/knife_rain.ron b/assets/common/abilities/custom/dullahan/knife_rain.ron new file mode 100644 index 0000000000..e1712cb2f3 --- /dev/null +++ b/assets/common/abilities/custom/dullahan/knife_rain.ron @@ -0,0 +1,16 @@ +BasicRanged( + energy_cost: 0.0, + buildup_duration: 1.1, + recover_duration: 0.2, + projectile: Knife( + damage: 34.0, + radius: 3.8, + knockback: 5.0, + energy_regen: 20.0, + min_falloff: 0.3, + ), + projectile_body: Object(SpectralSwordSmall), + projectile_speed: 20.0, + num_projectiles: 36, + projectile_spread: 0.4, +) \ No newline at end of file diff --git a/assets/common/abilities/custom/dullahan/melee.ron b/assets/common/abilities/custom/dullahan/melee.ron new file mode 100644 index 0000000000..c5e30bfc0e --- /dev/null +++ b/assets/common/abilities/custom/dullahan/melee.ron @@ -0,0 +1,55 @@ +ComboMelee( + stage_data: [ + ( + stage: 1, + base_damage: 35.5, + damage_increase: 0.0, + base_poise_damage: 15.0, + poise_damage_increase: 0.0, + knockback: 2.0, + range: 6.0, + angle: 60.0, + base_buildup_duration: 0.8, + base_swing_duration: 0.1, + hit_timing: 0.4, + base_recover_duration: 0.3, + forward_movement: 0.8, + damage_kind: Slashing, + damage_effect: Some(Buff(( + kind: Bleeding, + dur_secs: 3.0, + strength: DamageFraction(0.05), + chance: 0.3, + ))), + ), + ( + stage: 2, + base_damage: 38.5, + damage_increase: 0.0, + base_poise_damage: 20.0, + poise_damage_increase: 0.0, + knockback: 8.0, + range: 6.0, + angle: 60.0, + base_buildup_duration: 0.7, + base_swing_duration: 0.1, + hit_timing: 0.4, + base_recover_duration: 1.3, + forward_movement: 0.2, + damage_kind: Slashing, + damage_effect: Some(Buff(( + kind: Bleeding, + dur_secs: 3.0, + strength: DamageFraction(0.1), + chance: 0.15, + ))), + ), + ], + initial_energy_gain: 0, + max_energy_gain: 0, + energy_increase: 0, + speed_increase: 0.0, + max_speed_increase: 0.0, + scales_from_combo: 0, + ori_modifier: 0.6, +) diff --git a/assets/common/entity/dungeon/tier-4/miniboss.ron b/assets/common/entity/dungeon/tier-4/miniboss.ron index 50a3e08684..b17bb8aef1 100644 --- a/assets/common/entity/dungeon/tier-4/miniboss.ron +++ b/assets/common/entity/dungeon/tier-4/miniboss.ron @@ -1,7 +1,7 @@ #![enable(implicit_some)] ( - name: Name("Dullahan"), - body: RandomWith("dullahan"), + name: Automatic, + body: RandomWith("cyclops"), alignment: Alignment(Enemy), loot: LootTable("common.loot_tables.dungeon.tier-4.miniboss"), inventory: ( diff --git a/assets/common/entity/wild/aggressive/cyclops.ron b/assets/common/entity/wild/aggressive/dullahan.ron similarity index 75% rename from assets/common/entity/wild/aggressive/cyclops.ron rename to assets/common/entity/wild/aggressive/dullahan.ron index b5da54369f..23b6e2380d 100644 --- a/assets/common/entity/wild/aggressive/cyclops.ron +++ b/assets/common/entity/wild/aggressive/dullahan.ron @@ -1,7 +1,7 @@ #![enable(implicit_some)] ( - name: Automatic, - body: RandomWith("cyclops"), + name: Name("Dullahan"), + body: RandomWith("dullahan"), alignment: Alignment(Enemy), loot: LootTable("common.loot_tables.creature.biped_large.default"), inventory: ( diff --git a/assets/common/entity/wild/peaceful/mammoth.ron b/assets/common/entity/wild/peaceful/mammoth.ron index f1242dac20..d168f69c33 100644 --- a/assets/common/entity/wild/peaceful/mammoth.ron +++ b/assets/common/entity/wild/peaceful/mammoth.ron @@ -2,7 +2,7 @@ ( name: Automatic, body: RandomWith("mammoth"), - alignment: Alignment(Enemy), + alignment: Alignment(Wild), loot: LootTable("common.loot_tables.creature.quad_medium.mammoth"), inventory: ( loadout: FromBody, diff --git a/assets/common/items/npc_armor/biped_large/cyclops.ron b/assets/common/items/npc_armor/biped_large/cyclops.ron new file mode 100644 index 0000000000..ac1c0d1ca5 --- /dev/null +++ b/assets/common/items/npc_armor/biped_large/cyclops.ron @@ -0,0 +1,13 @@ +ItemDef( + name: "Cyclops Armor", + description: "Made of mysteries.", + kind: Armor(( + kind: Chest, + stats: Direct(( + protection: Some(Normal(120.0)), + poise_resilience: Some(Normal(60.0)), + )), + )), + quality: Moderate, + tags: [], +) diff --git a/assets/common/items/npc_armor/biped_large/dullahan.ron b/assets/common/items/npc_armor/biped_large/dullahan.ron new file mode 100644 index 0000000000..8cbe6e432b --- /dev/null +++ b/assets/common/items/npc_armor/biped_large/dullahan.ron @@ -0,0 +1,13 @@ +ItemDef( + name: "Dullahan Itself Armor", + description: "Made of It ownself.", + kind: Armor(( + kind: Chest, + stats: Direct(( + protection: Some(Normal(200.0)), + poise_resilience: Some(Normal(10.0)), + )), + )), + quality: Moderate, + tags: [], +) diff --git a/assets/common/items/npc_weapons/hammer/cyclops_hammer.ron b/assets/common/items/npc_weapons/hammer/cyclops_hammer.ron index ce9a3546ec..abf78a8bd9 100644 --- a/assets/common/items/npc_weapons/hammer/cyclops_hammer.ron +++ b/assets/common/items/npc_weapons/hammer/cyclops_hammer.ron @@ -17,5 +17,5 @@ ItemDef( )), quality: Low, tags: [], - ability_spec: Some(Custom("Hammer Simple")), + ability_spec: Some(Custom("Cyclops")), ) \ No newline at end of file diff --git a/assets/common/items/npc_weapons/sword/dullahan_sword.ron b/assets/common/items/npc_weapons/sword/dullahan_sword.ron index ccf989ca91..bd9b05a051 100644 --- a/assets/common/items/npc_weapons/sword/dullahan_sword.ron +++ b/assets/common/items/npc_weapons/sword/dullahan_sword.ron @@ -5,11 +5,11 @@ ItemDef( kind: Sword, hands: Two, stats: ( - equip_time_secs: 0.5, - power: 1.5, + equip_time_secs: 0.01, + power: 1.0, effect_power: 1.0, - speed: 0.75, - crit_chance: 0.0625, + speed: 1.0, + crit_chance: 0.0645, range: 1.0, energy_efficiency: 1.0, buff_strength: 1.0, @@ -17,5 +17,5 @@ ItemDef( )), quality: Low, tags: [], - ability_spec: Some(Custom("Sword Simple")), + ability_spec: Some(Custom("Dullahan")), ) \ No newline at end of file diff --git a/assets/voxygen/audio/sfx.ron b/assets/voxygen/audio/sfx.ron index c938ae4389..53e064e111 100644 --- a/assets/voxygen/audio/sfx.ron +++ b/assets/voxygen/audio/sfx.ron @@ -1244,6 +1244,18 @@ ], threshold: 0.2, ), + LaserBeam: ( + files: [ + "voxygen.audio.sfx.abilities.laser_beam", + ], + threshold: 1.25, + ), + CyclopsCharge: ( + files: [ + "voxygen.audio.sfx.abilities.cyclops_charge", + ], + threshold: 0.3, + ), GigaRoar: ( files: [ "voxygen.audio.sfx.abilities.gigas_frost_roar", diff --git a/assets/voxygen/audio/sfx/abilities/cyclops_charge.ogg b/assets/voxygen/audio/sfx/abilities/cyclops_charge.ogg new file mode 100644 index 0000000000..fd1f9a9e30 --- /dev/null +++ b/assets/voxygen/audio/sfx/abilities/cyclops_charge.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8acebb54a599590e712062ae74ee08ae8d6e3a73e17f9c7f3bb9b1673897810e +size 9987 diff --git a/assets/voxygen/audio/sfx/abilities/laser_beam.ogg b/assets/voxygen/audio/sfx/abilities/laser_beam.ogg new file mode 100644 index 0000000000..4af434f12f --- /dev/null +++ b/assets/voxygen/audio/sfx/abilities/laser_beam.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a4c8b0855c0c54d9a086b368786255bc1e5e519963e3e06c208a8505c63b5af +size 11634 diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index 1dd6faacb8..5bb05aa7b3 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -80,6 +80,7 @@ const int STEAM = 39; const int BARRELORGAN = 40; const int POTION_SICKNESS = 41; const int GIGA_SNOW = 42; +const int CYCLOPS_CHARGE = 43; // meters per second squared (acceleration) const float earth_gravity = 9.807; @@ -666,6 +667,16 @@ void main() { spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9) ); break; + case CYCLOPS_CHARGE: + f_reflect = 0.0; + float burn_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(burn_size), + vec4(vec3(6.9, 0.0, 0.0), 1), + spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9) + ); + break; default: attr = Attr( linear_motion( diff --git a/assets/voxygen/voxel/object_manifest.ron b/assets/voxygen/voxel/object_manifest.ron index 0907af4b76..c21739a238 100644 --- a/assets/voxygen/voxel/object_manifest.ron +++ b/assets/voxygen/voxel/object_manifest.ron @@ -889,4 +889,34 @@ central: ("armor.empty"), ) ), + SpectralSwordSmall: ( + bone0: ( + offset: (-0.5, -25.0, -8.5), + central: ("weapon.projectile.spectral_sword_small"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), + ) + ), + SpectralSwordLarge: ( + bone0: ( + offset: (-0.5, -30.0, -8.5), + central: ("weapon.projectile.spectral_sword_large"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), + ) + ), + LaserBeam: ( + bone0: ( + offset: (-6.0, -60.0, -17.0), + central: ("weapon.projectile.laser_beam"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), + ) + ), }) diff --git a/assets/voxygen/voxel/weapon/projectile/laser_beam.vox b/assets/voxygen/voxel/weapon/projectile/laser_beam.vox new file mode 100644 index 0000000000..b750dd43a7 --- /dev/null +++ b/assets/voxygen/voxel/weapon/projectile/laser_beam.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f211e806a5d40db89821b12081c823a5733a56c872a370a933267a7cf1710ed +size 50488 diff --git a/assets/voxygen/voxel/weapon/projectile/spectral_sword_large.vox b/assets/voxygen/voxel/weapon/projectile/spectral_sword_large.vox new file mode 100644 index 0000000000..34597b268c --- /dev/null +++ b/assets/voxygen/voxel/weapon/projectile/spectral_sword_large.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc38d49dc77afe339caf871fef8780b6638c145a3cc5dee53423ee0b695a2677 +size 2284 diff --git a/assets/voxygen/voxel/weapon/projectile/spectral_sword_small.vox b/assets/voxygen/voxel/weapon/projectile/spectral_sword_small.vox new file mode 100644 index 0000000000..c21f368f41 --- /dev/null +++ b/assets/voxygen/voxel/weapon/projectile/spectral_sword_small.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f29a75ca391fae872a5fad1858a2efba0774c19e6a3c9002715dc7811f735318 +size 1908 diff --git a/assets/world/wildlife/spawn/temperate/rare.ron b/assets/world/wildlife/spawn/temperate/rare.ron index e1bd3da4ed..076e7ea827 100644 --- a/assets/world/wildlife/spawn/temperate/rare.ron +++ b/assets/world/wildlife/spawn/temperate/rare.ron @@ -6,7 +6,7 @@ SpawnEntry ( groups: [ (1, (1, 1, "common.entity.wild.aggressive.ogre")), (1, (1, 1, "common.entity.wild.aggressive.swamp_troll")), - (1, (1, 1, "common.entity.wild.aggressive.cyclops")), + (1, (1, 1, "common.entity.wild.aggressive.dullahan")), ], spawn_mode: Land, day_period: [Night, Morning, Noon, Evening], diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index de07e6e590..c61081864b 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -783,12 +783,12 @@ impl Body { Body::FishSmall(_) => 3, Body::BipedLarge(biped_large) => match biped_large.species { biped_large::Species::Ogre => 320, - biped_large::Species::Cyclops => 320, + biped_large::Species::Cyclops => 1000, biped_large::Species::Wendigo => 280, biped_large::Species::Cavetroll => 240, biped_large::Species::Mountaintroll => 240, biped_large::Species::Swamptroll => 240, - biped_large::Species::Dullahan => 700, + biped_large::Species::Dullahan => 600, biped_large::Species::Mindflayer => 1250, biped_large::Species::Tidalwarrior => 1600, biped_large::Species::Yeti => 1200, @@ -895,10 +895,17 @@ impl Body { ), Body::BipedLarge(b) => matches!( b.species, - biped_large::Species::Huskbrute | biped_large::Species::Gigasfrost + biped_large::Species::Huskbrute + | biped_large::Species::Gigasfrost + | biped_large::Species::Dullahan ), _ => false, }, + BuffKind::Crippled => match self { + Body::Object(_) | Body::Golem(_) | Body::Ship(_) => true, + Body::BipedLarge(b) => matches!(b.species, biped_large::Species::Dullahan), + _ => false, + }, BuffKind::Burning => match self { Body::Golem(g) => matches!(g.species, golem::Species::ClayGolem), Body::BipedSmall(b) => matches!(b.species, biped_small::Species::Haniwa), @@ -915,6 +922,7 @@ impl Body { | bird_large::Species::WealdWyvern ), Body::Arthropod(b) => matches!(b.species, arthropod::Species::Moltencrawler), + Body::BipedLarge(b) => matches!(b.species, biped_large::Species::Cyclops), _ => false, }, BuffKind::Ensnared => match self { diff --git a/common/src/comp/body/object.rs b/common/src/comp/body/object.rs index f34cd39782..f0983ee475 100644 --- a/common/src/comp/body/object.rs +++ b/common/src/comp/body/object.rs @@ -101,6 +101,9 @@ make_case_elim!( DagonBomb = 86, BarrelOrgan = 87, IceBomb = 88, + SpectralSwordSmall = 89, + SpectralSwordLarge = 90, + LaserBeam = 91, } ); @@ -111,7 +114,7 @@ impl Body { } } -pub const ALL_OBJECTS: [Body; 89] = [ +pub const ALL_OBJECTS: [Body; 92] = [ Body::Arrow, Body::Bomb, Body::Scarecrow, @@ -124,6 +127,8 @@ pub const ALL_OBJECTS: [Body; 89] = [ Body::ChestLight, Body::ChestOpen, Body::ChestSkull, + Body::SpectralSwordSmall, + Body::SpectralSwordLarge, Body::Pumpkin, Body::Pumpkin2, Body::Pumpkin3, @@ -201,6 +206,7 @@ pub const ALL_OBJECTS: [Body; 89] = [ Body::DagonBomb, Body::BarrelOrgan, Body::IceBomb, + Body::LaserBeam, ]; impl From for super::Body { @@ -299,6 +305,9 @@ impl Body { Body::DagonBomb => "dagon_bomb", Body::BarrelOrgan => "barrel_organ", Body::IceBomb => "ice_bomb", + Body::SpectralSwordSmall => "spectral_sword_small", + Body::SpectralSwordLarge => "spectral_sword_large", + Body::LaserBeam => "laser_beam", } } @@ -321,7 +330,9 @@ impl Body { | Body::ArrowTurret | Body::MultiArrow | Body::Dart - | Body::DagonBomb => 500.0, + | Body::DagonBomb + | Body::SpectralSwordSmall + | Body::SpectralSwordLarge => 500.0, Body::Bomb => 2000.0, // I have no idea what it's supposed to be Body::Crate => 300.0, // let's say it's a lot of wood and maybe some contents Body::Scarecrow => 900.0, @@ -341,6 +352,8 @@ impl Body { Body::Arrow | Body::ArrowSnake | Body::ArrowTurret | Body::MultiArrow | Body::Dart => { 0.003 }, + Body::SpectralSwordSmall => 0.5, + Body::SpectralSwordLarge => 50.0, Body::BedBlue => 50.0, Body::Bedroll => 3.0, Body::Bench => 100.0, @@ -413,6 +426,7 @@ impl Body { Body::Coconut => 2.0, Body::GnarlingTotemRed | Body::GnarlingTotemGreen | Body::GnarlingTotemWhite => 100.0, Body::IceBomb => 12298.0, // 2.5 m diamter but ice + Body::LaserBeam => 80000.0, }; Mass(m) @@ -424,6 +438,8 @@ impl Body { Vec3::new(0.01, 0.8, 0.01) }, Body::BoltFire => Vec3::new(0.1, 0.1, 0.1), + Body::SpectralSwordSmall => Vec3::new(0.2, 0.9, 0.1), + Body::SpectralSwordLarge => Vec3::new(0.2, 1.5, 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.8, 0.8, 1.4), @@ -435,6 +451,7 @@ impl Body { }, Body::BarrelOrgan => Vec3::new(4.0, 2.0, 3.0), Body::IceBomb => Vec3::broadcast(2.5), + Body::LaserBeam => Vec3::new(8.0, 8.0, 8.0), // FIXME: this *must* be exhaustive match _ => Vec3::broadcast(0.5), } diff --git a/common/src/comp/inventory/loadout_builder.rs b/common/src/comp/inventory/loadout_builder.rs index 7909afada2..4fce8c76fc 100644 --- a/common/src/comp/inventory/loadout_builder.rs +++ b/common/src/comp/inventory/loadout_builder.rs @@ -910,13 +910,16 @@ impl LoadoutBuilder { Some("common.items.npc_armor.biped_large.harvester") }, biped_large::Species::Ogre - | biped_large::Species::Cyclops | biped_large::Species::Blueoni | biped_large::Species::Redoni | biped_large::Species::Cavetroll | biped_large::Species::Wendigo => { Some("common.items.npc_armor.biped_large.generic") }, + biped_large::Species::Cyclops => Some("common.items.npc_armor.biped_large.cyclops"), + biped_large::Species::Dullahan => { + Some("common.items.npc_armor.biped_large.dullahan") + }, biped_large::Species::Cultistwarlord => { Some("common.items.npc_armor.biped_large.warlord") }, diff --git a/common/src/comp/projectile.rs b/common/src/comp/projectile.rs index 27d8c1aba9..5dd38e7160 100644 --- a/common/src/comp/projectile.rs +++ b/common/src/comp/projectile.rs @@ -53,6 +53,11 @@ pub enum ProjectileConstructor { knockback: f32, energy_regen: f32, }, + Knife { + damage: f32, + knockback: f32, + energy_regen: f32, + }, Fireball { damage: f32, radius: f32, @@ -114,6 +119,12 @@ pub enum ProjectileConstructor { knockback: f32, min_falloff: f32, }, + LaserBeam { + damage: f32, + radius: f32, + knockback: f32, + min_falloff: f32, + }, } impl ProjectileConstructor { @@ -181,6 +192,59 @@ impl ProjectileConstructor { is_point: true, } }, + Knife { + damage, + knockback, + energy_regen, + } => { + let knockback = AttackEffect::new( + Some(GroupTarget::OutOfGroup), + CombatEffect::Knockback(Knockback { + strength: knockback, + direction: KnockbackDir::Away, + }) + .adjusted_by_stats(tool_stats), + ) + .with_requirement(CombatRequirement::AnyDamage); + let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen)) + .with_requirement(CombatRequirement::AnyDamage); + let buff = CombatEffect::Buff(CombatBuff { + kind: BuffKind::Bleeding, + dur_secs: 10.0, + strength: CombatBuffStrength::DamageFraction(0.1), + chance: 0.1, + }) + .adjusted_by_stats(tool_stats); + let mut damage = AttackDamage::new( + Damage { + source: DamageSource::Projectile, + kind: DamageKind::Piercing, + value: damage, + }, + Some(GroupTarget::OutOfGroup), + instance, + ) + .with_effect(buff); + if let Some(damage_effect) = damage_effect { + damage = damage.with_effect(damage_effect); + } + let attack = Attack::default() + .with_damage(damage) + .with_crit(crit_chance, crit_mult) + .with_effect(energy) + .with_effect(knockback) + .with_combo_increment(); + + Projectile { + hit_solid: vec![Effect::Stick, Effect::Bonk], + hit_entity: vec![Effect::Attack(attack), Effect::Vanish], + time_left: Duration::from_secs(15), + owner, + ignore_group: true, + is_sticky: true, + is_point: true, + } + }, Fireball { damage, radius, @@ -679,6 +743,53 @@ impl ProjectileConstructor { is_point: true, } }, + LaserBeam { + damage, + radius, + knockback, + min_falloff, + } => { + let knockback = AttackEffect::new( + Some(GroupTarget::OutOfGroup), + CombatEffect::Knockback(Knockback { + strength: knockback, + direction: KnockbackDir::Away, + }) + .adjusted_by_stats(tool_stats), + ) + .with_requirement(CombatRequirement::AnyDamage); + let damage = AttackDamage::new( + Damage { + source: DamageSource::Explosion, + kind: DamageKind::Energy, + value: damage, + }, + Some(GroupTarget::OutOfGroup), + instance, + ); + let attack = Attack::default() + .with_damage(damage) + .with_crit(crit_chance, crit_mult) + .with_effect(knockback); + let explosion = Explosion { + effects: vec![ + RadiusEffect::Attack(attack), + RadiusEffect::TerrainDestruction(10.0, Rgb::black()), + ], + radius, + reagent: Some(Reagent::Yellow), + min_falloff, + }; + Projectile { + hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish], + hit_entity: vec![Effect::Explode(explosion), Effect::Vanish], + time_left: Duration::from_secs(10), + owner, + ignore_group: true, + is_sticky: true, + is_point: true, + } + }, } } @@ -695,6 +806,14 @@ impl ProjectileConstructor { *damage *= power; *energy_regen *= regen; }, + Knife { + ref mut damage, + ref mut energy_regen, + .. + } => { + *damage *= power; + *energy_regen *= regen; + }, Fireball { ref mut damage, ref mut energy_regen, @@ -786,6 +905,14 @@ impl ProjectileConstructor { *damage *= power; *radius *= range; }, + LaserBeam { + ref mut damage, + ref mut radius, + .. + } => { + *damage *= power; + *radius *= range; + }, } self } @@ -794,6 +921,7 @@ impl ProjectileConstructor { use ProjectileConstructor::*; match self { Arrow { .. } => false, + Knife { .. } => false, Fireball { .. } => true, Frostball { .. } => true, Poisonball { .. } => true, @@ -806,6 +934,7 @@ impl ProjectileConstructor { SeaBomb { .. } => true, WindBomb { .. } => true, IceBomb { .. } => true, + LaserBeam { .. } => true, } } } diff --git a/common/src/outcome.rs b/common/src/outcome.rs index 68dd3e0b66..10a58150de 100644 --- a/common/src/outcome.rs +++ b/common/src/outcome.rs @@ -97,6 +97,12 @@ pub enum Outcome { FlashFreeze { pos: Vec3, }, + LaserBeam { + pos: Vec3, + }, + CyclopsCharge { + pos: Vec3, + }, Utterance { pos: Vec3, body: comp::Body, @@ -138,6 +144,8 @@ impl Outcome { | Outcome::IceCrack { pos } | Outcome::Utterance { pos, .. } | Outcome::SpriteDelete { pos, .. } + | Outcome::CyclopsCharge { pos } + | Outcome::LaserBeam { pos } | Outcome::Glider { pos, .. } => Some(*pos), Outcome::BreakBlock { pos, .. } | Outcome::SpriteUnlocked { pos } diff --git a/common/src/states/basic_ranged.rs b/common/src/states/basic_ranged.rs index f03e7810f2..d981543c1f 100644 --- a/common/src/states/basic_ranged.rs +++ b/common/src/states/basic_ranged.rs @@ -1,10 +1,11 @@ use crate::{ combat::CombatEffect, comp::{ - character_state::OutputEvents, Body, CharacterState, LightEmitter, Pos, - ProjectileConstructor, StateUpdate, + character_state::OutputEvents, object::Body::LaserBeam, Body, CharacterState, LightEmitter, + Pos, ProjectileConstructor, StateUpdate, }, - event::ServerEvent, + event::{LocalEvent, ServerEvent}, + outcome::Outcome, states::{ behavior::{CharacterBehavior, JoinData}, utils::*, @@ -66,6 +67,14 @@ impl CharacterBehavior for Data { timer: tick_attack_or_default(data, self.timer, None), ..*self }); + if self.static_data.projectile_body == Body::Object(LaserBeam) { + // Send local event used for frontend shenanigans + output_events.emit_local(LocalEvent::CreateOutcome( + Outcome::CyclopsCharge { + pos: data.pos.0 + *data.ori.look_dir() * (data.body.max_radius()), + }, + )); + } } else { // Transitions to recover section of stage update.character = CharacterState::BasicRanged(Data { diff --git a/common/src/states/shockwave.rs b/common/src/states/shockwave.rs index b4f3c0fa03..288d908487 100644 --- a/common/src/states/shockwave.rs +++ b/common/src/states/shockwave.rs @@ -143,10 +143,24 @@ impl CharacterBehavior for Data { ..*self }); // Send local event used for frontend shenanigans - if self.static_data.specifier == shockwave::FrontendSpecifier::IceSpikes { - output_events.emit_local(LocalEvent::CreateOutcome(Outcome::FlashFreeze { - pos: data.pos.0 + *data.ori.look_dir() * (data.body.max_radius()), - })); + match self.static_data.specifier { + shockwave::FrontendSpecifier::IceSpikes => { + output_events.emit_local(LocalEvent::CreateOutcome( + Outcome::FlashFreeze { + pos: data.pos.0 + + *data.ori.look_dir() * (data.body.max_radius()), + }, + )); + }, + shockwave::FrontendSpecifier::Ground => { + output_events.emit_local(LocalEvent::CreateOutcome( + Outcome::GroundSlam { + pos: data.pos.0 + + *data.ori.look_dir() * (data.body.max_radius()), + }, + )); + }, + _ => {}, } } else { // Transitions to recover diff --git a/server/agent/src/action_nodes.rs b/server/agent/src/action_nodes.rs index a5e8c66e7b..af815f59ad 100644 --- a/server/agent/src/action_nodes.rs +++ b/server/agent/src/action_nodes.rs @@ -951,6 +951,8 @@ impl<'a> AgentData<'a> { "Bird Medium Basic" => Tactic::BirdMediumBasic, "Mindflayer" => Tactic::Mindflayer, "Minotaur" => Tactic::Minotaur, + "Cyclops" => Tactic::Cyclops, + "Dullahan" => Tactic::Dullahan, "Clay Golem" => Tactic::ClayGolem, "Tidal Warrior" => Tactic::TidalWarrior, "Tidal Totem" @@ -1338,6 +1340,12 @@ impl<'a> AgentData<'a> { Tactic::Minotaur => { self.handle_minotaur_attack(agent, controller, &attack_data, tgt_data, read_data) }, + Tactic::Cyclops => { + self.handle_cyclops_attack(agent, controller, &attack_data, tgt_data, read_data) + }, + Tactic::Dullahan => { + self.handle_dullahan_attack(agent, controller, &attack_data, tgt_data, read_data) + }, Tactic::ClayGolem => { self.handle_clay_golem_attack(agent, controller, &attack_data, tgt_data, read_data) }, diff --git a/server/agent/src/attack.rs b/server/agent/src/attack.rs index 85e4a16010..fd8dcec868 100644 --- a/server/agent/src/attack.rs +++ b/server/agent/src/attack.rs @@ -3222,6 +3222,159 @@ impl<'a> AgentData<'a> { ); } + pub fn handle_cyclops_attack( + &self, + agent: &mut Agent, + controller: &mut Controller, + attack_data: &AttackData, + tgt_data: &TargetData, + read_data: &ReadData, + ) { + // Primary + const CYCLOPS_MELEE_RANGE: f32 = 9.0; + // Secondary + const CYCLOPS_FIRE_RANGE: f32 = 30.0; + // Ability(1) + const CYCLOPS_CHARGE_RANGE: f32 = 18.0; + // Ability(0) - Ablity (2) + const SHOCKWAVE_THRESHOLD: f32 = 0.6; + + enum FCounters { + ShockwaveThreshold = 0, + } + enum Timers { + AttackChange = 0, + } + + if agent.action_state.timers[Timers::AttackChange as usize] > 2.5 { + agent.action_state.timers[Timers::AttackChange as usize] = 0.0; + } + + 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 initialized + if !agent.action_state.initialized { + agent.action_state.counters[FCounters::ShockwaveThreshold as usize] = + 1.0 - SHOCKWAVE_THRESHOLD; + agent.action_state.initialized = true; + } else if health_fraction + < agent.action_state.counters[FCounters::ShockwaveThreshold as usize] + { + // Scream when threshold is reached + controller.push_basic_input(InputKind::Ability(2)); + + if matches!(self.char_state, CharacterState::SelfBuff(c) if matches!(c.stage_section, StageSection::Recover)) + { + agent.action_state.counters[FCounters::ShockwaveThreshold as usize] -= + SHOCKWAVE_THRESHOLD; + } + } else if matches!(self.char_state, CharacterState::DashMelee(c) if !matches!(c.stage_section, StageSection::Recover)) + { + // If already AOEing, keep AOEing if not in recover + controller.push_basic_input(InputKind::Ability(0)); + } else if attack_data.dist_sqrd > CYCLOPS_FIRE_RANGE.powi(2) { + // Chase + controller.push_basic_input(InputKind::Ability(1)); + } else if attack_data.dist_sqrd > CYCLOPS_CHARGE_RANGE.powi(2) { + // Shoot after target if they attempt to "flee" + controller.push_basic_input(InputKind::Secondary); + } else if attack_data.dist_sqrd < CYCLOPS_MELEE_RANGE { + if attack_data.angle < 60.0 { + // Melee target if close enough and within angle + controller.push_basic_input(InputKind::Primary); + } else if attack_data.angle > 60.0 { + // Scream if target exceeds angle but is close enough + controller.push_basic_input(InputKind::Ability(0)); + } + } + + // Always attempt to path towards target + self.path_toward_target( + agent, + controller, + tgt_data.pos.0, + read_data, + Path::Partial, + None, + ); + } + + pub fn handle_dullahan_attack( + &self, + agent: &mut Agent, + controller: &mut Controller, + attack_data: &AttackData, + tgt_data: &TargetData, + read_data: &ReadData, + ) { + // Primary (12 Default / Melee) + const MELEE_RANGE: f32 = 9.0; + // Secondary (30 Default / Range) + const LONG_RANGE: f32 = 30.0; + // Ability(0) (0.1 aka 10% Default / AOE) + const HP_THRESHOLD: f32 = 0.1; + // Ability(1) (18 Default / Dash) + const MID_RANGE: f32 = 18.0; + + enum FCounters { + HealthThreshold = 0, + } + enum Timers { + AttackChange = 0, + } + if agent.action_state.timers[Timers::AttackChange as usize] > 2.5 { + agent.action_state.timers[Timers::AttackChange as usize] = 0.0; + } + + 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 initialized + if !agent.action_state.initialized { + agent.action_state.counters[FCounters::HealthThreshold as usize] = 1.0 - HP_THRESHOLD; + agent.action_state.initialized = true; + } else if health_fraction < agent.action_state.counters[FCounters::HealthThreshold as usize] + { + // InputKind when threshold is reached (Default is Ability(0)) + controller.push_basic_input(InputKind::Ability(0)); + + if matches!( + self.char_state.ability_info().map(|ai| ai.input), + Some(InputKind::Ability(0)) + ) && matches!(self.char_state.stage_section(), Some(StageSection::Recover)) + { + agent.action_state.counters[FCounters::HealthThreshold as usize] -= HP_THRESHOLD; + } + } else if matches!(self.char_state, CharacterState::DashMelee(c) if !matches!(c.stage_section, StageSection::Recover)) + { + // If already InputKind, keep InputKind if not in recover (Default is Shockwave) + controller.push_basic_input(InputKind::Ability(0)); + } else if attack_data.dist_sqrd > LONG_RANGE.powi(2) { + // InputKind after target if they attempt to "flee" (>LONG) + controller.push_basic_input(InputKind::Ability(1)); + } else if attack_data.dist_sqrd > MID_RANGE.powi(2) { + // InputKind after target if they attempt to "flee" (MID-LONG) + controller.push_basic_input(InputKind::Secondary); + } else if attack_data.dist_sqrd < MELEE_RANGE { + if attack_data.angle < 60.0 { + // InputKind target if close enough and within angle ( 60.0 { + // InputKind if target exceeds angle but is close enough (FLANK/STRAFE) + controller.push_basic_input(InputKind::Ability(0)); + } + } + + // Always attempt to path towards target + self.path_toward_target( + agent, + controller, + tgt_data.pos.0, + read_data, + Path::Full, + None, + ); + } + pub fn handle_clay_golem_attack( &self, agent: &mut Agent, diff --git a/server/agent/src/data.rs b/server/agent/src/data.rs index 6ca3b9c9ce..6db84f7092 100644 --- a/server/agent/src/data.rs +++ b/server/agent/src/data.rs @@ -169,6 +169,8 @@ pub enum Tactic { Roshwalr, FrostGigas, BorealHammer, + Dullahan, + Cyclops, } #[derive(Copy, Clone)] diff --git a/voxygen/anim/src/biped_large/dash.rs b/voxygen/anim/src/biped_large/dash.rs index 21ab4a8a9f..167b0dcace 100644 --- a/voxygen/anim/src/biped_large/dash.rs +++ b/voxygen/anim/src/biped_large/dash.rs @@ -214,6 +214,40 @@ impl Animation for DashAnimation { }, _ => {}, }, + Some(ToolKind::Hammer) => match ability_id { + Some("common.abilities.custom.cyclops.dash") => { + next.control_l.position = Vec3::new(-1.0, 2.0, 12.0 + move3 * 3.0); + next.control_r.position = Vec3::new(1.0, 2.0, -2.0); + + next.control.position = Vec3::new( + 4.0 + move1 * -3.0 + move3 * -5.0, + (s_a.grip.0 / 1.0) + move1 * -1.0 + move3 * 1.0 + footrotl * 2.0, + (-s_a.grip.0 / 0.8) + move1 * 2.0 + move3 * -3.0, + ); + next.head.orientation = Quaternion::rotation_x(move1 * -0.5 + move3 * 0.5) + * Quaternion::rotation_z(move1 * 0.3 + move3 * 0.3); + next.upper_torso.orientation = + Quaternion::rotation_x(move1 * -0.4 + move3 * 0.9) + * Quaternion::rotation_z(move1 * 0.6 + move3 * -1.5); + next.lower_torso.orientation = + Quaternion::rotation_y(move1 * -0.2 + move3 * -0.1) + * Quaternion::rotation_x(move1 * 0.4 + move3 * -0.7 + footrotr * 0.1) + * Quaternion::rotation_z(move1 * -0.6 + move3 * 1.6); + + next.control_l.orientation = Quaternion::rotation_x(PI / 2.0 + move3 * 0.3) + * Quaternion::rotation_y(move1 * 0.7); + next.control_r.orientation = + Quaternion::rotation_x(PI / 2.0 + 0.2 + move3 * -0.2) + * Quaternion::rotation_y(0.0) + * Quaternion::rotation_z(0.0); + + next.control.orientation = + Quaternion::rotation_x(-1.0 + move1 * -0.2 + move3 * -0.2) + * Quaternion::rotation_y(-1.8 + move1 * -0.2 + move3 * -0.2) + * Quaternion::rotation_z(move1 * -0.8 + move3 * -0.1); + }, + _ => {}, + }, _ => {}, } diff --git a/voxygen/anim/src/biped_large/selfbuff.rs b/voxygen/anim/src/biped_large/selfbuff.rs index 67fb8f3b5b..b4f0f861c5 100644 --- a/voxygen/anim/src/biped_large/selfbuff.rs +++ b/voxygen/anim/src/biped_large/selfbuff.rs @@ -102,7 +102,7 @@ impl Animation for SelfBuffAnimation { // TODO: Remove clippy allow when second species is added #[allow(clippy::single_match)] match active_tool_kind { - Some(ToolKind::Axe) => { + Some(ToolKind::Axe | ToolKind::Hammer) => { next.control_l.position = Vec3::new(-1.0, 2.0, 12.0); next.control_r.position = Vec3::new(1.0, 2.0, -2.0); diff --git a/voxygen/anim/src/biped_large/shockwave.rs b/voxygen/anim/src/biped_large/shockwave.rs index 4dbda8d98d..953c170620 100644 --- a/voxygen/anim/src/biped_large/shockwave.rs +++ b/voxygen/anim/src/biped_large/shockwave.rs @@ -250,6 +250,68 @@ impl Animation for ShockwaveAnimation { }, _ => {}, }, + Some(ToolKind::Hammer) => match ability_id { + Some("common.abilities.custom.cyclops.hammer_shockwave") => { + next.second.scale = Vec3::one() * 0.0; + + next.head.orientation = Quaternion::rotation_x(move1pow * 0.8 + move2 * -1.2); + next.jaw.position = Vec3::new(0.0, s_a.jaw.0, s_a.jaw.1); + next.jaw.orientation = Quaternion::rotation_x(move2 * -0.3); + next.control_l.position = Vec3::new(-0.5, 4.0, 1.0); + next.control_r.position = Vec3::new(-0.5, 4.0, 1.0); + next.control_l.orientation = Quaternion::rotation_x(PI / 2.0); + next.control_r.orientation = Quaternion::rotation_x(PI / 2.0); + next.weapon_l.position = + Vec3::new(-12.0 + (move1pow * 20.0).min(10.0), -1.0, -15.0); + next.weapon_r.position = + Vec3::new(12.0 + (move1pow * -20.0).max(-10.0), -1.0, -15.0); + + next.weapon_l.orientation = Quaternion::rotation_x(-PI / 2.0 - 0.1) + * Quaternion::rotation_z(move1pow * -1.0); + next.weapon_r.orientation = Quaternion::rotation_x(-PI / 2.0 - 0.1) + * Quaternion::rotation_z(move1pow * 1.0); + + next.shoulder_l.orientation = + Quaternion::rotation_x(-0.3 + move1pow * 2.8 + move2 * -2.8); + + next.shoulder_r.orientation = + Quaternion::rotation_x(-0.3 + move1pow * 2.8 + move2 * -2.8); + + next.control.orientation = + Quaternion::rotation_x(move1pow * 2.5 + move2 * -2.0); + + let twist = move1 * 0.6 + move3 * -0.6; + next.upper_torso.position = + Vec3::new(0.0, s_a.upper_torso.0, s_a.upper_torso.1); + next.upper_torso.orientation = + Quaternion::rotation_x(move1pow * 0.8 + move2 * -1.1) + * Quaternion::rotation_z(twist * -0.2 + move1 * -0.1 + move2 * 0.3); + + next.lower_torso.orientation = + Quaternion::rotation_x(move1pow * -0.8 + move2 * 1.1) + * Quaternion::rotation_z(twist); + + next.foot_l.position = Vec3::new( + -s_a.foot.0, + s_a.foot.1 + move1pow * -7.0 + move2 * 7.0, + s_a.foot.2, + ); + next.foot_l.orientation = Quaternion::rotation_x(move1pow * -0.8 + move2 * 0.8) + * Quaternion::rotation_z(move1pow * 0.3 + move2 * -0.3); + + next.foot_r.position = Vec3::new( + s_a.foot.0, + s_a.foot.1 + move1pow * 5.0 + move2 * -5.0, + s_a.foot.2, + ); + next.foot_r.orientation = Quaternion::rotation_y(move1pow * -0.3 + move2 * 0.3) + * Quaternion::rotation_z(move1pow * 0.4 + move2 * -0.4); + + next.main.orientation = Quaternion::rotation_y(move1 * 0.4 + move2 * -0.6) + * Quaternion::rotation_x(move2 * -0.4); + }, + _ => {}, + }, _ => {}, } next diff --git a/voxygen/anim/src/biped_large/shoot.rs b/voxygen/anim/src/biped_large/shoot.rs index 6ee550f540..f78dc4ac9f 100644 --- a/voxygen/anim/src/biped_large/shoot.rs +++ b/voxygen/anim/src/biped_large/shoot.rs @@ -91,6 +91,79 @@ impl Animation for ShootAnimation { next.hand_r.orientation = Quaternion::rotation_x(0.0); match active_tool_kind { + Some(ToolKind::Sword) => match ability_id { + Some("common.abilities.custom.dullahan.knife_rain") + | Some("common.abilities.custom.dullahan.fierce_darts") => { + let (move1base, move2base, move3) = match stage_section { + Some(StageSection::Buildup) => (anim_time.powf(0.25), 0.0, 0.0), + Some(StageSection::Action) => (1.0, anim_time, 0.0), + Some(StageSection::Recover) => (1.0, 1.0, anim_time.powi(4)), + _ => (0.0, 0.0, 0.0), + }; + let pullback = 1.0 - move3; + let move1 = move1base * pullback; + let move2 = move2base * pullback; + next.main.position = Vec3::new(-10.0, -8.0, 12.0); + next.main.orientation = + Quaternion::rotation_y(2.5) * Quaternion::rotation_z(PI / 2.0); + next.hand_l.position = Vec3::new(-s_a.hand.0, s_a.hand.1 + 4.0, s_a.hand.2); + next.hand_r.position = Vec3::new(s_a.hand.0, s_a.hand.1 + 4.0, s_a.hand.2); + next.hand_l.orientation = Quaternion::rotation_x(move1 * 1.5) + * Quaternion::rotation_y(move1 * -1.0 + move2 * 1.5); + next.hand_r.orientation = Quaternion::rotation_x(move1 * 1.5) + * Quaternion::rotation_y(move1 * 1.0 + move2 * -1.5); + next.upper_torso.orientation = + Quaternion::rotation_y(move1 * -0.1 + move2 * 0.1) + * Quaternion::rotation_z(move1 * -0.1 + move2 * 0.1); + next.foot_l.orientation = Quaternion::rotation_y(move1 * 0.3 + move2 * -0.3); + next.foot_r.orientation = Quaternion::rotation_y(move1 * 0.3 + move2 * -0.3); + }, + _ => {}, + }, + Some(ToolKind::Hammer) => match ability_id { + Some("common.abilities.custom.cyclops.optic_blast") => { + let (move1base, _move1shake, move2base, move3) = match stage_section { + Some(StageSection::Buildup) => { + (anim_time, (anim_time * 10.0 + PI).sin(), 0.0, 0.0) + }, + Some(StageSection::Action) => (1.0, 1.0, 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; + let move2 = move2base * pullback; + next.head.orientation = Quaternion::rotation_x(move1 * 0.25 + move2 * -0.25) + * Quaternion::rotation_z(move1 * 0.25); + next.torso.orientation = Quaternion::rotation_x(move1 * -0.25 + move2 * 0.25); + next.upper_torso.orientation = + Quaternion::rotation_x(move1 * -0.1 + move2 * 0.1) + * Quaternion::rotation_z(move1 * -0.1 + move2 * 0.1); + next.foot_l.orientation = Quaternion::rotation_x(move1 * 0.3 + move2 * -0.3); + next.foot_r.orientation = Quaternion::rotation_x(move1 * 0.3 + move2 * -0.3); + next.main.position = Vec3::new(0.0, -10.0, 3.0); + next.main.orientation = Quaternion::rotation_x(PI / -2.0); + next.weapon_l.position = Vec3::new( + -s_a.hand.0 - 3.0 * move1, + s_a.hand.1 + 4.0 + 8.0 * move1, + -15.0 + 2.0 * move1, + ); + next.weapon_l.orientation = Quaternion::rotation_x(move1 * 0.6); + next.hand_r.position = Vec3::new( + s_a.hand.0 + 6.0 * move1, + s_a.hand.1 + 4.0, + s_a.hand.2 + 6.0 * move1, + ); + next.hand_r.orientation = + Quaternion::rotation_x(move1 * 1.0) * Quaternion::rotation_y(move1 * -1.4); + + next.shoulder_l.orientation = + Quaternion::rotation_x(move1 * 0.6) * Quaternion::rotation_y(move1 * 0.5); + next.shoulder_r.orientation = + Quaternion::rotation_x(move1 * 1.4) * Quaternion::rotation_y(move1 * -0.5); + }, + _ => {}, + }, Some(ToolKind::Staff) | Some(ToolKind::Sceptre) => { let (move1base, move1shake, move2base, move3) = match stage_section { Some(StageSection::Buildup) => { diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index 639861f53a..4827624d34 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -171,6 +171,8 @@ pub enum SfxEvent { IceCrack, Utterance(UtteranceKind, VoiceKind), Lightning, + CyclopsCharge, + LaserBeam, Music(ToolKind, AbilitySpec), } @@ -430,6 +432,14 @@ impl SfxMgr { let sfx_trigger_item = triggers.get_key_value(&SfxEvent::GroundSlam); audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater); }, + Outcome::LaserBeam { pos, .. } => { + let sfx_trigger_item = triggers.get_key_value(&SfxEvent::LaserBeam); + audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater); + }, + Outcome::CyclopsCharge { pos, .. } => { + let sfx_trigger_item = triggers.get_key_value(&SfxEvent::CyclopsCharge); + audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater); + }, Outcome::FlashFreeze { pos, .. } => { let sfx_trigger_item = triggers.get_key_value(&SfxEvent::FlashFreeze); audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater); @@ -461,7 +471,9 @@ impl SfxMgr { object::Body::Arrow | object::Body::MultiArrow | object::Body::ArrowSnake - | object::Body::ArrowTurret, + | object::Body::ArrowTurret + | object::Body::SpectralSwordSmall + | object::Body::SpectralSwordLarge, ) => { let sfx_trigger_item = triggers.get_key_value(&SfxEvent::ArrowShot); audio.emit_sfx(sfx_trigger_item, *pos, None, underwater); @@ -474,6 +486,10 @@ impl SfxMgr { let sfx_trigger_item = triggers.get_key_value(&SfxEvent::FireShot); audio.emit_sfx(sfx_trigger_item, *pos, None, underwater); }, + Body::Object(object::Body::LaserBeam) => { + let sfx_trigger_item = triggers.get_key_value(&SfxEvent::LaserBeam); + audio.emit_sfx(sfx_trigger_item, *pos, None, underwater); + }, _ => { // not mapped to sfx file }, @@ -490,7 +506,9 @@ impl SfxMgr { object::Body::Arrow | object::Body::MultiArrow | object::Body::ArrowSnake - | object::Body::ArrowTurret, + | object::Body::ArrowTurret + | object::Body::SpectralSwordSmall + | object::Body::SpectralSwordLarge, ) => { if target.is_none() { let sfx_trigger_item = triggers.get_key_value(&SfxEvent::ArrowMiss); diff --git a/voxygen/src/render/pipelines/particle.rs b/voxygen/src/render/pipelines/particle.rs index c0134532df..cb0accb69d 100644 --- a/voxygen/src/render/pipelines/particle.rs +++ b/voxygen/src/render/pipelines/particle.rs @@ -94,7 +94,8 @@ pub enum ParticleMode { BarrelOrgan = 40, PotionSickness = 41, GigaSnow = 42, - SnowStorm = 43, + CyclopsCharge = 43, + SnowStorm = 44, } impl ParticleMode { diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 83d3a530d2..a3980978c1 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -358,6 +358,15 @@ impl ParticleMgr { }, ); }, + Outcome::CyclopsCharge { pos } => { + self.particles.push(Particle::new_directed( + Duration::from_secs_f32(rng.gen_range(0.1..0.2)), + time, + ParticleMode::CyclopsCharge, + *pos + Vec3::new(0.0, 0.0, 5.3), + *pos + Vec3::new(0.0, 0.0, 5.6 + 0.5 * rng.gen_range(0.0..0.2)), + )); + }, Outcome::Death { pos, .. } => { self.particles.resize_with(self.particles.len() + 40, || { Particle::new( @@ -381,7 +390,8 @@ impl ParticleMgr { | Outcome::Utterance { .. } | Outcome::IceSpikes { .. } | Outcome::IceCrack { .. } - | Outcome::Glider { .. } => {}, + | Outcome::Glider { .. } + | Outcome::LaserBeam { .. } => {}, } }