From ae06016e9a28f74b42d53c068c35a073f05410de Mon Sep 17 00:00:00 2001 From: jshipsey Date: Tue, 10 Nov 2020 22:18:45 -0500 Subject: [PATCH] Wolf attacks and ai. initial wolf dash testing tool.rs Wolf attack AI --- .../abilities/unique/genericquadmed/dash.ron | 17 ++ .../npc_weapons/unique/genericquadmed.ron | 13 ++ common/src/comp/inventory/item/tool.rs | 1 + common/src/loadout_builder.rs | 10 +- common/src/sys/agent.rs | 166 +++++++++++------- .../src/anim/src/quadruped_medium/alpha.rs | 161 +++++++++-------- voxygen/src/anim/src/quadruped_medium/run.rs | 4 +- voxygen/src/scene/figure/mod.rs | 20 ++- 8 files changed, 256 insertions(+), 136 deletions(-) create mode 100644 assets/common/abilities/unique/genericquadmed/dash.ron create mode 100644 assets/common/items/npc_weapons/unique/genericquadmed.ron diff --git a/assets/common/abilities/unique/genericquadmed/dash.ron b/assets/common/abilities/unique/genericquadmed/dash.ron new file mode 100644 index 0000000000..cacca5b549 --- /dev/null +++ b/assets/common/abilities/unique/genericquadmed/dash.ron @@ -0,0 +1,17 @@ +DashMelee( + energy_cost: 0, + base_damage: 30, + max_damage: 40, + base_knockback: 8.0, + max_knockback: 15.0, + range: 2.0, + angle: 45.0, + energy_drain: 0, + forward_speed: 1.5, + buildup_duration: 1200, + charge_duration: 300, + swing_duration: 100, + recover_duration: 500, + infinite_charge: true, + is_interruptible: true, +) \ No newline at end of file diff --git a/assets/common/items/npc_weapons/unique/genericquadmed.ron b/assets/common/items/npc_weapons/unique/genericquadmed.ron new file mode 100644 index 0000000000..ba3657128a --- /dev/null +++ b/assets/common/items/npc_weapons/unique/genericquadmed.ron @@ -0,0 +1,13 @@ +ItemDef( + name: "Generic Quad Med", + description: "testing123", + kind: Tool( + ( + kind: Unique(GenericQuadMed), + stats: ( + equip_time_millis: 10, + power: 1.00), + ) + ), + quality: Low, +) diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index 5f46baca2e..d7c8425817 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -172,4 +172,5 @@ impl Asset for AbilityMap { pub enum UniqueKind { StoneGolemFist, BeastClaws, + GenericQuadMed, } diff --git a/common/src/loadout_builder.rs b/common/src/loadout_builder.rs index cba58c2e97..6f44abc339 100644 --- a/common/src/loadout_builder.rs +++ b/common/src/loadout_builder.rs @@ -1,7 +1,7 @@ use crate::comp::{ biped_large, golem, item::{tool::AbilityMap, Item, ItemKind}, - Body, CharacterAbility, ItemConfig, Loadout, + quadruped_medium, Body, CharacterAbility, ItemConfig, Loadout, }; use rand::Rng; @@ -103,6 +103,14 @@ impl LoadoutBuilder { }, _ => {}, }, + Body::QuadrupedMedium(quadruped_medium) => match quadruped_medium.species { + quadruped_medium::Species::Wolf => { + main_tool = Some(Item::new_from_asset_expect( + "common.items.npc_weapons.unique.genericquadmed", + )); + }, + _ => {}, + }, Body::BipedLarge(biped_large) => match (biped_large.species, biped_large.body_type) { (biped_large::Species::Occultsaurok, _) => { diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 2870852163..a8d1261ecb 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -28,6 +28,7 @@ use specs::{ saveload::{Marker, MarkerAllocator}, Entities, Join, Read, ReadExpect, ReadStorage, System, Write, WriteStorage, }; +use std::f32::consts::PI; use vek::*; /// This system will allow NPCs to modify their controller @@ -332,6 +333,7 @@ impl<'a> System<'a> for Sys { Bow, Staff, StoneGolemBoss, + Wolf, } let tactic = match loadout.active_item.as_ref().and_then(|ic| { @@ -349,6 +351,7 @@ impl<'a> System<'a> for Sys { Some(ToolKind::Unique(UniqueKind::StoneGolemFist)) => { Tactic::StoneGolemBoss }, + Some(ToolKind::Unique(UniqueKind::GenericQuadMed)) => Tactic::Wolf, _ => Tactic::Melee, }; @@ -439,73 +442,114 @@ impl<'a> System<'a> for Sys { } } else if (tactic == Tactic::Staff && dist_sqrd < (5.0 * MIN_ATTACK_DIST * scale).powf(2.0)) + || (tactic == Tactic::Wolf + && dist_sqrd < (3.0 * MIN_ATTACK_DIST * scale).powf(2.0)) || dist_sqrd < (MIN_ATTACK_DIST * scale).powf(2.0) { - // Close-range attack - inputs.move_dir = (tgt_pos.0 - pos.0) - .xy() - .try_normalized() - .unwrap_or(Vec2::unit_y()) - * 0.1; - + controller.actions.push(ControlAction::Wield); match tactic { - Tactic::Melee | Tactic::StoneGolemBoss => { - inputs.primary.set_state(true) + Tactic::Wolf => { + // Run away from target to get clear + controller.actions.push(ControlAction::Unwield); + inputs.move_dir = (pos.0 - tgt_pos.0) + .xy() + .try_normalized() + .unwrap_or(Vec2::unit_y()); }, - Tactic::Hammer => { - if *powerup > 4.0 { - inputs.secondary.set_state(false); - *powerup = 0.0; - } else if *powerup > 2.0 { - inputs.secondary.set_state(true); - *powerup += dt.0; - } else if energy.current() > 700 { - inputs.ability3.set_state(true); - *powerup += dt.0; - } else { - inputs.primary.set_state(true); - *powerup += dt.0; + _ => { + // Close-range attack + inputs.move_dir = (tgt_pos.0 - pos.0) + .xy() + .try_normalized() + .unwrap_or(Vec2::unit_y()) + * 0.1; + + match tactic { + Tactic::Hammer => { + if *powerup > 4.0 { + inputs.secondary.set_state(false); + *powerup = 0.0; + } else if *powerup > 2.0 { + inputs.secondary.set_state(true); + *powerup += dt.0; + } else if energy.current() > 700 { + inputs.ability3.set_state(true); + *powerup += dt.0; + } else { + inputs.primary.set_state(true); + *powerup += dt.0; + } + }, + Tactic::Staff => { + // Kind of arbitrary values, but feel right in game + if energy.current() > 800 + && thread_rng().gen::() > 0.8 + { + inputs.ability3.set_state(true) + } else if energy.current() > 10 { + inputs.secondary.set_state(true) + } else { + inputs.primary.set_state(true) + } + }, + Tactic::Sword => { + if *powerup < 2.0 && energy.current() > 500 { + inputs.ability3.set_state(true); + *powerup += dt.0; + } else if *powerup > 2.0 { + *powerup = 0.0; + } else { + inputs.primary.set_state(true); + *powerup += dt.0; + } + }, + Tactic::Axe => { + if *powerup > 6.0 { + inputs.secondary.set_state(false); + *powerup = 0.0; + } else if *powerup > 4.0 && energy.current() > 10 { + inputs.secondary.set_state(true); + *powerup += dt.0; + } else { + inputs.primary.set_state(true); + *powerup += dt.0; + } + }, + Tactic::Bow => inputs.roll.set_state(true), + _ => inputs.primary.set_state(true), } }, - Tactic::Staff => { - // Kind of arbitrary values, but feel right in game - if energy.current() > 800 && thread_rng().gen_bool(0.2) { - inputs.ability3.set_state(true) - } else if energy.current() > 10 { - inputs.secondary.set_state(true) - } else { - inputs.primary.set_state(true) - } - }, - Tactic::Sword => { - if *powerup < 2.0 && energy.current() > 500 { - inputs.ability3.set_state(true); - *powerup += dt.0; - } else if *powerup > 2.0 { - *powerup = 0.0; - } else { - inputs.primary.set_state(true); - *powerup += dt.0; - } - }, - Tactic::Axe => { - if *powerup > 6.0 { - inputs.secondary.set_state(false); - *powerup = 0.0; - } else if *powerup > 4.0 && energy.current() > 100 { - inputs.secondary.set_state(true); - *powerup += dt.0; - } else if energy.current() > 800 - && thread_rng().gen_bool(0.5) - { - inputs.ability3.set_state(true); - *powerup += dt.0; - } else { - inputs.primary.set_state(true); - *powerup += dt.0; - } - }, - Tactic::Bow => inputs.roll.set_state(true), + } + } else if matches!(tactic, Tactic::Wolf) + && dist_sqrd < (4.0 * MIN_ATTACK_DIST * scale).powf(2.0) + && dist_sqrd > (3.0 * MIN_ATTACK_DIST * scale).powf(2.0) + { + if *powerup < 2.0 { + controller.actions.push(ControlAction::Unwield); + inputs.move_dir = (tgt_pos.0 - pos.0) + .xy() + .rotated_z(0.45 * PI) + .try_normalized() + .unwrap_or(Vec2::unit_y()); + *powerup += dt.0; + } else if *powerup < 2.5 { + controller.actions.push(ControlAction::Wield); + inputs.primary.set_state(true); + *powerup += dt.0; + } else if *powerup < 4.5 { + controller.actions.push(ControlAction::Unwield); + inputs.move_dir = (tgt_pos.0 - pos.0) + .xy() + .rotated_z(-0.45 * PI) + .try_normalized() + .unwrap_or(Vec2::unit_y()); + *powerup += dt.0; + } else if *powerup < 5.0 { + controller.actions.push(ControlAction::Wield); + inputs.primary.set_state(true); + *powerup += dt.0; + } else { + *powerup = 0.0; } } else if dist_sqrd < MAX_CHASE_DIST.powf(2.0) || (dist_sqrd < SIGHT_DIST.powf(2.0) diff --git a/voxygen/src/anim/src/quadruped_medium/alpha.rs b/voxygen/src/anim/src/quadruped_medium/alpha.rs index 25c223c6f0..aadd23875b 100644 --- a/voxygen/src/anim/src/quadruped_medium/alpha.rs +++ b/voxygen/src/anim/src/quadruped_medium/alpha.rs @@ -2,12 +2,13 @@ use super::{ super::{vek::*, Animation}, QuadrupedMediumSkeleton, SkeletonAttr, }; +use common::{comp::item::ToolKind, states::utils::StageSection}; use std::f32::consts::PI; pub struct AlphaAnimation; impl Animation for AlphaAnimation { - type Dependency = (f32, f64); + type Dependency = (f32, f64, Option); type Skeleton = QuadrupedMediumSkeleton; #[cfg(feature = "use-dyn-lib")] @@ -16,81 +17,103 @@ impl Animation for AlphaAnimation { #[cfg_attr(feature = "be-dyn-lib", export_name = "quadruped_medium_alpha")] fn update_skeleton_inner( skeleton: &Self::Skeleton, - (velocity, _global_time): Self::Dependency, + (velocity, _global_time, stage_section): Self::Dependency, anim_time: f64, _rate: &mut f32, s_a: &SkeletonAttr, ) -> Self::Skeleton { let mut next = (*skeleton).clone(); + let speed = (Vec2::::from(velocity).magnitude()).min(24.0); - let short = (((1.0) - / (0.1 + 0.9 * ((anim_time as f32 * 4.0 + PI * 2.5).sin()).powf(2.0 as f32))) - .sqrt()) - * ((anim_time as f32 * 4.0 + PI * 2.5).sin()); - let quick = (((1.0) - / (0.001 + 0.9999 * ((anim_time as f32 * 4.0 + PI * 0.5).sin()).powf(2.0 as f32))) - .sqrt()) - * ((anim_time as f32 * 4.0 + PI * 0.5).sin()); - - next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1); - next.head.orientation = - Quaternion::rotation_y(short * -0.2) * Quaternion::rotation_x(0.1 + short * 0.2); - - next.neck.position = Vec3::new(0.0, s_a.neck.0, s_a.neck.1); - - next.jaw.position = Vec3::new(0.0, s_a.jaw.0, s_a.jaw.1); - next.jaw.orientation = Quaternion::rotation_x(-0.3 + quick * 0.4); - - next.tail.position = Vec3::new(0.0, s_a.tail.0, s_a.tail.1); - - next.torso_front.position = Vec3::new( - 0.0, - s_a.torso_front.0 + short * 2.8, - s_a.torso_front.1 + short * 1.0, - ) * s_a.scaler - / 11.0; - next.torso_front.orientation = Quaternion::rotation_y(short * -0.1); - - next.torso_back.position = Vec3::new(0.0, s_a.torso_back.0, s_a.torso_back.1); - next.torso_back.orientation = Quaternion::rotation_y(short * -0.1); - - next.ears.position = Vec3::new(0.0, s_a.ears.0, s_a.ears.1); - if velocity < 1.0 { - next.leg_fl.position = Vec3::new(-s_a.leg_f.0, s_a.leg_f.1, s_a.leg_f.2); - - next.leg_fl.orientation = - Quaternion::rotation_x(short * -0.1) * Quaternion::rotation_y(short * 0.15); - - next.leg_fr.position = Vec3::new(s_a.leg_f.0, s_a.leg_f.1, s_a.leg_f.2); - next.leg_fr.orientation = - Quaternion::rotation_x(short * 0.3) * Quaternion::rotation_y(short * -0.2); - - next.leg_bl.position = Vec3::new(-s_a.leg_b.0, s_a.leg_b.1, s_a.leg_b.2 + 1.0); - next.leg_bl.orientation = - Quaternion::rotation_x(-0.1 + short * -0.2) * Quaternion::rotation_y(short * 0.2); - - next.leg_br.position = Vec3::new(s_a.leg_b.0, s_a.leg_b.1, s_a.leg_b.2 + 1.0); - next.leg_br.orientation = Quaternion::rotation_x(-0.1 + short * -0.2) - * Quaternion::rotation_y(0.1 + short * 0.2); - - next.foot_fl.position = - Vec3::new(-s_a.feet_f.0, s_a.feet_f.1, s_a.feet_f.2 + short * -0.2); - next.foot_fl.orientation = Quaternion::rotation_x(short * -0.05); - - next.foot_fr.position = Vec3::new(s_a.feet_f.0, s_a.feet_f.1, s_a.feet_f.2); - next.foot_fr.orientation = - Quaternion::rotation_x(short * -0.4) * Quaternion::rotation_y(short * 0.15); - - next.foot_bl.position = - Vec3::new(-s_a.feet_b.0, s_a.feet_b.1, s_a.feet_b.2 + short * -0.8); - next.foot_bl.orientation = - Quaternion::rotation_x(-0.2 + short * 0.2) * Quaternion::rotation_y(short * 0.15); - - next.foot_br.position = Vec3::new(s_a.feet_b.0, s_a.feet_b.1, s_a.feet_b.2); - next.foot_br.orientation = - Quaternion::rotation_x(-0.2 + short * 0.2) * Quaternion::rotation_y(short * 0.15); - } else { + let (movement1, movement2, movement3) = match stage_section { + Some(StageSection::Buildup) => ((anim_time as f32).powf(0.25), 0.0, 0.0), + Some(StageSection::Swing) => (1.0, anim_time as f32, 0.0), + Some(StageSection::Recover) => (0.0, 1.0, (anim_time as f32).powf(4.0)), + _ => (0.0, 0.0, 0.0), }; + + if let Some(stage_section) = stage_section { + match stage_section { + StageSection::Buildup | StageSection::Recover | StageSection::Swing => { + let twitch1 = (movement1 * 20.0).sin(); + let twitch2 = (movement3 * 5.0).sin(); + let twitchmovement = twitch1 + twitch2; + + next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1); + next.head.orientation = + Quaternion::rotation_x( + (movement1 * 0.4 + movement2 * 0.4) * (1.0 - movement3), + ) * Quaternion::rotation_y(twitchmovement * 0.2 * (1.0 - movement3)); + + next.neck.position = Vec3::new(0.0, s_a.neck.0, s_a.neck.1); + next.neck.orientation = + Quaternion::rotation_x(movement1 * -0.7 * (1.0 - movement3)) + * Quaternion::rotation_y(twitchmovement * 0.1); + next.jaw.position = Vec3::new(0.0, s_a.jaw.0, s_a.jaw.1); + next.jaw.orientation = Quaternion::rotation_x(twitchmovement * 0.1); + next.jaw.scale = Vec3::one() * 1.02; + + next.tail.position = Vec3::new(0.0, s_a.tail.0, s_a.tail.1); + next.tail.orientation = Quaternion::rotation_z(twitchmovement * 1.0); + + next.torso_front.position = + Vec3::new(0.0, s_a.torso_front.0, s_a.torso_front.1) * s_a.scaler / 11.0; + next.torso_front.orientation = + Quaternion::rotation_x(movement1 * 0.2 * (1.0 - movement3)) + * Quaternion::rotation_y(twitchmovement * -0.1); + + next.torso_back.position = Vec3::new(0.0, s_a.torso_back.0, s_a.torso_back.1); + next.torso_back.orientation = + Quaternion::rotation_x(movement1 * -0.3 * (1.0 - movement3)) + * Quaternion::rotation_y(twitchmovement * 0.1); + + next.ears.position = Vec3::new(0.0, s_a.ears.0, s_a.ears.1); + next.ears.orientation = + Quaternion::rotation_x(twitchmovement * 0.1 * (1.0 - movement3)); + if speed < 0.5 { + next.leg_fl.position = Vec3::new(-s_a.leg_f.0, s_a.leg_f.1, s_a.leg_f.2); + next.leg_fl.orientation = + Quaternion::rotation_x(movement1 * 0.6 * (1.0 - movement3)) + * Quaternion::rotation_y(twitchmovement * 0.1); + + next.leg_fr.position = Vec3::new(s_a.leg_f.0, s_a.leg_f.1, s_a.leg_f.2); + next.leg_fr.orientation = + Quaternion::rotation_x(movement1 * 0.6 * (1.0 - movement3)) + * Quaternion::rotation_y(twitchmovement * 0.1); + + next.leg_bl.position = Vec3::new(-s_a.leg_b.0, s_a.leg_b.1, s_a.leg_b.2); + next.leg_bl.orientation = + Quaternion::rotation_x(movement1 * 0.5 * (1.0 - movement3)); + + next.leg_br.position = Vec3::new(s_a.leg_b.0, s_a.leg_b.1, s_a.leg_b.2); + next.leg_br.orientation = + Quaternion::rotation_x(movement1 * 0.5 * (1.0 - movement3)); + + next.foot_fl.position = + Vec3::new(-s_a.feet_f.0, s_a.feet_f.1, s_a.feet_f.2); + next.foot_fl.orientation = + Quaternion::rotation_x(movement1 * -0.5 * (1.0 - movement3)); + + next.foot_fr.position = Vec3::new(s_a.feet_f.0, s_a.feet_f.1, s_a.feet_f.2); + next.foot_fr.orientation = + Quaternion::rotation_x(movement1 * -0.5 * (1.0 - movement3)); + + next.foot_bl.position = + Vec3::new(-s_a.feet_b.0, s_a.feet_b.1, s_a.feet_b.2); + next.foot_bl.orientation = + Quaternion::rotation_x(movement1 * -1.0 * (1.0 - movement3)); + + next.foot_br.position = Vec3::new(s_a.feet_b.0, s_a.feet_b.1, s_a.feet_b.2); + next.foot_br.orientation = + Quaternion::rotation_x(movement1 * -1.0 * (1.0 - movement3)); + }; + }, + StageSection::Charge => { + next.jaw.orientation = Quaternion::rotation_x(-1.0); + }, + _ => {}, + } + } next } } diff --git a/voxygen/src/anim/src/quadruped_medium/run.rs b/voxygen/src/anim/src/quadruped_medium/run.rs index 2db9aeacb9..09e51b33f4 100644 --- a/voxygen/src/anim/src/quadruped_medium/run.rs +++ b/voxygen/src/anim/src/quadruped_medium/run.rs @@ -22,7 +22,7 @@ impl Animation for RunAnimation { s_a: &SkeletonAttr, ) -> Self::Skeleton { let mut next = (*skeleton).clone(); - let speed = Vec2::::from(velocity).magnitude(); + let speed = (Vec2::::from(velocity).magnitude()).min(24.0); *rate = 1.0; //let increasefreqtest = (((1.0/speed)*3.0).round()).min(5.0); let lab = 0.72; //0.72 @@ -141,7 +141,7 @@ impl Animation for RunAnimation { ) * s_a.scaler / 11.0; next.torso_front.orientation = Quaternion::rotation_x( - (amplitude * (short * -0.13).max(-0.2)) * s_a.spring + ((amplitude * (short * -0.13).max(-0.2)) * s_a.spring).min(0.1) + x_tilt * (canceler * 6.0).min(1.0), ) * Quaternion::rotation_y(tilt * 0.8) * Quaternion::rotation_z(tilt * -1.5); diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 1c90887e15..032721cb5e 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -1528,11 +1528,25 @@ impl FigureMgr { ), }; let target_bones = match &character { - CharacterState::BasicMelee(_) => { + CharacterState::DashMelee(s) => { + let stage_time = s.timer.as_secs_f64(); + let stage_progress = match s.stage_section { + StageSection::Buildup => { + stage_time / s.static_data.buildup_duration.as_secs_f64() + }, + StageSection::Charge => stage_time, + StageSection::Swing => { + stage_time / s.static_data.swing_duration.as_secs_f64() + }, + StageSection::Recover => { + stage_time / s.static_data.recover_duration.as_secs_f64() + }, + _ => 0.0, + }; anim::quadruped_medium::AlphaAnimation::update_skeleton( &target_base, - (vel.0.magnitude(), time), - state.state_time, + (vel.0.magnitude(), time, Some(s.stage_section)), + stage_progress, &mut state_animation_rate, skeleton_attr, )