diff --git a/assets/common/abilities/ability_set_manifest.ron b/assets/common/abilities/ability_set_manifest.ron index b8db252fd0..161fb4f37d 100644 --- a/assets/common/abilities/ability_set_manifest.ron +++ b/assets/common/abilities/ability_set_manifest.ron @@ -84,6 +84,11 @@ secondary: "common.abilities.custom.deadwood.dash", abilities: [], ), + Custom("Mandragora"): ( + primary: "common.abilities.custom.mandragora.basic", + secondary: "common.abilities.custom.mandragora.scream", + abilities: [], + ), Custom("Sword Simple"): ( primary: "common.abilities.swordsimple.doublestrike", secondary: "common.abilities.swordsimple.dash", diff --git a/assets/common/abilities/custom/mandragora/basic.ron b/assets/common/abilities/custom/mandragora/basic.ron new file mode 100644 index 0000000000..1ffb653d5f --- /dev/null +++ b/assets/common/abilities/custom/mandragora/basic.ron @@ -0,0 +1,16 @@ +BasicMelee( + energy_cost: 0, + buildup_duration: 0.2, + swing_duration: 0.05, + recover_duration: 0.3, + melee_constructor: ( + kind: Bash( + damage: 4, + poise: 5, + knockback: 0, + energy_regen: 0, + ), + range: 3, + angle: 30, + ), +) diff --git a/assets/common/abilities/custom/mandragora/scream.ron b/assets/common/abilities/custom/mandragora/scream.ron new file mode 100644 index 0000000000..f7625a5a76 --- /dev/null +++ b/assets/common/abilities/custom/mandragora/scream.ron @@ -0,0 +1,21 @@ +SpinMelee( + buildup_duration: 0.5, + swing_duration: 0.3, + recover_duration: 0.5, + melee_constructor: ( + kind: SonicWave( + damage: 5, + poise: 100, + knockback: 20, + ), + range: 10, + angle: 360.0, + ), + energy_cost: 0.0, + is_infinite: false, + movement_behavior: Stationary, + is_interruptible: false, + forward_speed: 0.0, + num_spins: 1, + specifier: None, +) diff --git a/assets/common/entity/dungeon/gnarling/mandragora.ron b/assets/common/entity/dungeon/gnarling/mandragora.ron index 32dc6d62fe..b8801c5aeb 100644 --- a/assets/common/entity/dungeon/gnarling/mandragora.ron +++ b/assets/common/entity/dungeon/gnarling/mandragora.ron @@ -2,7 +2,11 @@ EntityConfig ( name: Name("Mandragora"), body: RandomWith("mandragora"), alignment: Alignment(Enemy), - loadout: Asset(Loadout("common.loadout.dungeon.gnarling.mandragora")), + loadout: Extended( + hands: TwoHanded(Item("common.items.npc_weapons.biped_small.mandragora")), + base_asset: Loadout("common.loadout.dungeon.gnarling.mandragora"), + inventory: [], + ), loot: LootTable("common.loot_tables.dungeon.tier-0.miniboss"), meta: [], ) diff --git a/assets/common/items/npc_weapons/biped_small/mandragora.ron b/assets/common/items/npc_weapons/biped_small/mandragora.ron new file mode 100644 index 0000000000..e32559a916 --- /dev/null +++ b/assets/common/items/npc_weapons/biped_small/mandragora.ron @@ -0,0 +1,21 @@ +ItemDef( + name: "Mandragora", + description: "Testing", + kind: Tool(( + kind: Natural, + hands: Two, + stats: Direct(( + equip_time_secs: 0.0, + power: 1.0, + effect_power: 1.0, + speed: 1.0, + crit_chance: 0.1, + range: 1.0, + energy_efficiency: 1.0, + buff_strength: 1.0, + )), + )), + quality: Low, + tags: [], + ability_spec: Some(Custom("Mandragora")), +) \ No newline at end of file diff --git a/assets/voxygen/voxel/biped_weapon_manifest.ron b/assets/voxygen/voxel/biped_weapon_manifest.ron index 3b3143f875..6995af53b5 100644 --- a/assets/voxygen/voxel/biped_weapon_manifest.ron +++ b/assets/voxygen/voxel/biped_weapon_manifest.ron @@ -1179,4 +1179,8 @@ vox_spec: ("weapon.biped_small.axe.strategian", (-0.5, -6.0, -4.0)), color: None ), + "common.items.npc_weapons.biped_small.mandragora": ( + vox_spec: ("armor.empty", (0.0, 0.0, 0.0)), + color: None + ), }) diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 78ee584191..03f2525089 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -483,6 +483,7 @@ pub struct ActionState { pub counter: f32, pub condition: bool, pub int_counter: u8, + pub initialized: bool, } impl Agent { diff --git a/common/src/comp/melee.rs b/common/src/comp/melee.rs index 0207643778..d59cb0e713 100644 --- a/common/src/comp/melee.rs +++ b/common/src/comp/melee.rs @@ -232,6 +232,43 @@ impl MeleeConstructor { .with_effect(knockback) .with_combo_increment() }, + SonicWave { + damage, + poise, + knockback, + } => { + let mut damage = AttackDamage::new( + Damage { + source: DamageSource::Melee, + kind: DamageKind::Energy, + value: damage, + }, + Some(GroupTarget::OutOfGroup), + ); + + if let Some(damage_effect) = self.damage_effect { + damage = damage.with_effect(damage_effect); + } + + let poise = + AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise)) + .with_requirement(CombatRequirement::AnyDamage); + let knockback = AttackEffect::new( + Some(GroupTarget::OutOfGroup), + CombatEffect::Knockback(Knockback { + strength: knockback, + direction: KnockbackDir::Away, + }), + ) + .with_requirement(CombatRequirement::AnyDamage); + + Attack::default() + .with_damage(damage) + .with_crit(crit_chance, crit_mult) + .with_effect(poise) + .with_effect(knockback) + .with_combo_increment() + }, }; Melee { @@ -324,6 +361,22 @@ impl MeleeConstructor { pull: scale_values(a_pull, b_pull), lifesteal: scale_values(a_lifesteal, b_lifesteal), }, + ( + SonicWave { + damage: a_damage, + poise: a_poise, + knockback: a_knockback, + }, + SonicWave { + damage: b_damage, + poise: b_poise, + knockback: b_knockback, + }, + ) => SonicWave { + damage: scale_values(a_damage, b_damage), + poise: scale_values(a_poise, b_poise), + knockback: scale_values(a_knockback, b_knockback), + }, _ => { dev_panic!( "Attempted to scale on a melee attack between two different kinds of \ @@ -382,6 +435,11 @@ pub enum MeleeConstructorKind { pull: f32, lifesteal: f32, }, + SonicWave { + damage: f32, + poise: f32, + knockback: f32, + }, } impl MeleeConstructorKind { @@ -426,6 +484,14 @@ impl MeleeConstructorKind { } => { *damage *= stats.power; }, + SonicWave { + ref mut damage, + ref mut poise, + knockback: _, + } => { + *damage *= stats.power; + *poise *= stats.effect_power; + }, } self } diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index ae3c1c4752..2c36f98b0a 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -1748,6 +1748,7 @@ impl<'a> AgentData<'a> { "Gnarling Dagger" => Tactic::SimpleBackstab, "Gnarling Blowgun" => Tactic::ElevatedRanged, "Deadwood" => Tactic::Deadwood, + "Mandragora" => Tactic::Mandragora, _ => Tactic::SimpleMelee, }, AbilitySpec::Tool(tool_kind) => tool_tactic(*tool_kind), @@ -2128,6 +2129,9 @@ impl<'a> AgentData<'a> { Tactic::Deadwood => { self.handle_deadwood(agent, controller, &attack_data, tgt_data, read_data) }, + Tactic::Mandragora => { + self.handle_mandragora(agent, controller, &attack_data, tgt_data, read_data) + }, } } diff --git a/server/src/sys/agent/attack.rs b/server/src/sys/agent/attack.rs index 40cacc2a2f..1e4424f038 100644 --- a/server/src/sys/agent/attack.rs +++ b/server/src/sys/agent/attack.rs @@ -2144,4 +2144,52 @@ impl<'a> AgentData<'a> { self.path_toward_target(agent, controller, tgt_data, read_data, false, false, None); } } + + pub fn handle_mandragora( + &self, + agent: &mut Agent, + controller: &mut Controller, + attack_data: &AttackData, + tgt_data: &TargetData, + read_data: &ReadData, + ) { + const SCREAM_RANGE: f32 = 10.0; + + if !agent.action_state.initialized { + agent.action_state.counter = self.health.map_or(0.0, |h| h.maximum()); + agent.action_state.initialized = true; + } + + if !agent.action_state.condition { + // If mandragora is still "sleeping" and hasn't screamed yet, do nothing until + // target in range or until it's taken damage + if self + .health + .map_or(false, |h| h.current() < agent.action_state.counter) + || attack_data.dist_sqrd < SCREAM_RANGE.powi(2) + { + agent.action_state.condition = true; + controller.push_basic_input(InputKind::Secondary); + } + } else { + // Once mandragora has woken, move towards target and attack + if attack_data.in_min_range() { + controller.push_basic_input(InputKind::Primary); + } else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) + && can_see_tgt( + &read_data.terrain, + self.pos, + tgt_data.pos, + attack_data.dist_sqrd, + ) + { + // If in pathing range and can see target, move towards them + self.path_toward_target(agent, controller, tgt_data, read_data, false, false, None); + } else { + // Otherwise, go back to sleep + agent.action_state.condition = false; + agent.action_state.counter = self.health.map_or(0.0, |h| h.maximum()); + } + } + } } diff --git a/server/src/sys/agent/data.rs b/server/src/sys/agent/data.rs index 7f00020972..57dfbef116 100644 --- a/server/src/sys/agent/data.rs +++ b/server/src/sys/agent/data.rs @@ -115,6 +115,7 @@ pub enum Tactic { Harvester, StoneGolem, Deadwood, + Mandragora, } #[derive(SystemData)]