diff --git a/CHANGELOG.md b/CHANGELOG.md index ef5794ef44..d53f415afb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -92,6 +92,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Debug Kit is split to "admin_cosmetics" and "debug" - Potion Kit is renamed to "consumables" and gives potions and mushroom curry - Cultist Kit gives cape, rings and necklace in addition to armour and weapons. +- Reworked minotaur to have unique attacks. ### Removed diff --git a/Cargo.lock b/Cargo.lock index 96368ecc90..458eb85209 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5542,6 +5542,8 @@ dependencies = [ "specs-idvs", "spin_sleep", "structopt", + "strum", + "strum_macros", "tracing", "tracing-subscriber", "uuid", diff --git a/assets/common/abilities/ability_set_manifest.ron b/assets/common/abilities/ability_set_manifest.ron index 18f67e45e6..33150c51e5 100644 --- a/assets/common/abilities/ability_set_manifest.ron +++ b/assets/common/abilities/ability_set_manifest.ron @@ -207,6 +207,14 @@ (None, "common.abilities.custom.mindflayer.summonminions"), ], ), + Custom("Minotaur"): ( + primary: "common.abilities.custom.minotaur.cleave", + secondary: "common.abilities.custom.minotaur.cripplingstrike", + abilities: [ + (None, "common.abilities.custom.minotaur.charge"), + (None, "common.abilities.custom.minotaur.frenzy"), + ], + ), Custom("Bird Large Breathe"): ( primary: "common.abilities.custom.birdlargebreathe.firebomb", secondary: "common.abilities.custom.birdlargebreathe.triplestrike", diff --git a/assets/common/abilities/custom/beastclaws/basic.ron b/assets/common/abilities/custom/beastclaws/basic.ron index 6104f35b8b..449aa4b206 100644 --- a/assets/common/abilities/custom/beastclaws/basic.ron +++ b/assets/common/abilities/custom/beastclaws/basic.ron @@ -8,4 +8,5 @@ BasicMelee( base_poise_damage: 40, range: 5.0, max_angle: 120.0, + damage_effect: None, ) diff --git a/assets/common/abilities/custom/mindflayer/cursedflames.ron b/assets/common/abilities/custom/mindflayer/cursedflames.ron index e98d16a0c8..4559957598 100644 --- a/assets/common/abilities/custom/mindflayer/cursedflames.ron +++ b/assets/common/abilities/custom/mindflayer/cursedflames.ron @@ -2,7 +2,7 @@ BasicBeam( buildup_duration: 0.40, recover_duration: 0.50, beam_duration: 1.0, - damage: 350, + damage: 500, tick_rate: 0.9, range: 22.0, max_angle: 15.0, diff --git a/assets/common/abilities/custom/mindflayer/necroticvortex.ron b/assets/common/abilities/custom/mindflayer/necroticvortex.ron index 3b9e308d96..82f8416777 100644 --- a/assets/common/abilities/custom/mindflayer/necroticvortex.ron +++ b/assets/common/abilities/custom/mindflayer/necroticvortex.ron @@ -2,7 +2,7 @@ SpinMelee( buildup_duration: 0.5, swing_duration: 0.2, recover_duration: 0.6, - base_damage: 80.0, + base_damage: 100.0, base_poise_damage: 1.0, knockback: ( strength: 7.0, direction: Towards), range: 16.0, diff --git a/assets/common/abilities/custom/minotaur/charge.ron b/assets/common/abilities/custom/minotaur/charge.ron new file mode 100644 index 0000000000..8c85351616 --- /dev/null +++ b/assets/common/abilities/custom/minotaur/charge.ron @@ -0,0 +1,19 @@ +DashMelee( + energy_cost: 0, + base_damage: 150, + scaled_damage: 600, + base_poise_damage: 25, + scaled_poise_damage: 100, + base_knockback: 10.0, + scaled_knockback: 30.0, + range: 5.0, + angle: 90.0, + energy_drain: 0, + forward_speed: 5.0, + buildup_duration: 0.4, + charge_duration: 4.0, + swing_duration: 0.1, + recover_duration: 0.5, + charge_through: false, + is_interruptible: false, +) \ No newline at end of file diff --git a/assets/common/abilities/custom/minotaur/cleave.ron b/assets/common/abilities/custom/minotaur/cleave.ron new file mode 100644 index 0000000000..5f92519648 --- /dev/null +++ b/assets/common/abilities/custom/minotaur/cleave.ron @@ -0,0 +1,18 @@ +ChargedMelee( + energy_cost: 0, + energy_drain: 0, + initial_damage: 0, + scaled_damage: 500, + initial_poise_damage: 50, + scaled_poise_damage: 150, + initial_knockback: 0.0, + scaled_knockback: 0.0, + range: 5.0, + max_angle: 45.0, + speed: 1.0, + charge_duration: 1.5, + swing_duration: 0.1, + hit_timing: 0.8, + recover_duration: 0.5, + specifier: Some(GroundCleave), +) diff --git a/assets/common/abilities/custom/minotaur/cripplingstrike.ron b/assets/common/abilities/custom/minotaur/cripplingstrike.ron new file mode 100644 index 0000000000..f941011081 --- /dev/null +++ b/assets/common/abilities/custom/minotaur/cripplingstrike.ron @@ -0,0 +1,17 @@ +BasicMelee( + energy_cost: 0, + buildup_duration: 0.3, + swing_duration: 0.1, + recover_duration: 0.6, + base_damage: 150.0, + base_poise_damage: 60.0, + knockback: 15.0, + range: 5.0, + max_angle: 60.0, + damage_effect: Some(Buff(( + kind: Crippled, + dur_secs: 15.0, + strength: Value(0.5), + chance: 1.0, + ))), +) diff --git a/assets/common/abilities/custom/minotaur/frenzy.ron b/assets/common/abilities/custom/minotaur/frenzy.ron new file mode 100644 index 0000000000..160a527abc --- /dev/null +++ b/assets/common/abilities/custom/minotaur/frenzy.ron @@ -0,0 +1,9 @@ +SelfBuff( + buildup_duration: 0.25, + cast_duration: 0.8, + recover_duration: 0.25, + buff_kind: Frenzied, + buff_strength: 0.5, + buff_duration: Some(300.0), + energy_cost: 0, +) \ No newline at end of file diff --git a/assets/common/abilities/custom/quadmedhoof/basic.ron b/assets/common/abilities/custom/quadmedhoof/basic.ron index a451c250e0..73c4e7d739 100644 --- a/assets/common/abilities/custom/quadmedhoof/basic.ron +++ b/assets/common/abilities/custom/quadmedhoof/basic.ron @@ -8,4 +8,5 @@ BasicMelee( knockback: 25.0, range: 1.2, max_angle: 50.0, + damage_effect: None, ) diff --git a/assets/common/abilities/dagger/tempbasic.ron b/assets/common/abilities/dagger/tempbasic.ron index 27bed0eca3..5149caf41c 100644 --- a/assets/common/abilities/dagger/tempbasic.ron +++ b/assets/common/abilities/dagger/tempbasic.ron @@ -8,4 +8,5 @@ BasicMelee( knockback: 0.0, range: 3.5, max_angle: 20.0, + damage_effect: None, ) diff --git a/assets/common/abilities/empty/basic.ron b/assets/common/abilities/empty/basic.ron index e6c7d2f0ff..10d9ae81d8 100644 --- a/assets/common/abilities/empty/basic.ron +++ b/assets/common/abilities/empty/basic.ron @@ -8,4 +8,5 @@ BasicMelee( knockback: 0.0, range: 3.5, max_angle: 15.0, + damage_effect: None, ) diff --git a/assets/common/abilities/farming/basic.ron b/assets/common/abilities/farming/basic.ron index 2d8ff06774..44e52a5711 100644 --- a/assets/common/abilities/farming/basic.ron +++ b/assets/common/abilities/farming/basic.ron @@ -8,4 +8,5 @@ BasicMelee( knockback: 0.0, range: 3.5, max_angle: 20.0, + damage_effect: None, ) diff --git a/assets/common/abilities/pick/swing.ron b/assets/common/abilities/pick/swing.ron index 2d8ff06774..44e52a5711 100644 --- a/assets/common/abilities/pick/swing.ron +++ b/assets/common/abilities/pick/swing.ron @@ -8,4 +8,5 @@ BasicMelee( knockback: 0.0, range: 3.5, max_angle: 20.0, + damage_effect: None, ) diff --git a/assets/common/abilities/shield/tempbasic.ron b/assets/common/abilities/shield/tempbasic.ron index 6998b394b5..5e7e156fe7 100644 --- a/assets/common/abilities/shield/tempbasic.ron +++ b/assets/common/abilities/shield/tempbasic.ron @@ -8,4 +8,5 @@ BasicMelee( knockback: 0.0, range: 3.0, max_angle: 120.0, + damage_effect: None, ) diff --git a/assets/common/items/npc_weapons/axe/minotaur_axe.ron b/assets/common/items/npc_weapons/axe/minotaur_axe.ron index d1f47d9cdf..db469f4d84 100644 --- a/assets/common/items/npc_weapons/axe/minotaur_axe.ron +++ b/assets/common/items/npc_weapons/axe/minotaur_axe.ron @@ -6,14 +6,14 @@ ItemDef( hands: Two, stats: Direct(( equip_time_secs: 0.5, - power: 1.8, + power: 1.0, poise_strength: 1.0, speed: 1.0, - crit_chance: 0.048611112, - crit_mult: 1.6530613, + crit_chance: 0.1, + crit_mult: 1.5, )), )), - quality: Low, + quality: Legendary, tags: [], - ability_spec: Some(Custom("Axe Simple")), + ability_spec: Some(Custom("Minotaur")), ) \ No newline at end of file diff --git a/assets/common/items/weapons/hammer/mjolnir.ron b/assets/common/items/weapons/hammer/mjolnir.ron index 831e3a0a99..cd812ba36a 100644 --- a/assets/common/items/weapons/hammer/mjolnir.ron +++ b/assets/common/items/weapons/hammer/mjolnir.ron @@ -6,9 +6,9 @@ ItemDef( hands: Two, stats: Direct(( equip_time_secs: 0.5, - power: 4.0, + power: 2.5, poise_strength: 1.0, - speed: 0.5, + speed: 0.8, crit_chance: 0.078125, crit_mult: 1.3657143, )), diff --git a/assets/common/items/weapons/sceptre/caduceus.ron b/assets/common/items/weapons/sceptre/caduceus.ron index 09bf7152b7..2e0e790e50 100644 --- a/assets/common/items/weapons/sceptre/caduceus.ron +++ b/assets/common/items/weapons/sceptre/caduceus.ron @@ -6,9 +6,9 @@ ItemDef( hands: Two, stats: Direct(( equip_time_secs: 0.4, - power: 2.3, + power: 1.67, poise_strength: 1.5, - speed: 1.0, + speed: 1.2, crit_chance: 0.078125, crit_mult: 1.3657143, )), diff --git a/assets/common/items/weapons/sceptre/root_evil.ron b/assets/common/items/weapons/sceptre/root_evil.ron index d33ba4f461..dd26418355 100644 --- a/assets/common/items/weapons/sceptre/root_evil.ron +++ b/assets/common/items/weapons/sceptre/root_evil.ron @@ -6,9 +6,9 @@ ItemDef( hands: Two, stats: Direct(( equip_time_secs: 0.4, - power: 4.0, - poise_strength: 1.5, - speed: 0.5, + power: 2.5, + poise_strength: 1.0, + speed: 0.8, crit_chance: 0.078125, crit_mult: 1.3657143, )), diff --git a/assets/common/items/weapons/sceptre/sceptre_velorite_0.ron b/assets/common/items/weapons/sceptre/sceptre_velorite_0.ron index 9dfbc9e842..d650b11ada 100644 --- a/assets/common/items/weapons/sceptre/sceptre_velorite_0.ron +++ b/assets/common/items/weapons/sceptre/sceptre_velorite_0.ron @@ -6,9 +6,9 @@ ItemDef( hands: Two, stats: Direct(( equip_time_secs: 0.4, - power: 1.8, + power: 2.0, poise_strength: 1.5, - speed: 1.2, + speed: 1.0, crit_chance: 0.21153846, crit_mult: 1.4502164, )), diff --git a/assets/common/items/weapons/staff/laevateinn.ron b/assets/common/items/weapons/staff/laevateinn.ron index 29c302e35e..9b7fca332b 100644 --- a/assets/common/items/weapons/staff/laevateinn.ron +++ b/assets/common/items/weapons/staff/laevateinn.ron @@ -6,9 +6,9 @@ ItemDef( hands: Two, stats: Direct(( equip_time_secs: 0.6, - power: 1.8, + power: 1.67, poise_strength: 1.0, - speed: 1.6, + speed: 1.2, crit_chance: 0.2002994, crit_mult: 1.3798152, )), diff --git a/assets/common/items/weapons/staff/phoenix.ron b/assets/common/items/weapons/staff/phoenix.ron index efaa30e77d..39acf85d40 100644 --- a/assets/common/items/weapons/staff/phoenix.ron +++ b/assets/common/items/weapons/staff/phoenix.ron @@ -6,9 +6,9 @@ ItemDef( hands: Two, stats: Direct(( equip_time_secs: 0.6, - power: 3.0, + power: 2.5, poise_strength: 1.0, - speed: 0.67, + speed: 0.8, crit_chance: 0.1002994, crit_mult: 1.3798152, )), diff --git a/assets/server/manifests/kits.ron b/assets/server/manifests/kits.ron index dfb76abe25..eeb423c649 100644 --- a/assets/server/manifests/kits.ron +++ b/assets/server/manifests/kits.ron @@ -39,5 +39,20 @@ ("common.items.consumable.potion_med", 100), ("common.items.consumable.potion_big", 100), ("common.items.food.apple_mushroom_curry", 100), - ] + ], + "tier-4": [ + ("common.items.armor.steel.belt", 1), + ("common.items.armor.steel.chest", 1), + ("common.items.armor.steel.foot", 1), + ("common.items.armor.steel.hand", 1), + ("common.items.armor.steel.pants", 1), + ("common.items.armor.steel.shoulder", 1), + ("common.items.weapons.sword.cobalt-0", 1), + ("common.items.weapons.axe.cobalt_axe-0", 1), + ("common.items.weapons.hammer.cobalt_hammer-0", 1), + ("common.items.weapons.bow.frostwood-0", 1), + ("common.items.weapons.staff.frostwood_torch", 1), + ("common.items.weapons.sceptre.fork0", 1), + ("common.items.consumable.potion_med", 100), + ], }) diff --git a/assets/voxygen/audio/sfx.ron b/assets/voxygen/audio/sfx.ron index d0ceebf109..bd9434f765 100644 --- a/assets/voxygen/audio/sfx.ron +++ b/assets/voxygen/audio/sfx.ron @@ -824,5 +824,12 @@ ], threshold: 1.25, ), + GroundSlam: ( + files: [ + "voxygen.audio.sfx.abilities.minotaur_smash_1", + "voxygen.audio.sfx.abilities.minotaur_smash_2", + ], + threshold: 0.2, + ), } ) diff --git a/assets/voxygen/audio/sfx/abilities/minotaur_smash_1.ogg b/assets/voxygen/audio/sfx/abilities/minotaur_smash_1.ogg new file mode 100644 index 0000000000..bd31e209d4 --- /dev/null +++ b/assets/voxygen/audio/sfx/abilities/minotaur_smash_1.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9972ac391a94b0e815d667c7efeb49d5dda09f6e6eaaebd5f4989defa49b1573 +size 15485 diff --git a/assets/voxygen/audio/sfx/abilities/minotaur_smash_2.ogg b/assets/voxygen/audio/sfx/abilities/minotaur_smash_2.ogg new file mode 100644 index 0000000000..35f4af1a45 --- /dev/null +++ b/assets/voxygen/audio/sfx/abilities/minotaur_smash_2.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a89ff1f4324fda9e415adbcac2b5ea5c14c8c2c441f034ee6a0b61d1fcebffbc +size 15723 diff --git a/assets/voxygen/element/de_buffs/buff_frenzy_0.png b/assets/voxygen/element/de_buffs/buff_frenzy_0.png new file mode 100644 index 0000000000..fe6ca236cb --- /dev/null +++ b/assets/voxygen/element/de_buffs/buff_frenzy_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d67ffc40362607cc885ff15d494396bad67cbb735c8e705a300fd867a7ab6de1 +size 293 diff --git a/assets/voxygen/element/de_buffs/debuff_cripple_0.png b/assets/voxygen/element/de_buffs/debuff_cripple_0.png new file mode 100644 index 0000000000..898145c2d6 --- /dev/null +++ b/assets/voxygen/element/de_buffs/debuff_cripple_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d6f2e4416e8e38478ac6154b404776fc5dff1a63ae6af36d288f4f22ed699c2 +size 272 diff --git a/assets/voxygen/i18n/en/buff.ron b/assets/voxygen/i18n/en/buff.ron index cbd6d30f19..bd1f8dd33d 100644 --- a/assets/voxygen/i18n/en/buff.ron +++ b/assets/voxygen/i18n/en/buff.ron @@ -19,6 +19,8 @@ "buff.desc.invulnerability": "You cannot be damaged by any attack.", "buff.title.protectingward": "Protecting Ward", "buff.desc.protectingward": "You are protected, somewhat, from attacks.", + "buff.title.frenzied": "Frenzied", + "buff.desc.frenzied": "You are imbued with unnatural speed and can ignore minor injuries.", // Debuffs "buff.title.bleed": "Bleeding", "buff.desc.bleed": "Inflicts regular damage.", @@ -26,6 +28,8 @@ "buff.desc.cursed": "You are cursed.", "buff.title.burn": "On Fire", "buff.desc.burn": "You are burning alive", + "buff.title.crippled": "Crippled", + "buff.desc.crippled": "Your movement is crippled as your legs are heavily injured.", // Buffs stats "buff.stat.health": "Restores {str_total} Health", "buff.stat.increase_max_stamina": "Raises Maximum Stamina by {strength}", diff --git a/assets/voxygen/i18n/en/hud/chat.ron b/assets/voxygen/i18n/en/hud/chat.ron index 1e46dea342..394db4c2dc 100644 --- a/assets/voxygen/i18n/en/hud/chat.ron +++ b/assets/voxygen/i18n/en/hud/chat.ron @@ -7,6 +7,7 @@ "hud.outcome.burning": "died of: burning", "hud.outcome.curse": "died of: curse", "hud.outcome.bleeding": "died of: bleeding", + "hud.outcome.crippled": "died of: crippled", // Chat outputs "hud.chat.online_msg": "[{name}] is online now", diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index dd3ca7b466..d94534313d 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -64,6 +64,8 @@ const int LIFESTEAL_BEAM = 22; const int CULTIST_FLAME = 23; const int STATIC_SMOKE = 24; const int BLOOD = 25; +const int ENRAGED = 26; +const int BIG_SHRAPNEL = 27; // meters per second squared (acceleration) const float earth_gravity = 9.807; @@ -218,6 +220,17 @@ void main() { vec4(vec3(0.25), 1), spin_in_axis(vec3(1,0,0),0) ); + } else if (inst_mode == BIG_SHRAPNEL) { + float brown_color = 0.05 + 0.1 * rand1; + attr = Attr( + linear_motion( + vec3(0), + normalize(vec3(rand4, rand5, rand6)) * 15.0 + grav_vel(earth_gravity) + ), + vec3(5 * (1 - percent())), + vec4(vec3(brown_color, brown_color / 2, 0), 1), + spin_in_axis(vec3(1,0,0),0) + ); } else if (inst_mode == FIREWORK_BLUE) { f_reflect = 0.0; // Fire doesn't reflect light, it emits it attr = Attr( @@ -421,6 +434,15 @@ void main() { vec4(1, 0, 0, 1), spin_in_axis(vec3(1,0,0),0) ); + } else if (inst_mode == ENRAGED) { + f_reflect = 0.0; + float red_color = 1.2 + 0.3 * rand3; + attr = Attr( + (inst_dir * slow_end(1.5)) + vec3(rand0, rand1, rand2) * (percent() + 2) * 0.1, + vec3((3.5 * (1 - slow_start(0.2)))), + vec4(red_color, 0.0, 0.0, 1), + spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9) + ); } else { attr = Attr( linear_motion( diff --git a/assets/voxygen/voxel/biped_weapon_manifest.ron b/assets/voxygen/voxel/biped_weapon_manifest.ron index ae60874a09..57dfec123d 100644 --- a/assets/voxygen/voxel/biped_weapon_manifest.ron +++ b/assets/voxygen/voxel/biped_weapon_manifest.ron @@ -1043,7 +1043,7 @@ color: None ), "common.items.npc_weapons.axe.minotaur_axe": ( - vox_spec: ("weapon.axe.2haxe_minotaur", (-2.5, -9.0, -6.0)), + vox_spec: ("weapon.axe.2haxe_minotaur", (-2.5, -9.0, -8.0)), color: None ), "common.items.npc_weapons.hammer.yeti_hammer": ( diff --git a/assets/voxygen/voxel/weapon/axe/2haxe_minotaur.vox b/assets/voxygen/voxel/weapon/axe/2haxe_minotaur.vox index f8c4cd5649..452452357f 100644 --- a/assets/voxygen/voxel/weapon/axe/2haxe_minotaur.vox +++ b/assets/voxygen/voxel/weapon/axe/2haxe_minotaur.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e9b3f271457750c4e6d86fd23554322c15439b9583e23322b6143ef1cee86cf -size 3652 +oid sha256:5ecbd8d48af72ec42fb7f8d265cb4ffe0f0732b1fe9b5eafe0ff1d1a19018ab5 +size 3484 diff --git a/common/Cargo.toml b/common/Cargo.toml index 533a966a6f..2d8f2ed24e 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -49,7 +49,7 @@ ron = { version = "0.6", default-features = false } serde_json = "1.0.50" serde_repr = "0.1.6" -# esv export +# csv export csv = { version = "1.1.3", optional = true } structopt = { version = "0.3.13", optional = true } @@ -59,6 +59,10 @@ slotmap = { version = "1.0", features = ["serde"] } indexmap = "1.3.0" slab = "0.4.2" +# Strum +strum = "0.20" +strum_macros = "0.20" + # ECS specs = { git = "https://github.com/amethyst/specs.git", features = ["serde", "storage-event-control", "nightly"], rev = "5a9b71035007be0e3574f35184acac1cd4530496" } specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git", rev = "b65fb220e94f5d3c9bc30074a076149763795556" } diff --git a/common/src/cmd.rs b/common/src/cmd.rs index 008bf3f89d..911f30d32c 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -1,4 +1,8 @@ -use crate::{assets, comp, npc, terrain}; +use crate::{ + assets, + comp::{self, buff::BuffKind}, + npc, terrain, +}; use assets::AssetExt; use hashbrown::HashMap; use lazy_static::lazy_static; @@ -8,6 +12,7 @@ use std::{ path::Path, str::FromStr, }; +use strum::IntoEnumIterator; use tracing::warn; /// Struct representing a command that a user can run from server chat. @@ -210,21 +215,40 @@ lazy_static! { .map(|s| s.to_string()) .collect(); - static ref BUFFS: Vec = vec![ - // Debuffs - "burning", "bleeding", "curse", - // Heal - "regeneration", "saturation", "potion", "campfire_heal", - // Outmaxing stats - "increase_max_energy", "increase_max_health", - // Defensive buffs - "invulnerability", "protecting_ward", - // One command to rule them all - "all", - ] - .iter() - .map(|b| b.to_string()) - .collect(); + pub static ref BUFF_PARSER: HashMap = { + let string_from_buff = |kind| match kind { + BuffKind::Burning => "burning", + BuffKind::Regeneration => "regeration", + BuffKind::Saturation => "saturation", + BuffKind::Bleeding => "bleeding", + BuffKind::Cursed => "cursed", + BuffKind::Potion => "potion", + BuffKind::CampfireHeal => "campfire_heal", + BuffKind::IncreaseMaxEnergy => "increase_max_energy", + BuffKind::IncreaseMaxHealth => "increase_max_health", + BuffKind::Invulnerability => "invulnerability", + BuffKind::ProtectingWard => "protecting_ward", + BuffKind::Frenzied => "frenzied", + BuffKind::Crippled => "crippled", + }; + let mut buff_parser = HashMap::new(); + BuffKind::iter().for_each(|kind| {buff_parser.insert(string_from_buff(kind).to_string(), kind);}); + buff_parser + }; + + pub static ref BUFF_PACK: Vec = { + let mut buff_pack: Vec<_> = BUFF_PARSER.keys().cloned().collect(); + // Remove invulnerability as it removes debuffs + buff_pack.retain(|kind| kind != "invulnerability"); + buff_pack + }; + + static ref BUFFS: Vec = { + let mut buff_pack: Vec<_> = BUFF_PARSER.keys().cloned().collect(); + // Add all as valid command + buff_pack.push("all".to_string()); + buff_pack + }; static ref BLOCK_KINDS: Vec = terrain::block::BLOCK_KINDS .keys() diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index e9a1de7c2c..37a4da3fe8 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -2,8 +2,8 @@ use crate::{ assets::{self, Asset}, combat::{self, CombatEffect, Knockback}, comp::{ - aura, beam, inventory::item::tool::ToolKind, projectile::ProjectileConstructor, skills, - Body, CharacterState, EnergySource, LightEmitter, StateUpdate, + aura, beam, buff, inventory::item::tool::ToolKind, projectile::ProjectileConstructor, + skills, Body, CharacterState, EnergySource, LightEmitter, StateUpdate, }, states::{ behavior::JoinData, @@ -30,6 +30,7 @@ pub enum CharacterAbilityType { BasicBeam, RepeaterRanged, BasicAura, + SelfBuff, } impl From<&CharacterState> for CharacterAbilityType { @@ -49,6 +50,7 @@ impl From<&CharacterState> for CharacterAbilityType { CharacterState::BasicBeam(_) => Self::BasicBeam, CharacterState::RepeaterRanged(_) => Self::RepeaterRanged, CharacterState::BasicAura(_) => Self::BasicAura, + CharacterState::SelfBuff(_) => Self::SelfBuff, _ => Self::BasicMelee, } } @@ -66,6 +68,7 @@ pub enum CharacterAbility { knockback: f32, range: f32, max_angle: f32, + damage_effect: Option, }, BasicRanged { energy_cost: f32, @@ -188,6 +191,7 @@ pub enum CharacterAbility { swing_duration: f32, hit_timing: f32, recover_duration: f32, + specifier: Option, }, ChargedRanged { energy_cost: f32, @@ -268,6 +272,15 @@ pub enum CharacterAbility { summon_amount: u32, summon_info: basic_summon::SummonInfo, }, + SelfBuff { + buildup_duration: f32, + cast_duration: f32, + recover_duration: f32, + buff_kind: buff::BuffKind, + buff_strength: f32, + buff_duration: Option, + energy_cost: f32, + }, } impl Default for CharacterAbility { @@ -282,6 +295,7 @@ impl Default for CharacterAbility { knockback: 0.0, range: 3.5, max_angle: 15.0, + damage_effect: None, } } } @@ -313,7 +327,8 @@ impl CharacterAbility { | CharacterAbility::ChargedMelee { energy_cost, .. } | CharacterAbility::Shockwave { energy_cost, .. } | CharacterAbility::BasicAura { energy_cost, .. } - | CharacterAbility::BasicBlock { energy_cost, .. } => update + | CharacterAbility::BasicBlock { energy_cost, .. } + | CharacterAbility::SelfBuff { energy_cost, .. } => update .energy .try_change_by(-(*energy_cost as i32), EnergySource::Ability) .is_ok(), @@ -334,7 +349,11 @@ impl CharacterAbility { .is_ok() }, CharacterAbility::HealingBeam { .. } => data.combo.counter() > 0, - _ => true, + CharacterAbility::ComboMelee { .. } + | CharacterAbility::Boost { .. } + | CharacterAbility::BasicBeam { .. } + | CharacterAbility::Blink { .. } + | CharacterAbility::BasicSummon { .. } => true, } } @@ -586,6 +605,18 @@ impl CharacterAbility { *cast_duration /= speed; *recover_duration /= speed; }, + SelfBuff { + ref mut buff_strength, + ref mut buildup_duration, + ref mut cast_duration, + ref mut recover_duration, + .. + } => { + *buff_strength *= power; + *buildup_duration /= speed; + *cast_duration /= speed; + *recover_duration /= speed; + }, } self } @@ -605,7 +636,8 @@ impl CharacterAbility { | Shockwave { energy_cost, .. } | HealingBeam { energy_cost, .. } | BasicAura { energy_cost, .. } - | BasicBlock { energy_cost, .. } => *energy_cost as u32, + | BasicBlock { energy_cost, .. } + | SelfBuff { energy_cost, .. } => *energy_cost as u32, BasicBeam { energy_drain, .. } => { if *energy_drain > f32::EPSILON { 1 @@ -1164,6 +1196,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { knockback, range, max_angle, + damage_effect, energy_cost: _, } => CharacterState::BasicMelee(basic_melee::Data { static_data: basic_melee::StaticData { @@ -1175,6 +1208,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { knockback: *knockback, range: *range, max_angle: *max_angle, + damage_effect: *damage_effect, ability_info, }, timer: Duration::default(), @@ -1419,6 +1453,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { recover_duration, range, max_angle, + specifier, } => CharacterState::ChargedMelee(charged_melee::Data { static_data: charged_melee::StaticData { energy_cost: *energy_cost, @@ -1437,6 +1472,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { hit_timing: *hit_timing, recover_duration: Duration::from_secs_f32(*recover_duration), ability_info, + specifier: *specifier, }, stage_section: StageSection::Charge, timer: Duration::default(), @@ -1657,6 +1693,27 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { timer: Duration::default(), stage_section: StageSection::Buildup, }), + CharacterAbility::SelfBuff { + buildup_duration, + cast_duration, + recover_duration, + buff_kind, + buff_strength, + buff_duration, + energy_cost: _, + } => CharacterState::SelfBuff(self_buff::Data { + static_data: self_buff::StaticData { + buildup_duration: Duration::from_secs_f32(*buildup_duration), + cast_duration: Duration::from_secs_f32(*cast_duration), + recover_duration: Duration::from_secs_f32(*recover_duration), + buff_kind: *buff_kind, + buff_strength: *buff_strength, + buff_duration: buff_duration.map(Duration::from_secs_f32), + ability_info, + }, + timer: Duration::default(), + stage_section: StageSection::Buildup, + }), } } } diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 946e937153..f2334fca5f 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -40,6 +40,7 @@ pub enum Tactic { Mindflayer, BirdLargeBreathe, BirdLargeFire, + Minotaur, } #[derive(Copy, Clone, Debug, PartialEq)] @@ -299,10 +300,17 @@ pub struct Agent { pub behavior: Behavior, pub psyche: Psyche, pub inbox: VecDeque, - pub action_timer: f32, + pub action_state: ActionState, pub bearing: Vec2, } +#[derive(Clone, Debug, Default)] +pub struct ActionState { + pub timer: f32, + pub counter: f32, + pub condition: bool, +} + impl Agent { pub fn with_patrol_origin(mut self, origin: Vec3) -> Self { self.patrol_origin = Some(origin); diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index d40719d958..2b29a2f480 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -441,10 +441,10 @@ impl Body { biped_large::Species::Wendigo => 2800, biped_large::Species::Troll => 2400, biped_large::Species::Dullahan => 3000, - biped_large::Species::Mindflayer => 8000, + biped_large::Species::Mindflayer => 25000, biped_large::Species::Tidalwarrior => 2500, biped_large::Species::Yeti => 4000, - biped_large::Species::Minotaur => 5000, + biped_large::Species::Minotaur => 30000, biped_large::Species::Harvester => 3000, biped_large::Species::Blueoni => 2400, biped_large::Species::Redoni => 2400, @@ -553,10 +553,12 @@ impl Body { biped_large::Species::Wendigo => 80, biped_large::Species::Troll => 60, biped_large::Species::Dullahan => 120, - biped_large::Species::Mindflayer => 250, biped_large::Species::Tidalwarrior => 90, biped_large::Species::Yeti => 80, biped_large::Species::Harvester => 80, + // Boss enemies have their health set, not adjusted by level. + biped_large::Species::Mindflayer => 0, + biped_large::Species::Minotaur => 0, _ => 100, }, Body::BipedSmall(_) => 10, @@ -605,7 +607,11 @@ impl Body { pub fn combat_multiplier(&self) -> f32 { match self { Body::Object(_) | Body::Ship(_) => 0.0, - Body::BipedLarge(b) if matches!(b.species, biped_large::Species::Mindflayer) => 4.0, + Body::BipedLarge(b) => match b.species { + biped_large::Species::Mindflayer => 4.8, + biped_large::Species::Minotaur => 3.2, + _ => 1.0, + }, _ => 1.0, } } diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index 790313db6c..b7bbe5bacb 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -9,33 +9,59 @@ use specs::{Component, DerefFlaggedStorage}; use specs_idvs::IdvStorage; #[cfg(not(target_arch = "wasm32"))] use std::{cmp::Ordering, time::Duration}; +use strum_macros::EnumIter; /// De/buff Kind. /// This is used to determine what effects a buff will have -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, PartialOrd, Ord)] +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, PartialOrd, Ord, EnumIter, +)] pub enum BuffKind { - /// Does damage to a creature over time - Burning, + // Buffs /// Restores health/time for some period + /// Strength should be 10x the healing per second Regeneration, /// Restores health/time for some period for consumables + /// Strength should be 10x the healing per second Saturation, - /// Lowers health over time for some duration - Bleeding, - /// Lower a creature's max health over time - Cursed, /// Applied when drinking a potion + /// Strength should be 10x the healing per second Potion, /// Applied when sitting at a campfire + /// Strength is fraction of health resotred per second CampfireHeal, /// Raises maximum stamina + /// Strength should be 10x the effect to max energy IncreaseMaxEnergy, /// Raises maximum health + /// Strength should be 10x the effect to max health IncreaseMaxHealth, /// Makes you immune to attacks + /// Strength does not affect this buff Invulnerability, /// Reduces incoming damage + /// Strength scales the damage reduction non-linearly. 0.5 provides 50% DR, + /// 1.0 provides 67% DR ProtectingWard, + /// Increases movement speed and gives health regeneration + /// Strength scales the movement speed linearly. 0.5 is 150% speed, 1.0 is + /// 200% speed. Provides regeneration at 10x the value of the strength + Frenzied, + // Debuffs + /// Does damage to a creature over time + /// Strength should be 10x the DPS of the debuff + Burning, + /// Lowers health over time for some duration + /// Strength should be 10x the DPS of the debuff + Bleeding, + /// Lower a creature's max health over time + /// Strength only affects the target max health, 0.5 targets 50% of base + /// max, 1.0 targets 100% of base max + Cursed, + /// Reduces movement speed and causes bleeding damage + /// Strength scales the movement speed debuff non-linearly. 0.5 is 50% + /// speed, 1.0 is 33% speed. Bleeding is at 10x the value of the strength. + Crippled, } #[cfg(not(target_arch = "wasm32"))] @@ -54,6 +80,8 @@ impl BuffKind { BuffKind::Invulnerability => true, BuffKind::ProtectingWard => true, BuffKind::Burning => false, + BuffKind::Crippled => false, + BuffKind::Frenzied => true, } } @@ -118,6 +146,8 @@ pub enum BuffEffect { kind: ModifierKind, target_fraction: f32, }, + /// Modifies move speed of target + MovementSpeed(f32), } /// Actual de/buff. @@ -174,6 +204,8 @@ impl Buff { cat_ids: Vec, source: BuffSource, ) -> Self { + // Normalized nonlinear scaling + let nn_scaling = |a| a / (a + 0.5); let (effects, time) = match kind { BuffKind::Bleeding => ( vec![BuffEffect::HealthChangeOverTime { @@ -235,7 +267,7 @@ impl Buff { // Causes non-linearity in effect strength, but necessary to allow for tool // power and other things to affect the strength. 0.5 also still provides 50% // damage reduction. - data.strength / (0.5 + data.strength), + nn_scaling(data.strength), )], data.duration, ), @@ -247,6 +279,28 @@ impl Buff { }], data.duration, ), + BuffKind::Crippled => ( + vec![ + BuffEffect::MovementSpeed(1.0 - nn_scaling(data.strength)), + BuffEffect::HealthChangeOverTime { + rate: -data.strength * 40.0, + accumulated: 0.0, + kind: ModifierKind::Additive, + }, + ], + data.duration, + ), + BuffKind::Frenzied => ( + vec![ + BuffEffect::MovementSpeed(1.0 + data.strength), + BuffEffect::HealthChangeOverTime { + rate: data.strength * 100.0, + accumulated: 0.0, + kind: ModifierKind::Additive, + }, + ], + data.duration, + ), }; Buff { kind, diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index 4b9abe1cb2..d73be47340 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -99,6 +99,8 @@ pub enum CharacterState { Blink(blink::Data), /// Summons creatures that fight for the caster BasicSummon(basic_summon::Data), + /// Inserts a buff on the caster + SelfBuff(self_buff::Data), } impl CharacterState { @@ -120,6 +122,7 @@ impl CharacterState { | CharacterState::BasicBeam(_) | CharacterState::BasicAura(_) | CharacterState::HealingBeam(_) + | CharacterState::SelfBuff(_) ) } @@ -143,6 +146,7 @@ impl CharacterState { | CharacterState::BasicBeam(_) | CharacterState::BasicAura(_) | CharacterState::HealingBeam(_) + | CharacterState::SelfBuff(_) ) } diff --git a/common/src/comp/stats.rs b/common/src/comp/stats.rs index 55a49ae343..b7d428102d 100644 --- a/common/src/comp/stats.rs +++ b/common/src/comp/stats.rs @@ -25,6 +25,7 @@ pub struct Stats { pub name: String, pub damage_reduction: f32, pub max_health_modifier: f32, + pub move_speed_modifier: f32, } impl Stats { @@ -33,6 +34,7 @@ impl Stats { name, damage_reduction: 0.0, max_health_modifier: 1.0, + move_speed_modifier: 1.0, } } @@ -43,6 +45,7 @@ impl Stats { name: "".to_owned(), damage_reduction: 0.0, max_health_modifier: 1.0, + move_speed_modifier: 1.0, } } @@ -50,6 +53,7 @@ impl Stats { pub fn reset_temp_modifiers(&mut self) { self.damage_reduction = 0.0; self.max_health_modifier = 1.0; + self.move_speed_modifier = 1.0; } } diff --git a/common/src/outcome.rs b/common/src/outcome.rs index afdef6ae47..559139d23a 100644 --- a/common/src/outcome.rs +++ b/common/src/outcome.rs @@ -68,6 +68,9 @@ pub enum Outcome { pos: Vec3, state: PoiseState, }, + GroundSlam { + pos: Vec3, + }, } impl Outcome { @@ -81,7 +84,8 @@ impl Outcome { | Outcome::SummonedCreature { pos, .. } | Outcome::Damage { pos, .. } | Outcome::Block { pos, .. } - | Outcome::PoiseChange { pos, .. } => Some(*pos), + | Outcome::PoiseChange { pos, .. } + | Outcome::GroundSlam { pos } => Some(*pos), Outcome::BreakBlock { pos, .. } => Some(pos.map(|e| e as f32 + 0.5)), Outcome::ExpChange { .. } | Outcome::ComboChange { .. } => None, } diff --git a/common/src/states/basic_melee.rs b/common/src/states/basic_melee.rs index 7346190829..c59d175de5 100644 --- a/common/src/states/basic_melee.rs +++ b/common/src/states/basic_melee.rs @@ -29,6 +29,8 @@ pub struct StaticData { pub range: f32, /// Max angle (45.0 will give you a 90.0 angle window) pub max_angle: f32, + /// Adds an effect onto the main damage of the attack + pub damage_effect: Option, /// What key is used to press ability pub ability_info: AbilityInfo, } @@ -50,6 +52,7 @@ impl CharacterBehavior for Data { fn behavior(&self, data: &JoinData) -> StateUpdate { let mut update = StateUpdate::from(data); + handle_orientation(data, &mut update, 1.0); handle_move(data, &mut update, 0.7); handle_jump(data, &mut update, 1.0); handle_orientation(data, &mut update, 0.35); @@ -97,15 +100,20 @@ impl CharacterBehavior for Data { .with_requirement(CombatRequirement::AnyDamage); let energy = AttackEffect::new(None, CombatEffect::EnergyReward(50.0)) .with_requirement(CombatRequirement::AnyDamage); - let buff = CombatEffect::Buff(CombatBuff::default_physical()); - let damage = AttackDamage::new( + let mut damage = AttackDamage::new( Damage { source: DamageSource::Melee, value: self.static_data.base_damage as f32, }, Some(GroupTarget::OutOfGroup), - ) - .with_effect(buff); + ); + match self.static_data.damage_effect { + Some(effect) => damage = damage.with_effect(effect), + None => { + let buff = CombatEffect::Buff(CombatBuff::default_physical()); + damage = damage.with_effect(buff); + }, + } let (crit_chance, crit_mult) = get_crit_data(data, self.static_data.ability_info); let attack = Attack::default() diff --git a/common/src/states/charged_melee.rs b/common/src/states/charged_melee.rs index 30285b964d..bb12d5b462 100644 --- a/common/src/states/charged_melee.rs +++ b/common/src/states/charged_melee.rs @@ -1,6 +1,8 @@ use crate::{ combat::{Attack, AttackDamage, AttackEffect, CombatBuff, CombatEffect, CombatRequirement}, comp::{tool::ToolKind, CharacterState, EnergyChange, EnergySource, Melee, StateUpdate}, + event::LocalEvent, + outcome::Outcome, states::{ behavior::{CharacterBehavior, JoinData}, utils::{StageSection, *}, @@ -45,6 +47,8 @@ pub struct StaticData { pub recover_duration: Duration, /// What key is used to press ability pub ability_info: AbilityInfo, + /// Used to specify the melee attack to the frontend + pub specifier: Option, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -199,6 +203,17 @@ impl CharacterBehavior for Data { }) .filter(|(_, tool)| tool == &Some(ToolKind::Pick)), }); + + if let Some(FrontendSpecifier::GroundCleave) = self.static_data.specifier { + // Send local event used for frontend shenanigans + update.local_events.push_front(LocalEvent::CreateOutcome( + Outcome::GroundSlam { + pos: data.pos.0 + + *data.ori.look_dir() + * (data.body.radius() + self.static_data.range), + }, + )); + } } else if self.timer < self.static_data.swing_duration { // Swings update.character = CharacterState::ChargedMelee(Data { @@ -250,3 +265,9 @@ impl CharacterBehavior for Data { update } } + +/// Used to specify a particular effect for frontend purposes +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum FrontendSpecifier { + GroundCleave, +} diff --git a/common/src/states/dash_melee.rs b/common/src/states/dash_melee.rs index f7f0a58e39..1cea70c771 100644 --- a/common/src/states/dash_melee.rs +++ b/common/src/states/dash_melee.rs @@ -71,12 +71,12 @@ impl CharacterBehavior for Data { fn behavior(&self, data: &JoinData) -> StateUpdate { let mut update = StateUpdate::from(data); - handle_orientation(data, &mut update, 1.0); handle_move(data, &mut update, 0.1); match self.stage_section { StageSection::Buildup => { if self.timer < self.static_data.buildup_duration { + handle_orientation(data, &mut update, 1.0); // Build up update.character = CharacterState::DashMelee(Data { timer: self @@ -106,6 +106,7 @@ impl CharacterBehavior for Data { / self.static_data.charge_duration.as_secs_f32()) .min(1.0); + handle_orientation(data, &mut update, 0.6); handle_forced_movement( data, &mut update, diff --git a/common/src/states/mod.rs b/common/src/states/mod.rs index 15cb6c72d6..30b86e3766 100644 --- a/common/src/states/mod.rs +++ b/common/src/states/mod.rs @@ -21,6 +21,7 @@ pub mod idle; pub mod leap_melee; pub mod repeater_ranged; pub mod roll; +pub mod self_buff; pub mod shockwave; pub mod sit; pub mod sneak; diff --git a/common/src/states/self_buff.rs b/common/src/states/self_buff.rs new file mode 100644 index 0000000000..85a99e46ab --- /dev/null +++ b/common/src/states/self_buff.rs @@ -0,0 +1,131 @@ +use crate::{ + comp::{ + buff::{Buff, BuffChange, BuffData, BuffKind, BuffSource}, + CharacterState, StateUpdate, + }, + event::ServerEvent, + states::{ + behavior::{CharacterBehavior, JoinData}, + utils::*, + }, +}; +use serde::{Deserialize, Serialize}; +use std::time::Duration; + +/// Separated out to condense update portions of character state +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct StaticData { + /// How long until state should create the aura + pub buildup_duration: Duration, + /// How long the state is creating an aura + pub cast_duration: Duration, + /// How long the state has until exiting + pub recover_duration: Duration, + /// What kind of buff is created + pub buff_kind: BuffKind, + /// Strength of the created buff + pub buff_strength: f32, + /// How long buff lasts + pub buff_duration: Option, + /// What key is used to press ability + pub ability_info: AbilityInfo, +} + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Data { + /// Struct containing data that does not change over the course of the + /// character state + pub static_data: StaticData, + /// Timer for each stage + pub timer: Duration, + /// What section the character stage is in + pub stage_section: StageSection, +} + +impl CharacterBehavior for Data { + fn behavior(&self, data: &JoinData) -> StateUpdate { + let mut update = StateUpdate::from(data); + + handle_move(data, &mut update, 0.8); + handle_jump(data, &mut update, 1.0); + + match self.stage_section { + StageSection::Buildup => { + if self.timer < self.static_data.buildup_duration { + // Build up + update.character = CharacterState::SelfBuff(Data { + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + ..*self + }); + } else { + // Creates buff + let buff = Buff::new( + self.static_data.buff_kind, + BuffData { + strength: self.static_data.buff_strength, + duration: self.static_data.buff_duration, + }, + Vec::new(), + BuffSource::Character { by: *data.uid }, + ); + update.server_events.push_front(ServerEvent::Buff { + entity: data.entity, + buff_change: BuffChange::Add(buff), + }); + // Build up + update.character = CharacterState::SelfBuff(Data { + timer: Duration::default(), + stage_section: StageSection::Cast, + ..*self + }); + } + }, + StageSection::Cast => { + if self.timer < self.static_data.cast_duration { + // Cast + update.character = CharacterState::SelfBuff(Data { + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + ..*self + }); + } else { + update.character = CharacterState::SelfBuff(Data { + timer: Duration::default(), + stage_section: StageSection::Recover, + ..*self + }); + } + }, + StageSection::Recover => { + if self.timer < self.static_data.recover_duration { + update.character = CharacterState::SelfBuff(Data { + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + ..*self + }); + } else { + // Done + update.character = CharacterState::Wielding; + } + }, + _ => { + // If it somehow ends up in an incorrect stage section + update.character = CharacterState::Wielding; + }, + } + + // At end of state logic so an interrupt isn't overwritten + if !input_is_pressed(data, self.static_data.ability_info.input) { + handle_state_interrupt(data, &mut update, false); + } + + update + } +} diff --git a/common/src/states/spin_melee.rs b/common/src/states/spin_melee.rs index 232a84b1b1..8730d86f34 100644 --- a/common/src/states/spin_melee.rs +++ b/common/src/states/spin_melee.rs @@ -175,7 +175,6 @@ impl CharacterBehavior for Data { }, 0.1, ); - handle_orientation(data, &mut update, 1.0); } // Swings diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index b8fadff6c0..7b155d1787 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -71,7 +71,7 @@ impl Body { biped_large::Species::Occultsaurok => 100.0, biped_large::Species::Mightysaurok => 100.0, biped_large::Species::Mindflayer => 90.0, - biped_large::Species::Minotaur => 90.0, + biped_large::Species::Minotaur => 60.0, _ => 80.0, }, Body::BirdMedium(_) => 80.0, @@ -243,6 +243,8 @@ pub fn handle_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) { /// Updates components to move player as if theyre on ground or in air #[allow(clippy::assign_op_pattern)] // TODO: Pending review in #587 fn basic_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) { + let efficiency = efficiency * data.stats.move_speed_modifier; + let accel = if data.physics.on_ground { data.body.base_accel() } else { @@ -267,6 +269,8 @@ pub fn handle_forced_movement( movement: ForcedMovement, efficiency: f32, ) { + let efficiency = efficiency * data.stats.move_speed_modifier; + match movement { ForcedMovement::Forward { strength } => { if let Some(accel) = data.physics.on_ground.then_some(data.body.base_accel()) { @@ -324,6 +328,7 @@ pub fn handle_orientation(data: &JoinData, update: &mut StateUpdate, efficiency: /// Updates components to move player as if theyre swimming fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32, submersion: f32) -> bool { + let efficiency = efficiency * data.stats.move_speed_modifier; if let Some(force) = data.body.swim_thrust() { let force = efficiency * force; let mut water_accel = force / data.mass.0; @@ -361,6 +366,8 @@ fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32, submers /// Updates components to move entity as if it's flying pub fn fly_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) -> bool { + let efficiency = efficiency * data.stats.move_speed_modifier; + let glider = match data.character { CharacterState::Glide(data) => Some(data), _ => None, diff --git a/common/systems/src/buff.rs b/common/systems/src/buff.rs index 9d379af810..71137c4540 100644 --- a/common/systems/src/buff.rs +++ b/common/systems/src/buff.rs @@ -189,6 +189,9 @@ impl<'a> System<'a> for Sys { stat.max_health_modifier *= current_fraction; } }, + BuffEffect::MovementSpeed(ms) => { + stat.move_speed_modifier *= *ms; + }, }; } } diff --git a/common/systems/src/character_behavior.rs b/common/systems/src/character_behavior.rs index d93791c78e..a97350888b 100644 --- a/common/systems/src/character_behavior.rs +++ b/common/systems/src/character_behavior.rs @@ -341,6 +341,7 @@ impl<'a> System<'a> for Sys { CharacterState::HealingBeam(data) => data.handle_event(&j, action), CharacterState::Blink(data) => data.handle_event(&j, action), CharacterState::BasicSummon(data) => data.handle_event(&j, action), + CharacterState::SelfBuff(data) => data.handle_event(&j, action), }; local_emitter.append(&mut state_update.local_events); server_emitter.append(&mut state_update.server_events); @@ -395,6 +396,7 @@ impl<'a> System<'a> for Sys { CharacterState::HealingBeam(data) => data.behavior(&j), CharacterState::Blink(data) => data.behavior(&j), CharacterState::BasicSummon(data) => data.behavior(&j), + CharacterState::SelfBuff(data) => data.behavior(&j), }; local_emitter.append(&mut state_update.local_events); diff --git a/common/systems/src/stats.rs b/common/systems/src/stats.rs index 4d999387db..18ef179826 100644 --- a/common/systems/src/stats.rs +++ b/common/systems/src/stats.rs @@ -248,7 +248,8 @@ impl<'a> System<'a> for Sys { | CharacterState::BasicAura { .. } | CharacterState::HealingBeam { .. } | CharacterState::Blink { .. } - | CharacterState::BasicSummon { .. } => { + | CharacterState::BasicSummon { .. } + | CharacterState::SelfBuff { .. } => { if energy.get_unchecked().regen_rate != 0.0 { energy.get_mut_unchecked().regen_rate = 0.0 } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 20724afbfe..4828c36c4c 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -11,7 +11,7 @@ use authc::Uuid; use chrono::{NaiveTime, Timelike}; use common::{ assets, - cmd::{ChatCommand, CHAT_COMMANDS, CHAT_SHORTCUTS}, + cmd::{ChatCommand, BUFF_PACK, BUFF_PARSER, CHAT_COMMANDS, CHAT_SHORTCUTS}, comp::{ self, aura::{Aura, AuraKind, AuraTarget}, @@ -36,13 +36,11 @@ use common_net::{ }; use common_state::{BuildAreaError, BuildAreas}; use core::{convert::TryFrom, ops::Not, time::Duration}; -use hashbrown::HashSet; +use hashbrown::{HashMap, HashSet}; use rand::Rng; use specs::{Builder, Entity as EcsEntity, Join, WorldExt}; -use wiring::{Circuit, Wire, WiringAction, WiringActionEffect, WiringElement}; - -use hashbrown::HashMap; use vek::*; +use wiring::{Circuit, Wire, WiringAction, WiringActionEffect, WiringElement}; use world::util::Sampler; use crate::{client::Client, login_provider::LoginProvider, wiring}; @@ -2615,22 +2613,6 @@ fn handle_apply_buff( args: String, action: &ChatCommand, ) -> CmdResult<()> { - const BUFF_PACK: &[&str] = &[ - // Debuffs - "burning", - "bleeding", - "curse", - // Healing - "regeneration", - "saturation", - "potion", - "campfire_heal", - // Outmaxing stats - "increase_max_energy", - "increase_max_health", - // Defensive buffs (invulnerability is skipped because it ruins all debuffs) - "protecting_ward", - ]; if let (Some(buff), strength, duration) = scan_fmt_some!(&args, &action.arg_fmt(), String, f32, f64) { @@ -2640,7 +2622,7 @@ fn handle_apply_buff( if buff != "all" { cast_buff(&buff, buffdata, server, target) } else { - for kind in BUFF_PACK { + for kind in BUFF_PACK.iter() { cast_buff(kind, buffdata, server, target)?; } Ok(()) @@ -2663,19 +2645,4 @@ fn cast_buff(kind: &str, data: BuffData, server: &mut Server, target: EcsEntity) } } -fn parse_buffkind(buff: &str) -> Option { - match buff { - "burning" => Some(BuffKind::Burning), - "regeneration" => Some(BuffKind::Regeneration), - "saturation" => Some(BuffKind::Saturation), - "bleeding" => Some(BuffKind::Bleeding), - "curse" => Some(BuffKind::Cursed), - "potion" => Some(BuffKind::Potion), - "campfire_heal" => Some(BuffKind::CampfireHeal), - "increase_max_energy" => Some(BuffKind::IncreaseMaxEnergy), - "increase_max_health" => Some(BuffKind::IncreaseMaxHealth), - "invulnerability" => Some(BuffKind::Invulnerability), - "protecting_ward" => Some(BuffKind::ProtectingWard), - _ => None, - } -} +fn parse_buffkind(buff: &str) -> Option { BUFF_PARSER.get(buff).copied() } diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index 8ebc872bc7..a6c684646c 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -550,10 +550,10 @@ impl<'a> AgentData<'a> { } // Interact if incoming messages if !agent.inbox.is_empty() { - agent.action_timer = 0.1; + agent.action_state.timer = 0.1; } - if agent.action_timer > 0.0 { - if agent.action_timer + if agent.action_state.timer > 0.0 { + if agent.action_state.timer < (if agent.behavior.is(BehaviorState::TRADING) { TRADE_INTERACTION_TIME } else { @@ -562,7 +562,7 @@ impl<'a> AgentData<'a> { { self.interact(agent, controller, &read_data, event_emitter); } else { - agent.action_timer = 0.0; + agent.action_state.timer = 0.0; agent.target = None; controller.actions.push(ControlAction::Stand); self.idle(agent, controller, &read_data); @@ -582,7 +582,7 @@ impl<'a> AgentData<'a> { event_emitter: &mut Emitter<'_, ServerEvent>, ) { if self.damage < HEALING_ITEM_THRESHOLD && self.heal_self(agent, controller) { - agent.action_timer = 0.01; + agent.action_state.timer = 0.01; return; } @@ -596,12 +596,15 @@ impl<'a> AgentData<'a> { let dist_sqrd = self.pos.0.distance_squared(tgt_pos.0); // Should the agent flee? if 1.0 - agent.psyche.aggro > self.damage && self.flees { - if agent.action_timer == 0.0 && agent.behavior.can(BehaviorCapability::SPEAK) { + if agent.action_state.timer == 0.0 + && agent.behavior.can(BehaviorCapability::SPEAK) + { let msg = "npc.speech.villager_under_attack".to_string(); event_emitter .emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg))); - agent.action_timer = 0.01; - } else if agent.action_timer < FLEE_DURATION || dist_sqrd < MAX_FLEE_DIST { + agent.action_state.timer = 0.01; + } else if agent.action_state.timer < FLEE_DURATION || dist_sqrd < MAX_FLEE_DIST + { self.flee( agent, controller, @@ -610,7 +613,7 @@ impl<'a> AgentData<'a> { &read_data.dt, ); } else { - agent.action_timer = 0.0; + agent.action_state.timer = 0.0; agent.target = None; self.idle(agent, controller, &read_data); } @@ -705,11 +708,11 @@ impl<'a> AgentData<'a> { }; if self.damage < HEALING_ITEM_THRESHOLD && self.heal_self(agent, controller) { - agent.action_timer = 0.01; + agent.action_state.timer = 0.01; return; } - agent.action_timer = 0.0; + agent.action_state.timer = 0.0; if let Some((travel_to, _destination)) = &agent.rtsim_controller.travel_to { // if it has an rtsim destination and can fly then it should // if it is flying and bumps something above it then it should move down @@ -901,7 +904,7 @@ impl<'a> AgentData<'a> { // .events // .push(ControlEvent::InviteResponse(InviteResponse::Decline)); // } - agent.action_timer += read_data.dt.0; + agent.action_state.timer += read_data.dt.0; let msg = agent.inbox.pop_back(); match msg { Some(AgentEvent::Talk(by, subject)) => { @@ -1250,7 +1253,7 @@ impl<'a> AgentData<'a> { if let Some(Target { target, .. }) = &agent.target { self.look_toward(controller, read_data, target); } else { - agent.action_timer = 0.0; + agent.action_state.timer = 0.0; } } }, @@ -1314,7 +1317,7 @@ impl<'a> AgentData<'a> { self.jump_if(controller, bearing.z > 1.5); controller.inputs.move_z = bearing.z; } - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } /// Attempt to consume a healing item, and return whether any healing items @@ -1379,7 +1382,7 @@ impl<'a> AgentData<'a> { read_data: &ReadData, event_emitter: &mut Emitter<'_, ServerEvent>, ) { - agent.action_timer = 0.0; + agent.action_state.timer = 0.0; // Search area let target = self.cached_spatial_grid.0 @@ -1538,6 +1541,7 @@ impl<'a> AgentData<'a> { "Bird Large Breathe" => Tactic::BirdLargeBreathe, "Bird Large Fire" => Tactic::BirdLargeFire, "Mindflayer" => Tactic::Mindflayer, + "Minotaur" => Tactic::Minotaur, _ => Tactic::Melee, }, AbilitySpec::Tool(tool_kind) => tool_tactic(*tool_kind), @@ -1644,16 +1648,16 @@ impl<'a> AgentData<'a> { Tactic::Axe => { if dist_sqrd < min_attack_dist.powi(2) && angle < 45.0 { controller.inputs.move_dir = Vec2::zero(); - if agent.action_timer > 6.0 { + if agent.action_state.timer > 6.0 { controller .actions .push(ControlAction::CancelInput(InputKind::Secondary)); - agent.action_timer = 0.0; - } else if agent.action_timer > 4.0 && self.energy.current() > 10 { + agent.action_state.timer = 0.0; + } else if agent.action_state.timer > 4.0 && self.energy.current() > 10 { controller .actions .push(ControlAction::basic_input(InputKind::Secondary)); - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } else if self.skill_set.has_skill(Skill::Axe(AxeSkill::UnlockLeap)) && self.energy.current() > 800 && thread_rng().gen_bool(0.5) @@ -1661,12 +1665,12 @@ impl<'a> AgentData<'a> { controller .actions .push(ControlAction::basic_input(InputKind::Ability(0))); - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } else { controller .actions .push(ControlAction::basic_input(InputKind::Primary)); - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } } else if dist_sqrd < MAX_CHASE_DIST.powi(2) { if let Some((bearing, speed)) = agent.chaser.chase( @@ -1699,16 +1703,16 @@ impl<'a> AgentData<'a> { Tactic::Hammer => { if dist_sqrd < min_attack_dist.powi(2) && angle < 45.0 { controller.inputs.move_dir = Vec2::zero(); - if agent.action_timer > 4.0 { + if agent.action_state.timer > 4.0 { controller .actions .push(ControlAction::CancelInput(InputKind::Secondary)); - agent.action_timer = 0.0; - } else if agent.action_timer > 2.0 { + agent.action_state.timer = 0.0; + } else if agent.action_state.timer > 2.0 { controller .actions .push(ControlAction::basic_input(InputKind::Secondary)); - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } else if self .skill_set .has_skill(Skill::Hammer(HammerSkill::UnlockLeap)) @@ -1718,12 +1722,12 @@ impl<'a> AgentData<'a> { controller .actions .push(ControlAction::basic_input(InputKind::Ability(0))); - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } else { controller .actions .push(ControlAction::basic_input(InputKind::Primary)); - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } } else if dist_sqrd < MAX_CHASE_DIST.powi(2) { if let Some((bearing, speed)) = agent.chaser.chase( @@ -1742,14 +1746,14 @@ impl<'a> AgentData<'a> { if self .skill_set .has_skill(Skill::Hammer(HammerSkill::UnlockLeap)) - && agent.action_timer > 5.0 + && agent.action_state.timer > 5.0 { controller .actions .push(ControlAction::basic_input(InputKind::Ability(0))); - agent.action_timer = 0.0; + agent.action_state.timer = 0.0; } else { - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } } else { controller.inputs.move_dir = @@ -1776,20 +1780,20 @@ impl<'a> AgentData<'a> { if self .skill_set .has_skill(Skill::Sword(SwordSkill::UnlockSpin)) - && agent.action_timer < 2.0 + && agent.action_state.timer < 2.0 && self.energy.current() > 600 { controller .actions .push(ControlAction::basic_input(InputKind::Ability(0))); - agent.action_timer += dt.0; - } else if agent.action_timer > 2.0 { - agent.action_timer = 0.0; + agent.action_state.timer += dt.0; + } else if agent.action_state.timer > 2.0 { + agent.action_state.timer = 0.0; } else { controller .actions .push(ControlAction::basic_input(InputKind::Primary)); - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } } else if dist_sqrd < MAX_CHASE_DIST.powi(2) { if let Some((bearing, speed)) = agent.chaser.chase( @@ -1805,13 +1809,13 @@ impl<'a> AgentData<'a> { if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) { controller.inputs.move_dir = bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed; - if agent.action_timer > 4.0 && angle < 45.0 { + if agent.action_state.timer > 4.0 && angle < 45.0 { controller .actions .push(ControlAction::basic_input(InputKind::Secondary)); - agent.action_timer = 0.0; + agent.action_state.timer = 0.0; } else { - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } } else { controller.inputs.move_dir = @@ -1886,16 +1890,17 @@ impl<'a> AgentData<'a> { .try_normalized() .unwrap_or_else(Vec2::zero) * speed; - if agent.action_timer > 4.0 { + if agent.action_state.timer > 4.0 { controller .actions .push(ControlAction::CancelInput(InputKind::Secondary)); - agent.action_timer = 0.0; - } else if agent.action_timer > 2.0 && self.energy.current() > 300 { + agent.action_state.timer = 0.0; + } else if agent.action_state.timer > 2.0 && self.energy.current() > 300 + { controller .actions .push(ControlAction::basic_input(InputKind::Secondary)); - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } else if self .skill_set .has_skill(Skill::Bow(BowSkill::UnlockRepeater)) @@ -1908,7 +1913,7 @@ impl<'a> AgentData<'a> { controller .actions .push(ControlAction::basic_input(InputKind::Ability(0))); - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } else { controller .actions @@ -1916,7 +1921,7 @@ impl<'a> AgentData<'a> { controller .actions .push(ControlAction::basic_input(InputKind::Primary)); - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } } else { controller.inputs.move_dir = @@ -1961,22 +1966,22 @@ impl<'a> AgentData<'a> { .actions .push(ControlAction::basic_input(InputKind::Roll)); } else if dist_sqrd < (5.0 * min_attack_dist).powi(2) && angle < 15.0 { - if agent.action_timer < 1.5 { + if agent.action_state.timer < 1.5 { controller.inputs.move_dir = (tgt_pos.0 - self.pos.0) .xy() .rotated_z(0.47 * PI) .try_normalized() .unwrap_or_else(Vec2::unit_y); - agent.action_timer += dt.0; - } else if agent.action_timer < 3.0 { + agent.action_state.timer += dt.0; + } else if agent.action_state.timer < 3.0 { controller.inputs.move_dir = (tgt_pos.0 - self.pos.0) .xy() .rotated_z(-0.47 * PI) .try_normalized() .unwrap_or_else(Vec2::unit_y); - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } else { - agent.action_timer = 0.0; + agent.action_state.timer = 0.0; } if self .skill_set @@ -2080,13 +2085,13 @@ impl<'a> AgentData<'a> { if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) && angle < 90.0 { controller.inputs.move_dir = bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed; - if agent.action_timer > 5.0 { + if agent.action_state.timer > 5.0 { controller .actions .push(ControlAction::basic_input(InputKind::Secondary)); - agent.action_timer = 0.0; + agent.action_state.timer = 0.0; } else { - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } } else { controller.inputs.move_dir = @@ -2116,7 +2121,7 @@ impl<'a> AgentData<'a> { } else if dist_sqrd < ((radius as f32 + 1.0) * min_attack_dist).powi(2) && dist_sqrd > (radius as f32 * min_attack_dist).powi(2) { - if agent.action_timer < circle_time as f32 { + if agent.action_state.timer < circle_time as f32 { let move_dir = (tgt_pos.0 - self.pos.0) .xy() .rotated_z(0.47 * PI) @@ -2133,16 +2138,16 @@ impl<'a> AgentData<'a> { .1 .map_or(true, |b| b.is_some()); if obstacle_left { - agent.action_timer = circle_time as f32; + agent.action_state.timer = circle_time as f32; } controller.inputs.move_dir = move_dir; - agent.action_timer += dt.0; - } else if agent.action_timer < circle_time as f32 + 0.5 { + agent.action_state.timer += dt.0; + } else if agent.action_state.timer < circle_time as f32 + 0.5 { controller .actions .push(ControlAction::basic_input(InputKind::Secondary)); - agent.action_timer += dt.0; - } else if agent.action_timer < 2.0 * circle_time as f32 + 0.5 { + agent.action_state.timer += dt.0; + } else if agent.action_state.timer < 2.0 * circle_time as f32 + 0.5 { let move_dir = (tgt_pos.0 - self.pos.0) .xy() .rotated_z(-0.47 * PI) @@ -2159,20 +2164,20 @@ impl<'a> AgentData<'a> { .1 .map_or(true, |b| b.is_some()); if obstacle_right { - agent.action_timer = 2.0 * circle_time as f32 + 0.5; + agent.action_state.timer = 2.0 * circle_time as f32 + 0.5; } controller.inputs.move_dir = move_dir; - agent.action_timer += dt.0; - } else if agent.action_timer < 2.0 * circle_time as f32 + 1.0 { - if agent.action_timer < 2.0 * circle_time as f32 { - agent.action_timer = 2.0 * circle_time as f32; + agent.action_state.timer += dt.0; + } else if agent.action_state.timer < 2.0 * circle_time as f32 + 1.0 { + if agent.action_state.timer < 2.0 * circle_time as f32 { + agent.action_state.timer = 2.0 * circle_time as f32; } controller .actions .push(ControlAction::basic_input(InputKind::Secondary)); - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } else { - agent.action_timer = 0.0; + agent.action_state.timer = 0.0; } } else if dist_sqrd < MAX_CHASE_DIST.powi(2) { if let Some((bearing, speed)) = agent.chaser.chase( @@ -2215,16 +2220,16 @@ impl<'a> AgentData<'a> { }, ) { if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) && angle < 15.0 { - if agent.action_timer > 5.0 { - agent.action_timer = 0.0; - } else if agent.action_timer > 2.5 { + if agent.action_state.timer > 5.0 { + agent.action_state.timer = 0.0; + } else if agent.action_state.timer > 2.5 { controller.inputs.move_dir = (tgt_pos.0 - self.pos.0) .xy() .rotated_z(1.75 * PI) .try_normalized() .unwrap_or_else(Vec2::zero) * speed; - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } else { controller.inputs.move_dir = (tgt_pos.0 - self.pos.0) .xy() @@ -2232,7 +2237,7 @@ impl<'a> AgentData<'a> { .try_normalized() .unwrap_or_else(Vec2::zero) * speed; - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } controller .actions @@ -2254,21 +2259,21 @@ impl<'a> AgentData<'a> { }, Tactic::TailSlap => { if dist_sqrd < (1.5 * min_attack_dist).powi(2) && angle < 90.0 { - if agent.action_timer > 4.0 { + if agent.action_state.timer > 4.0 { controller .actions .push(ControlAction::CancelInput(InputKind::Primary)); - agent.action_timer = 0.0; - } else if agent.action_timer > 1.0 { + agent.action_state.timer = 0.0; + } else if agent.action_state.timer > 1.0 { controller .actions .push(ControlAction::basic_input(InputKind::Primary)); - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } else { controller .actions .push(ControlAction::basic_input(InputKind::Secondary)); - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } controller.inputs.move_dir = (tgt_pos.0 - self.pos.0) .xy() @@ -2336,18 +2341,18 @@ impl<'a> AgentData<'a> { Tactic::QuadLowBasic => { if dist_sqrd < (1.5 * min_attack_dist).powi(2) { controller.inputs.move_dir = Vec2::zero(); - if agent.action_timer > 5.0 { - agent.action_timer = 0.0; - } else if agent.action_timer > 2.0 && angle < 90.0 { + if agent.action_state.timer > 5.0 { + agent.action_state.timer = 0.0; + } else if agent.action_state.timer > 2.0 && angle < 90.0 { controller .actions .push(ControlAction::basic_input(InputKind::Secondary)); - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } else if angle < 90.0 { controller .actions .push(ControlAction::basic_input(InputKind::Primary)); - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } } else if dist_sqrd < MAX_CHASE_DIST.powi(2) { if let Some((bearing, speed)) = agent.chaser.chase( @@ -2410,18 +2415,18 @@ impl<'a> AgentData<'a> { Tactic::QuadMedBasic => { if dist_sqrd < min_attack_dist.powi(2) && angle < 90.0 { controller.inputs.move_dir = Vec2::zero(); - if agent.action_timer < 2.0 { + if agent.action_state.timer < 2.0 { controller .actions .push(ControlAction::basic_input(InputKind::Secondary)); - agent.action_timer += dt.0; - } else if agent.action_timer < 3.0 { + agent.action_state.timer += dt.0; + } else if agent.action_state.timer < 3.0 { controller .actions .push(ControlAction::basic_input(InputKind::Primary)); - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } else { - agent.action_timer = 0.0; + agent.action_state.timer = 0.0; } } else if dist_sqrd < MAX_CHASE_DIST.powi(2) { if let Some((bearing, speed)) = agent.chaser.chase( @@ -2450,7 +2455,7 @@ impl<'a> AgentData<'a> { .actions .push(ControlAction::basic_input(InputKind::Secondary)); } else if dist_sqrd < (7.0 * min_attack_dist).powi(2) && angle < 15.0 { - if agent.action_timer < 2.0 { + if agent.action_state.timer < 2.0 { controller.inputs.move_dir = (tgt_pos.0 - self.pos.0) .xy() .rotated_z(0.47 * PI) @@ -2459,8 +2464,8 @@ impl<'a> AgentData<'a> { controller .actions .push(ControlAction::basic_input(InputKind::Primary)); - agent.action_timer += dt.0; - } else if agent.action_timer < 4.0 && angle < 15.0 { + agent.action_state.timer += dt.0; + } else if agent.action_state.timer < 4.0 && angle < 15.0 { controller.inputs.move_dir = (tgt_pos.0 - self.pos.0) .xy() .rotated_z(-0.47 * PI) @@ -2469,14 +2474,14 @@ impl<'a> AgentData<'a> { controller .actions .push(ControlAction::basic_input(InputKind::Primary)); - agent.action_timer += dt.0; - } else if agent.action_timer < 6.0 && angle < 15.0 { + agent.action_state.timer += dt.0; + } else if agent.action_state.timer < 6.0 && angle < 15.0 { controller .actions .push(ControlAction::basic_input(InputKind::Ability(0))); - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } else { - agent.action_timer = 0.0; + agent.action_state.timer = 0.0; } } else if dist_sqrd < MAX_CHASE_DIST.powi(2) { if let Some((bearing, speed)) = agent.chaser.chase( @@ -2563,21 +2568,20 @@ impl<'a> AgentData<'a> { const MINDFLAYER_ATTACK_DIST: f32 = 16.0; const MINION_SUMMON_THRESHOLD: f32 = 0.20; let health_fraction = self.health.map_or(0.5, |h| h.fraction()); - // Extreme hack to set action_timer at start of combat - if agent.action_timer < MINION_SUMMON_THRESHOLD - && health_fraction > MINION_SUMMON_THRESHOLD - { - agent.action_timer = health_fraction - MINION_SUMMON_THRESHOLD; + // Sets counter at start of combat + if agent.action_state.condition { + agent.action_state.counter = 1.0 - MINION_SUMMON_THRESHOLD; + agent.action_state.condition = true; } let mindflayer_is_far = dist_sqrd > MINDFLAYER_ATTACK_DIST.powi(2); - if agent.action_timer > health_fraction { + if agent.action_state.counter > health_fraction { // Summon minions at particular thresholds of health controller .actions .push(ControlAction::basic_input(InputKind::Ability(1))); if matches!(self.char_state, CharacterState::BasicSummon(c) if matches!(c.stage_section, StageSection::Recover)) { - agent.action_timer -= MINION_SUMMON_THRESHOLD; + agent.action_state.counter -= MINION_SUMMON_THRESHOLD; } } else if mindflayer_is_far { // If too far from target, blink to them. @@ -2605,7 +2609,7 @@ impl<'a> AgentData<'a> { controller .actions .push(ControlAction::basic_input(InputKind::Secondary)); - } else if thread_rng().gen_bool(health_fraction.into()) && angle < 30.0 { + } else if thread_rng().gen_bool(health_fraction.into()) { // Else if at high health, use primary controller .actions @@ -2866,18 +2870,92 @@ impl<'a> AgentData<'a> { self.jump_if(controller, bearing.z > 1.5); controller.inputs.move_z = bearing.z; } - } else if self.energy.current() > 600 && agent.action_timer < 3.0 && angle < 15.0 { + } else if self.energy.current() > 600 + && agent.action_state.timer < 3.0 + && angle < 15.0 + { controller .actions .push(ControlAction::basic_input(InputKind::Ability(0))); - agent.action_timer += dt.0; - } else if agent.action_timer < 6.0 && angle < 90.0 { + agent.action_state.timer += dt.0; + } else if agent.action_state.timer < 6.0 && angle < 90.0 { controller .actions .push(ControlAction::basic_input(InputKind::Secondary)); - agent.action_timer += dt.0; + agent.action_state.timer += dt.0; } else { - agent.action_timer = 0.0; + agent.action_state.timer = 0.0; + } + }, + Tactic::Minotaur => { + const MINOTAUR_FRENZY_THRESHOLD: f32 = 0.5; + const MINOTAUR_ATTACK_RANGE: f32 = 5.0; + const MINOTAUR_CHARGE_DISTANCE: f32 = 15.0; + let minotaur_attack_distance = + self.body.map_or(0.0, |b| b.radius()) + MINOTAUR_ATTACK_RANGE; + let health_fraction = self.health.map_or(1.0, |h| h.fraction()); + // Sets action float at start of combat + if agent.action_state.counter < MINOTAUR_FRENZY_THRESHOLD + && health_fraction > MINOTAUR_FRENZY_THRESHOLD + { + agent.action_state.counter = MINOTAUR_FRENZY_THRESHOLD; + } + if health_fraction < agent.action_state.counter { + // Makes minotaur buff itself with frenzy + controller + .actions + .push(ControlAction::basic_input(InputKind::Ability(1))); + if matches!(self.char_state, CharacterState::SelfBuff(c) if matches!(c.stage_section, StageSection::Recover)) + { + agent.action_state.counter = 0.0; + } + } else if matches!(self.char_state, CharacterState::DashMelee(c) if !matches!(c.stage_section, StageSection::Recover)) + { + // If already charging, keep charging if not in recover + controller + .actions + .push(ControlAction::basic_input(InputKind::Ability(0))); + } else if matches!(self.char_state, CharacterState::ChargedMelee(c) if matches!(c.stage_section, StageSection::Charge) && c.timer < c.static_data.charge_duration) + { + // If already charging a melee attack, keep charging it if charging + controller + .actions + .push(ControlAction::basic_input(InputKind::Primary)); + } else if dist_sqrd > MINOTAUR_CHARGE_DISTANCE.powi(2) { + // Charges at target if they are far enough away + if angle < 60.0 { + controller + .actions + .push(ControlAction::basic_input(InputKind::Ability(0))); + } + } else if dist_sqrd < minotaur_attack_distance.powi(2) { + if agent.action_state.condition && !self.char_state.is_attack() { + // Cripple target if not just used cripple + controller + .actions + .push(ControlAction::basic_input(InputKind::Secondary)); + agent.action_state.condition = false; + } else if !self.char_state.is_attack() { + // Cleave target if not just used cleave + controller + .actions + .push(ControlAction::basic_input(InputKind::Primary)); + agent.action_state.condition = true; + } + } + // Make minotaur move towards target + if let Some((bearing, speed)) = agent.chaser.chase( + &*terrain, + self.pos.0, + self.vel.0, + tgt_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; } }, } diff --git a/voxygen/anim/src/biped_large/alpha.rs b/voxygen/anim/src/biped_large/alpha.rs index 27d6f70049..c5ac772d68 100644 --- a/voxygen/anim/src/biped_large/alpha.rs +++ b/voxygen/anim/src/biped_large/alpha.rs @@ -63,7 +63,8 @@ impl Animation for AlphaAnimation { let pullback = 1.0 - move3; let move1 = move1base * pullback; let move2 = move2base * pullback; - + next.second.position = Vec3::new(0.0, 0.0, 0.0); + next.second.orientation = Quaternion::rotation_x(0.0); next.shoulder_l.position = Vec3::new( -s_a.shoulder.0, s_a.shoulder.1, @@ -79,7 +80,6 @@ impl Animation for AlphaAnimation { ); next.shoulder_r.orientation = Quaternion::rotation_x(move1 * 0.8 + 0.6 * speednorm + (footrotl * -0.2) * speednorm); - next.torso.orientation = Quaternion::rotation_z(0.0); next.main.position = Vec3::new(0.0, 0.0, 0.0); next.main.orientation = Quaternion::rotation_x(0.0); @@ -231,6 +231,38 @@ impl Animation for AlphaAnimation { * Quaternion::rotation_z(move1 * -0.5 + move2 * 0.6); next.head.orientation = Quaternion::rotation_x(move1 * 0.3); }, + "Minotaur" => { + next.control_l.position = Vec3::new(0.0, 4.0, 5.0); + next.control_r.position = Vec3::new(0.0, 4.0, 5.0); + next.weapon_l.position = Vec3::new( + -12.0 + move1 * -9.0 + move2 * 16.0, + -6.0 + move2 * 8.0, + -18.0 + move1 * 8.0 + move2 * -4.0, + ); + next.weapon_r.position = Vec3::new( + 12.0 + move1 * 9.0 + move2 * -16.0, + -6.0 + move2 * 8.0, + -18.0 + move1 * 8.0 + move2 * -8.0, + ); + + next.weapon_l.orientation = Quaternion::rotation_x(-1.67) + * Quaternion::rotation_y(move1 * -0.3 + move2 * 1.0) + * Quaternion::rotation_z(move1 * 0.8 + move2 * -1.8); + next.weapon_r.orientation = Quaternion::rotation_x(-1.67) + * Quaternion::rotation_y(move1 * 0.3 + move2 * -0.6) + * Quaternion::rotation_z(move1 * -0.8 + move2 * 1.8); + + next.control_l.orientation = Quaternion::rotation_x(1.57 + move2 * 1.0); + next.control_r.orientation = Quaternion::rotation_x(1.57 + move2 * 1.0); + + next.shoulder_l.orientation = Quaternion::rotation_x(-0.3) + * Quaternion::rotation_y(move1 * 0.7 + move2 * -0.7); + + next.shoulder_r.orientation = Quaternion::rotation_x(-0.3) + * Quaternion::rotation_y(move1 * -0.7 + move2 * 0.7); + next.head.orientation = + Quaternion::rotation_x(move1 * -0.6 + move2 * 0.4) + }, _ => {}, } } diff --git a/voxygen/anim/src/biped_large/chargemelee.rs b/voxygen/anim/src/biped_large/chargemelee.rs new file mode 100644 index 0000000000..0a0862de8a --- /dev/null +++ b/voxygen/anim/src/biped_large/chargemelee.rs @@ -0,0 +1,159 @@ +use super::{ + super::{vek::*, Animation}, + BipedLargeSkeleton, SkeletonAttr, +}; +use common::{ + comp::item::tool::{AbilitySpec, ToolKind}, + states::utils::StageSection, +}; +use std::f32::consts::PI; + +pub struct ChargeMeleeAnimation; + +impl Animation for ChargeMeleeAnimation { + #[allow(clippy::type_complexity)] + type Dependency<'a> = ( + (Option, Option<&'a AbilitySpec>), + (Option, Option<&'a AbilitySpec>), + Vec3, + f32, + Option, + f32, + ); + type Skeleton = BipedLargeSkeleton; + + #[cfg(feature = "use-dyn-lib")] + const UPDATE_FN: &'static [u8] = b"biped_large_chargemelee\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "biped_large_chargemelee")] + #[allow(clippy::approx_constant)] // TODO: Pending review in #587 + fn update_skeleton_inner<'a>( + skeleton: &Self::Skeleton, + ( + (active_tool_kind, active_tool_spec), + _second_tool, + velocity, + _global_time, + stage_section, + acc_vel, + ): Self::Dependency<'a>, + anim_time: f32, + rate: &mut f32, + s_a: &SkeletonAttr, + ) -> Self::Skeleton { + *rate = 1.0; + let mut next = (*skeleton).clone(); + let speed = Vec2::::from(velocity).magnitude(); + + let lab: f32 = 0.65 * s_a.tempo; + let speednorm = (speed / 12.0).powf(0.4); + let foothoril = (acc_vel * lab + PI * 1.45).sin() * speednorm; + let foothorir = (acc_vel * lab + PI * (0.45)).sin() * speednorm; + let footrotl = ((1.0 / (0.5 + (0.5) * ((acc_vel * lab + PI * 1.4).sin()).powi(2))).sqrt()) + * ((acc_vel * lab + PI * 1.4).sin()); + + let footrotr = ((1.0 / (0.5 + (0.5) * ((acc_vel * lab + PI * 0.4).sin()).powi(2))).sqrt()) + * ((acc_vel * lab + PI * 0.4).sin()); + let (move1base, move2base, movement3, tension) = match stage_section { + Some(StageSection::Charge) => ( + (anim_time.powf(0.25)).min(1.0), + 0.0, + 0.0, + (anim_time * 100.0).sin(), + ), + Some(StageSection::Swing) => (1.0, anim_time.powf(0.25), 0.0, 0.0), + Some(StageSection::Recover) => (1.0, 1.0, anim_time.powi(4), 0.0), + _ => (0.0, 0.0, 0.0, 0.0), + }; + + let pullback = 1.0 - movement3; + let move1 = move1base * pullback; + let move2 = move2base * pullback; + next.second.position = Vec3::new(0.0, 0.0, 0.0); + next.second.orientation = Quaternion::rotation_x(0.0); + next.shoulder_l.position = Vec3::new( + -s_a.shoulder.0, + s_a.shoulder.1, + s_a.shoulder.2 - foothorir * 1.0, + ); + next.shoulder_l.orientation = + Quaternion::rotation_x(move1 * 0.8 + 0.6 * speednorm + (footrotr * -0.2) * speednorm); + + next.shoulder_r.position = Vec3::new( + s_a.shoulder.0, + s_a.shoulder.1, + s_a.shoulder.2 - foothoril * 1.0, + ); + next.shoulder_r.orientation = + Quaternion::rotation_x(move1 * 0.8 + 0.6 * speednorm + (footrotl * -0.2) * speednorm); + next.torso.orientation = Quaternion::rotation_z(0.0); + + next.main.position = Vec3::new(0.0, 0.0, 0.0); + next.main.orientation = Quaternion::rotation_x(0.0); + + next.hand_l.position = Vec3::new(0.0, 0.0, s_a.grip.0); + next.hand_r.position = Vec3::new(0.0, 0.0, s_a.grip.0); + + next.hand_l.orientation = Quaternion::rotation_x(0.0); + next.hand_r.orientation = Quaternion::rotation_x(0.0); + + #[allow(clippy::single_match)] + match active_tool_kind { + Some(ToolKind::Natural) => { + if let Some(AbilitySpec::Custom(spec)) = active_tool_spec { + match spec.as_str() { + "Minotaur" => { + next.upper_torso.orientation = + Quaternion::rotation_x(move1 * 0.3 + move2 * -0.9); + next.lower_torso.orientation = + Quaternion::rotation_x(move1 * -0.3 + move2 * 0.9); + next.head.orientation = + Quaternion::rotation_x(move1 * -0.5 + move2 * 0.5); + + next.control_l.position = Vec3::new(0.0, 4.0, 5.0); + next.control_r.position = Vec3::new(0.0, 4.0, 5.0); + next.weapon_l.position = Vec3::new( + -12.0 + move2 * 5.0, + -6.0 + move1 * 22.0 + move2 * 8.0, + -18.0 + move1 * 16.0 + move2 * -19.0, + ); + next.weapon_r.position = Vec3::new( + 12.0 + move2 * -5.0, + -6.0 + move1 * 22.0 + move2 * 8.0, + -18.0 + move1 * 14.0 + move2 * -19.0, + ); + next.torso.position = Vec3::new(0.0, move2 * 1.5, 0.0); + + next.weapon_l.orientation = + Quaternion::rotation_x( + -1.67 + move1 * 2.8 + tension * 0.03 + move2 * -2.3, + ) * Quaternion::rotation_y(move1 * 0.3 + move2 * 0.5); + next.weapon_r.orientation = Quaternion::rotation_x( + -1.67 + move1 * 1.6 + tension * -0.03 + move2 * -0.7, + ) * Quaternion::rotation_y( + move1 * -0.3 + move2 * -0.5, + ) * Quaternion::rotation_z(0.0); + + next.control_l.orientation = + Quaternion::rotation_x(1.57 + move1 * 0.2 + move2 * 0.1); + next.control_r.orientation = + Quaternion::rotation_x(1.57 + move1 * 0.4 + move2 * -0.4); + + next.control.orientation = + Quaternion::rotation_x(0.0) * Quaternion::rotation_y(0.0); + next.shoulder_l.orientation = + Quaternion::rotation_x(-0.3 + move1 * 1.0); + + next.shoulder_r.orientation = + Quaternion::rotation_x(-0.3 + move1 * 1.0); + }, + _ => {}, + } + } + }, + _ => {}, + } + + next + } +} diff --git a/voxygen/anim/src/biped_large/dash.rs b/voxygen/anim/src/biped_large/dash.rs index 8257b9d075..f969de6df9 100644 --- a/voxygen/anim/src/biped_large/dash.rs +++ b/voxygen/anim/src/biped_large/dash.rs @@ -2,15 +2,19 @@ use super::{ super::{vek::*, Animation}, BipedLargeSkeleton, SkeletonAttr, }; -use common::{comp::item::ToolKind, states::utils::StageSection}; +use common::{ + comp::item::tool::{AbilitySpec, ToolKind}, + states::utils::StageSection, +}; use std::f32::consts::PI; pub struct DashAnimation; impl Animation for DashAnimation { + #[allow(clippy::type_complexity)] type Dependency<'a> = ( - Option, - Option, + (Option, Option<&'a AbilitySpec>), + (Option, Option<&'a AbilitySpec>), Vec3, f32, Option, @@ -25,7 +29,14 @@ impl Animation for DashAnimation { #[allow(clippy::single_match)] // TODO: Pending review in #587 fn update_skeleton_inner<'a>( skeleton: &Self::Skeleton, - (active_tool_kind, _second_tool_kind, velocity, _global_time, stage_section, acc_vel): Self::Dependency<'a>, + ( + (active_tool_kind, active_tool_spec), + _second_tool, + velocity, + _global_time, + stage_section, + acc_vel, + ): Self::Dependency<'a>, anim_time: f32, rate: &mut f32, s_a: &SkeletonAttr, @@ -49,7 +60,8 @@ impl Animation for DashAnimation { next.hand_l.position = Vec3::new(0.0, 0.0, s_a.grip.0); next.hand_r.position = Vec3::new(0.0, 0.0, s_a.grip.0); - + next.second.position = Vec3::new(0.0, 0.0, 0.0); + next.second.orientation = Quaternion::rotation_x(0.0); next.hand_l.orientation = Quaternion::rotation_x(0.0); next.hand_r.orientation = Quaternion::rotation_x(0.0); let (move1base, move2base, move3base, move4) = match stage_section { @@ -136,6 +148,51 @@ impl Animation for DashAnimation { * Quaternion::rotation_y(-1.8 + move1 * -0.2 + move3 * -0.2) * Quaternion::rotation_z(move1 * -0.8 + move3 * -0.1); }, + Some(ToolKind::Natural) => { + if let Some(AbilitySpec::Custom(spec)) = active_tool_spec { + match spec.as_str() { + "Minotaur" => { + next.head.orientation = + Quaternion::rotation_x(move1 * 0.4 + move3 * 0.5) + * Quaternion::rotation_z(move1 * -0.3 + move3 * -0.3); + next.upper_torso.orientation = + Quaternion::rotation_x(move1 * -0.4 + move3 * 0.9) + * Quaternion::rotation_z(move1 * 0.6 + move3 * -1.5); + next.lower_torso.orientation = + Quaternion::rotation_y(move1 * -0.2 + move3 * -0.1) + * Quaternion::rotation_x( + move1 * 0.4 + move3 * -0.7 + footrotr * 0.1, + ) + * Quaternion::rotation_z(move1 * -0.6 + move3 * 1.6); + next.control_l.position = Vec3::new(0.0, 4.0, 5.0); + next.control_r.position = Vec3::new(0.0, 4.0, 5.0); + next.weapon_l.position = Vec3::new(-12.0 + move1 * -3.0, -6.0, -18.0); + next.weapon_r.position = Vec3::new( + 12.0 + move1 * -3.0, + -6.0 + move1 * 2.0, + -18.0 + move1 * 2.0, + ); + + next.weapon_l.orientation = Quaternion::rotation_x(-1.67 + move1 * 0.4) + * Quaternion::rotation_y(move1 * 0.4 + move2 * 0.2) + * Quaternion::rotation_z(move3 * -0.5); + next.weapon_r.orientation = Quaternion::rotation_x(-1.67 + move1 * 0.3) + * Quaternion::rotation_y(move1 * 0.6 + move2 * -0.6) + * Quaternion::rotation_z(move3 * -0.5); + + next.control_l.orientation = Quaternion::rotation_x(1.57); + next.control_r.orientation = Quaternion::rotation_x(1.57); + + next.control.orientation = + Quaternion::rotation_x(0.0) * Quaternion::rotation_y(0.0); + next.shoulder_l.orientation = Quaternion::rotation_x(-0.3); + + next.shoulder_r.orientation = Quaternion::rotation_x(-0.3); + }, + _ => {}, + } + } + }, _ => {}, } diff --git a/voxygen/anim/src/biped_large/idle.rs b/voxygen/anim/src/biped_large/idle.rs index 62bbf6c87b..92ca199c0c 100644 --- a/voxygen/anim/src/biped_large/idle.rs +++ b/voxygen/anim/src/biped_large/idle.rs @@ -101,8 +101,11 @@ impl Animation for IdleAnimation { next.main.orientation = Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57); }, Some(ToolKind::Hammer) | Some(ToolKind::Axe) => { - next.main.position = Vec3::new(-10.0, -8.0, 12.0); + next.main.position = Vec3::new(-6.0, -8.0, 8.0); next.main.orientation = Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57); + next.second.position = Vec3::new(6.0, -8.0, 8.0); + next.second.orientation = + Quaternion::rotation_y(-2.5) * Quaternion::rotation_z(1.57); }, _ => { next.main.position = Vec3::new(-2.0, -5.0, -6.0); diff --git a/voxygen/anim/src/biped_large/jump.rs b/voxygen/anim/src/biped_large/jump.rs index e9586d068c..7724daea85 100644 --- a/voxygen/anim/src/biped_large/jump.rs +++ b/voxygen/anim/src/biped_large/jump.rs @@ -28,6 +28,7 @@ impl Animation for JumpAnimation { let torso = (anim_time * lab + 1.5 * PI).sin(); let wave_slow = (anim_time * 0.8).sin(); + next.hold.scale = Vec3::one() * 0.0; next.head.scale = Vec3::one() * 1.02; @@ -57,7 +58,7 @@ impl Animation for JumpAnimation { next.second.position = Vec3::new(0.0, 0.0, 0.0); next.second.orientation = Quaternion::rotation_x(PI) * Quaternion::rotation_y(0.0) * Quaternion::rotation_z(0.0); - next.second.scale = Vec3::one() * 0.0; + next.second.scale = Vec3::one() * 1.0; match active_tool_kind { Some(ToolKind::Bow) => { diff --git a/voxygen/anim/src/biped_large/mod.rs b/voxygen/anim/src/biped_large/mod.rs index 85155ca677..1db51f23b6 100644 --- a/voxygen/anim/src/biped_large/mod.rs +++ b/voxygen/anim/src/biped_large/mod.rs @@ -3,12 +3,14 @@ pub mod beam; pub mod beta; pub mod blink; pub mod charge; +pub mod chargemelee; pub mod dash; pub mod equip; pub mod idle; pub mod jump; pub mod leapmelee; pub mod run; +pub mod selfbuff; pub mod shockwave; pub mod shoot; pub mod spin; @@ -20,11 +22,11 @@ pub mod wield; // Reexports pub use self::{ alpha::AlphaAnimation, beam::BeamAnimation, beta::BetaAnimation, blink::BlinkAnimation, - charge::ChargeAnimation, dash::DashAnimation, equip::EquipAnimation, idle::IdleAnimation, - jump::JumpAnimation, leapmelee::LeapAnimation, run::RunAnimation, - shockwave::ShockwaveAnimation, shoot::ShootAnimation, spin::SpinAnimation, - spinmelee::SpinMeleeAnimation, stunned::StunnedAnimation, summon::SummonAnimation, - wield::WieldAnimation, + charge::ChargeAnimation, chargemelee::ChargeMeleeAnimation, dash::DashAnimation, + equip::EquipAnimation, idle::IdleAnimation, jump::JumpAnimation, leapmelee::LeapAnimation, + run::RunAnimation, selfbuff::SelfBuffAnimation, shockwave::ShockwaveAnimation, + shoot::ShootAnimation, spin::SpinAnimation, spinmelee::SpinMeleeAnimation, + stunned::StunnedAnimation, summon::SummonAnimation, wield::WieldAnimation, }; use super::{make_bone, vek::*, FigureBoneData, Skeleton}; @@ -54,6 +56,8 @@ skeleton_impls!(struct BipedLargeSkeleton { control, control_l, control_r, + weapon_l, + weapon_r, leg_control_l, leg_control_r, arm_control_l, @@ -78,8 +82,11 @@ impl Skeleton for BipedLargeSkeleton { let torso_mat = base_mat * Mat4::::from(self.torso); let upper_torso_mat = torso_mat * upper_torso; + let control_mat = Mat4::::from(self.control); let control_l_mat = Mat4::::from(self.control_l); let control_r_mat = Mat4::::from(self.control_r); + let weapon_l_mat = control_mat * Mat4::::from(self.weapon_l); + let weapon_r_mat = control_mat * Mat4::::from(self.weapon_r); let lower_torso_mat = upper_torso_mat * Mat4::::from(self.lower_torso); let leg_l = Mat4::::from(self.leg_l); @@ -92,8 +99,6 @@ impl Skeleton for BipedLargeSkeleton { let arm_control_r = upper_torso_mat * Mat4::::from(self.arm_control_r); let head_mat = upper_torso_mat * Mat4::::from(self.head); - let control_mat = Mat4::::from(self.control); - let hand_l_mat = Mat4::::from(self.hand_l); *(<&mut [_; Self::BONE_COUNT]>::try_from(&mut buf[0..Self::BONE_COUNT]).unwrap()) = [ @@ -102,12 +107,16 @@ impl Skeleton for BipedLargeSkeleton { make_bone(upper_torso_mat), make_bone(lower_torso_mat), make_bone(lower_torso_mat * Mat4::::from(self.tail)), - make_bone(upper_torso_mat * control_mat * Mat4::::from(self.main)), - make_bone(upper_torso_mat * control_mat * Mat4::::from(self.second)), + make_bone(upper_torso_mat * weapon_l_mat * Mat4::::from(self.main)), + make_bone(upper_torso_mat * weapon_r_mat * Mat4::::from(self.second)), make_bone(arm_control_l * Mat4::::from(self.shoulder_l)), make_bone(arm_control_r * Mat4::::from(self.shoulder_r)), - make_bone(arm_control_l * control_mat * control_l_mat * Mat4::::from(self.hand_l)), - make_bone(arm_control_r * control_mat * control_r_mat * Mat4::::from(self.hand_r)), + make_bone( + arm_control_l * weapon_l_mat * control_l_mat * Mat4::::from(self.hand_l), + ), + make_bone( + arm_control_r * weapon_r_mat * control_r_mat * Mat4::::from(self.hand_r), + ), make_bone(leg_control_l * leg_l), make_bone(leg_control_r * leg_r), make_bone(leg_control_l * Mat4::::from(self.foot_l)), diff --git a/voxygen/anim/src/biped_large/run.rs b/voxygen/anim/src/biped_large/run.rs index 2b8a6d67d5..b39791fd20 100644 --- a/voxygen/anim/src/biped_large/run.rs +++ b/voxygen/anim/src/biped_large/run.rs @@ -271,12 +271,12 @@ impl Animation for RunAnimation { s_a.upper_torso.1 + shortalt * -1.5 * speednorm, ); next.upper_torso.orientation = - Quaternion::rotation_z(short * 0.18 * speednorm + tilt * -1.0) + Quaternion::rotation_z(short * 0.07 * speednorm + tilt * -1.0) * Quaternion::rotation_y(tilt); next.lower_torso.position = Vec3::new(0.0, s_a.lower_torso.0, s_a.lower_torso.1); next.lower_torso.orientation = - Quaternion::rotation_z(short * 0.15 * speednorm + tilt * 0.5) + Quaternion::rotation_z(short * 0.05 * speednorm + tilt * 0.5) * Quaternion::rotation_y(tilt * -0.5) * Quaternion::rotation_x(0.14 * speednorm); @@ -287,7 +287,7 @@ impl Animation for RunAnimation { next.tail.orientation = Quaternion::rotation_x(shortalt * 0.3 * speednorm); next.second.position = Vec3::new(0.0, 0.0, 0.0); - next.second.orientation = Quaternion::rotation_x(PI) + next.second.orientation = Quaternion::rotation_x(0.0) * Quaternion::rotation_y(0.0) * Quaternion::rotation_z(0.0); @@ -308,9 +308,12 @@ impl Animation for RunAnimation { Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57); }, Some(ToolKind::Hammer) | Some(ToolKind::Axe) => { - next.main.position = Vec3::new(-10.0, -8.0, 12.0); + next.main.position = Vec3::new(-6.0, -8.0, 8.0); next.main.orientation = Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57); + next.second.position = Vec3::new(6.0, -8.0, 8.0); + next.second.orientation = + Quaternion::rotation_y(-2.5) * Quaternion::rotation_z(1.57); }, _ => { next.main.position = Vec3::new(-2.0, -5.0, -6.0); diff --git a/voxygen/anim/src/biped_large/selfbuff.rs b/voxygen/anim/src/biped_large/selfbuff.rs new file mode 100644 index 0000000000..059e4481e6 --- /dev/null +++ b/voxygen/anim/src/biped_large/selfbuff.rs @@ -0,0 +1,163 @@ +use super::{ + super::{vek::*, Animation}, + BipedLargeSkeleton, SkeletonAttr, +}; +use common::{ + comp::item::tool::{AbilitySpec, ToolKind}, + states::utils::StageSection, +}; +use std::f32::consts::PI; + +pub struct SelfBuffAnimation; + +impl Animation for SelfBuffAnimation { + #[allow(clippy::type_complexity)] + type Dependency<'a> = ( + (Option, Option<&'a AbilitySpec>), + (Option, Option<&'a AbilitySpec>), + Vec3, + f32, + Option, + f32, + ); + type Skeleton = BipedLargeSkeleton; + + #[cfg(feature = "use-dyn-lib")] + const UPDATE_FN: &'static [u8] = b"biped_large_selfbuff\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "biped_large_selfbuff")] + fn update_skeleton_inner<'a>( + skeleton: &Self::Skeleton, + ( + (active_tool_kind, active_tool_spec), + _second_tool, + velocity, + _global_time, + stage_section, + acc_vel, + ): Self::Dependency<'a>, + anim_time: f32, + rate: &mut f32, + s_a: &SkeletonAttr, + ) -> Self::Skeleton { + *rate = 1.0; + let mut next = (*skeleton).clone(); + let speed = Vec2::::from(velocity).magnitude(); + + let lab: f32 = 0.65 * s_a.tempo; + let speednorm = (speed / 12.0).powf(0.4); + let foothoril = (acc_vel * lab + PI * 1.45).sin() * speednorm; + let foothorir = (acc_vel * lab + PI * (0.45)).sin() * speednorm; + let footrotl = ((1.0 / (0.5 + (0.5) * ((acc_vel * lab + PI * 1.4).sin()).powi(2))).sqrt()) + * ((acc_vel * lab + PI * 1.4).sin()); + + let footrotr = ((1.0 / (0.5 + (0.5) * ((acc_vel * lab + PI * 0.4).sin()).powi(2))).sqrt()) + * ((acc_vel * lab + PI * 0.4).sin()); + let (move1base, movement3, tensionbase, tension2base) = match stage_section { + Some(StageSection::Buildup) => ( + (anim_time.powf(0.25)).min(1.0), + 0.0, + (anim_time * 10.0).sin(), + 0.0, + ), + Some(StageSection::Cast) => { + (1.0, 0.0, (anim_time * 30.0).sin(), (anim_time * 12.0).sin()) + }, + Some(StageSection::Recover) => (1.0, anim_time.powi(4), 1.0, 1.0), + _ => (0.0, 0.0, 0.0, 0.0), + }; + + let pullback = 1.0 - movement3; + let move1 = move1base * pullback; + let tension = tensionbase * pullback; + let tension2 = tension2base * pullback; + + next.second.position = Vec3::new(0.0, 0.0, 0.0); + next.second.orientation = Quaternion::rotation_x(0.0); + next.shoulder_l.position = Vec3::new( + -s_a.shoulder.0, + s_a.shoulder.1, + s_a.shoulder.2 - foothorir * 1.0, + ); + next.shoulder_l.orientation = + Quaternion::rotation_x(move1 * 0.8 + 0.6 * speednorm + (footrotr * -0.2) * speednorm); + + next.shoulder_r.position = Vec3::new( + s_a.shoulder.0, + s_a.shoulder.1, + s_a.shoulder.2 - foothoril * 1.0, + ); + next.shoulder_r.orientation = + Quaternion::rotation_x(move1 * 0.8 + 0.6 * speednorm + (footrotl * -0.2) * speednorm); + next.torso.orientation = Quaternion::rotation_z(0.0); + + next.main.position = Vec3::new(0.0, 0.0, 0.0); + next.main.orientation = Quaternion::rotation_x(0.0); + + next.hand_l.position = Vec3::new(0.0, 0.0, s_a.grip.0); + next.hand_r.position = Vec3::new(0.0, 0.0, s_a.grip.0); + + next.hand_l.orientation = Quaternion::rotation_x(0.0); + next.hand_r.orientation = Quaternion::rotation_x(0.0); + + // TODO: Remove clippy allow when second species is added + #[allow(clippy::single_match)] + match active_tool_kind { + Some(ToolKind::Natural) => { + if let Some(AbilitySpec::Custom(spec)) = active_tool_spec { + match spec.as_str() { + "Minotaur" => { + next.upper_torso.orientation = + Quaternion::rotation_x(move1 * -0.1 + tension2 * 0.05); + next.lower_torso.orientation = + Quaternion::rotation_x(move1 * 0.1 + tension2 * -0.05); + + next.head.orientation = + Quaternion::rotation_x(move1 * 0.8 + tension2 * -0.1) + * Quaternion::rotation_y(tension2 * -0.1); + + next.control_l.position = Vec3::new(0.0, 4.0, 5.0); + next.control_r.position = Vec3::new(0.0, 4.0, 5.0); + next.weapon_l.position = Vec3::new( + -12.0 + move1 * -15.0, + -6.0 + move1 * 13.0, + -18.0 + move1 * 16.0 + tension2 * 3.0, + ); + next.weapon_r.position = Vec3::new( + 12.0 + move1 * 1.0, + -6.0 + move1 * 7.0 + tension * 0.3, + -18.0 + move1 * -2.0, + ); + + next.weapon_l.orientation = Quaternion::rotation_x(-1.67 + move1 * 1.9) + * Quaternion::rotation_y(move1 * 0.25 + tension2 * 0.06) + * Quaternion::rotation_z(move1 * 1.3); + next.weapon_r.orientation = Quaternion::rotation_x(-1.67 + move1 * 0.8) + * Quaternion::rotation_y(move1 * -0.85 + tension * 0.12) + * Quaternion::rotation_z(move1 * 0.7); + + next.control_l.orientation = Quaternion::rotation_x(1.57 + move1 * 0.1) + * Quaternion::rotation_y(0.0); + next.control_r.orientation = Quaternion::rotation_x(1.57 + move1 * 0.1) + * Quaternion::rotation_y(0.0); + + next.control.orientation = + Quaternion::rotation_x(0.0) * Quaternion::rotation_y(0.0); + next.shoulder_l.orientation = + Quaternion::rotation_x(-0.3 + move1 * 2.2 + tension2 * 0.17) + * Quaternion::rotation_y(move1 * 0.95); + + next.shoulder_r.orientation = + Quaternion::rotation_x(-0.3 + move1 * 0.1) + * Quaternion::rotation_y(move1 * -0.35); + }, + _ => {}, + } + } + }, + _ => {}, + } + + next + } +} diff --git a/voxygen/anim/src/biped_large/stunned.rs b/voxygen/anim/src/biped_large/stunned.rs index cfd429d812..f8f0ff1e53 100644 --- a/voxygen/anim/src/biped_large/stunned.rs +++ b/voxygen/anim/src/biped_large/stunned.rs @@ -65,7 +65,8 @@ impl Animation for StunnedAnimation { let short = (acc_vel * lab).sin() * speednorm; let shortalt = (anim_time * lab * 16.0 + PI / 2.0).sin(); - + next.second.position = Vec3::new(0.0, 0.0, 0.0); + next.second.orientation = Quaternion::rotation_x(0.0); if s_a.beast { next.jaw.position = Vec3::new(0.0, s_a.jaw.0, s_a.jaw.1); } else { @@ -391,6 +392,24 @@ impl Animation for StunnedAnimation { next.torso.orientation = Quaternion::rotation_x(-0.25); } }, + "Minotaur" => { + next.control_l.position = Vec3::new(0.0, 4.0, 5.0); + next.control_r.position = Vec3::new(0.0, 4.0, 5.0); + next.weapon_l.position = Vec3::new(-12.0, -6.0, -18.0); + next.weapon_r.position = Vec3::new(12.0, -6.0, -18.0); + + next.weapon_l.orientation = Quaternion::rotation_x(-1.57 - 0.1); + next.weapon_r.orientation = Quaternion::rotation_x(-1.57 - 0.1); + + next.control_l.orientation = Quaternion::rotation_x(1.57); + next.control_r.orientation = Quaternion::rotation_x(1.57); + + next.control.orientation = + Quaternion::rotation_x(0.0) * Quaternion::rotation_y(0.0); + next.shoulder_l.orientation = Quaternion::rotation_x(-0.3); + + next.shoulder_r.orientation = Quaternion::rotation_x(-0.3); + }, _ => {}, } } diff --git a/voxygen/anim/src/biped_large/wield.rs b/voxygen/anim/src/biped_large/wield.rs index f37bf0dc30..6a280feb08 100644 --- a/voxygen/anim/src/biped_large/wield.rs +++ b/voxygen/anim/src/biped_large/wield.rs @@ -91,6 +91,8 @@ impl Animation for WieldAnimation { let short = (acc_vel * lab).sin() * speednorm; let shortalt = (anim_time * lab * 16.0 + PI / 2.0).sin(); + next.second.position = Vec3::new(0.0, 0.0, 0.0); + next.second.orientation = Quaternion::rotation_x(0.0); if s_a.beast { next.jaw.position = Vec3::new(0.0, s_a.jaw.0, s_a.jaw.1); @@ -169,12 +171,12 @@ impl Animation for WieldAnimation { 5.0 + s_a.grip.0 / 1.2, -4.0 + -s_a.grip.0 / 2.0 + short * -1.5, ); + next.second.scale = Vec3::one() * 0.0; next.control_l.orientation = Quaternion::rotation_x(PI / 2.0) * Quaternion::rotation_y(-0.2); - next.control_r.orientation = Quaternion::rotation_x(PI / 2.2) - * Quaternion::rotation_y(0.2) - * Quaternion::rotation_z(0.0); + next.control_r.orientation = + Quaternion::rotation_x(PI / 2.2) * Quaternion::rotation_y(0.2); next.control.orientation = Quaternion::rotation_x(-0.2 + short * 0.2) * Quaternion::rotation_y(-0.1); @@ -430,6 +432,24 @@ impl Animation for WieldAnimation { next.torso.orientation = Quaternion::rotation_x(-0.25); } }, + "Minotaur" => { + next.control_l.position = Vec3::new(0.0, 4.0, 5.0); + next.control_r.position = Vec3::new(0.0, 4.0, 5.0); + next.weapon_l.position = Vec3::new(-12.0, -6.0, -18.0); + next.weapon_r.position = Vec3::new(12.0, -6.0, -18.0); + + next.weapon_l.orientation = Quaternion::rotation_x(-1.57 - 0.1); + next.weapon_r.orientation = Quaternion::rotation_x(-1.57 - 0.1); + + next.control_l.orientation = Quaternion::rotation_x(1.57); + next.control_r.orientation = Quaternion::rotation_x(1.57); + + next.control.orientation = + Quaternion::rotation_x(0.0) * Quaternion::rotation_y(0.0); + next.shoulder_l.orientation = Quaternion::rotation_x(-0.3); + + next.shoulder_r.orientation = Quaternion::rotation_x(-0.3); + }, _ => {}, } } diff --git a/voxygen/src/audio/sfx/event_mapper/combat/tests.rs b/voxygen/src/audio/sfx/event_mapper/combat/tests.rs index 0247bf85c3..35cb0f7c24 100644 --- a/voxygen/src/audio/sfx/event_mapper/combat/tests.rs +++ b/voxygen/src/audio/sfx/event_mapper/combat/tests.rs @@ -79,6 +79,7 @@ fn maps_basic_melee() { range: 1.0, max_angle: 1.0, ability_info: empty_ability_info(), + damage_effect: None, }, timer: Duration::default(), stage_section: states::utils::StageSection::Buildup, diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index 60bf4b4594..78f91c532f 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -181,6 +181,7 @@ pub enum SfxEvent { FireShot, FlameThrower, PoiseChange(PoiseState), + GroundSlam, } #[derive(Clone, Debug, PartialEq, Deserialize, Hash, Eq)] @@ -325,6 +326,10 @@ impl SfxMgr { false, ); }, + Outcome::GroundSlam { pos, .. } => { + let sfx_trigger_item = triggers.get_key_value(&SfxEvent::GroundSlam); + audio.emit_sfx(sfx_trigger_item, *pos, Some(1.0), false); + }, Outcome::ProjectileShot { pos, body, .. } => { match body { Body::Object( diff --git a/voxygen/src/hud/chat.rs b/voxygen/src/hud/chat.rs index f91a5a0182..0795e3c930 100644 --- a/voxygen/src/hud/chat.rs +++ b/voxygen/src/hud/chat.rs @@ -628,6 +628,7 @@ fn insert_killing_buff(buff: BuffKind, localized_strings: &Localization, templat BuffKind::Burning => localized_strings.get("hud.outcome.burning"), BuffKind::Bleeding => localized_strings.get("hud.outcome.bleeding"), BuffKind::Cursed => localized_strings.get("hud.outcome.curse"), + BuffKind::Crippled => localized_strings.get("hud.outcome.crippled"), BuffKind::Regeneration | BuffKind::Saturation | BuffKind::Potion @@ -635,7 +636,8 @@ fn insert_killing_buff(buff: BuffKind, localized_strings: &Localization, templat | BuffKind::IncreaseMaxEnergy | BuffKind::IncreaseMaxHealth | BuffKind::Invulnerability - | BuffKind::ProtectingWard => { + | BuffKind::ProtectingWard + | BuffKind::Frenzied => { tracing::error!("Player was killed by a positive buff!"); localized_strings.get("hud.outcome.mysterious") }, diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index b74af470be..2e670e7517 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -569,11 +569,13 @@ image_ids! { buff_healthplus_0: "voxygen.element.de_buffs.buff_healthplus_0", buff_invincibility_0: "voxygen.element.de_buffs.buff_invincibility_0", buff_dmg_red_0: "voxygen.element.de_buffs.buff_damage_reduce_0", + buff_frenzy_0: "voxygen.element.de_buffs.buff_frenzy_0", // Debuffs debuff_skull_0: "voxygen.element.de_buffs.debuff_skull_0", debuff_bleed_0: "voxygen.element.de_buffs.debuff_bleed_0", debuff_burning_0: "voxygen.element.de_buffs.debuff_burning_0", + debuff_crippled_0: "voxygen.element.de_buffs.debuff_cripple_0", // Animation Frames // Buff Frame diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 339e5a2cbf..6ac6c7a17b 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -3622,10 +3622,12 @@ pub fn get_buff_image(buff: BuffKind, imgs: &Imgs) -> conrod_core::image::Id { BuffKind::IncreaseMaxHealth { .. } => imgs.buff_healthplus_0, BuffKind::Invulnerability => imgs.buff_invincibility_0, BuffKind::ProtectingWard => imgs.buff_dmg_red_0, + BuffKind::Frenzied { .. } => imgs.buff_frenzy_0, // Debuffs BuffKind::Bleeding { .. } => imgs.debuff_bleed_0, BuffKind::Cursed { .. } => imgs.debuff_skull_0, BuffKind::Burning { .. } => imgs.debuff_burning_0, + BuffKind::Crippled { .. } => imgs.debuff_crippled_0, } } @@ -3640,10 +3642,12 @@ pub fn get_buff_title(buff: BuffKind, localized_strings: &Localization) -> &str BuffKind::IncreaseMaxEnergy { .. } => localized_strings.get("buff.title.staminaup"), BuffKind::Invulnerability => localized_strings.get("buff.title.invulnerability"), BuffKind::ProtectingWard => localized_strings.get("buff.title.protectingward"), + BuffKind::Frenzied => localized_strings.get("buff.title.frenzied"), // Debuffs BuffKind::Bleeding { .. } => localized_strings.get("buff.title.bleed"), BuffKind::Cursed { .. } => localized_strings.get("buff.title.cursed"), BuffKind::Burning { .. } => localized_strings.get("buff.title.burn"), + BuffKind::Crippled { .. } => localized_strings.get("buff.title.crippled"), } } @@ -3670,10 +3674,12 @@ pub fn get_buff_desc(buff: BuffKind, data: BuffData, localized_strings: &Localiz BuffKind::ProtectingWard => { Cow::Borrowed(localized_strings.get("buff.desc.protectingward")) }, + BuffKind::Frenzied => Cow::Borrowed(localized_strings.get("buff.desc.frenzied")), // Debuffs BuffKind::Bleeding { .. } => Cow::Borrowed(localized_strings.get("buff.desc.bleed")), BuffKind::Cursed { .. } => Cow::Borrowed(localized_strings.get("buff.desc.cursed")), BuffKind::Burning { .. } => Cow::Borrowed(localized_strings.get("buff.desc.burn")), + BuffKind::Crippled { .. } => Cow::Borrowed(localized_strings.get("buff.desc.crippled")), } } diff --git a/voxygen/src/hud/util.rs b/voxygen/src/hud/util.rs index c61eee530f..68271e7ebb 100644 --- a/voxygen/src/hud/util.rs +++ b/voxygen/src/hud/util.rs @@ -118,7 +118,9 @@ pub fn consumable_desc(effects: &[Effect], i18n: &Localization) -> String { | BuffKind::Burning | BuffKind::CampfireHeal | BuffKind::Cursed - | BuffKind::ProtectingWard => continue, + | BuffKind::ProtectingWard + | BuffKind::Crippled + | BuffKind::Frenzied => continue, }; write!(&mut description, "{}", buff_desc).unwrap(); @@ -138,7 +140,9 @@ pub fn consumable_desc(effects: &[Effect], i18n: &Localization) -> String { | BuffKind::Potion | BuffKind::CampfireHeal | BuffKind::Cursed - | BuffKind::ProtectingWard => continue, + | BuffKind::ProtectingWard + | BuffKind::Crippled + | BuffKind::Frenzied => continue, } } else if let BuffKind::Saturation | BuffKind::Regeneration = buff.kind { i18n.get("buff.text.every_second").to_string() diff --git a/voxygen/src/render/pipelines/particle.rs b/voxygen/src/render/pipelines/particle.rs index 32a73abfde..32b9cebd37 100644 --- a/voxygen/src/render/pipelines/particle.rs +++ b/voxygen/src/render/pipelines/particle.rs @@ -122,6 +122,8 @@ pub enum ParticleMode { CultistFlame = 23, StaticSmoke = 24, Blood = 25, + Enraged = 26, + BigShrapnel = 27, } impl ParticleMode { diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index 1a0dde31f7..754ffd8eec 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -3495,7 +3495,6 @@ struct SidedBLCentralVoxSpec { torso_upper: BipedLargeCentralSubSpec, torso_lower: BipedLargeCentralSubSpec, tail: BipedLargeCentralSubSpec, - second: BipedLargeCentralSubSpec, } #[derive(Deserialize)] struct BipedLargeCentralSubSpec { @@ -3523,13 +3522,16 @@ struct BipedLargeLateralSubSpec { lateral: VoxSimple, } #[derive(Deserialize)] -struct BipedLargeWeaponSpec(HashMap); +struct BipedLargeMainSpec(HashMap); +#[derive(Deserialize)] +struct BipedLargeSecondSpec(HashMap); make_vox_spec!( biped_large::Body, struct BipedLargeSpec { central: BipedLargeCentralSpec = "voxygen.voxel.biped_large_central_manifest", lateral: BipedLargeLateralSpec = "voxygen.voxel.biped_large_lateral_manifest", - weapon: BipedLargeWeaponSpec = "voxygen.voxel.biped_weapon_manifest", + main: BipedLargeMainSpec = "voxygen.voxel.biped_weapon_manifest", + second: BipedLargeSecondSpec = "voxygen.voxel.biped_weapon_manifest", }, |FigureKey { body, extra }, spec| { const DEFAULT_LOADOUT: super::cache::CharacterCacheKey = super::cache::CharacterCacheKey { @@ -3567,15 +3569,17 @@ make_vox_spec!( body.body_type, )), tool.and_then(|tool| tool.active.as_ref()).map(|tool| { - spec.weapon.read().0.mesh_main( + spec.main.read().0.mesh_main( + &tool.name, + false, + ) + }), + tool.and_then(|tool| tool.active.as_ref()).map(|tool| { + spec.second.read().0.mesh_second( &tool.name, false, ) }), - Some(spec.central.read().0.mesh_second( - body.species, - body.body_type, - )), Some(spec.lateral.read().0.mesh_shoulder_l( body.species, body.body_type, @@ -3693,22 +3697,6 @@ impl BipedLargeCentralSpec { (central, Vec3::from(spec.tail.offset)) } - - fn mesh_second(&self, species: BLSpecies, body_type: BLBodyType) -> BoneMeshes { - let spec = match self.0.get(&(species, body_type)) { - Some(spec) => spec, - None => { - error!( - "No second weapon specification exists for the combination of {:?} and {:?}", - species, body_type - ); - return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5)); - }, - }; - let central = graceful_load_segment(&spec.second.central.0); - - (central, Vec3::from(spec.second.offset)) - } } impl BipedLargeLateralSpec { fn mesh_shoulder_l(&self, species: BLSpecies, body_type: BLBodyType) -> BoneMeshes { @@ -3839,7 +3827,7 @@ impl BipedLargeLateralSpec { (lateral, Vec3::from(spec.foot_r.offset)) } } -impl BipedLargeWeaponSpec { +impl BipedLargeMainSpec { fn mesh_main(&self, item_definition_id: &str, flipped: bool) -> BoneMeshes { let spec = match self.0.get(item_definition_id) { Some(spec) => spec, @@ -3868,6 +3856,35 @@ impl BipedLargeWeaponSpec { (tool_kind_segment, offset) } } +impl BipedLargeSecondSpec { + fn mesh_second(&self, item_definition_id: &str, flipped: bool) -> BoneMeshes { + let spec = match self.0.get(item_definition_id) { + Some(spec) => spec, + None => { + error!(?item_definition_id, "No tool/weapon specification exists"); + return load_mesh("not_found", Vec3::new(-1.5, -1.5, -7.0)); + }, + }; + + let tool_kind_segment = if flipped { + graceful_load_segment_flipped(&spec.vox_spec.0, true) + } else { + graceful_load_segment(&spec.vox_spec.0) + }; + + let offset = Vec3::new( + if flipped { + 0.0 - spec.vox_spec.1[0] - (tool_kind_segment.sz.x as f32) + } else { + spec.vox_spec.1[0] + }, + spec.vox_spec.1[1], + spec.vox_spec.1[2], + ); + + (tool_kind_segment, offset) + } +} //// #[derive(Deserialize)] struct GolemCentralSpec(HashMap<(GSpecies, GBodyType), SidedGCentralVoxSpec>); diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index c502b07ddf..f0411c9d72 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -3713,7 +3713,84 @@ impl FigureMgr { skeleton_attr, ) }, - CharacterState::BasicMelee(_) => { + CharacterState::ChargedMelee(s) => { + let stage_time = s.timer.as_secs_f32(); + + let stage_progress = match s.stage_section { + StageSection::Charge => { + stage_time / s.static_data.charge_duration.as_secs_f32() + }, + StageSection::Swing => { + stage_time / s.static_data.swing_duration.as_secs_f32() + }, + StageSection::Recover => { + stage_time / s.static_data.recover_duration.as_secs_f32() + }, + _ => 0.0, + }; + + anim::biped_large::ChargeMeleeAnimation::update_skeleton( + &target_base, + ( + (active_tool_kind, active_tool_spec), + (second_tool_kind, second_tool_spec), + rel_vel, + time, + Some(s.stage_section), + state.acc_vel, + ), + 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::Cast => { + 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::biped_large::SelfBuffAnimation::update_skeleton( + &target_base, + ( + (active_tool_kind, active_tool_spec), + (second_tool_kind, second_tool_spec), + rel_vel, + time, + Some(s.stage_section), + state.acc_vel, + ), + stage_progress, + &mut state_animation_rate, + skeleton_attr, + ) + }, + CharacterState::BasicMelee(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::Swing => { + stage_time / s.static_data.swing_duration.as_secs_f32() + }, + StageSection::Recover => { + stage_time / s.static_data.recover_duration.as_secs_f32() + }, + _ => 0.0, + }; + anim::biped_large::AlphaAnimation::update_skeleton( &target_base, ( @@ -3721,10 +3798,10 @@ impl FigureMgr { (second_tool_kind, second_tool_spec), rel_vel, time, - None, + Some(s.stage_section), state.acc_vel, ), - state.state_time, + stage_progress, &mut state_animation_rate, skeleton_attr, ) @@ -3875,8 +3952,8 @@ impl FigureMgr { anim::biped_large::DashAnimation::update_skeleton( &target_base, ( - active_tool_kind, - second_tool_kind, + (active_tool_kind, active_tool_spec), + (second_tool_kind, second_tool_spec), rel_vel, time, Some(s.stage_section), diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 53ae2aea42..fdd166abaf 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -215,6 +215,16 @@ impl ParticleMgr { }); } }, + Outcome::GroundSlam { pos, .. } => { + self.particles.resize_with(self.particles.len() + 100, || { + Particle::new( + Duration::from_millis(1000), + time, + ParticleMode::BigShrapnel, + *pos, + ) + }); + }, Outcome::ProjectileShot { .. } | Outcome::Beam { .. } | Outcome::ExpChange { .. } @@ -643,6 +653,36 @@ impl ParticleMgr { }, ); }, + CharacterState::SelfBuff(c) => { + use buff::BuffKind; + if let BuffKind::Frenzied = c.static_data.buff_kind { + if matches!(c.stage_section, StageSection::Cast) { + self.particles.resize_with( + self.particles.len() + + usize::from( + self.scheduler.heartbeats(Duration::from_millis(5)), + ), + || { + let start_pos = pos.0 + + Vec3::new( + body.radius(), + body.radius(), + body.height() / 2.0, + ) + .map(|d| d * rng.gen_range(-1.0..1.0)); + let end_pos = pos.0 + (start_pos - pos.0) * 6.0; + Particle::new_directed( + Duration::from_secs(1), + time, + ParticleMode::Enraged, + start_pos, + end_pos, + ) + }, + ); + } + } + }, _ => {}, } } @@ -818,9 +858,9 @@ impl ParticleMgr { .join() { for (buff_kind, _) in buffs.kinds.iter() { - #[allow(clippy::single_match)] + use buff::BuffKind; match buff_kind { - buff::BuffKind::Cursed | buff::BuffKind::Burning => { + BuffKind::Cursed | BuffKind::Burning => { self.particles.resize_with( self.particles.len() + usize::from(self.scheduler.heartbeats(Duration::from_millis(15))), @@ -850,6 +890,29 @@ impl ParticleMgr { }, ); }, + BuffKind::Frenzied => { + self.particles.resize_with( + self.particles.len() + + usize::from(self.scheduler.heartbeats(Duration::from_millis(15))), + || { + let start_pos = pos.0 + + Vec3::new(body.radius(), body.radius(), body.height() / 2.0) + .map(|d| d * rng.gen_range(-1.0..1.0)); + let end_pos = start_pos + + Vec3::unit_z() * body.height() + + Vec3::::zero() + .map(|_| rng.gen_range(-1.0..1.0)) + .normalized(); + Particle::new_directed( + Duration::from_secs(1), + time, + ParticleMode::Enraged, + start_pos, + end_pos, + ) + }, + ); + }, _ => {}, } }