diff --git a/assets/common/abilities/ability_set_manifest.ron b/assets/common/abilities/ability_set_manifest.ron index 5f2fbf0812..062240bbc0 100644 --- a/assets/common/abilities/ability_set_manifest.ron +++ b/assets/common/abilities/ability_set_manifest.ron @@ -716,10 +716,14 @@ ], ), Custom("Bird Large Fire"): ( - primary: Simple(None, "common.abilities.custom.birdlargefire.firebomb"), - secondary: Simple(None, "common.abilities.custom.birdlargefire.triplestrike"), + primary: Simple(None, "common.abilities.custom.birdlargefire.longstrike"), + secondary: Simple(None, "common.abilities.custom.birdlargefire.shortstrike"), abilities: [ - Simple(None, "common.abilities.custom.birdlargefire.fireshockwave"), + Simple(None, "common.abilities.custom.birdlargefire.legstrike"), + Simple(None, "common.abilities.custom.birdlargefire.summontornadoes"), + Simple(None, "common.abilities.custom.birdlargefire.firerain"), + Simple(None, "common.abilities.custom.birdlargefire.heat_laser"), + Simple(None, "common.abilities.custom.birdlargefire.from_the_ashes"), ], ), Custom("Flame Wyvern"): ( @@ -807,6 +811,11 @@ secondary: Simple(None, "common.abilities.empty.basic"), abilities: [], ), + Custom("FieryTornado"): ( + primary: Simple(None, "common.abilities.custom.fiery_tornado.fiery_spin"), + secondary: Simple(None, "common.abilities.custom.fiery_tornado.fiery_aura"), + abilities: [], + ), Custom("Golf Club"): ( primary: Simple(None, "common.abilities.hammer.singlestrike"), secondary: Simple(None, "common.abilities.tool.golf_club.charged"), diff --git a/assets/common/abilities/custom/birdlargefire/firebomb.ron b/assets/common/abilities/custom/birdlargefire/firebomb.ron deleted file mode 100644 index cb309e83ec..0000000000 --- a/assets/common/abilities/custom/birdlargefire/firebomb.ron +++ /dev/null @@ -1,20 +0,0 @@ -BasicRanged( - energy_cost: 0, - buildup_duration: 1.0, - recover_duration: 0.7, - projectile: Fireball( - damage: 20.0, - radius: 5.0, - energy_regen: 5.0, - min_falloff: 0.5, - ), - projectile_body: Object(BoltFire), - /*projectile_light: Some(LightEmitter { - col: (1.0, 0.75, 0.11).into(), - ..Default::default() - }),*/ - projectile_speed: 60.0, - num_projectiles: 1, - projectile_spread: 0.0, - move_efficiency: 0.3, -) diff --git a/assets/common/abilities/custom/birdlargefire/firerain.ron b/assets/common/abilities/custom/birdlargefire/firerain.ron new file mode 100644 index 0000000000..081d5a79d5 --- /dev/null +++ b/assets/common/abilities/custom/birdlargefire/firerain.ron @@ -0,0 +1,32 @@ +RepeaterRanged( + energy_cost: 0.0, + buildup_duration: 0.2, + shoot_duration: 0.3, + recover_duration: 0.5, + max_speed: 8.0, + half_speed_at: 5, + projectile: FireDroplet( + damage: 30.0, + radius: 12.0, + min_falloff: 0.5, + energy_regen: 0, + reagent: Some(Phoenix), + ), + projectile_body: Object(FireRainDrop), + projectile_light: Some(LightEmitter( + col: Rgb( + r: 1.0, + g: 0.8, + b: 0.3, + ), + strength: 10.0, + flicker: 5.0, + animated: true, + )), + projectile_speed: 0.0, + properties_of_aoe: Some(ProjectileOffset( + radius: 30, + height: 20, + )), + specifier: Some(FireRain), +) \ No newline at end of file diff --git a/assets/common/abilities/custom/birdlargefire/flamethrower.ron b/assets/common/abilities/custom/birdlargefire/flamethrower.ron deleted file mode 100644 index 50c9cec231..0000000000 --- a/assets/common/abilities/custom/birdlargefire/flamethrower.ron +++ /dev/null @@ -1,19 +0,0 @@ -BasicBeam( - buildup_duration: 0.8, - recover_duration: 0.5, - beam_duration: 0.5, - damage: 10.0, - tick_rate: 3.0, - range: 15.0, - max_angle: 22.5, - damage_effect: Some(Buff(( - kind: Burning, - dur_secs: 10.0, - strength: DamageFraction(0.5), - chance: 0.25, - ))), - energy_regen: 0, - energy_drain: 0, - ori_rate: 0.3, - specifier: Flamethrower, -) diff --git a/assets/common/abilities/custom/birdlargefire/from_the_ashes.ron b/assets/common/abilities/custom/birdlargefire/from_the_ashes.ron new file mode 100644 index 0000000000..758a7818ab --- /dev/null +++ b/assets/common/abilities/custom/birdlargefire/from_the_ashes.ron @@ -0,0 +1,10 @@ +SelfBuff( + buildup_duration: 1.25, + cast_duration: 1.8, + recover_duration: 1.25, + buff_kind: Regeneration, + buff_strength: 1500.0, + buff_duration: Some(3.0), + energy_cost: 0, + specifier: Some(FromTheAshes), +) diff --git a/assets/common/abilities/custom/birdlargefire/heat_laser.ron b/assets/common/abilities/custom/birdlargefire/heat_laser.ron new file mode 100644 index 0000000000..a509626242 --- /dev/null +++ b/assets/common/abilities/custom/birdlargefire/heat_laser.ron @@ -0,0 +1,19 @@ +BasicBeam( + buildup_duration: 0.5, + recover_duration: 0.3, + beam_duration: 1.0, + damage: 30.0, + tick_rate: 1.0, + range: 120.0, + max_angle: 1.0, + damage_effect: Some(Buff(( + kind: Burning, + dur_secs: 5.0, + strength: DamageFraction(0.75), + chance: 0.75, + ))), + energy_regen: 0, + energy_drain: 0, + ori_rate: 0.07, + specifier: PhoenixLaser, +) \ No newline at end of file diff --git a/assets/common/abilities/custom/birdlargefire/legstrike.ron b/assets/common/abilities/custom/birdlargefire/legstrike.ron new file mode 100644 index 0000000000..0fbbdd3a51 --- /dev/null +++ b/assets/common/abilities/custom/birdlargefire/legstrike.ron @@ -0,0 +1,26 @@ +ComboMelee2( + strikes: [ + ( + melee_constructor: ( + kind: Slash( + damage: 35, + poise: 0, + knockback: 15, + energy_regen: 0, + ), + range: 6.0, + angle: 90.0, + ), + buildup_duration: 0.1, + swing_duration: 0.1, + hit_timing: 0.5, + recover_duration: 0.1, + movement: ( + swing: Some(Forward(0.5)), + ), + ori_modifier: 0.7, + ), + ], + energy_cost_per_strike: 0, + auto_progress: false, +) \ No newline at end of file diff --git a/assets/common/abilities/custom/birdlargefire/longstrike.ron b/assets/common/abilities/custom/birdlargefire/longstrike.ron new file mode 100644 index 0000000000..59cb7f63d4 --- /dev/null +++ b/assets/common/abilities/custom/birdlargefire/longstrike.ron @@ -0,0 +1,26 @@ +ComboMelee2( + strikes: [ + ( + melee_constructor: ( + kind: Slash( + damage: 30, + poise: 0, + knockback: 10, + energy_regen: 0, + ), + range: 6.0, + angle: 90.0, + ), + buildup_duration: 0.6, + swing_duration: 0.3, + hit_timing: 0.5, + recover_duration: 0.1, + movement: ( + swing: Some(Forward(0.5)), + ), + ori_modifier: 0.7, + ), + ], + energy_cost_per_strike: 0, + auto_progress: true, +) \ No newline at end of file diff --git a/assets/common/abilities/custom/birdlargefire/shortstrike.ron b/assets/common/abilities/custom/birdlargefire/shortstrike.ron new file mode 100644 index 0000000000..8e91e422c9 --- /dev/null +++ b/assets/common/abilities/custom/birdlargefire/shortstrike.ron @@ -0,0 +1,26 @@ +ComboMelee2( + strikes: [ + ( + melee_constructor: ( + kind: Slash( + damage: 20, + poise: 0, + knockback: 5, + energy_regen: 0, + ), + range: 6.0, + angle: 90.0, + ), + buildup_duration: 0.2, + swing_duration: 0.1, + hit_timing: 0.5, + recover_duration: 0.1, + movement: ( + swing: Some(Forward(0.5)), + ), + ori_modifier: 0.7, + ), + ], + energy_cost_per_strike: 0, + auto_progress: true, +) \ No newline at end of file diff --git a/assets/common/abilities/custom/birdlargefire/summontornadoes.ron b/assets/common/abilities/custom/birdlargefire/summontornadoes.ron new file mode 100644 index 0000000000..4bdc91d7a4 --- /dev/null +++ b/assets/common/abilities/custom/birdlargefire/summontornadoes.ron @@ -0,0 +1,18 @@ +BasicSummon( + buildup_duration: 0.75, + cast_duration: 0.25, + recover_duration: 0.5, + summon_amount: 12, + summon_distance: (8, 10), + summon_info: ( + body: Object(FieryTornado), + scale: None, + has_health: false, + loadout_config: None, + skillset_config: None, + ), + duration: Some(( + secs: 10, + nanos: 0, + )), +) \ No newline at end of file diff --git a/assets/common/abilities/custom/dagon/steamheal.ron b/assets/common/abilities/custom/dagon/steamheal.ron index efdec29b4f..d0e80bc563 100644 --- a/assets/common/abilities/custom/dagon/steamheal.ron +++ b/assets/common/abilities/custom/dagon/steamheal.ron @@ -11,7 +11,7 @@ BasicAura( category: Magical, ), ], - aura_duration: 2.0, + aura_duration: Some(2.0), range: 10.0, energy_cost: 0.0, scales_with_combo: false, diff --git a/assets/common/abilities/custom/dwarves/hermit_alligator/heal.ron b/assets/common/abilities/custom/dwarves/hermit_alligator/heal.ron index ba5b8b74ee..e4038fffde 100644 --- a/assets/common/abilities/custom/dwarves/hermit_alligator/heal.ron +++ b/assets/common/abilities/custom/dwarves/hermit_alligator/heal.ron @@ -11,7 +11,7 @@ BasicAura( category: Magical, ), ], - aura_duration: 2.0, + aura_duration: Some(2.0), range: 10.0, energy_cost: 0.0, scales_with_combo: false, diff --git a/assets/common/abilities/custom/fiery_tornado/fiery_aura.ron b/assets/common/abilities/custom/fiery_tornado/fiery_aura.ron new file mode 100644 index 0000000000..95d68df375 --- /dev/null +++ b/assets/common/abilities/custom/fiery_tornado/fiery_aura.ron @@ -0,0 +1,19 @@ +BasicAura( + buildup_duration: 0.0, + cast_duration: 0.0, + recover_duration: 0.0, + targets: OutOfGroup, + auras: [ + ( + kind: Heatstroke, + strength: 1.0, + duration: Some(5.0), + category: Magical, + ), + ], + aura_duration: None, + range: 20.0, + energy_cost: 0.0, + scales_with_combo: false, + specifier: Some(FieryAura), +) diff --git a/assets/common/abilities/custom/fiery_tornado/fiery_spin.ron b/assets/common/abilities/custom/fiery_tornado/fiery_spin.ron new file mode 100644 index 0000000000..af79474a99 --- /dev/null +++ b/assets/common/abilities/custom/fiery_tornado/fiery_spin.ron @@ -0,0 +1,25 @@ +RapidMelee( + buildup_duration: 0.0, + swing_duration: 0.5, + recover_duration: 0.0, + melee_constructor: ( + kind: Slash( + damage: 80, + poise: 0, + knockback: 50, + energy_regen: 0, + ), + range: 3.5, + angle: 360, + multi_target: Some(Normal), + damage_effect: Some(Buff(( + kind: Burning, + dur_secs: 8.0, + strength: Value(0.3), + chance: 1.0, + ))), + ), + energy_cost: 0, + ori_modifier: 1.0, + move_modifier: 1.0, +) \ No newline at end of file diff --git a/assets/common/abilities/custom/organ/organaura.ron b/assets/common/abilities/custom/organ/organaura.ron index 00aa669fd7..6c06bf221f 100644 --- a/assets/common/abilities/custom/organ/organaura.ron +++ b/assets/common/abilities/custom/organ/organaura.ron @@ -11,7 +11,7 @@ BasicAura( category: Magical, ), ], - aura_duration: 34.75, + aura_duration: Some(34.75), range: 18.0, energy_cost: 0.0, scales_with_combo: false, diff --git a/assets/common/abilities/gnarling/totem/green.ron b/assets/common/abilities/gnarling/totem/green.ron index b00647dc59..90da58c86c 100644 --- a/assets/common/abilities/gnarling/totem/green.ron +++ b/assets/common/abilities/gnarling/totem/green.ron @@ -17,7 +17,7 @@ BasicAura( category: Magical, ), ], - aura_duration: 2, + aura_duration: Some(2), range: 50, energy_cost: 0, scales_with_combo: false, diff --git a/assets/common/abilities/gnarling/totem/red.ron b/assets/common/abilities/gnarling/totem/red.ron index 834f3f2bea..0e58b9d20f 100644 --- a/assets/common/abilities/gnarling/totem/red.ron +++ b/assets/common/abilities/gnarling/totem/red.ron @@ -11,7 +11,7 @@ BasicAura( category: Magical, ), ], - aura_duration: 2, + aura_duration: Some(2), range: 50, energy_cost: 0, scales_with_combo: false, diff --git a/assets/common/abilities/gnarling/totem/white.ron b/assets/common/abilities/gnarling/totem/white.ron index 9c55f4c302..8b95ed0661 100644 --- a/assets/common/abilities/gnarling/totem/white.ron +++ b/assets/common/abilities/gnarling/totem/white.ron @@ -11,7 +11,7 @@ BasicAura( category: Magical, ), ], - aura_duration: 2, + aura_duration: Some(2), range: 50, energy_cost: 0, scales_with_combo: false, diff --git a/assets/common/abilities/sceptre/healingaura.ron b/assets/common/abilities/sceptre/healingaura.ron index 76c71d14d7..eb164f109c 100644 --- a/assets/common/abilities/sceptre/healingaura.ron +++ b/assets/common/abilities/sceptre/healingaura.ron @@ -11,7 +11,7 @@ BasicAura( category: Magical, ), ], - aura_duration: 1.0, + aura_duration: Some(1.0), range: 25.0, energy_cost: 20.0, scales_with_combo: true, diff --git a/assets/common/abilities/sceptre/wardingaura.ron b/assets/common/abilities/sceptre/wardingaura.ron index 5c908408e8..fb3a41fdea 100644 --- a/assets/common/abilities/sceptre/wardingaura.ron +++ b/assets/common/abilities/sceptre/wardingaura.ron @@ -11,7 +11,7 @@ BasicAura( category: Magical, ), ], - aura_duration: 1.0, + aura_duration: Some(1.0), range: 25.0, energy_cost: 35.0, scales_with_combo: false, diff --git a/assets/common/entity/wild/aggressive/blue_oni.ron b/assets/common/entity/wild/aggressive/blue_oni.ron index abb7994412..100264e89d 100644 --- a/assets/common/entity/wild/aggressive/blue_oni.ron +++ b/assets/common/entity/wild/aggressive/blue_oni.ron @@ -3,7 +3,7 @@ name: Automatic, body: RandomWith("oni_blue"), alignment: Alignment(Enemy), - loot: LootTable("common.loot_tables.creature.biped_large.default"), + loot: LootTable("common.loot_tables.creature.biped_large.blue_oni"), inventory: ( loadout: FromBody, ), diff --git a/assets/common/entity/wild/peaceful/phoenix.ron b/assets/common/entity/wild/aggressive/phoenix.ron similarity index 100% rename from assets/common/entity/wild/peaceful/phoenix.ron rename to assets/common/entity/wild/aggressive/phoenix.ron diff --git a/assets/common/entity/wild/aggressive/red_oni.ron b/assets/common/entity/wild/aggressive/red_oni.ron index fe27215400..58df36673d 100644 --- a/assets/common/entity/wild/aggressive/red_oni.ron +++ b/assets/common/entity/wild/aggressive/red_oni.ron @@ -3,7 +3,7 @@ name: Automatic, body: RandomWith("oni_red"), alignment: Alignment(Enemy), - loot: LootTable("common.loot_tables.creature.biped_large.default"), + loot: LootTable("common.loot_tables.creature.biped_large.red_oni"), inventory: ( loadout: FromBody, ), diff --git a/assets/common/items/npc_armor/bird_large/phoenix.ron b/assets/common/items/npc_armor/bird_large/phoenix.ron new file mode 100644 index 0000000000..07a61788c0 --- /dev/null +++ b/assets/common/items/npc_armor/bird_large/phoenix.ron @@ -0,0 +1,13 @@ +ItemDef( + name: "Phoenix Armor", + description: "The thickest feather you have ever seen!", + kind: Armor(( + kind: Chest, + stats: Direct(( + protection: Some(Normal(60.0)), + poise_resilience: Some(Normal(0.0)), + )), + )), + quality: Epic, + tags: [], +) diff --git a/assets/common/items/npc_weapons/unique/birdlargefire.ron b/assets/common/items/npc_weapons/unique/birdlargefire.ron index ab776eb043..79e4732dcd 100644 --- a/assets/common/items/npc_weapons/unique/birdlargefire.ron +++ b/assets/common/items/npc_weapons/unique/birdlargefire.ron @@ -1,6 +1,6 @@ ItemDef( name: "Bird Large Fire", - description: "testing123", + description: "Fiery touch of a mighty aerial beast", kind: Tool(( kind: Natural, hands: Two, @@ -14,7 +14,7 @@ ItemDef( buff_strength: 1.0, ), )), - quality: Low, + quality: Epic, tags: [], ability_spec: Some(Custom("Bird Large Fire")), ) \ No newline at end of file diff --git a/assets/common/items/npc_weapons/unique/fiery_tornado.ron b/assets/common/items/npc_weapons/unique/fiery_tornado.ron new file mode 100644 index 0000000000..5e769c0247 --- /dev/null +++ b/assets/common/items/npc_weapons/unique/fiery_tornado.ron @@ -0,0 +1,20 @@ +ItemDef( + name: "FieryTornado", + description: "Fiery Tornado weapon", + kind: Tool(( + kind: Natural, + hands: Two, + stats: ( + equip_time_secs: 0.01, + power: 1.0, + effect_power: 1.0, + speed: 1.0, + range: 1.0, + energy_efficiency: 1.0, + buff_strength: 1.0, + ), + )), + quality: Low, + tags: [], + ability_spec: Some(Custom("FieryTornado")), +) \ No newline at end of file diff --git a/assets/common/items/npc_weapons/unique/tornado.ron b/assets/common/items/npc_weapons/unique/tornado.ron index 0e80ce2071..711780fdaf 100644 --- a/assets/common/items/npc_weapons/unique/tornado.ron +++ b/assets/common/items/npc_weapons/unique/tornado.ron @@ -7,7 +7,7 @@ ItemDef( stats: ( equip_time_secs: 0.01, power: 1.0, - effect_power: 0.0, + effect_power: 1.0, speed: 1.0, range: 1.0, energy_efficiency: 1.0, diff --git a/assets/common/loot_tables/creature/biped_large/blue_oni.ron b/assets/common/loot_tables/creature/biped_large/blue_oni.ron new file mode 100644 index 0000000000..4d3665cee3 --- /dev/null +++ b/assets/common/loot_tables/creature/biped_large/blue_oni.ron @@ -0,0 +1,5 @@ +[ + (1.0, LootTable("common.loot_tables.food.prepared")), + (2.0, LootTable("common.loot_tables.cave_large")), + (0.4, Item("common.items.glider.morpho")), +] \ No newline at end of file diff --git a/assets/common/loot_tables/creature/biped_large/dullahan.ron b/assets/common/loot_tables/creature/biped_large/dullahan.ron index cae38edd1b..66ebf13e76 100644 --- a/assets/common/loot_tables/creature/biped_large/dullahan.ron +++ b/assets/common/loot_tables/creature/biped_large/dullahan.ron @@ -1,9 +1,8 @@ [ - (1.8, All([ + (1.9, All([ MultiDrop(Item("common.items.utility.coins"), 200, 500), MultiDrop(Item("common.items.mineral.ingot.bloodsteel"), 2, 4), MultiDrop(Item("common.items.mineral.ingot.silver"), 0, 2), ])), - (0.1, Item("common.items.glider.morpho")), (0.1, Item("common.items.weapons.sword.caladbolg")), ] \ No newline at end of file diff --git a/assets/common/loot_tables/creature/biped_large/red_oni.ron b/assets/common/loot_tables/creature/biped_large/red_oni.ron new file mode 100644 index 0000000000..2238f8ab37 --- /dev/null +++ b/assets/common/loot_tables/creature/biped_large/red_oni.ron @@ -0,0 +1,5 @@ +[ + (1.0, LootTable("common.loot_tables.food.prepared")), + (2.0, LootTable("common.loot_tables.cave_large")), + (0.4, Item("common.items.glider.monarch")), +] \ No newline at end of file diff --git a/assets/common/loot_tables/creature/bird_large/phoenix.ron b/assets/common/loot_tables/creature/bird_large/phoenix.ron index d8a4708452..c7a0994b80 100644 --- a/assets/common/loot_tables/creature/bird_large/phoenix.ron +++ b/assets/common/loot_tables/creature/bird_large/phoenix.ron @@ -1,5 +1,4 @@ [ - (1.2, Nothing), - (0.4, Item("common.items.glider.monarch")), + (1.6, Nothing), (0.4, Item("common.items.weapons.bow.sagitta")), ] \ No newline at end of file diff --git a/assets/voxygen/audio/sfx.ron b/assets/voxygen/audio/sfx.ron index 73b79072fc..86289a130b 100644 --- a/assets/voxygen/audio/sfx.ron +++ b/assets/voxygen/audio/sfx.ron @@ -1517,6 +1517,13 @@ threshold: 0.8, subtitle: "subtitle-portal-teleported", ), + FromTheAshes: ( + files: [ + "voxygen.audio.sfx.abilities.heal", + ], + threshold: 1.8, + subtitle: "subtitle-attack-from-the-ashes", + ), // Utterances (NPCs) @@ -1557,6 +1564,14 @@ threshold: 1.3, subtitle: "subtitle-utterance-wyvern-angry", ), + Utterance(Angry, Phoenix): ( + files: [ + "voxygen.audio.sfx.utterance.phoenix_angry_0", + "voxygen.audio.sfx.utterance.phoenix_angry_1", + ], + threshold: 3.1, + subtitle: "subtitle-utterance-phoenix-angry", + ), Utterance(Angry, Adlet): ( files: [ "voxygen.audio.sfx.utterance.adlet_angry1", @@ -1765,6 +1780,14 @@ threshold: 1.2, subtitle: "subtitle-utterance-wyvern-hurt", ), + Utterance(Hurt, Phoenix): ( + files: [ + "voxygen.audio.sfx.utterance.phoenix_hurt_0", + "voxygen.audio.sfx.utterance.phoenix_hurt_1", + ], + threshold: 1.5, + subtitle: "subtitle-utterance-phoenix-hurt", + ), Utterance(Angry, Wendigo): ( files: [ "voxygen.audio.sfx.utterance.wendigo_angry1", diff --git a/assets/voxygen/audio/sfx/abilities/heal.ogg b/assets/voxygen/audio/sfx/abilities/heal.ogg new file mode 100644 index 0000000000..0162fcafb1 --- /dev/null +++ b/assets/voxygen/audio/sfx/abilities/heal.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6464ef526e0a5db6dbf3b4d3315da84a00d0b2648693eadf3a3aadc54393d93 +size 47118 diff --git a/assets/voxygen/audio/sfx/utterance/phoenix_angry_0.ogg b/assets/voxygen/audio/sfx/utterance/phoenix_angry_0.ogg new file mode 100644 index 0000000000..215ede09cc --- /dev/null +++ b/assets/voxygen/audio/sfx/utterance/phoenix_angry_0.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b10c889c2d4cf82eea72f666ba647d8b397b7471ad03c9667e6b06f94d3fa011 +size 16518 diff --git a/assets/voxygen/audio/sfx/utterance/phoenix_angry_1.ogg b/assets/voxygen/audio/sfx/utterance/phoenix_angry_1.ogg new file mode 100644 index 0000000000..945ac6db46 --- /dev/null +++ b/assets/voxygen/audio/sfx/utterance/phoenix_angry_1.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11c024be662c161ae2cd30a827462c04228cbcd46f4aede2132123a37719c0c8 +size 66739 diff --git a/assets/voxygen/audio/sfx/utterance/phoenix_hurt_0.ogg b/assets/voxygen/audio/sfx/utterance/phoenix_hurt_0.ogg new file mode 100644 index 0000000000..2fc5a03201 --- /dev/null +++ b/assets/voxygen/audio/sfx/utterance/phoenix_hurt_0.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa2eca1ee3930748d3efd05d14c67c1a46c3163c9825a1860cc5626894944e8d +size 16520 diff --git a/assets/voxygen/audio/sfx/utterance/phoenix_hurt_1.ogg b/assets/voxygen/audio/sfx/utterance/phoenix_hurt_1.ogg new file mode 100644 index 0000000000..5c49702229 --- /dev/null +++ b/assets/voxygen/audio/sfx/utterance/phoenix_hurt_1.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eae509c0389629de5a8a15e981b9c1b05d15e4a94890e0621184021a67ffeeb0 +size 32911 diff --git a/assets/voxygen/element/de_buffs/debuff_heatstroke_0.png b/assets/voxygen/element/de_buffs/debuff_heatstroke_0.png new file mode 100644 index 0000000000..2ce6510629 --- /dev/null +++ b/assets/voxygen/element/de_buffs/debuff_heatstroke_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d016237f28a7b59763e201c7f71aced45124051a67a9d65f56cca575648f54f0 +size 290 diff --git a/assets/voxygen/i18n/en/buff.ftl b/assets/voxygen/i18n/en/buff.ftl index 49f74d0989..6c05bafc53 100644 --- a/assets/voxygen/i18n/en/buff.ftl +++ b/assets/voxygen/i18n/en/buff.ftl @@ -109,6 +109,9 @@ buff-desc-bloodfeast = You restore life on attacks against bleeding enemies ## Berserk buff-title-berserk = Berserk buff-desc-berserk = You are in a berserking rage, causing your attacks to be more powerful and swift, and increasing your speed. However, as a result your defensive capability is less. +## Heatstroke +buff-title-heatstroke = Heatstroke +buff-desc-heatstroke = You were exposed to heat and now suffer from heatstroke, your energy reward and movement speed are cut down. Chill. ## Util buff-text-over_seconds = over { $dur_secs } seconds buff-text-for_seconds = for { $dur_secs } seconds diff --git a/assets/voxygen/i18n/en/hud/subtitles.ftl b/assets/voxygen/i18n/en/hud/subtitles.ftl index 561890670f..8fe36b0a2e 100644 --- a/assets/voxygen/i18n/en/hud/subtitles.ftl +++ b/assets/voxygen/i18n/en/hud/subtitles.ftl @@ -111,6 +111,7 @@ subtitle-attack-icy_spikes = Icy spikes subtitle-attack-ice_crack = Ice crack subtitle-attack-steam = Steam subtitle-attack-shovel = Shovel digging +subtitle-attack-from-the-ashes = Heal Sound subtitle-consume_potion = Drinking potion subtitle-consume_apple = Eating apple @@ -153,4 +154,6 @@ subtitle-utterance-wendigo-calm = Wendigo mumbling subtitle-utterance-wolf-angry = Wolf growling subtitle-utterance-wolf-hurt = Wolf whining subtitle-utterance-wyvern-angry = Wyvern roaring -subtitle-utterance-wyvern-hurt = Wyvern hurting \ No newline at end of file +subtitle-utterance-wyvern-hurt = Wyvern hurting +subtitle-utterance-phoenix-angry = Phoenix screaming +subtitle-utterance-phoenix-hurt = Phoenix hurting \ No newline at end of file diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index 4e8d9b0a79..33283c538d 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -84,6 +84,16 @@ const int CYCLOPS_CHARGE = 43; const int PORTAL_FIZZ = 45; const int INK = 46; const int WHIRLWIND = 47; +const int FIERY_BURST = 48; +const int FIERY_BURST_VORTEX = 49; +const int FIERY_BURST_SPARKS = 50; +const int FIERY_BURST_ASH = 51; +const int FIERY_TORNADO = 52; +const int PHOENIX_CLOUD = 53; +const int FIERY_DROPLET_TRACE = 54; +const int ENERGY_PHOENIX = 55; +const int PHOENIX_BEAM = 56; +const int PHOENIX_BUILD_UP_AIM = 57; // meters per second squared (acceleration) const float earth_gravity = 9.807; @@ -727,6 +737,240 @@ void main() { spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9) ); break; + case FIERY_BURST: + f_reflect = 0.0; + float fiery_radius = start_end(1.0 - pow(abs(rand5), 5.0), 1.0) * length(inst_dir); + float fiery_color1 = (7.0 + 1.0 * percent()) * min(1.0, percent() * 4.0) * 1.5; + float fiery_color2 = (4.0 - 2.0 * percent() + 1.3 * rand5 * slow_end(0.0)) * min(1.0, percent() * 4.0) * 1.3; + float fiery_color3 = 1.0 + 0.3 * percent(); + attr = Attr( + spiral_motion( + vec3( + 0.0, + 0.0, + (rand3 + 1.0) + * max( + ((percent() * 8.0) * (1.0 - step(0.2, percent()))), + ((2.0 * (1.0 - percent())) * (step(0.2, percent()))) + ) + ), + fiery_radius, + lifetime, + max(0.1, step(0.6, percent())) * 3.0 * abs(rand0), + rand1 * 2.0 * PI) + vec3(0.0, 0.0, rand2), + vec3(6.0 * abs(rand4) * (1.0 - slow_start(2.0)) * pow(fiery_radius / length(inst_dir), 0.5)), + vec4(fiery_color1, fiery_color2, fiery_color3, slow_end(0.4)), + spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3.0) + ); + break; + case FIERY_BURST_VORTEX: + f_reflect = 0.0; + float fiery_vortex_color1 = (min(1, percent() * 2) * (5 + 1 * percent() + 1 * slow_end(0)) * 1.5); + float fiery_vortex_color2 = (min(1, percent() * 2) * (4 - 2.4 * percent() + 1.3 * rand5 * slow_end(0)) * 1.3); + float fiery_vortex_color3 = 0; + attr = Attr( + spiral_motion( + vec3( + 0, + 0, + (0 + 0.5 * rand4 ) + 4.0 + * max( + ((percent() * 8) * (1 - step(0.2, percent()))), // first 20% of lifetime particle moves up, then goes down + ((2 * (1 - percent())) * (step(0.2, percent())))// to avoid tearing multi should have same proportion as edge(here: 8 before, 2 after) + ) + ), + abs(rand0) + 0.5 * 10 * percent(), + percent(), + 10.0 * abs(rand2), + rand3), + vec3((2.5 * (1 - slow_start(0.05)))), + vec4(fiery_vortex_color1, fiery_vortex_color2, fiery_vortex_color3, start_end(0.5, 1.5) * abs(rand2)), + spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9) + ); + break; + case FIERY_BURST_SPARKS: + f_reflect = 0.0; + // sparks should flicker, so it stops glowing for 18% of time 4 times per second, same thing used in 4th float of RGBA vector + float fiery_sparks_color1 = 2 + 1 * rand2 + 2 * step(0.18, fract(tick.x*4)); + float fiery_sparks_color2 = 4 + 1 * rand2 + 4 * step(0.18, fract(tick.x*4)); + float fiery_sparks_color3 = 4 + 6 * step(0.18, fract(tick.x*4)); + attr = Attr( + spiral_motion(vec3(0, 0, 5), abs(rand0) + abs(rand1) * percent() * 4.0, percent(), 8.0 * abs(rand2), rand3), + vec3((2.5 * (1 - slow_start(0.05)))), + vec4(fiery_sparks_color1, fiery_sparks_color2, fiery_sparks_color3, 0.5 + 0.5 * step(0.18, fract(tick.x*4))), + spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9) + ); + break; + case FIERY_BURST_ASH: + f_reflect = 0.0; + /// inst_dir holds info about: + /// .x: radius of random spawn + float fiery_ash_rand_rad = inst_dir.x; + /// .y: + /// in fract: relative time of "setting on fire" + /// in int: radius of curve + float fiery_ash_radius = floor(inst_dir.y); + float fiery_ash_edge = inst_dir.y - fiery_ash_radius; + /// .z: height of the flight + float fiery_ash_height = inst_dir.z; + // {FOR PHOENIX "from the ashes"}sets ash on fire at 0.4 of lifetime, then makes it lose glow, representing losing heat + float fiery_ash_color1 = (2 + 1 * percent() * slow_end(0)) + * (max( + 1, + 8 * step(fiery_ash_edge, percent()) * (1.4 - percent())) + ); + float fiery_ash_color2 = (2 - 1 * percent() + 0.3 * abs(rand5) * slow_end(0.5)) + * (max( + 1, + 6.5 * step(fiery_ash_edge, percent()) * (1.4 - percent())) + ); + float fiery_ash_color3 = 1.5; + attr = Attr( + spiral_motion( + vec3( + 0.0, + 0.0, + fiery_ash_height// {FOR PHOENIX "from the ashes"} 8.58 + ), + abs(rand0 / 2.0 + 1.0) + * max(1.0, ((percent() * fiery_ash_radius * 0.8) * (1.0 - step(0.2, percent())))) // part of lifetime particle moves to periphery + * max(1.0, (fiery_ash_radius * 0.2 * (1.0 - percent()) * (step(0.2, percent())))),// then back to center + percent(), + 6.0 * abs(rand2), + rand3 * 5.0 + ) + + vec3((rand6 + rand5) * fiery_ash_rand_rad, (rand8 + rand3) * fiery_ash_rand_rad, abs(rand0)),//makes it apear randomly above base animation (Fiery Burst) + vec3((2.5 * (1 - slow_start(0.0)))), + vec4(fiery_ash_color1, fiery_ash_color2, fiery_ash_color3, abs(rand2) * slow_end(0.3)), + spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9) + ); + break; + case FIERY_TORNADO: + f_reflect = 0.0; + float fiery_tornado_color1 = (2.6 + 0.5 * percent()) + * 4.0 * max(0.5, percent() * 1.2); + float fiery_tornado_color2 = (1.7 - 0.6 * pow(1.0 - percent(), 2.0) + 0.3 * abs(rand5)) + * 2.0 * max(0.45, percent() * 1.2); + float fiery_tornado_color3 = 1.5 * max(0.6, percent()); + attr = Attr( + spiral_motion(vec3(0, 0, 6.0 + rand3 * 1.5), abs(rand0) + abs(rand1) * percent() * 3.0, percent(), 15.0 * abs(rand2), -inst_time), + vec3((2.5 * (1 - slow_start(0.05)))), + vec4(fiery_tornado_color1, fiery_tornado_color2, fiery_tornado_color3, 0.5), + spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9) + ); + break; + case PHOENIX_CLOUD: + float PC_spin = floor(inst_dir.x); + float refl = floor(inst_dir.y); + float PC_size = floor(inst_dir.z); + //best is 0.4 - reflects some light but only part as + f_reflect = refl * 0.1; + // modifies by + 5% to -15%, if color is less than 0.5 it will get from +10% to +25% to it's value + float PC_rand_color_factor = rand0 * 0.05; + float PC_R = inst_dir.x - PC_spin; + PC_R += PC_R * PC_rand_color_factor * step(0.05, PC_R) * -abs(PC_rand_color_factor * 2.0) + + PC_R * (1.0 - step(0.05, PC_R)) * max(abs(PC_rand_color_factor), 0.02) * 5.0; + float PC_G = inst_dir.y - refl; + PC_G += PC_G * PC_rand_color_factor * step(0.05, PC_G) * -abs(PC_rand_color_factor * 2.0) + + PC_G * (1.0 - step(0.05, PC_G)) * max(abs(PC_rand_color_factor), 0.02) * 5.0; + float PC_B = inst_dir.z - PC_size; + PC_B += PC_B * PC_rand_color_factor * step(0.05, PC_B) * -abs(PC_rand_color_factor * 2.0) + + PC_B * (1.0 - step(0.05, PC_B)) * max(abs(PC_rand_color_factor), 0.02) * 5.0; + attr = Attr( + linear_motion( + vec3(0.0, 0.0, 0.0), + vec3(rand4, rand5, rand6 * 2.5) + ), + vec3(8.0 * min(percent() * 3.0, 1.0) * min((1.0 - percent()) * 2.0, 1.0)), + vec4( + PC_R, + PC_G, + PC_B, + PC_size * 1.2) * 10.0, + spin_in_axis(vec3(rand6 + rand5, rand7 + rand9, rand8 + rand2), percent() * PC_spin) + ); + break; + case FIERY_DROPLET_TRACE: + float m_r = 4.0; + f_reflect = 0.0; // Fire doesn't reflect light, it emits it + float prcnt = percent(); //idk if compiler would optimize it or not but as we have a lot of those particles... i'll just try + float droplet_color1 = 1 * (5 + 1 * prcnt + 1 * slow_end(0)) * 1.5; + float droplet_color2 = 1 * (4 - 2.4 * prcnt + 1.3 * rand5 * slow_end(0)) * 1.3; + float droplet_color3 = 0; + attr = Attr( + quadratic_bezier_motion( + vec3(0.0), + vec3(m_r * rand0, m_r * rand1, 0.0), + vec3(m_r * rand0, m_r * rand1, 4.0) + ), + vec3(1), + vec4(droplet_color1, + droplet_color2, + droplet_color3, + 1 * prcnt * (1 - step(0.5, prcnt)) + (1 - prcnt) * (step(0.5, prcnt))), + spin_in_axis(vec3(1,0,0),0) + ); + break; + case ENERGY_PHOENIX: + f_reflect = 0.0; + float fiery_r = (2 + 1 * percent() * slow_end(0)) + * 6 * (1.4 - percent()); + float fiery_g = (2 - 1 * percent() + 0.3 * abs(rand5) * slow_end(0.5)) + * 4.5 * (1.4 - percent()); + float fiery_b = 1.5; + spiral_radius = length(inst_dir); + attr = Attr( + spiral_motion(vec3(0.0, 0.0, 0.01), spiral_radius + abs(rand1), lifetime / 0.5, abs(rand0), rand1 * 2.0 * PI) + vec3(0.0, 0.0, rand2), + vec3(6.0 * abs(rand4) * (1 - slow_start(2.0))), + vec4(vec3(fiery_r, fiery_g, fiery_b), 1.0), + spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3.0) + ); + break; + case PHOENIX_BEAM: + f_reflect = 0.0; // Fire doesn't reflect light, it emits it + float beam_r = 6.0 - (4.0 * percent()) + 15.0 * fract(percent() * 4 + rand0 * rand0) * (1 - percent()); + float beam_g = 2.0 + 6.6 * fract(percent() * 4 + rand0 * rand0) * (1 - percent()); + float beam_b = 1.4; + + vec3 factor_rand = vec3((rand0 * 0.2) * (rand5 * 0.1) + rand6 * 0.9, (rand1 * 0.2) * (rand4 * 0.1) + rand7 * 0.9, (rand2 * 0.2) * (rand3 * 0.1) + rand8 * 0.9); + start_pos += factor_rand + normalize(inst_dir) * 0.6; + attr = Attr( + spiral_motion(inst_dir - factor_rand * 0.4, 0.3 * ((rand2 + 0.5) * 5.5) * (1.0 - min(linear_scale(1.5), 1.0)), lifetime / inst_lifespan, 24.0, -inst_time * 8.0), + vec3((2.5 * (1 - slow_start(0.2)))), + vec4(beam_r, beam_g, beam_b, 1.0), + spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10.0 + 3.0 * rand9) + ); + break; + case PHOENIX_BUILD_UP_AIM: + + f_reflect = 0.0; // Fire doesn't reflect light, it emits it + + float perc_t = percent(); // in case compiler wont optimize, idk + + float aim_r = rand0 * 0.25 + 3.0 + 4.5 * perc_t * (1 - step(0.79, perc_t)) + 8.0 * step(0.81, perc_t) * perc_t; + float aim_g = rand0 * 0.25 + 2.0 - 1.0 * perc_t * (1 - step(0.79, perc_t)) + 2.0 * step(0.81, perc_t) * perc_t; + float aim_b = 1.4 * ((1 - perc_t) + step(0.74, perc_t)); + + + vec3 dir_aim = inst_dir * 1.0; + vec3 rand_pos_aim = (cross( + (1.0 - 2.0 * step(0.0, rand2)) * normalize(inst_dir), + vec3(0.0, 0.0, 1.0))); + + vec3 rand_fact = vec3(rand1 * 1, rand0 * 1, rand2 * 1); + start_pos += vec3(0.0, 0.0, 5.0) + rand_fact; + attr = Attr( + spiral_motion( + inst_dir + vec3(0.0, 0.0, -(6.0 - 3.0 * pow(perc_t, 2.5))) - rand_fact, + 1.2 * rand9 * max(1.0 - perc_t, 0.0), + perc_t, + 6.0, + inst_time * 8.0), + vec3((1.9 * (1 - slow_start(0.2)))), + vec4(aim_r, aim_g, aim_b, 1.0), + spin_in_axis(vec3(rand6, rand7, rand8), perc_t * 10.0 + 3.0 * rand9) + ); + break; default: attr = Attr( linear_motion( diff --git a/assets/voxygen/voxel/object_manifest.ron b/assets/voxygen/voxel/object_manifest.ron index 76d393ad15..0b299202e3 100644 --- a/assets/voxygen/voxel/object_manifest.ron +++ b/assets/voxygen/voxel/object_manifest.ron @@ -629,6 +629,16 @@ central: ("armor.empty"), ) ), + FireRainDrop: ( + bone0: ( + offset: (-2.0, -10.0, -2.0), + central: ("weapon.projectile.firerain_droplet1"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), + ) + ), TrainingDummy: ( bone0: ( offset: (-7.0, -5.0, 0.0), @@ -809,6 +819,16 @@ central: ("armor.empty"), ) ), + FieryTornado: ( + bone0: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), + ) + ), Apple: ( bone0: ( offset: (-3.5, -3.5, 0.0), diff --git a/assets/voxygen/voxel/weapon/projectile/firerain_droplet1.vox b/assets/voxygen/voxel/weapon/projectile/firerain_droplet1.vox new file mode 100644 index 0000000000..336dabd041 --- /dev/null +++ b/assets/voxygen/voxel/weapon/projectile/firerain_droplet1.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ec0a2df9dff625befdc816edc366abf216155f1046c4fcf56b3e641941571e7 +size 1548 diff --git a/common/src/cmd.rs b/common/src/cmd.rs index 1d84f1ed5f..b7bc6e0e1d 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -183,6 +183,7 @@ lazy_static! { BuffKind::Defiance => "defiance", BuffKind::Bloodfeast => "bloodfeast", BuffKind::Berserk => "berserk", + BuffKind::Heatstroke => "heatstroke" }; let mut buff_parser = HashMap::new(); for kind in BuffKind::iter() { diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index f74fd70b93..a6669cad24 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -721,6 +721,8 @@ pub enum CharacterAbility { projectile_light: Option, projectile_speed: f32, damage_effect: Option, + properties_of_aoe: Option, + specifier: Option, #[serde(default)] meta: AbilityMeta, }, @@ -906,7 +908,7 @@ pub enum CharacterAbility { recover_duration: f32, targets: combat::GroupTarget, auras: Vec, - aura_duration: Secs, + aura_duration: Option, range: f32, energy_cost: f32, scales_with_combo: bool, @@ -947,6 +949,7 @@ pub enum CharacterAbility { combo_scaling: Option, #[serde(default)] meta: AbilityMeta, + specifier: Option, }, SpriteSummon { buildup_duration: f32, @@ -1228,6 +1231,8 @@ impl CharacterAbility { projectile_light: _, ref mut projectile_speed, damage_effect: _, + properties_of_aoe: _, + specifier: _, meta: _, } => { *buildup_duration /= stats.speed; @@ -1578,6 +1583,7 @@ impl CharacterAbility { combo_cost: _, combo_scaling: _, meta: _, + specifier: _, } => { *buff_strength *= stats.diminished_buff_strength(); *buildup_duration /= stats.speed; @@ -2597,6 +2603,8 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState { projectile_light, projectile_speed, damage_effect, + properties_of_aoe, + specifier, meta: _, } => CharacterState::RepeaterRanged(repeater_ranged::Data { static_data: repeater_ranged::StaticData { @@ -2613,6 +2621,8 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState { projectile_speed: *projectile_speed, ability_info, damage_effect: *damage_effect, + properties_of_aoe: *properties_of_aoe, + specifier: *specifier, }, timer: Duration::default(), stage_section: StageSection::Buildup, @@ -2776,6 +2786,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState { combo_scaling, enforced_limit, meta: _, + specifier, } => CharacterState::SelfBuff(self_buff::Data { static_data: self_buff::StaticData { buildup_duration: Duration::from_secs_f32(*buildup_duration), @@ -2789,6 +2800,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState { combo_on_use: data.combo.map_or(0, |c| c.counter()), enforced_limit: *enforced_limit, ability_info, + specifier: *specifier, }, timer: Duration::default(), stage_section: StageSection::Buildup, diff --git a/common/src/comp/aura.rs b/common/src/comp/aura.rs index e087813c1c..212c73365d 100644 --- a/common/src/comp/aura.rs +++ b/common/src/comp/aura.rs @@ -78,6 +78,7 @@ pub enum Specifier { WardingAura, HealingAura, Frozen, + FieryAura, } impl From<(Option, Option<&Uid>)> for AuraTarget { diff --git a/common/src/comp/beam.rs b/common/src/comp/beam.rs index 431a099792..6ed495d81d 100644 --- a/common/src/comp/beam.rs +++ b/common/src/comp/beam.rs @@ -44,4 +44,5 @@ pub enum FrontendSpecifier { Poison, Ink, Lightning, + PhoenixLaser, } diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index 31e103a173..9331aff0dd 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -519,7 +519,7 @@ impl Body { | bird_large::Species::CloudWyvern | bird_large::Species::SeaWyvern | bird_large::Species::WealdWyvern => Vec3::new(2.5, 9.0, 4.5), - _ => Vec3::new(2.0, 6.0, 3.5), + _ => Vec3::new(2.0, 6.0, 4.4), }, Body::Dragon(_) => Vec3::new(16.0, 10.0, 16.0), Body::FishMedium(_) => Vec3::new(0.5, 2.0, 0.8), @@ -842,6 +842,7 @@ impl Body { | bird_large::Species::FrostWyvern | bird_large::Species::SeaWyvern | bird_large::Species::WealdWyvern => 1000, + bird_large::Species::Phoenix => 2000, _ => 300, }, Body::BirdMedium(bird_medium) => match bird_medium.species { @@ -940,7 +941,7 @@ impl Body { quadruped_low::Species::Maneater => 130, quadruped_low::Species::Sandshark => 110, quadruped_low::Species::Hakulaq => 120, - quadruped_low::Species::Dagon => 1200, + quadruped_low::Species::Dagon => 1500, quadruped_low::Species::Lavadrake => 160, quadruped_low::Species::Basilisk => 200, quadruped_low::Species::Deadwood => 120, diff --git a/common/src/comp/body/object.rs b/common/src/comp/body/object.rs index 313e617f07..2c6dbbdaa3 100644 --- a/common/src/comp/body/object.rs +++ b/common/src/comp/body/object.rs @@ -112,6 +112,8 @@ make_case_elim!( SpearIcicle = 97, Portal = 98, PortalActive = 99, + FieryTornado = 100, + FireRainDrop = 101, } ); @@ -122,7 +124,7 @@ impl Body { } } -pub const ALL_OBJECTS: [Body; 100] = [ +pub const ALL_OBJECTS: [Body; 102] = [ Body::Arrow, Body::Bomb, Body::Scarecrow, @@ -223,6 +225,8 @@ pub const ALL_OBJECTS: [Body; 100] = [ Body::SpearIcicle, Body::Portal, Body::PortalActive, + Body::FieryTornado, + Body::FireRainDrop, ]; impl From for super::Body { @@ -332,6 +336,8 @@ impl Body { Body::SpearIcicle => "spear_icicle", Body::Portal => "portal", Body::PortalActive => "portal_active", + Body::FieryTornado => "fiery_tornado", + Body::FireRainDrop => "fire_rain_drop", } } @@ -343,6 +349,7 @@ impl Body { Reagent::Red => Body::FireworkRed, Reagent::White => Body::FireworkWhite, Reagent::Yellow => Body::FireworkYellow, + Reagent::Phoenix => Body::FireRainDrop, } } @@ -384,7 +391,11 @@ impl Body { Body::BedBlue => 50.0, Body::Bedroll => 3.0, Body::Bench => 100.0, - Body::BoltFire | Body::BoltFireBig | Body::BoltNature | Body::BoltIcicle => 1.0, + Body::BoltFire + | Body::BoltFireBig + | Body::BoltNature + | Body::BoltIcicle + | Body::FireRainDrop => 1.0, Body::SpitPoison => 100.0, Body::Bomb | Body::DagonBomb => { 0.5 * IRON_DENSITY * std::f32::consts::PI / 6.0 * self.dimensions().x.powi(3) @@ -445,7 +456,7 @@ impl Body { Body::FishMeat => 10.0, Body::BirdMeat => 10.0, Body::SmallMeat => 10.0, - Body::Tornado => 50.0, + Body::Tornado | Body::FieryTornado => 50.0, Body::Apple => 2.0, Body::Hive => 2.0, Body::Coconut => 2.0, @@ -479,7 +490,7 @@ impl Body { Body::HaniwaSentry => Vec3::new(0.8, 0.8, 1.4), Body::SeaLantern => Vec3::new(0.8, 0.8, 1.4), Body::Snowball => Vec3::broadcast(2.5), - Body::Tornado => Vec3::new(2.0, 2.0, 3.4), + Body::Tornado | Body::FieryTornado => Vec3::new(2.0, 2.0, 3.4), Body::TrainingDummy => Vec3::new(1.5, 1.5, 3.0), Body::GnarlingTotemRed | Body::GnarlingTotemGreen | Body::GnarlingTotemWhite => { Vec3::new(0.8, 0.8, 1.4) @@ -489,6 +500,7 @@ impl Body { Body::LaserBeam => Vec3::new(8.0, 8.0, 8.0), Body::Mine => Vec3::new(0.8, 0.8, 0.5), Body::LightningBolt | Body::SpearIcicle => Vec3::new(1.0, 1.0, 1.0), + Body::FireRainDrop => Vec3::new(0.01, 0.01, 0.02), // FIXME: this *must* be exhaustive match _ => Vec3::broadcast(0.5), } diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index aac831abfb..821a3768b1 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -162,6 +162,12 @@ pub enum BuffKind { PotionSickness, /// Changed into another body. Polymorphed, + /// Slows movement speed and reduces energy reward. + /// Both scales non-linearly to strength, 0.5 lead to movespeed reduction + /// by 25% and energy reward reduced by 150%, 1.0 lead to MS reduction by + /// 33.3% and energy reward reduced by 200%. Energy reward can't be + /// reduced by more than 200%, to a minimum value of -100%. + Heatstroke, } impl BuffKind { @@ -201,7 +207,8 @@ impl BuffKind { | BuffKind::Poisoned | BuffKind::Parried | BuffKind::PotionSickness - | BuffKind::Polymorphed => false, + | BuffKind::Polymorphed + | BuffKind::Heatstroke => false, } } @@ -409,6 +416,10 @@ impl BuffKind { BuffEffect::AttackSpeed(1.0 + nn_scaling(data.strength) / 2.0), BuffEffect::MovementSpeed(1.0 + nn_scaling(data.strength) / 4.0), ], + BuffKind::Heatstroke => vec![ + BuffEffect::MovementSpeed(1.0 - nn_scaling(data.strength) * 0.5), + BuffEffect::EnergyReward((1.0 - nn_scaling(data.strength) * 3.0).max(-1.0)), + ], } } diff --git a/common/src/comp/inventory/item/mod.rs b/common/src/comp/inventory/item/mod.rs index ca65c93b54..1b2f7f1880 100644 --- a/common/src/comp/inventory/item/mod.rs +++ b/common/src/comp/inventory/item/mod.rs @@ -44,6 +44,7 @@ pub enum Reagent { Red, White, Yellow, + Phoenix, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] diff --git a/common/src/comp/inventory/loadout_builder.rs b/common/src/comp/inventory/loadout_builder.rs index c363150199..3bdcf6ed44 100644 --- a/common/src/comp/inventory/loadout_builder.rs +++ b/common/src/comp/inventory/loadout_builder.rs @@ -780,6 +780,9 @@ fn default_main_tool(body: &Body) -> Item { object::Body::Tornado => Some(Item::new_from_asset_expect( "common.items.npc_weapons.unique.tornado", )), + object::Body::FieryTornado => Some(Item::new_from_asset_expect( + "common.items.npc_weapons.unique.fiery_tornado", + )), object::Body::GnarlingTotemRed => Some(Item::new_from_asset_expect( "common.items.npc_weapons.biped_small.gnarling.redtotem", )), @@ -984,6 +987,7 @@ impl LoadoutBuilder { | bird_large::Species::WealdWyvern => { Some("common.items.npc_armor.bird_large.wyvern") }, + bird_large::Species::Phoenix => Some("common.items.npc_armor.bird_large.phoenix"), _ => None, }, Body::Golem(body) => match body.species { diff --git a/common/src/comp/projectile.rs b/common/src/comp/projectile.rs index cb97c6a5f3..a802fecf96 100644 --- a/common/src/comp/projectile.rs +++ b/common/src/comp/projectile.rs @@ -59,6 +59,13 @@ pub enum ProjectileConstructor { knockback: f32, energy_regen: f32, }, + FireDroplet { + damage: f32, + radius: f32, + energy_regen: f32, + min_falloff: f32, + reagent: Option, + }, Fireball { damage: f32, radius: f32, @@ -253,6 +260,56 @@ impl ProjectileConstructor { is_point: true, } }, + FireDroplet { + damage, + radius, + energy_regen, + min_falloff, + reagent, + } => { + let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen)) + .with_requirement(CombatRequirement::AnyDamage); + let buff = CombatEffect::Buff(CombatBuff { + kind: BuffKind::Burning, + dur_secs: 4.0, + strength: CombatBuffStrength::DamageFraction(1.0), + chance: 1.0, + }) + .adjusted_by_stats(tool_stats); + let damage = AttackDamage::new( + Damage { + source: DamageSource::Explosion, + kind: DamageKind::Energy, + value: damage, + }, + Some(GroupTarget::OutOfGroup), + instance, + ) + .with_effect(buff); + let attack = Attack::default() + .with_damage(damage) + .with_precision(precision_mult) + .with_effect(energy) + .with_combo_increment(); + let explosion = Explosion { + effects: vec![ + RadiusEffect::Attack(attack), + RadiusEffect::TerrainDestruction(2.0, Rgb::black()), + ], + radius, + reagent, + 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, + } + }, Fireball { damage, radius, @@ -915,6 +972,16 @@ impl ProjectileConstructor { *damage *= power; *energy_regen *= regen; }, + FireDroplet { + ref mut damage, + ref mut energy_regen, + ref mut radius, + .. + } => { + *damage *= power; + *energy_regen *= regen; + *radius *= range; + }, Fireball { ref mut damage, ref mut energy_regen, @@ -1034,6 +1101,7 @@ impl ProjectileConstructor { match self { Arrow { .. } => false, Knife { .. } => false, + FireDroplet { .. } => true, Fireball { .. } => true, Frostball { .. } => true, Poisonball { .. } => true, diff --git a/common/src/outcome.rs b/common/src/outcome.rs index b226f885a6..b20d587258 100644 --- a/common/src/outcome.rs +++ b/common/src/outcome.rs @@ -146,6 +146,9 @@ pub enum Outcome { TeleportedByPortal { pos: Vec3, }, + FromTheAshes { + pos: Vec3, + }, } impl Outcome { @@ -177,6 +180,7 @@ impl Outcome { | Outcome::GroundDig { pos } | Outcome::PortalActivated { pos } | Outcome::TeleportedByPortal { pos} + | Outcome::FromTheAshes { pos } | Outcome::Glider { pos, .. } => Some(*pos), Outcome::BreakBlock { pos, .. } | Outcome::SpriteUnlocked { pos } diff --git a/common/src/states/basic_aura.rs b/common/src/states/basic_aura.rs index a7fea2ae93..0a07c34b0b 100644 --- a/common/src/states/basic_aura.rs +++ b/common/src/states/basic_aura.rs @@ -29,7 +29,7 @@ pub struct StaticData { /// Has information used to construct the auras pub auras: Vec, /// How long aura lasts - pub aura_duration: Secs, + pub aura_duration: Option, /// Radius of aura pub range: f32, /// What key is used to press ability @@ -78,7 +78,8 @@ impl CharacterBehavior for Data { let mut aura = aura_data.to_aura( data.uid, self.static_data.range, - Some(self.static_data.aura_duration), + // check for indefinite aura + self.static_data.aura_duration, targets, *data.time, ); diff --git a/common/src/states/basic_beam.rs b/common/src/states/basic_beam.rs index 859e9abf80..25b56bfa90 100644 --- a/common/src/states/basic_beam.rs +++ b/common/src/states/basic_beam.rs @@ -4,8 +4,11 @@ use crate::{ DamageKind, DamageSource, GroupTarget, }, comp::{ - beam, body::biped_large, character_state::OutputEvents, object::Body::Flamethrower, Body, - CharacterState, Ori, StateUpdate, + beam, + body::{biped_large, bird_large}, + character_state::OutputEvents, + object::Body::Flamethrower, + Body, CharacterState, Ori, StateUpdate, }, event::LocalEvent, outcome::Outcome, @@ -242,8 +245,13 @@ impl CharacterBehavior for Data { fn height_offset(body: &Body, look_dir: Dir, velocity: Vec3, on_ground: Option) -> f32 { match body { // Hack to make the beam offset correspond to the animation - Body::BirdLarge(_) => { - body.height() * 0.3 + Body::BirdLarge(b) => { + let height_factor = match b.species { + bird_large::Species::Phoenix => 0.5, + bird_large::Species::Cockatrice => 0.4, + _ => 0.3, + }; + body.height() * height_factor + if on_ground.is_none() { (2.0 - velocity.xy().magnitude() * 0.25).max(-1.0) } else { diff --git a/common/src/states/basic_summon.rs b/common/src/states/basic_summon.rs index 5b174e943b..337013b3dd 100644 --- a/common/src/states/basic_summon.rs +++ b/common/src/states/basic_summon.rs @@ -3,8 +3,11 @@ use crate::{ self, character_state::OutputEvents, inventory::loadout_builder::{self, LoadoutBuilder}, + object::Body::FieryTornado, skillset::skills, - Behavior, BehaviorCapability, CharacterState, Projectile, StateUpdate, + Behavior, BehaviorCapability, + Body::Object, + CharacterState, Projectile, StateUpdate, }, event::{LocalEvent, NpcBuilder, ServerEvent}, npc::NPC_NAMES, @@ -185,11 +188,19 @@ impl CharacterBehavior for Data { is_sticky: false, is_point: false, }); + let extra_height = + if self.static_data.summon_info.body == Object(FieryTornado) { + 5.0 + } else { + 0.0 + }; let mut rng = rand::thread_rng(); // Send server event to create npc output_events.emit_server(ServerEvent::CreateNpc { - pos: comp::Pos(collision_vector - Vec3::unit_z() * obstacle_z), + pos: comp::Pos( + collision_vector - Vec3::unit_z() * obstacle_z + extra_height, + ), ori: comp::Ori::from(Dir::random_2d(&mut rng)), npc: NpcBuilder::new(stats, body, comp::Alignment::Owned(*data.uid)) .with_skill_set(skill_set) diff --git a/common/src/states/repeater_ranged.rs b/common/src/states/repeater_ranged.rs index ff971c1114..2f00c2be22 100644 --- a/common/src/states/repeater_ranged.rs +++ b/common/src/states/repeater_ranged.rs @@ -9,9 +9,11 @@ use crate::{ behavior::{CharacterBehavior, JoinData}, utils::{StageSection, *}, }, + util::Dir, }; +use rand::{thread_rng, Rng}; use serde::{Deserialize, Serialize}; -use std::time::Duration; +use std::{f32::consts::TAU, time::Duration}; #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] /// Separated out to condense update portions of character state @@ -37,6 +39,19 @@ pub struct StaticData { pub ability_info: AbilityInfo, /// Adds an effect onto the main damage of the attack pub damage_effect: Option, + /// Whether ablity should be casted from above as aoe or shoot projectiles + /// as normal + pub properties_of_aoe: Option, + /// Used to specify the attack to the frontend + pub specifier: Option, +} + +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub struct ProjectileOffset { + /// Radius of AOE + pub radius: f32, + /// Height of shooting point for AOE's projectiles + pub height: f32, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -94,10 +109,35 @@ impl CharacterBehavior for Data { let precision_mult = combat::compute_precision_mult(data.inventory, data.msm); let tool_stats = get_tool_stats(data, self.static_data.ability_info); // Gets offsets - let body_offsets = data - .body - .projectile_offsets(update.ori.look_vec(), data.scale.map_or(1.0, |s| s.0)); - let pos = Pos(data.pos.0 + body_offsets); + let pos: Pos = self.static_data.properties_of_aoe.as_ref().map_or_else( + || { + // Default position + let body_offsets = data.body.projectile_offsets( + update.ori.look_vec(), + data.scale.map_or(1.0, |s| s.0), + ); + Pos(data.pos.0 + body_offsets) + }, + |aoe_data| { + // Position calculated from aoe_data + let rand_pos = { + let mut rng = thread_rng(); + let theta = rng.gen::() * TAU; + let radius = aoe_data.radius * rng.gen::().sqrt(); + let x = radius * theta.sin(); + let y = radius * theta.cos(); + vek::Vec2::new(x, y) + }; + Pos(data.pos.0 + rand_pos.with_z(aoe_data.height)) + }, + ); + + let direction: Dir = if self.static_data.properties_of_aoe.is_some() { + Dir::down() + } else { + data.inputs.look_dir + }; + let projectile = self.static_data.projectile.create_projectile( Some(*data.uid), precision_mult, @@ -107,7 +147,7 @@ impl CharacterBehavior for Data { output_events.emit_server(ServerEvent::Shoot { entity: data.entity, pos, - dir: data.inputs.look_dir, + dir: direction, body: self.static_data.projectile_body, projectile, light: self.static_data.projectile_light, @@ -167,3 +207,8 @@ impl CharacterBehavior for Data { update } } + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub enum FrontendSpecifier { + FireRain, +} diff --git a/common/src/states/self_buff.rs b/common/src/states/self_buff.rs index 2dfd58d9ea..196d22cb26 100644 --- a/common/src/states/self_buff.rs +++ b/common/src/states/self_buff.rs @@ -4,7 +4,8 @@ use crate::{ character_state::OutputEvents, CharacterState, StateUpdate, }, - event::ServerEvent, + event::{LocalEvent, ServerEvent}, + outcome::Outcome, resources::Secs, states::{ behavior::{CharacterBehavior, JoinData}, @@ -41,6 +42,8 @@ pub struct StaticData { pub enforced_limit: bool, /// What key is used to press ability pub ability_info: AbilityInfo, + /// Used to specify an outcome for the buff + pub specifier: Option, } #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -145,6 +148,12 @@ impl CharacterBehavior for Data { timer: tick_attack_or_default(data, self.timer, None), ..*self }); + if let Some(FrontendSpecifier::FromTheAshes) = self.static_data.specifier { + // Send local event used for frontend shenanigans + output_events.emit_local(LocalEvent::CreateOutcome( + Outcome::FromTheAshes { pos: data.pos.0 }, + )); + } } else { update.character = CharacterState::SelfBuff(Data { timer: Duration::default(), @@ -176,3 +185,8 @@ impl CharacterBehavior for Data { update } } +/// Used to specify a particular effect for frontend purposes +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub enum FrontendSpecifier { + FromTheAshes, +} diff --git a/rtsim/src/gen/mod.rs b/rtsim/src/gen/mod.rs index ec0b0c334a..67213bdaca 100644 --- a/rtsim/src/gen/mod.rs +++ b/rtsim/src/gen/mod.rs @@ -211,35 +211,52 @@ impl Data { } } - for (site_id, site) in this.sites.iter() - // TODO: Stupid - .filter(|(_, site)| site.world_site.map_or(false, |ws| - matches!(&index.sites.get(ws).kind, SiteKind::Dungeon(_)))) - { + for (site_id, site) in this.sites.iter() { let rand_wpos = |rng: &mut SmallRng| { - let wpos2d = site.wpos.map(|e| e + rng.gen_range(-10..10)); + // don't spawn in buildings + let spread_factor = rng.gen_range(-3..3) * 50; + let spread = if spread_factor == 0 { + 100 + } else { + spread_factor + }; + let wpos2d = site.wpos.map(|e| e + spread); wpos2d .map(|e| e as f32 + 0.5) .with_z(world.sim().get_alt_approx(wpos2d).unwrap_or(0.0)) }; - - let species = [ - comp::body::bird_large::Species::Phoenix, - comp::body::bird_large::Species::Cockatrice, - comp::body::bird_large::Species::Roc, - comp::body::bird_large::Species::FlameWyvern, - comp::body::bird_large::Species::CloudWyvern, - comp::body::bird_large::Species::FrostWyvern, - comp::body::bird_large::Species::SeaWyvern, - comp::body::bird_large::Species::WealdWyvern, + let site_kind = site.world_site.map(|ws| &index.sites.get(ws).kind); + let Some(species) = [ + Some(comp::body::bird_large::Species::Phoenix) + .filter(|_| matches!(site_kind, Some(SiteKind::Dungeon(_)))), + Some(comp::body::bird_large::Species::Cockatrice) + .filter(|_| matches!(site_kind, Some(SiteKind::Dungeon(_)))), + Some(comp::body::bird_large::Species::Roc) + .filter(|_| matches!(site_kind, Some(SiteKind::Dungeon(_)))), + Some(comp::body::bird_large::Species::FlameWyvern) + .filter(|_| matches!(site_kind, Some(SiteKind::Dungeon(_)))), + Some(comp::body::bird_large::Species::CloudWyvern) + .filter(|_| matches!(site_kind, Some(SiteKind::Dungeon(_)))), + Some(comp::body::bird_large::Species::FrostWyvern) + .filter(|_| matches!(site_kind, Some(SiteKind::Adlet(_)))), + Some(comp::body::bird_large::Species::SeaWyvern) + .filter(|_| matches!(site_kind, Some(SiteKind::ChapelSite(_)))), + Some(comp::body::bird_large::Species::WealdWyvern) + .filter(|_| matches!(site_kind, Some(SiteKind::GiantTree(_)))), ] - .choose(&mut rng) - .unwrap(); + .into_iter() + .flatten() + .choose(&mut rng) else { + continue; + }; + this.npcs.create_npc( Npc::new( rng.gen(), rand_wpos(&mut rng), - Body::BirdLarge(comp::body::bird_large::Body::random_with(&mut rng, species)), + Body::BirdLarge(comp::body::bird_large::Body::random_with( + &mut rng, &species, + )), Role::Wild, ) .with_home(site_id), diff --git a/rtsim/src/rule/npc_ai.rs b/rtsim/src/rule/npc_ai.rs index a1d6f39d65..8609dea34a 100644 --- a/rtsim/src/rule/npc_ai.rs +++ b/rtsim/src/rule/npc_ai.rs @@ -16,7 +16,7 @@ use crate::{ use common::{ astar::{Astar, PathResult}, comp::{ - self, + self, bird_large, compass::{Direction, Distance}, dialogue::Subject, Content, @@ -1210,15 +1210,21 @@ fn bird_large() -> impl Action { .map(|e| e + ctx.rng.gen_range(-0.1..0.1)) .try_normalized() .unwrap_or_default(); - let bearing_dist = 24.0; + let bearing_dist = 15.0; let mut pos = ctx.npc.wpos.xy() + *bearing * bearing_dist; - let is_deep_water = ctx - .world - .sim() - .get(pos.as_().wpos_to_cpos()) - .map_or(true, |c| { - c.alt - c.water_alt < -120.0 && (c.river.is_ocean() || c.river.is_lake()) - }); + let is_deep_water = + matches!(ctx.npc.body, common::comp::Body::BirdLarge(b) if matches!(b.species, bird_large::Species::SeaWyvern)) + || ctx + .world + .sim() + .get(pos.as_().wpos_to_cpos()) + .map_or(true, |c| { + c.alt - c.water_alt < -120.0 && (c.river.is_ocean() || c.river.is_lake()) + }); + if is_deep_water { + *bearing *= -1.0; + pos = ctx.npc.wpos.xy() + *bearing * bearing_dist; + }; // when high tree_density fly high, otherwise fly low-mid let npc_pos = ctx.npc.wpos.xy(); let trees = ctx @@ -1231,44 +1237,65 @@ fn bird_large() -> impl Action { } else { ctx.rng.gen_range(0.4..0.9) }; - if !is_deep_water { - goto_2d_flying( - pos, - 0.1, - bearing_dist, - 8.0, - 8.0, - ctx.npc.body.flying_height() * height_factor, - ) - } else { - *bearing *= -1.0; - - pos = ctx.npc.wpos.xy() + *bearing * 24.0; - - goto_2d_flying( - pos, - 0.1, - bearing_dist, - 8.0, - 8.0, - ctx.npc.body.flying_height() * height_factor, - ) + let data = ctx.state.data(); + // without destination site fly to next waypoint + let mut dest_site = pos; + if let Some(home) = ctx.npc.home { + let is_home = ctx.npc.current_site.map_or(false, |site| home == site); + if is_home { + if let Some((id, _)) = data + .sites + .iter() + .filter(|(id, site)| { + *id != home + && site.world_site.map_or(false, |site| { + match ctx.npc.body { + common::comp::Body::BirdLarge(b) => match b.species { + bird_large::Species::SeaWyvern => matches!(&ctx.index.sites.get(site).kind, SiteKind::ChapelSite(_)), + bird_large::Species::FrostWyvern => matches!(&ctx.index.sites.get(site).kind, SiteKind::Adlet(_)), + bird_large::Species::WealdWyvern => matches!(&ctx.index.sites.get(site).kind, SiteKind::GiantTree(_)), + _ => matches!(&ctx.index.sites.get(site).kind, SiteKind::Dungeon(_)), + }, + _ => matches!(&ctx.index.sites.get(site).kind, SiteKind::Dungeon(_)), + } + }) + }) + /*choose closest destination: + .min_by_key(|(_, site)| site.wpos.as_().distance(npc_pos) as i32)*/ + //choose random destination: + .choose(&mut ctx.rng) + { + ctx.controller.set_new_home(id) + } + } else if let Some(site) = data.sites.get(home) { + dest_site = site.wpos.as_::() + } } - // If we are too far away from our goal position we can stop since we aren't going to a specific place. + goto_2d_flying( + pos, + 0.2, + bearing_dist, + 8.0, + 8.0, + ctx.npc.body.flying_height() * height_factor, + ) + // If we are too far away from our waypoint position we can stop since we aren't going to a specific place. + // If waypoint position is further away from destination site find a new waypoint .stop_if(move |ctx: &mut NpcCtx| { ctx.npc.wpos.xy().distance_squared(pos) > (bearing_dist + 5.0).powi(2) + || dest_site.distance_squared(pos) > dest_site.distance_squared(npc_pos) }) - // If goal position wasn't reached within 20 seconds we're probably stuck and need to find a new goal position. + // If waypoint position wasn't reached within 10 seconds we're probably stuck and need to find a new waypoint. .stop_if(timeout(10.0)) .debug({ let bearing = *bearing; move || format!("Moving with a bearing of {:?}", bearing) }) }) - .repeat() - .with_state(Vec2::::zero()) - .map(|_, _| ()) + .repeat() + .with_state(Vec2::::zero()) + .map(|_, _| ()) } fn monster() -> impl Action { diff --git a/server/agent/src/action_nodes.rs b/server/agent/src/action_nodes.rs index edda8417bb..abf3ab97e3 100644 --- a/server/agent/src/action_nodes.rs +++ b/server/agent/src/action_nodes.rs @@ -1075,6 +1075,7 @@ impl<'a> AgentData<'a> { | "Gnarling Totem Red" | "Gnarling Totem Green" | "Gnarling Totem White" => Tactic::RadialTurret, + "FieryTornado" => Tactic::FieryTornado, "Yeti" => Tactic::Yeti, "Harvester" => Tactic::Harvester, "Cardinal" => Tactic::Cardinal, @@ -1500,6 +1501,7 @@ impl<'a> AgentData<'a> { read_data, ), Tactic::RadialTurret => self.handle_radial_turret_attack(controller), + Tactic::FieryTornado => self.handle_fiery_tornado_attack(agent, controller), Tactic::Yeti => { self.handle_yeti_attack(agent, controller, &attack_data, tgt_data, read_data) }, diff --git a/server/agent/src/attack.rs b/server/agent/src/attack.rs index e5e885d695..c3e5bf1bc7 100644 --- a/server/agent/src/attack.rs +++ b/server/agent/src/attack.rs @@ -2678,6 +2678,23 @@ impl<'a> AgentData<'a> { controller.push_basic_input(InputKind::Primary); } + pub fn handle_fiery_tornado_attack(&self, agent: &mut Agent, controller: &mut Controller) { + enum Conditions { + AuraEmited = 0, + } + if matches!(self.char_state, CharacterState::BasicAura(c) if matches!(c.stage_section, StageSection::Recover)) + { + agent.combat_state.conditions[Conditions::AuraEmited as usize] = true; + } + // 1 time use of aura + if !agent.combat_state.conditions[Conditions::AuraEmited as usize] { + controller.push_basic_input(InputKind::Secondary); + } else { + // Spin + controller.push_basic_input(InputKind::Primary); + } + } + pub fn handle_mindflayer_attack( &self, agent: &mut Agent, @@ -2975,106 +2992,141 @@ impl<'a> AgentData<'a> { read_data: &ReadData, _rng: &mut impl Rng, ) { - enum ActionStateTimers { - AttackTimer = 0, + const PHOENIX_HEAL_THRESHOLD: f32 = 0.20; + + enum Conditions { + Healed = 0, } - // Set fly to false - controller.push_cancel_input(InputKind::Fly); - if attack_data.dist_sqrd > 30.0_f32.powi(2) { - if entities_have_line_of_sight( - self.pos, - self.body, - self.scale, - tgt_data.pos, - tgt_data.body, - tgt_data.scale, - read_data, - ) && attack_data.angle < 15.0 + enum ActionStateTimers { + AttackTimer1, + AttackTimer2, + } + + let attack_timer_1 = + if agent.combat_state.timers[ActionStateTimers::AttackTimer1 as usize] < 2.0 { + 0 + } else if agent.combat_state.timers[ActionStateTimers::AttackTimer1 as usize] < 4.0 { + 1 + } else if agent.combat_state.timers[ActionStateTimers::AttackTimer1 as usize] < 6.0 { + 2 + } else { + 3 + }; + agent.combat_state.timers[ActionStateTimers::AttackTimer1 as usize] += read_data.dt.0; + if agent.combat_state.timers[ActionStateTimers::AttackTimer1 as usize] > 8.0 { + // Reset timer + agent.combat_state.timers[ActionStateTimers::AttackTimer1 as usize] = 0.0; + } + let (attack_timer_2, speed) = + if agent.combat_state.timers[ActionStateTimers::AttackTimer2 as usize] < 3.0 { + // fly high + (0, 2.0) + } else if agent.combat_state.timers[ActionStateTimers::AttackTimer2 as usize] < 6.0 { + // attack_mid_1 + (1, 2.0) + } else if agent.combat_state.timers[ActionStateTimers::AttackTimer2 as usize] < 9.0 { + // fly high + (0, 3.0) + } else if agent.combat_state.timers[ActionStateTimers::AttackTimer2 as usize] < 16.0 { + // attack_mid_2 + (2, 1.0) + } else if agent.combat_state.timers[ActionStateTimers::AttackTimer2 as usize] < 20.0 { + // fly low + (5, 20.0) + } else { + // attack_close + (3, 1.0) + }; + agent.combat_state.timers[ActionStateTimers::AttackTimer2 as usize] += read_data.dt.0; + if agent.combat_state.timers[ActionStateTimers::AttackTimer2 as usize] > 28.0 { + // Reset timer + agent.combat_state.timers[ActionStateTimers::AttackTimer2 as usize] = 0.0; + } + // Fly to target + let dir_to_target = ((tgt_data.pos.0 + Vec3::unit_z() * 1.5) - self.pos.0) + .try_normalized() + .unwrap_or_else(Vec3::zero); + controller.inputs.move_dir = dir_to_target.xy() * speed; + + // Always fly! If the floor can't touch you, it can't hurt you... + controller.push_basic_input(InputKind::Fly); + // Flee from the ground! The internet told me it was lava! + // If on the ground, jump with every last ounce of energy, holding onto + // all that is dear in life and straining for the wide open skies. + if self.physics_state.on_ground.is_some() { + controller.push_basic_input(InputKind::Jump); + } else { + // Use a proportional controller with a coefficient of 1.0 to + // maintain altidude at the the provided set point + let mut maintain_altitude = |set_point| { + let alt = read_data + .terrain + .ray(self.pos.0, self.pos.0 - (Vec3::unit_z() * 7.0)) + .until(Block::is_solid) + .cast() + .0; + let error = set_point - alt; + controller.inputs.move_z = error; + }; + // heal once - from_the_ashes + let health_fraction = self.health.map_or(0.5, |h| h.fraction()); + if matches!(self.char_state, CharacterState::SelfBuff(c) if matches!(c.stage_section, StageSection::Recover)) { - controller.push_basic_input(InputKind::Primary); + agent.combat_state.conditions[Conditions::Healed as usize] = true; } - if let Some((bearing, speed)) = agent.chaser.chase( - &*read_data.terrain, - self.pos.0, - self.vel.0, - tgt_data.pos.0, - TraversalConfig { - min_tgt_dist: 1.25, - ..self.traversal_config - }, - ) { - controller.inputs.move_dir = - bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed; - if (self.pos.0.z - tgt_data.pos.0.z) < 20.0 { - controller.push_basic_input(InputKind::Fly); - controller.inputs.move_z = 1.0; + if !agent.combat_state.conditions[Conditions::Healed as usize] + && PHOENIX_HEAL_THRESHOLD > health_fraction + { + controller.push_basic_input(InputKind::Ability(4)); + } else if (tgt_data.pos.0 - self.pos.0).xy().magnitude_squared() > (35.0_f32).powi(2) { + // heat laser + maintain_altitude(2.0); + controller.push_basic_input(InputKind::Ability(3)) + } else { + match attack_timer_2 { + 0 => maintain_altitude(3.0), + 1 => { + //summontornados + controller.push_basic_input(InputKind::Ability(1)); + }, + 2 => { + // firerain + controller.push_basic_input(InputKind::Ability(2)); + }, + 3 => { + if attack_data.dist_sqrd < 4.0_f32.powi(2) && attack_data.angle < 150.0 { + // close range attack + match attack_timer_1 { + 1 => { + // short strike + controller.push_basic_input(InputKind::Primary); + }, + 3 => { + // long strike + controller.push_basic_input(InputKind::Secondary) + }, + _ => { + // leg strike + controller.push_basic_input(InputKind::Ability(0)) + }, + } + } else { + match attack_timer_1 { + 0 | 2 => { + maintain_altitude(2.0); + }, + _ => { + // heat laser + controller.push_basic_input(InputKind::Ability(3)) + }, + } + } + }, + _ => { + maintain_altitude(2.0); + }, } } - } else if !read_data - .terrain - .ray(self.pos.0, self.pos.0 - (Vec3::unit_z() * 2.0)) - .until(Block::is_solid) - .cast() - .1 - .map_or(true, |b| b.is_some()) - { - // Do not increment the timer during this movement - // The next stage shouldn't trigger until the entity - // is on the ground - controller.push_basic_input(InputKind::Fly); - let move_dir = tgt_data.pos.0 - self.pos.0; - controller.inputs.move_dir = - move_dir.xy().try_normalized().unwrap_or_else(Vec2::zero) * 2.0; - controller.inputs.move_z = move_dir.z - 0.5; - if attack_data.dist_sqrd > (4.0 * attack_data.min_attack_dist).powi(2) - && attack_data.angle < 15.0 - { - controller.push_basic_input(InputKind::Primary); - } - } else if attack_data.dist_sqrd > (3.0 * attack_data.min_attack_dist).powi(2) { - self.path_toward_target( - agent, - controller, - tgt_data.pos.0, - read_data, - Path::Separate, - None, - ); - } else if self.energy.current() > 60.0 - && agent.combat_state.timers[ActionStateTimers::AttackTimer as usize] < 3.0 - && attack_data.angle < 15.0 - { - // Shockwave - controller.push_basic_input(InputKind::Ability(0)); - // Move towards the target slowly - self.path_toward_target( - agent, - controller, - tgt_data.pos.0, - read_data, - Path::Separate, - Some(0.5), - ); - agent.combat_state.timers[ActionStateTimers::AttackTimer as usize] += read_data.dt.0; - } else if agent.combat_state.timers[ActionStateTimers::AttackTimer as usize] < 6.0 - && attack_data.angle < 90.0 - && attack_data.in_min_range() - { - // Triple strike - controller.push_basic_input(InputKind::Secondary); - agent.combat_state.timers[ActionStateTimers::AttackTimer as usize] += read_data.dt.0; - } else { - // Reset timer - agent.combat_state.timers[ActionStateTimers::AttackTimer as usize] = 0.0; - // Target is behind us or the timer needs to be reset. Chase target - self.path_toward_target( - agent, - controller, - tgt_data.pos.0, - read_data, - Path::Separate, - None, - ); } } diff --git a/server/agent/src/data.rs b/server/agent/src/data.rs index f521704f5d..e14ce4d889 100755 --- a/server/agent/src/data.rs +++ b/server/agent/src/data.rs @@ -176,6 +176,7 @@ pub enum Tactic { FixedTurret, RotatingTurret, RadialTurret, + FieryTornado, SimpleDouble, // u8s are weights that each ability gets used, if it can be used RandomAbilities { diff --git a/server/src/rtsim/tick.rs b/server/src/rtsim/tick.rs index fc8184bdb8..7a997be35b 100644 --- a/server/src/rtsim/tick.rs +++ b/server/src/rtsim/tick.rs @@ -162,7 +162,7 @@ fn get_npc_entity_info(npc: &Npc, sites: &Sites, index: IndexRef) -> EntityInfo } else { let config_asset = match npc.body { Body::BirdLarge(body) => match body.species { - comp::bird_large::Species::Phoenix => "common.entity.wild.peaceful.phoenix", + comp::bird_large::Species::Phoenix => "common.entity.wild.aggressive.phoenix", comp::bird_large::Species::Cockatrice => "common.entity.wild.aggressive.cockatrice", comp::bird_large::Species::Roc => "common.entity.wild.aggressive.roc", comp::bird_large::Species::CloudWyvern => { diff --git a/voxygen/anim/src/bird_large/aura.rs b/voxygen/anim/src/bird_large/aura.rs new file mode 100644 index 0000000000..3f007630da --- /dev/null +++ b/voxygen/anim/src/bird_large/aura.rs @@ -0,0 +1,93 @@ +use super::{ + super::{vek::*, Animation}, + BirdLargeSkeleton, SkeletonAttr, +}; +use common::states::utils::StageSection; + +pub struct AuraAnimation; + +impl Animation for AuraAnimation { + type Dependency<'a> = (Option, bool); + type Skeleton = BirdLargeSkeleton; + + #[cfg(feature = "use-dyn-lib")] + const UPDATE_FN: &'static [u8] = b"bird_large_aura\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "bird_large_aura")] + fn update_skeleton_inner( + skeleton: &Self::Skeleton, + (stage_section, on_ground): Self::Dependency<'_>, + anim_time: f32, + _rate: &mut f32, + s_a: &SkeletonAttr, + ) -> Self::Skeleton { + let mut next = (*skeleton).clone(); + + let (movement1base, movement2base, movement3, _twitch) = match stage_section { + Some(StageSection::Buildup) => (anim_time.powf(0.5), 0.0, 0.0, 0.0), + Some(StageSection::Action) => (1.0, anim_time.min(1.0).powf(0.1), 0.0, anim_time), + Some(StageSection::Recover) => (1.0, 1.0, anim_time, 1.0), + _ => (0.0, 0.0, 0.0, 0.0), + }; + + let pullback = 1.0 - movement3; + + let movement1abs = movement1base * pullback; + let _movement2abs = movement2base * pullback; + + let wave_slow_cos = (anim_time * 4.5).cos(); + + next.chest.position = Vec3::new(0.0, s_a.chest.0, s_a.chest.1 + movement1abs * 1.5); + next.chest.orientation = Quaternion::rotation_x(movement1abs * 1.0); + + next.neck.position = Vec3::new(0.0, s_a.neck.0, s_a.neck.1); + next.neck.orientation = Quaternion::rotation_x(-0.2); + + next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1); + next.head.orientation = Quaternion::rotation_x(wave_slow_cos * 0.01); + + next.beak.position = Vec3::new(0.0, s_a.beak.0, s_a.beak.1); + next.beak.orientation = Quaternion::rotation_x(wave_slow_cos * -0.02 - 0.02); + + next.tail_front.position = Vec3::new(0.0, s_a.tail_front.0, s_a.tail_front.1); + next.tail_front.orientation = Quaternion::rotation_x(0.6); + next.tail_rear.position = Vec3::new(0.0, s_a.tail_rear.0, s_a.tail_rear.1); + next.tail_rear.orientation = Quaternion::rotation_x(-0.2); + + if on_ground { + next.wing_in_l.position = Vec3::new(-s_a.wing_in.0, s_a.wing_in.1, s_a.wing_in.2); + next.wing_in_r.position = Vec3::new(s_a.wing_in.0, s_a.wing_in.1, s_a.wing_in.2); + + next.wing_in_l.orientation = Quaternion::rotation_y(-0.8 + movement1abs * 1.5) + * Quaternion::rotation_z(0.2 + movement1abs * -0.8); + next.wing_in_r.orientation = Quaternion::rotation_y(0.8 + movement1abs * -1.5) + * Quaternion::rotation_z(-0.2 + movement1abs * 0.8); + + next.wing_mid_l.position = Vec3::new(-s_a.wing_mid.0, s_a.wing_mid.1, s_a.wing_mid.2); + next.wing_mid_r.position = Vec3::new(s_a.wing_mid.0, s_a.wing_mid.1, s_a.wing_mid.2); + next.wing_mid_l.orientation = + Quaternion::rotation_y(-0.1) * Quaternion::rotation_z(0.7); + next.wing_mid_r.orientation = + Quaternion::rotation_y(0.1) * Quaternion::rotation_z(-0.7); + + next.wing_out_l.position = Vec3::new(-s_a.wing_out.0, s_a.wing_out.1, s_a.wing_out.2); + next.wing_out_r.position = Vec3::new(s_a.wing_out.0, s_a.wing_out.1, s_a.wing_out.2); + next.wing_out_l.orientation = + Quaternion::rotation_y(-0.4) * Quaternion::rotation_z(0.2); + next.wing_out_r.orientation = + Quaternion::rotation_y(0.4) * Quaternion::rotation_z(-0.2); + + next.leg_l.position = Vec3::new(-s_a.leg.0, s_a.leg.1, s_a.leg.2); + next.leg_l.orientation = Quaternion::rotation_x(0.0); + next.leg_r.position = Vec3::new(s_a.leg.0, s_a.leg.1, s_a.leg.2); + next.leg_r.orientation = Quaternion::rotation_x(0.0); + + next.foot_l.position = Vec3::new(-s_a.foot.0, s_a.foot.1, s_a.foot.2); + next.foot_l.orientation = Quaternion::rotation_x(0.0); + next.foot_r.position = Vec3::new(s_a.foot.0, s_a.foot.1, s_a.foot.2); + next.foot_r.orientation = Quaternion::rotation_x(0.0); + } + + next + } +} diff --git a/voxygen/anim/src/bird_large/breathe.rs b/voxygen/anim/src/bird_large/breathe.rs index 3fa8c8b31b..cbd2371493 100644 --- a/voxygen/anim/src/bird_large/breathe.rs +++ b/voxygen/anim/src/bird_large/breathe.rs @@ -6,7 +6,7 @@ use common::{states::utils::StageSection, util::Dir}; pub struct BreatheAnimation; -type BreatheAnimationDependency = ( +type BreatheAnimationDependency<'a> = ( Vec3, f32, Vec3, @@ -15,10 +15,11 @@ type BreatheAnimationDependency = ( f32, Dir, bool, + Option<&'a str>, ); impl Animation for BreatheAnimation { - type Dependency<'a> = BreatheAnimationDependency; + type Dependency<'a> = BreatheAnimationDependency<'a>; type Skeleton = BirdLargeSkeleton; #[cfg(feature = "use-dyn-lib")] @@ -27,7 +28,17 @@ impl Animation for BreatheAnimation { #[cfg_attr(feature = "be-dyn-lib", export_name = "bird_large_breathe")] fn update_skeleton_inner( skeleton: &Self::Skeleton, - (velocity,global_time, _orientation, _last_ori, stage_section, timer, look_dir, on_ground): Self::Dependency<'_>, + ( + velocity, + global_time, + _orientation, + _last_ori, + stage_section, + timer, + look_dir, + on_ground, + ability_id, + ): Self::Dependency<'_>, anim_time: f32, _rate: &mut f32, s_a: &SkeletonAttr, @@ -106,18 +117,38 @@ impl Animation for BreatheAnimation { next.tail_rear.orientation = Quaternion::rotation_x(-movement1abs * 0.1 + movement2abs * 0.1 + twitch2 * -0.2); } else { - next.neck.orientation = Quaternion::rotation_x( - movement1abs * -0.4 - + movement2abs * (-0.5 + velocity.xy().magnitude() * 0.2).min(0.0), - ); + match ability_id { + Some("common.abilities.custom.birdlargefire.heat_laser") => { + next.chest.orientation = Quaternion::rotation_x( + movement1abs * 0.2 - movement2abs * 0.5 + twitch2 * 0.03, + ); + next.chest.position = + Vec3::new(0.0, s_a.chest.0, s_a.chest.1 + movement2abs * -3.0); + next.neck.orientation = Quaternion::rotation_x( + movement1abs * -0.4 + + movement2abs * (-0.5 + velocity.xy().magnitude() * 0.2).min(0.0), + ); - next.head.orientation = Quaternion::rotation_x( - movement1abs * 0.5 - + movement2abs * (-0.5 + velocity.xy().magnitude() * 0.2).min(0.0) - + look_dir.z * 0.4, - ); + next.head.orientation = Quaternion::rotation_x( + movement1abs * 0.5 + + movement2abs * (-0.5 + velocity.xy().magnitude() * 0.2).min(0.0) + + look_dir.z * 0.4, + ); + }, + _ => { + next.neck.orientation = Quaternion::rotation_x( + movement1abs * -0.4 + + movement2abs * (-0.5 + velocity.xy().magnitude() * 0.2).min(0.0), + ); + + next.head.orientation = Quaternion::rotation_x( + movement1abs * 0.5 + + movement2abs * (-0.5 + velocity.xy().magnitude() * 0.2).min(0.0) + + look_dir.z * 0.4, + ); + }, + }; } - next } } diff --git a/voxygen/anim/src/bird_large/combomelee.rs b/voxygen/anim/src/bird_large/combomelee.rs index 8461f845aa..2f13a1c7cf 100644 --- a/voxygen/anim/src/bird_large/combomelee.rs +++ b/voxygen/anim/src/bird_large/combomelee.rs @@ -25,7 +25,7 @@ impl Animation for ComboAnimation { fn update_skeleton_inner( skeleton: &Self::Skeleton, ( - _ability_id, + ability_id, stage_section, current_strike, global_time, @@ -79,67 +79,86 @@ impl Animation for ComboAnimation { } * 1.3; for strike in 0..=current_strike { - match strike { - 0..=2 => { - next.chest.position = Vec3::new( - 0.0, - s_a.chest.0, - s_a.chest.1 + wave_slow_cos * 0.06 + move2 * -6.0, - ); - next.chest.orientation = Quaternion::rotation_x(move1 * 0.5 - move2 * 0.8); - - next.neck.position = Vec3::new(0.0, s_a.neck.0, s_a.neck.1); - next.neck.orientation = Quaternion::rotation_x(move1 * 0.5 - move2 * 0.4) - * Quaternion::rotation_z(move1 * tilt * 1.5) - * Quaternion::rotation_y(move1mirror * 0.3); - - next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1); - next.head.orientation = Quaternion::rotation_x(move1 * -0.2 - move2 * 0.2) - * Quaternion::rotation_y(move1mirror * 0.5); - - next.beak.position = Vec3::new(0.0, s_a.beak.0, s_a.beak.1); - next.beak.orientation = - Quaternion::rotation_x(wave_slow_cos * -0.02 + move1 * -0.5 + move2 * 0.5); - - if on_ground { - next.tail_front.position = - Vec3::new(0.0, s_a.tail_front.0, s_a.tail_front.1); - next.tail_front.orientation = Quaternion::rotation_x(-move1 * 0.2); - next.tail_rear.position = Vec3::new(0.0, s_a.tail_rear.0, s_a.tail_rear.1); - next.tail_rear.orientation = Quaternion::rotation_x(0.0); - - next.wing_in_l.position = - Vec3::new(-s_a.wing_in.0, s_a.wing_in.1, s_a.wing_in.2); - next.wing_in_r.position = - Vec3::new(s_a.wing_in.0, s_a.wing_in.1, s_a.wing_in.2); - - next.wing_in_l.orientation = Quaternion::rotation_y( - -1.0 + wave_slow_cos * 0.06 + move1 * 1.0 + move2 * 0.5, - ) * Quaternion::rotation_z(0.2); - next.wing_in_r.orientation = Quaternion::rotation_y( - 1.0 - wave_slow_cos * 0.06 + move1 * -1.0 + move2 * -0.5, - ) * Quaternion::rotation_z(-0.2); - - next.wing_mid_l.position = - Vec3::new(-s_a.wing_mid.0, s_a.wing_mid.1, s_a.wing_mid.2); - next.wing_mid_r.position = - Vec3::new(s_a.wing_mid.0, s_a.wing_mid.1, s_a.wing_mid.2); - next.wing_mid_l.orientation = Quaternion::rotation_y(-0.1 + move1 * -0.5) - * Quaternion::rotation_z(0.7 + move1 * -0.7); - next.wing_mid_r.orientation = Quaternion::rotation_y(0.1 + move1 * 0.5) - * Quaternion::rotation_z(-0.7 + move1 * 0.7); - - next.wing_out_l.position = - Vec3::new(-s_a.wing_out.0, s_a.wing_out.1, s_a.wing_out.2); - next.wing_out_r.position = - Vec3::new(s_a.wing_out.0, s_a.wing_out.1, s_a.wing_out.2); - next.wing_out_l.orientation = Quaternion::rotation_y(-0.2 + move1 * -0.3) - * Quaternion::rotation_z(0.2); - next.wing_out_r.orientation = Quaternion::rotation_y(0.2 + move1 * 0.3) - * Quaternion::rotation_z(-0.2); - } + match ability_id { + Some("common.abilities.custom.birdlargefire.legstrike") => match strike { + 0..=2 => { + next.chest.position = Vec3::new( + 0.0, + s_a.chest.0, + s_a.chest.1 + wave_slow_cos * 0.06 + move2 * -6.0, + ); + next.chest.orientation = Quaternion::rotation_x(move1 * 0.5 - move2 * 0.8); + next.leg_r.orientation = Quaternion::rotation_x(move1 * 1.5 + move2 * -2.5); + next.leg_l.orientation = Quaternion::rotation_x(move1 * 1.5 + move2 * -2.5); + }, + _ => {}, + }, + _ => match strike { + 0..=2 => { + next.chest.position = Vec3::new( + 0.0, + s_a.chest.0, + s_a.chest.1 + wave_slow_cos * 0.06 + move2 * -6.0, + ); + next.chest.orientation = Quaternion::rotation_x(move1 * 0.5 - move2 * 0.8); + + next.neck.position = Vec3::new(0.0, s_a.neck.0, s_a.neck.1); + next.neck.orientation = Quaternion::rotation_x(move1 * 0.5 - move2 * 0.4) + * Quaternion::rotation_z(move1 * tilt * 1.5) + * Quaternion::rotation_y(move1mirror * 0.3); + + next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1); + next.head.orientation = Quaternion::rotation_x(move1 * -0.2 - move2 * 0.2) + * Quaternion::rotation_y(move1mirror * 0.5); + + next.beak.position = Vec3::new(0.0, s_a.beak.0, s_a.beak.1); + next.beak.orientation = Quaternion::rotation_x( + wave_slow_cos * -0.02 + move1 * -0.5 + move2 * 0.5, + ); + + if on_ground { + next.tail_front.position = + Vec3::new(0.0, s_a.tail_front.0, s_a.tail_front.1); + next.tail_front.orientation = Quaternion::rotation_x(-move1 * 0.2); + next.tail_rear.position = + Vec3::new(0.0, s_a.tail_rear.0, s_a.tail_rear.1); + next.tail_rear.orientation = Quaternion::rotation_x(0.0); + + next.wing_in_l.position = + Vec3::new(-s_a.wing_in.0, s_a.wing_in.1, s_a.wing_in.2); + next.wing_in_r.position = + Vec3::new(s_a.wing_in.0, s_a.wing_in.1, s_a.wing_in.2); + + next.wing_in_l.orientation = Quaternion::rotation_y( + -1.0 + wave_slow_cos * 0.06 + move1 * 1.0 + move2 * 0.5, + ) * Quaternion::rotation_z(0.2); + next.wing_in_r.orientation = Quaternion::rotation_y( + 1.0 - wave_slow_cos * 0.06 + move1 * -1.0 + move2 * -0.5, + ) * Quaternion::rotation_z(-0.2); + + next.wing_mid_l.position = + Vec3::new(-s_a.wing_mid.0, s_a.wing_mid.1, s_a.wing_mid.2); + next.wing_mid_r.position = + Vec3::new(s_a.wing_mid.0, s_a.wing_mid.1, s_a.wing_mid.2); + next.wing_mid_l.orientation = + Quaternion::rotation_y(-0.1 + move1 * -0.5) + * Quaternion::rotation_z(0.7 + move1 * -0.7); + next.wing_mid_r.orientation = Quaternion::rotation_y(0.1 + move1 * 0.5) + * Quaternion::rotation_z(-0.7 + move1 * 0.7); + + next.wing_out_l.position = + Vec3::new(-s_a.wing_out.0, s_a.wing_out.1, s_a.wing_out.2); + next.wing_out_r.position = + Vec3::new(s_a.wing_out.0, s_a.wing_out.1, s_a.wing_out.2); + next.wing_out_l.orientation = + Quaternion::rotation_y(-0.2 + move1 * -0.3) + * Quaternion::rotation_z(0.2); + next.wing_out_r.orientation = Quaternion::rotation_y(0.2 + move1 * 0.3) + * Quaternion::rotation_z(-0.2); + } + }, + _ => {}, }, - _ => {}, } } diff --git a/voxygen/anim/src/bird_large/mod.rs b/voxygen/anim/src/bird_large/mod.rs index 433ab53d01..2178bfc829 100644 --- a/voxygen/anim/src/bird_large/mod.rs +++ b/voxygen/anim/src/bird_large/mod.rs @@ -1,4 +1,5 @@ pub mod alpha; +pub mod aura; pub mod breathe; pub mod combomelee; pub mod dash; @@ -6,6 +7,7 @@ pub mod feed; pub mod fly; pub mod idle; pub mod run; +pub mod selfbuff; pub mod shockwave; pub mod shoot; pub mod stunned; @@ -14,10 +16,11 @@ pub mod swim; // Reexports pub use self::{ - alpha::AlphaAnimation, breathe::BreatheAnimation, combomelee::ComboAnimation, - dash::DashAnimation, feed::FeedAnimation, fly::FlyAnimation, idle::IdleAnimation, - run::RunAnimation, shockwave::ShockwaveAnimation, shoot::ShootAnimation, - stunned::StunnedAnimation, summon::SummonAnimation, swim::SwimAnimation, + alpha::AlphaAnimation, aura::AuraAnimation, breathe::BreatheAnimation, + combomelee::ComboAnimation, dash::DashAnimation, feed::FeedAnimation, fly::FlyAnimation, + idle::IdleAnimation, run::RunAnimation, selfbuff::SelfBuffAnimation, + shockwave::ShockwaveAnimation, shoot::ShootAnimation, stunned::StunnedAnimation, + summon::SummonAnimation, swim::SwimAnimation, }; use super::{make_bone, vek::*, FigureBoneData, Offsets, Skeleton}; diff --git a/voxygen/anim/src/bird_large/selfbuff.rs b/voxygen/anim/src/bird_large/selfbuff.rs new file mode 100644 index 0000000000..77062f4fbc --- /dev/null +++ b/voxygen/anim/src/bird_large/selfbuff.rs @@ -0,0 +1,99 @@ +use super::{ + super::{vek::*, Animation}, + BirdLargeSkeleton, SkeletonAttr, +}; +use common::states::utils::StageSection; + +pub struct SelfBuffAnimation; + +impl Animation for SelfBuffAnimation { + type Dependency<'a> = (Option, bool); + type Skeleton = BirdLargeSkeleton; + + #[cfg(feature = "use-dyn-lib")] + const UPDATE_FN: &'static [u8] = b"bird_large_selfbuff\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "bird_large_selfbuff")] + fn update_skeleton_inner( + skeleton: &Self::Skeleton, + (stage_section, on_ground): Self::Dependency<'_>, + anim_time: f32, + _rate: &mut f32, + s_a: &SkeletonAttr, + ) -> Self::Skeleton { + let mut next = (*skeleton).clone(); + + let (movement1base, movement2base, movement3, _twitch) = match stage_section { + Some(StageSection::Buildup) => (anim_time.powf(0.5), 0.0, 0.0, 0.0), + Some(StageSection::Action) => (1.0, anim_time.min(1.0).powf(0.1), 0.0, anim_time), + Some(StageSection::Recover) => (1.0, 1.0, anim_time, 1.0), + _ => (0.0, 0.0, 0.0, 0.0), + }; + + let pullback = 1.0 - movement3; + + let movement1abs = movement1base * pullback; + let movement2abs = movement2base * pullback; + + let wave_slow_cos = (anim_time * 4.5).cos(); + + next.chest.position = Vec3::new( + 0.0, + s_a.chest.0, + s_a.chest.1 + movement1abs * 2.5 + movement2abs * 2.5, + ); + next.chest.orientation = Quaternion::rotation_x(movement1abs * 1.0 + movement2abs * 1.0); + + next.neck.position = Vec3::new(0.0, s_a.neck.0, s_a.neck.1); + next.neck.orientation = Quaternion::rotation_x(-0.2); + + next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1); + next.head.orientation = Quaternion::rotation_x(wave_slow_cos * 0.01); + + next.beak.position = Vec3::new(0.0, s_a.beak.0, s_a.beak.1); + next.beak.orientation = Quaternion::rotation_x(wave_slow_cos * -0.02 - 0.02); + + next.tail_front.position = Vec3::new(0.0, s_a.tail_front.0, s_a.tail_front.1); + next.tail_front.orientation = Quaternion::rotation_x(0.6); + next.tail_rear.position = Vec3::new(0.0, s_a.tail_rear.0, s_a.tail_rear.1); + next.tail_rear.orientation = Quaternion::rotation_x(-0.2); + + if on_ground { + next.wing_in_l.position = Vec3::new(-s_a.wing_in.0, s_a.wing_in.1, s_a.wing_in.2); + next.wing_in_r.position = Vec3::new(s_a.wing_in.0, s_a.wing_in.1, s_a.wing_in.2); + + next.wing_in_l.orientation = + Quaternion::rotation_y(-0.8 + movement1abs * 1.6 + movement2abs * 1.6) + * Quaternion::rotation_z(0.2 + movement1abs * -0.8); + next.wing_in_r.orientation = + Quaternion::rotation_y(0.8 + movement1abs * -1.6 + movement2abs * -1.6) + * Quaternion::rotation_z(-0.2 + movement1abs * 0.8); + + next.wing_mid_l.position = Vec3::new(-s_a.wing_mid.0, s_a.wing_mid.1, s_a.wing_mid.2); + next.wing_mid_r.position = Vec3::new(s_a.wing_mid.0, s_a.wing_mid.1, s_a.wing_mid.2); + next.wing_mid_l.orientation = + Quaternion::rotation_y(-0.1) * Quaternion::rotation_z(0.7); + next.wing_mid_r.orientation = + Quaternion::rotation_y(0.1) * Quaternion::rotation_z(-0.7); + + next.wing_out_l.position = Vec3::new(-s_a.wing_out.0, s_a.wing_out.1, s_a.wing_out.2); + next.wing_out_r.position = Vec3::new(s_a.wing_out.0, s_a.wing_out.1, s_a.wing_out.2); + next.wing_out_l.orientation = + Quaternion::rotation_y(-0.4) * Quaternion::rotation_z(0.2); + next.wing_out_r.orientation = + Quaternion::rotation_y(0.4) * Quaternion::rotation_z(-0.2); + + next.leg_l.position = Vec3::new(-s_a.leg.0, s_a.leg.1, s_a.leg.2); + next.leg_l.orientation = Quaternion::rotation_x(0.0); + next.leg_r.position = Vec3::new(s_a.leg.0, s_a.leg.1, s_a.leg.2); + next.leg_r.orientation = Quaternion::rotation_x(0.0); + + next.foot_l.position = Vec3::new(-s_a.foot.0, s_a.foot.1, s_a.foot.2); + next.foot_l.orientation = Quaternion::rotation_x(0.0); + next.foot_r.position = Vec3::new(s_a.foot.0, s_a.foot.1, s_a.foot.2); + next.foot_r.orientation = Quaternion::rotation_x(0.0); + } + + next + } +} diff --git a/voxygen/anim/src/bird_large/shoot.rs b/voxygen/anim/src/bird_large/shoot.rs index ac23e85e40..8d9289a97a 100644 --- a/voxygen/anim/src/bird_large/shoot.rs +++ b/voxygen/anim/src/bird_large/shoot.rs @@ -6,10 +6,18 @@ use common::{states::utils::StageSection, util::Dir}; pub struct ShootAnimation; -type ShootAnimationDependency = (Vec3, f32, Option, f32, Dir, bool); +type ShootAnimationDependency<'a> = ( + Vec3, + f32, + Option, + f32, + Dir, + bool, + Option<&'a str>, +); impl Animation for ShootAnimation { - type Dependency<'a> = ShootAnimationDependency; + type Dependency<'a> = ShootAnimationDependency<'a>; type Skeleton = BirdLargeSkeleton; #[cfg(feature = "use-dyn-lib")] @@ -18,73 +26,149 @@ impl Animation for ShootAnimation { #[cfg_attr(feature = "be-dyn-lib", export_name = "bird_large_shoot")] fn update_skeleton_inner( skeleton: &Self::Skeleton, - (velocity, global_time, stage_section, timer, look_dir, on_ground): Self::Dependency<'_>, + (velocity, global_time, stage_section, timer, look_dir, on_ground, ability_id, + ): Self::Dependency<'_>, anim_time: f32, _rate: &mut f32, s_a: &SkeletonAttr, ) -> Self::Skeleton { let mut next = (*skeleton).clone(); - let (movement1base, movement3, twitch) = match stage_section { - Some(StageSection::Buildup) => (anim_time.powf(0.25), 0.0, 0.0), - Some(StageSection::Recover) => (1.0, anim_time.powf(0.25), anim_time), - _ => (0.0, 0.0, 0.0), - }; + match ability_id { + Some("common.abilities.custom.birdlargefire.firerain") => { + let (movement1base, movement2base, movement3, _twitch) = match stage_section { + Some(StageSection::Buildup) => (anim_time.powf(0.5), 0.0, 0.0, 0.0), + Some(StageSection::Action) => { + (1.0, anim_time.min(1.0).powf(0.1), 0.0, anim_time) + }, + Some(StageSection::Recover) => (1.0, 1.0, anim_time, 1.0), + _ => (0.0, 0.0, 0.0, 0.0), + }; - let pullback = 1.0 - movement3; - let subtract = global_time - timer; - let check = subtract - subtract.trunc(); - let mirror = (check - 0.5).signum(); - let twitch2 = mirror * (twitch * 20.0).sin() * pullback; - let movement1abs = movement1base * pullback; - let movement1mirror = movement1abs * mirror; + let pullback = 1.0 - movement3; + let movement1abs = movement1base * pullback; + let _movement2abs = movement2base * pullback; + let wave_slow_cos = (anim_time * 4.5).cos(); - let wave_slow_cos = (anim_time * 4.5).cos(); + next.chest.position = Vec3::new(0.0, s_a.chest.0, s_a.chest.1 + movement1abs * 2.5); + next.chest.orientation = Quaternion::rotation_x(movement1abs * 1.0); - next.chest.position = Vec3::new( - 0.0, - s_a.chest.0, - s_a.chest.1 + wave_slow_cos * 0.06 + twitch2 * 0.1, - ); + next.neck.position = Vec3::new(0.0, s_a.neck.0, s_a.neck.1); + next.neck.orientation = Quaternion::rotation_x(-0.2); - next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1); - next.head.orientation = - Quaternion::rotation_x(movement1abs * 0.5 + look_dir.z * 0.4 + twitch2) - * Quaternion::rotation_y(movement1mirror * 0.5); + next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1); + next.head.orientation = Quaternion::rotation_x(wave_slow_cos * 0.01); - next.beak.position = Vec3::new(0.0, s_a.beak.0, s_a.beak.1); - next.beak.orientation = Quaternion::rotation_x(movement1abs * -0.7 + twitch2 * 0.1); + next.beak.position = Vec3::new(0.0, s_a.beak.0, s_a.beak.1); + next.beak.orientation = Quaternion::rotation_x(wave_slow_cos * -0.02 - 0.02); - if on_ground { - next.chest.position = Vec3::new( - 0.0, - s_a.chest.0, - s_a.chest.1 + wave_slow_cos * 0.06 + twitch2 * 0.1 + movement1abs * -3.0, - ); - next.neck.position = Vec3::new(0.0, s_a.neck.0, s_a.neck.1); - next.neck.orientation = Quaternion::rotation_x(movement1abs * 0.5) - * Quaternion::rotation_y(movement1mirror * 0.2); + next.tail_front.position = Vec3::new(0.0, s_a.tail_front.0, s_a.tail_front.1); + next.tail_front.orientation = Quaternion::rotation_x(0.6); + next.tail_rear.position = Vec3::new(0.0, s_a.tail_rear.0, s_a.tail_rear.1); + next.tail_rear.orientation = Quaternion::rotation_x(-0.2); - next.chest.orientation = Quaternion::rotation_x(movement1abs * 0.1); + if on_ground { + next.wing_in_l.position = + Vec3::new(-s_a.wing_in.0, s_a.wing_in.1, s_a.wing_in.2); + next.wing_in_r.position = + Vec3::new(s_a.wing_in.0, s_a.wing_in.1, s_a.wing_in.2); - next.tail_front.position = Vec3::new(0.0, s_a.tail_front.0, s_a.tail_front.1); - next.tail_front.orientation = - Quaternion::rotation_x(-movement1abs * 0.1 + twitch2 * 0.02); - next.tail_rear.position = Vec3::new(0.0, s_a.tail_rear.0, s_a.tail_rear.1); - next.tail_rear.orientation = - Quaternion::rotation_x(-movement1abs * 0.1 + twitch2 * 0.02); + next.wing_in_l.orientation = Quaternion::rotation_y(-0.8 + movement1abs * 1.6) + * Quaternion::rotation_z(0.2 + movement1abs * -0.8); + next.wing_in_r.orientation = Quaternion::rotation_y(0.8 + movement1abs * -1.6) + * Quaternion::rotation_z(-0.2 + movement1abs * 0.8); - next.leg_l.orientation = Quaternion::rotation_x(movement1abs * -0.5); - next.leg_r.orientation = Quaternion::rotation_x(movement1abs * -0.5); + next.wing_mid_l.position = + Vec3::new(-s_a.wing_mid.0, s_a.wing_mid.1, s_a.wing_mid.2); + next.wing_mid_r.position = + Vec3::new(s_a.wing_mid.0, s_a.wing_mid.1, s_a.wing_mid.2); + next.wing_mid_l.orientation = + Quaternion::rotation_y(-0.1) * Quaternion::rotation_z(0.7); + next.wing_mid_r.orientation = + Quaternion::rotation_y(0.1) * Quaternion::rotation_z(-0.7); - next.foot_l.orientation = Quaternion::rotation_x(movement1abs * 0.3); - next.foot_r.orientation = Quaternion::rotation_x(movement1abs * 0.3); - } - if velocity.xy().magnitude() < 1.0 { - next.wing_in_l.orientation = Quaternion::rotation_y(-1.0 + movement1abs * 0.8) - * Quaternion::rotation_z(0.2 - movement1abs * 0.8); - next.wing_in_r.orientation = Quaternion::rotation_y(1.0 - movement1abs * 0.8) - * Quaternion::rotation_z(-0.2 + movement1abs * 0.8); + next.wing_out_l.position = + Vec3::new(-s_a.wing_out.0, s_a.wing_out.1, s_a.wing_out.2); + next.wing_out_r.position = + Vec3::new(s_a.wing_out.0, s_a.wing_out.1, s_a.wing_out.2); + next.wing_out_l.orientation = + Quaternion::rotation_y(-0.4) * Quaternion::rotation_z(0.2); + next.wing_out_r.orientation = + Quaternion::rotation_y(0.4) * Quaternion::rotation_z(-0.2); + + next.leg_l.position = Vec3::new(-s_a.leg.0, s_a.leg.1, s_a.leg.2); + next.leg_l.orientation = Quaternion::rotation_x(0.0); + next.leg_r.position = Vec3::new(s_a.leg.0, s_a.leg.1, s_a.leg.2); + next.leg_r.orientation = Quaternion::rotation_x(0.0); + + next.foot_l.position = Vec3::new(-s_a.foot.0, s_a.foot.1, s_a.foot.2); + next.foot_l.orientation = Quaternion::rotation_x(0.0); + next.foot_r.position = Vec3::new(s_a.foot.0, s_a.foot.1, s_a.foot.2); + next.foot_r.orientation = Quaternion::rotation_x(0.0); + } + }, + _ => { + let (movement1base, movement3, twitch) = match stage_section { + Some(StageSection::Buildup) => (anim_time.powf(0.25), 0.0, 0.0), + Some(StageSection::Recover) => (1.0, anim_time.powf(0.25), anim_time), + _ => (0.0, 0.0, 0.0), + }; + + let pullback = 1.0 - movement3; + let subtract = global_time - timer; + let check = subtract - subtract.trunc(); + let mirror = (check - 0.5).signum(); + let twitch2 = mirror * (twitch * 20.0).sin() * pullback; + let movement1abs = movement1base * pullback; + let movement1mirror = movement1abs * mirror; + + let wave_slow_cos = (anim_time * 4.5).cos(); + next.chest.position = Vec3::new( + 0.0, + s_a.chest.0, + s_a.chest.1 + wave_slow_cos * 0.06 + twitch2 * 0.1, + ); + + next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1); + next.head.orientation = + Quaternion::rotation_x(movement1abs * 0.5 + look_dir.z * 0.4 + twitch2) + * Quaternion::rotation_y(movement1mirror * 0.5); + + next.beak.position = Vec3::new(0.0, s_a.beak.0, s_a.beak.1); + next.beak.orientation = Quaternion::rotation_x(movement1abs * -0.7 + twitch2 * 0.1); + + if on_ground { + next.chest.position = Vec3::new( + 0.0, + s_a.chest.0, + s_a.chest.1 + wave_slow_cos * 0.06 + twitch2 * 0.1 + movement1abs * -3.0, + ); + next.neck.position = Vec3::new(0.0, s_a.neck.0, s_a.neck.1); + next.neck.orientation = Quaternion::rotation_x(movement1abs * 0.5) + * Quaternion::rotation_y(movement1mirror * 0.2); + + next.chest.orientation = Quaternion::rotation_x(movement1abs * 0.1); + + next.tail_front.position = Vec3::new(0.0, s_a.tail_front.0, s_a.tail_front.1); + next.tail_front.orientation = + Quaternion::rotation_x(-movement1abs * 0.1 + twitch2 * 0.02); + next.tail_rear.position = Vec3::new(0.0, s_a.tail_rear.0, s_a.tail_rear.1); + next.tail_rear.orientation = + Quaternion::rotation_x(-movement1abs * 0.1 + twitch2 * 0.02); + + next.leg_l.orientation = Quaternion::rotation_x(movement1abs * -0.5); + next.leg_r.orientation = Quaternion::rotation_x(movement1abs * -0.5); + + next.foot_l.orientation = Quaternion::rotation_x(movement1abs * 0.3); + next.foot_r.orientation = Quaternion::rotation_x(movement1abs * 0.3); + } + if velocity.xy().magnitude() < 1.0 { + next.wing_in_l.orientation = Quaternion::rotation_y(-1.0 + movement1abs * 0.8) + * Quaternion::rotation_z(0.2 - movement1abs * 0.8); + next.wing_in_r.orientation = Quaternion::rotation_y(1.0 - movement1abs * 0.8) + * Quaternion::rotation_z(-0.2 + movement1abs * 0.8); + } + }, }; next } diff --git a/voxygen/i18n-helpers/src/lib.rs b/voxygen/i18n-helpers/src/lib.rs index b4196b644f..41c8f787e2 100644 --- a/voxygen/i18n-helpers/src/lib.rs +++ b/voxygen/i18n-helpers/src/lib.rs @@ -137,7 +137,8 @@ pub fn localize_chat_message( | BuffKind::Poisoned | BuffKind::Parried | BuffKind::PotionSickness - | BuffKind::Polymorphed => { + | BuffKind::Polymorphed + | BuffKind::Heatstroke => { tracing::error!("Player was killed by a debuff that doesn't do damage!"); "mysterious" }, diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index 5e0bc4a5ce..4fccce9e88 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -186,6 +186,7 @@ pub enum SfxEvent { GroundDig, PortalActivated, TeleportedByPortal, + FromTheAshes, } #[derive(Copy, Clone, Debug, PartialEq, Deserialize, Hash, Eq)] @@ -219,6 +220,7 @@ pub enum VoiceKind { Truffler, Wolf, Wyvern, + Phoenix, } fn body_to_voice(body: &Body) -> Option { @@ -274,6 +276,7 @@ fn body_to_voice(body: &Body) -> Option { | bird_large::Species::FrostWyvern | bird_large::Species::SeaWyvern | bird_large::Species::WealdWyvern => VoiceKind::Wyvern, + bird_large::Species::Phoenix => VoiceKind::Phoenix, _ => VoiceKind::Bird, }, Body::BipedSmall(body) => match body.species { @@ -492,6 +495,11 @@ impl SfxMgr { let sfx_trigger_item = triggers.get_key_value(&SfxEvent::DeepLaugh); audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater); }, + Body::Object(object::Body::Tornado) + | Body::Object(object::Body::FieryTornado) => { + let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Swoosh); + audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater); + }, _ => { // not mapped to sfx file }, } @@ -524,6 +532,10 @@ impl SfxMgr { let sfx_trigger_item = triggers.get_key_value(&SfxEvent::FlameThrower); audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater); }, + Outcome::FromTheAshes { pos, .. } => { + let sfx_trigger_item = triggers.get_key_value(&SfxEvent::FromTheAshes); + audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater); + }, Outcome::ProjectileShot { pos, body, .. } => { match body { Body::Object( @@ -633,7 +645,9 @@ impl SfxMgr { audio.emit_sfx(sfx_trigger_item, *pos, None, underwater); }; }, - beam::FrontendSpecifier::Flamethrower | beam::FrontendSpecifier::Cultist => { + beam::FrontendSpecifier::Flamethrower + | beam::FrontendSpecifier::Cultist + | beam::FrontendSpecifier::PhoenixLaser => { if thread_rng().gen_bool(0.5) { let sfx_trigger_item = triggers.get_key_value(&SfxEvent::FlameThrower); audio.emit_sfx(sfx_trigger_item, *pos, None, underwater); diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index 1e7fedb214..13a99e3483 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -795,6 +795,7 @@ image_ids! { debuff_parried_0: "voxygen.element.de_buffs.debuff_parried_0", debuff_potionsickness_0: "voxygen.element.de_buffs.debuff_potionsickness_0", debuff_polymorphed: "voxygen.element.de_buffs.debuff_polymorphed", + debuff_heatstroke_0: "voxygen.element.de_buffs.debuff_heatstroke_0", // Animation Frames // Buff Frame diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 559f84fc06..94045298f0 100755 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -5193,6 +5193,7 @@ pub fn get_buff_image(buff: BuffKind, imgs: &Imgs) -> conrod_core::image::Id { BuffKind::Parried => imgs.debuff_parried_0, BuffKind::PotionSickness => imgs.debuff_potionsickness_0, BuffKind::Polymorphed => imgs.debuff_polymorphed, + BuffKind::Heatstroke => imgs.debuff_heatstroke_0, } } @@ -5234,6 +5235,7 @@ pub fn get_buff_title(buff: BuffKind, localized_strings: &Localization) -> Cow localized_strings.get_msg("buff-title-parried"), BuffKind::PotionSickness => localized_strings.get_msg("buff-title-potionsickness"), BuffKind::Polymorphed => localized_strings.get_msg("buff-title-polymorphed"), + BuffKind::Heatstroke => localized_strings.get_msg("buff-title-heatstroke"), } } @@ -5279,6 +5281,7 @@ pub fn get_buff_desc(buff: BuffKind, data: BuffData, localized_strings: &Localiz BuffKind::Parried => localized_strings.get_msg("buff-desc-parried"), BuffKind::PotionSickness => localized_strings.get_msg("buff-desc-potionsickness"), BuffKind::Polymorphed => localized_strings.get_msg("buff-desc-polymorphed"), + BuffKind::Heatstroke => localized_strings.get_msg("buff-desc-heatstroke"), } } diff --git a/voxygen/src/hud/util.rs b/voxygen/src/hud/util.rs index a08df42240..7e6d76384f 100644 --- a/voxygen/src/hud/util.rs +++ b/voxygen/src/hud/util.rs @@ -215,7 +215,8 @@ pub fn consumable_desc(effects: &Effects, i18n: &Localization) -> Vec { | BuffKind::Sunderer | BuffKind::Defiance | BuffKind::Bloodfeast - | BuffKind::Berserk => Cow::Borrowed(""), + | BuffKind::Berserk + | BuffKind::Heatstroke => Cow::Borrowed(""), }; write!(&mut description, "{}", buff_desc).unwrap(); @@ -263,7 +264,8 @@ pub fn consumable_desc(effects: &Effects, i18n: &Localization) -> Vec { | BuffKind::Sunderer | BuffKind::Defiance | BuffKind::Bloodfeast - | BuffKind::Berserk => Cow::Borrowed(""), + | BuffKind::Berserk + | BuffKind::Heatstroke => Cow::Borrowed(""), } } else if let BuffKind::Saturation | BuffKind::Regeneration diff --git a/voxygen/src/render/pipelines/particle.rs b/voxygen/src/render/pipelines/particle.rs index dbfca5d213..ee80e61dc2 100644 --- a/voxygen/src/render/pipelines/particle.rs +++ b/voxygen/src/render/pipelines/particle.rs @@ -99,6 +99,16 @@ pub enum ParticleMode { PortalFizz = 45, Ink = 46, Whirlwind = 47, + FieryBurst = 48, + FieryBurstVortex = 49, + FieryBurstSparks = 50, + FieryBurstAsh = 51, + FieryTornado = 52, + PhoenixCloud = 53, + FieryDropletTrace = 54, + EnergyPhoenix = 55, + PhoenixBeam = 56, + PhoenixBuildUpAim = 57, } impl ParticleMode { diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index f28a15d120..84f2d9d753 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -4931,6 +4931,7 @@ impl FigureMgr { state.state_time, look_dir, physics.on_ground.is_some(), + ability_id, ), stage_progress, &mut state_animation_rate, @@ -4993,6 +4994,36 @@ impl FigureMgr { state.state_time, look_dir, physics.on_ground.is_some(), + ability_id, + ), + stage_progress, + &mut state_animation_rate, + skeleton_attr, + ) + }, + CharacterState::RepeaterRanged(s) => { + let stage_time = s.timer.as_secs_f32(); + + let stage_progress = match s.stage_section { + StageSection::Buildup => { + stage_time / s.static_data.buildup_duration.as_secs_f32() + }, + StageSection::Recover => { + stage_time / s.static_data.recover_duration.as_secs_f32() + }, + + _ => 0.0, + }; + anim::bird_large::ShootAnimation::update_skeleton( + &target_base, + ( + rel_vel, + time, + Some(s.stage_section), + state.state_time, + look_dir, + physics.on_ground.is_some(), + ability_id, ), stage_progress, &mut state_animation_rate, @@ -5021,6 +5052,50 @@ impl FigureMgr { skeleton_attr, ) }, + CharacterState::BasicAura(s) => { + let stage_time = s.timer.as_secs_f32(); + let stage_progress = match s.stage_section { + StageSection::Buildup => { + stage_time / s.static_data.buildup_duration.as_secs_f32() + }, + StageSection::Action => { + stage_time / s.static_data.cast_duration.as_secs_f32() + }, + StageSection::Recover => { + stage_time / s.static_data.recover_duration.as_secs_f32() + }, + _ => 0.0, + }; + anim::bird_large::AuraAnimation::update_skeleton( + &target_base, + (Some(s.stage_section), physics.on_ground.is_some()), + stage_progress, + &mut state_animation_rate, + skeleton_attr, + ) + }, + CharacterState::SelfBuff(s) => { + let stage_time = s.timer.as_secs_f32(); + let stage_progress = match s.stage_section { + StageSection::Buildup => { + stage_time / s.static_data.buildup_duration.as_secs_f32() + }, + StageSection::Action => { + stage_time / s.static_data.cast_duration.as_secs_f32() + }, + StageSection::Recover => { + stage_time / s.static_data.recover_duration.as_secs_f32() + }, + _ => 0.0, + }; + anim::bird_large::SelfBuffAnimation::update_skeleton( + &target_base, + (Some(s.stage_section), physics.on_ground.is_some()), + stage_progress, + &mut state_animation_rate, + skeleton_attr, + ) + }, CharacterState::BasicSummon(s) => { let stage_time = s.timer.as_secs_f32(); let stage_progress = match s.stage_section { diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index efa6c761ed..a6c1bd245d 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -492,6 +492,7 @@ impl Scene { Rgb::new(1.0, 0.0, 0.0) } }, + Some(Reagent::Phoenix) => Rgb::new(1.0, 0.8, 0.3), Some(Reagent::White) => Rgb::new(1.0, 1.0, 1.0), Some(Reagent::Yellow) => Rgb::new(1.0, 1.0, 0.0), None => Rgb::new(1.0, 0.5, 0.0), diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 0a63e22150..16ff4e9a9a 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -15,7 +15,7 @@ use common::{ item::Reagent, object, shockwave::{self, ShockwaveDodgeable}, - Beam, Body, CharacterState, Ori, Pos, Scale, Shockwave, Vel, + Beam, Body, CharacterActivity, CharacterState, Ori, Pos, Scale, Shockwave, Vel, }, figure::Segment, outcome::Outcome, @@ -170,6 +170,23 @@ impl ParticleMgr { }, ); }, + Some(Reagent::Phoenix) => { + self.particles.resize_with( + self.particles.len() + (5.0 * power.abs()) as usize, + || { + Particle::new_directed( + Duration::from_millis(300), + time, + ParticleMode::Explosion, + *pos, + *pos + Vec3::::zero() + .map(|_| rng.gen_range(-1.0..1.0)) + .normalized() + * *radius, + ) + }, + ); + }, _ => {}, } } else { @@ -186,6 +203,7 @@ impl ParticleMgr { Some(Reagent::Red) => ParticleMode::FireworkRed, Some(Reagent::White) => ParticleMode::FireworkWhite, Some(Reagent::Yellow) => ParticleMode::FireworkYellow, + Some(Reagent::Phoenix) => ParticleMode::FireworkYellow, None => ParticleMode::Shrapnel, }, *pos, @@ -431,6 +449,7 @@ impl ParticleMgr { | Outcome::Steam { .. } | Outcome::FireShockwave { .. } | Outcome::PortalActivated { .. } + | Outcome::FromTheAshes { .. } | Outcome::LaserBeam { .. } => {}, } } @@ -501,12 +520,18 @@ impl ParticleMgr { Body::Object(object::Body::BoltFireBig) => { self.maintain_boltfirebig_particles(scene_data, interpolated.pos, vel) }, + Body::Object(object::Body::FireRainDrop) => { + self.maintain_fireraindrop_particles(scene_data, interpolated.pos, vel) + }, Body::Object(object::Body::BoltNature) => { self.maintain_boltnature_particles(scene_data, interpolated.pos, vel) }, Body::Object(object::Body::Tornado) => { self.maintain_tornado_particles(scene_data, interpolated.pos) }, + Body::Object(object::Body::FieryTornado) => { + self.maintain_fiery_tornado_particles(scene_data, interpolated.pos) + }, Body::Object(object::Body::Mine) => { self.maintain_mine_particles(scene_data, interpolated.pos) }, @@ -672,6 +697,38 @@ impl ParticleMgr { ); } + fn maintain_fireraindrop_particles( + &mut self, + scene_data: &SceneData, + pos: Vec3, + vel: Option<&Vel>, + ) { + span!( + _guard, + "fireraindrop_particles", + "ParticleMgr::maintain_fireraindrop_particles" + ); + let time = scene_data.state.get_time(); + let dt = scene_data.state.get_delta_time(); + let mut rng = thread_rng(); + + // trace + self.particles.resize_with( + self.particles.len() + + usize::from(self.scheduler.heartbeats(Duration::from_millis(100))), + || { + Particle::new( + Duration::from_millis(300), + time, + ParticleMode::FieryDropletTrace, + pos.map(|e| e + rng.gen_range(-0.25..0.25)) + + Vec3::new(0.0, 0.0, 0.5) + + vel.map_or(Vec3::zero(), |v| -v.0 * dt * rng.gen::()), + ) + }, + ); + } + fn maintain_boltnature_particles( &mut self, scene_data: &SceneData, @@ -715,6 +772,24 @@ impl ParticleMgr { ); } + fn maintain_fiery_tornado_particles(&mut self, scene_data: &SceneData, pos: Vec3) { + let time = scene_data.state.get_time(); + let mut rng = thread_rng(); + + // air particles + self.particles.resize_with( + self.particles.len() + usize::from(self.scheduler.heartbeats(Duration::from_millis(5))), + || { + Particle::new( + Duration::from_millis(1000), + time, + ParticleMode::FieryTornado, + pos.map(|e| e + rng.gen_range(-0.25..0.25)), + ) + }, + ); + } + fn maintain_bomb_particles( &mut self, scene_data: &SceneData, @@ -830,12 +905,14 @@ impl ParticleMgr { let dt = scene_data.state.get_delta_time(); let mut rng = thread_rng(); - for (entity, interpolated, vel, character_state, body) in ( + for (entity, interpolated, vel, character_state, body, ori, character_activity) in ( &ecs.entities(), &ecs.read_storage::(), ecs.read_storage::().maybe(), &ecs.read_storage::(), &ecs.read_storage::(), + &ecs.read_storage::(), + &ecs.read_storage::(), ) .join() { @@ -957,6 +1034,87 @@ impl ParticleMgr { } } }, + CharacterState::RepeaterRanged(repeater) => { + if let Some(specifier) = repeater.static_data.specifier { + match specifier { + states::repeater_ranged::FrontendSpecifier::FireRain => { + // base, dark clouds + self.particles.resize_with( + self.particles.len() + + 2 * usize::from( + self.scheduler.heartbeats(Duration::from_millis(25)), + ), + || { + let rand_pos = { + let theta = rng.gen::() * TAU; + let radius = repeater + .static_data + .properties_of_aoe + .map(|aoe| aoe.radius) + .unwrap_or_default() + * rng.gen::().sqrt(); + let x = radius * theta.sin(); + let y = radius * theta.cos(); + Vec2::new(x, y) + interpolated.pos.xy() + }; + let pos1 = rand_pos.with_z( + repeater + .static_data + .properties_of_aoe + .map(|aoe| aoe.height) + .unwrap_or_default() + + interpolated.pos.z + + 2.0 * rng.gen::(), + ); + Particle::new_directed( + Duration::from_secs_f32(3.0), + time, + ParticleMode::PhoenixCloud, + pos1, + pos1 + Vec3::new(7.09, 4.09, 18.09), + ) + }, + ); + self.particles.resize_with( + self.particles.len() + + 2 * usize::from( + self.scheduler.heartbeats(Duration::from_millis(25)), + ), + || { + let rand_pos = { + let theta = rng.gen::() * TAU; + let radius = repeater + .static_data + .properties_of_aoe + .map(|aoe| aoe.radius) + .unwrap_or_default() + * rng.gen::().sqrt(); + let x = radius * theta.sin(); + let y = radius * theta.cos(); + Vec2::new(x, y) + interpolated.pos.xy() + }; + let pos1 = rand_pos.with_z( + repeater + .static_data + .properties_of_aoe + .map(|aoe| aoe.height) + .unwrap_or_default() + + interpolated.pos.z + + 1.5 * rng.gen::(), + ); + Particle::new_directed( + Duration::from_secs_f32(2.5), + time, + ParticleMode::PhoenixCloud, + pos1, + pos1 + Vec3::new(10.025, 4.025, 17.025), + ) + }, + ); + }, + } + } + }, CharacterState::Blink(c) => { self.particles.resize_with( self.particles.len() @@ -992,6 +1150,94 @@ impl ParticleMgr { ); }, CharacterState::SelfBuff(c) => { + if let Some(specifier) = c.static_data.specifier { + match specifier { + states::self_buff::FrontendSpecifier::FromTheAshes => { + if matches!(c.stage_section, StageSection::Action) { + let pos = interpolated.pos; + self.particles.resize_with( + self.particles.len() + + 2 * usize::from( + self.scheduler.heartbeats(Duration::from_millis(1)), + ), + || { + let start_pos = pos + Vec3::unit_z() - 1.0; + let end_pos = pos + + Vec3::new( + 4.0 * rng.gen::() - 1.0, + 4.0 * rng.gen::() - 1.0, + 0.0, + ) + .normalized() + * 1.5 + + Vec3::unit_z() + + 5.0 * rng.gen::(); + + Particle::new_directed( + Duration::from_secs_f32(0.5), + time, + ParticleMode::FieryBurst, + start_pos, + end_pos, + ) + }, + ); + self.particles.resize_with( + self.particles.len() + + usize::from( + self.scheduler + .heartbeats(Duration::from_millis(10)), + ), + || { + Particle::new( + Duration::from_millis(650), + time, + ParticleMode::FieryBurstVortex, + pos.map(|e| e + rng.gen_range(-0.25..0.25)) + + Vec3::new(0.0, 0.0, 1.0), + ) + }, + ); + self.particles.resize_with( + self.particles.len() + + usize::from( + self.scheduler + .heartbeats(Duration::from_millis(40)), + ), + || { + Particle::new( + Duration::from_millis(1000), + time, + ParticleMode::FieryBurstSparks, + pos.map(|e| e + rng.gen_range(-0.25..0.25)), + ) + }, + ); + self.particles.resize_with( + self.particles.len() + + usize::from( + self.scheduler + .heartbeats(Duration::from_millis(14)), + ), + || { + let pos1 = pos.map(|e| e + rng.gen_range(-0.25..0.25)); + Particle::new_directed( + Duration::from_millis(1000), + time, + ParticleMode::FieryBurstAsh, + pos1, + Vec3::new( + 4.5, // radius of rand spawn + 20.4, // integer part - radius of the curve part, fractional part - relative time of setting particle on fire + 8.58) // height of the flight + + pos1, + ) + }, + ); + } + }, + } + } use buff::BuffKind; if let BuffKind::Frenzied = c.static_data.buff_kind { if matches!(c.stage_section, StageSection::Action) { @@ -1022,6 +1268,44 @@ impl ParticleMgr { } } }, + CharacterState::BasicBeam(beam) => { + let ori = *ori; + let _look_dir = *character_activity.look_dir.unwrap_or(ori.look_dir()); + let dir = ori.look_dir(); //.with_z(look_dir.z); + let specifier = beam.static_data.specifier; + if specifier == beam::FrontendSpecifier::PhoenixLaser + && matches!(beam.stage_section, StageSection::Buildup) + { + self.particles.resize_with( + self.particles.len() + + 2 * usize::from( + self.scheduler.heartbeats(Duration::from_millis(2)), + ), + || { + let mut left_right_alignment = + dir.cross(Vec3::new(0.0, 0.0, 1.0)).normalized(); + if rng.gen_bool(0.5) { + left_right_alignment *= -1.0; + } + let start = interpolated.pos + + left_right_alignment * 4.0 + + dir.normalized() * 6.0; + let lifespan = Duration::from_secs_f32(0.5); + Particle::new_directed( + lifespan, + time, + ParticleMode::PhoenixBuildUpAim, + start, + interpolated.pos + + dir.normalized() * 3.0 + + left_right_alignment * 0.4 + + vel + .map_or(Vec3::zero(), |v| v.0 * lifespan.as_secs_f32()), + ) + }, + ); + } + }, _ => {}, } } @@ -1285,6 +1569,20 @@ impl ParticleMgr { }, ); }, + beam::FrontendSpecifier::PhoenixLaser => { + self.particles.resize_with( + self.particles.len() + usize::from(beam_tick_count) / 2, + || { + Particle::new_directed( + Duration::from_secs_f64(beam.duration.0), + time, + ParticleMode::PhoenixBeam, + beam.bezier.start, + beam.bezier.start + beam_dir * beam.range, + ) + }, + ); + }, } } } @@ -1458,6 +1756,62 @@ impl ParticleMgr { ); } }, + aura::AuraKind::Buff { + kind: buff::BuffKind::Heatstroke, + .. + } => { + let heartbeats = self.scheduler.heartbeats(Duration::from_millis(5)); + self.particles.resize_with( + self.particles.len() + + aura.radius.powi(2) as usize * usize::from(heartbeats) / 900, + || { + let rand_dist = aura.radius * (1.0 - rng.gen::().powi(100)); + let init_pos = Vec3::new(rand_dist, 0_f32, 0_f32); + let duration = Duration::from_secs_f64( + aura.end_time + .map_or(1.0, |end| end.0 - time) + .clamp(0.0, 1.0), + ); + Particle::new_directed( + duration, + time, + ParticleMode::EnergyPhoenix, + pos, + pos + init_pos, + ) + }, + ); + + let num_particles = aura.radius.powi(2) * dt / 50.0; + let num_particles = num_particles.floor() as usize + + usize::from(rng.gen_bool(f64::from(num_particles % 1.0))); + self.particles + .resize_with(self.particles.len() + num_particles, || { + let rand_pos = { + let theta = rng.gen::() * TAU; + let radius = aura.radius * rng.gen::().sqrt(); + let x = radius * theta.sin(); + let y = radius * theta.cos(); + Vec2::new(x, y) + pos.xy() + }; + let duration = Duration::from_secs_f64( + aura.end_time + .map_or(1.0, |end| end.0 - time) + .clamp(0.0, 1.0), + ); + Particle::new_directed( + duration, + time, + ParticleMode::FieryBurstAsh, + pos, + Vec3::new( + 0.0, // radius of rand spawn + 20.0, // integer part - radius of the curve part, fractional part - relative time of setting particle on fire + 5.5) // height of the flight + + rand_pos.with_z(pos.z), + ) + }); + }, _ => {}, } }