From 9d31baf500adbe2888c06b2b612d85a1769df9c0 Mon Sep 17 00:00:00 2001 From: James Melkonian Date: Sat, 14 Jan 2023 18:03:18 -0800 Subject: [PATCH] Make bats easier to combat and fix hitbox scaling bug --- CHANGELOG.md | 3 ++ common/src/comp/body.rs | 4 +- common/src/states/utils.rs | 78 ++++++++++++++++++++++---------- server/agent/src/action_nodes.rs | 25 +++++----- server/agent/src/attack.rs | 27 ++++------- server/src/cmd.rs | 2 +- voxygen/src/scene/mod.rs | 13 ++++-- 7 files changed, 88 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da0488b1af..d2e8c80f7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Command to toggle experimental shaders. ### Changed +- Bats move slower and use a simple proportional controller to maintain altitude +- Bats now have less health ### Removed ### Fixed - Doors +- Debug hitboxes now scale with the `Scale` component ## [0.14.0] - 2023-01-07 diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index f3422499c7..262e1e9a40 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -425,7 +425,7 @@ impl Body { bird_medium::Species::Duck => Vec3::new(0.9, 1.0, 1.4), bird_medium::Species::Goose => Vec3::new(1.0, 1.2, 1.5), bird_medium::Species::Peacock => Vec3::new(1.3, 1.1, 1.4), - bird_medium::Species::Bat => Vec3::new(2.0, 2.0, 1.5), + bird_medium::Species::Bat => Vec3::new(4.0, 2.0, 2.0), _ => Vec3::new(2.0, 1.0, 1.5), }, Body::BirdLarge(body) => match body.species { @@ -717,7 +717,7 @@ impl Body { bird_medium::Species::Eagle => 45, bird_medium::Species::Owl => 45, bird_medium::Species::Duck => 10, - bird_medium::Species::Bat => 20, + bird_medium::Species::Bat => 1, _ => 15, }, Body::FishMedium(_) => 15, diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 2729aa2d69..acbc8b6b2b 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -3,14 +3,14 @@ use crate::{ combat, comp::{ ability::{Ability, AbilityInput, AbilityMeta, Capability}, - arthropod, biped_large, biped_small, + arthropod, biped_large, biped_small, bird_medium, character_state::OutputEvents, inventory::slot::{ArmorSlot, EquipSlot, Slot}, - item::{armor::Friction, Hands, ItemKind, ToolKind, tool::AbilityContext}, + item::{armor::Friction, tool::AbilityContext, Hands, ItemKind, ToolKind}, quadruped_low, quadruped_medium, quadruped_small, skills::{Skill, SwimSkill, SKILL_MODIFIERS}, theropod, Body, CharacterAbility, CharacterState, Density, InputAttr, InputKind, - InventoryAction, StateUpdate, Melee, + InventoryAction, Melee, StateUpdate, }, consts::{FRIC_GROUND, GRAVITY, MAX_PICKUP_RANGE}, event::{LocalEvent, ServerEvent}, @@ -250,7 +250,10 @@ impl Body { /// Returns thrust force if the body type can fly, otherwise None pub fn fly_thrust(&self) -> Option { match self { - Body::BirdMedium(_) => Some(GRAVITY * self.mass().0 * 2.0), + Body::BirdMedium(body) => match body.species { + bird_medium::Species::Bat => Some(GRAVITY * self.mass().0 * 0.5), + _ => Some(GRAVITY * self.mass().0 * 2.0), + }, Body::BirdLarge(_) => Some(GRAVITY * self.mass().0 * 0.5), Body::Dragon(_) => Some(200_000.0), Body::Ship(ship) if ship.can_fly() => Some(300_000.0), @@ -1074,7 +1077,11 @@ pub fn handle_dodge_input(data: &JoinData<'_>, update: &mut StateUpdate) -> bool )); if let CharacterState::Roll(roll) = &mut update.character { if let CharacterState::ComboMelee(c) = data.character { - roll.was_combo = c.static_data.ability_info.input.map(|input| (input, c.stage)); + roll.was_combo = c + .static_data + .ability_info + .input + .map(|input| (input, c.stage)); roll.was_wielded = true; } else { if data.character.is_wield() { @@ -1095,10 +1102,7 @@ pub fn handle_dodge_input(data: &JoinData<'_>, update: &mut StateUpdate) -> bool } /// Returns whether an interrupt occurred -pub fn handle_interrupts( - data: &JoinData, - update: &mut StateUpdate, -) -> bool { +pub fn handle_interrupts(data: &JoinData, update: &mut StateUpdate) -> bool { let can_dodge = { let in_buildup = data .character @@ -1106,16 +1110,22 @@ pub fn handle_interrupts( .map_or(true, |stage_section| { matches!(stage_section, StageSection::Buildup) }); - let interruptible = data.character.ability_info().and_then(|info| info.ability_meta).map_or(false, |meta| { - meta.capabilities - .contains(Capability::ROLL_INTERRUPT) - }); + let interruptible = data + .character + .ability_info() + .and_then(|info| info.ability_meta) + .map_or(false, |meta| { + meta.capabilities.contains(Capability::ROLL_INTERRUPT) + }); in_buildup || interruptible }; - let can_block = data.character.ability_info().and_then(|info| info.ability_meta).map_or(false, |meta| { - meta.capabilities - .contains(Capability::BLOCK_INTERRUPT) - }); + let can_block = data + .character + .ability_info() + .and_then(|info| info.ability_meta) + .map_or(false, |meta| { + meta.capabilities.contains(Capability::BLOCK_INTERRUPT) + }); if can_dodge { handle_dodge_input(data, update) } else if can_block { @@ -1348,8 +1358,13 @@ impl AbilityInfo { None }; - // If this ability should not be returned to, check if this ability was going to return to another ability, and return to that one instead - let return_ability = return_ability.or_else(|| char_state.ability_info().and_then(|info| info.return_ability)); + // If this ability should not be returned to, check if this ability was going to + // return to another ability, and return to that one instead + let return_ability = return_ability.or_else(|| { + char_state + .ability_info() + .and_then(|info| info.return_ability) + }); Self { tool: None, @@ -1364,19 +1379,32 @@ impl AbilityInfo { } pub fn end_ability(data: &JoinData<'_>, update: &mut StateUpdate) { - // If an ability has a return ability specified, and is not itself an ability that should be returned to (to prevent bouncing between two abilities), attempt to return to the specified ability, otherwise return to wield or idle depending on whether or not leaving a wield state - let returned = if let Some(return_ability) = (!data.character.should_be_returned_to()).then_some(data.character.ability_info().and_then(|info| info.return_ability)).flatten() { + // If an ability has a return ability specified, and is not itself an ability + // that should be returned to (to prevent bouncing between two abilities), + // attempt to return to the specified ability, otherwise return to wield or idle + // depending on whether or not leaving a wield state + let returned = if let Some(return_ability) = (!data.character.should_be_returned_to()) + .then_some( + data.character + .ability_info() + .and_then(|info| info.return_ability), + ) + .flatten() + { handle_ability(data, update, return_ability) } else { false }; if !returned { if data.character.is_wield() || data.character.was_wielded() { - update.character = - CharacterState::Wielding(wielding::Data { is_sneaking: data.character.is_stealthy() }); + update.character = CharacterState::Wielding(wielding::Data { + is_sneaking: data.character.is_stealthy(), + }); } else { - update.character = - CharacterState::Idle(idle::Data { is_sneaking: data.character.is_stealthy(), footwear: None }); + update.character = CharacterState::Idle(idle::Data { + is_sneaking: data.character.is_stealthy(), + footwear: None, + }); } } } diff --git a/server/agent/src/action_nodes.rs b/server/agent/src/action_nodes.rs index a62622ac91..6ed89c6285 100644 --- a/server/agent/src/action_nodes.rs +++ b/server/agent/src/action_nodes.rs @@ -348,23 +348,20 @@ impl<'a> AgentData<'a> { { // Bats don't like the ground, so make sure they are always flying controller.push_basic_input(InputKind::Fly); - if read_data + // Use a proportional controller with a coefficient of 1.0 to + // maintain altitude + let alt = read_data .terrain - .ray(self.pos.0, self.pos.0 - (Vec3::unit_z() * 5.0)) + .ray(self.pos.0, self.pos.0 - (Vec3::unit_z() * 7.0)) .until(Block::is_solid) .cast() - .1 - .map_or(true, |b| b.is_some()) - { - // Fly up - controller.inputs.move_z = 1.0; - // If on the ground, jump - if self.physics_state.on_ground.is_some() { - controller.push_basic_input(InputKind::Jump); - } - } else { - // Fly down - controller.inputs.move_z = -1.0; + .0; + let set_point = 5.0; + let error = set_point - alt; + controller.inputs.move_z = error; + // If on the ground, jump + if self.physics_state.on_ground.is_some() { + controller.push_basic_input(InputKind::Jump); } } agent.bearing += Vec2::new(rng.gen::() - 0.5, rng.gen::() - 0.5) * 0.1 diff --git a/server/agent/src/attack.rs b/server/agent/src/attack.rs index 67bc3d10bf..e5799d9dbb 100644 --- a/server/agent/src/attack.rs +++ b/server/agent/src/attack.rs @@ -73,31 +73,24 @@ impl<'a> AgentData<'a> { // Always fly! If the floor can't touch you, it can't hurt you... controller.push_basic_input(InputKind::Fly); // Flee from the ground! The internet told me it was lava! - // If on the ground, jump with every last ounce of energy, holding onto all that - // is dear in life and straining for the wide open skies. + // If on the ground, jump with every last ounce of energy, holding onto + // all that is dear in life and straining for the wide open skies. if self.physics_state.on_ground.is_some() { controller.push_basic_input(InputKind::Jump); } else { - // Only fly down if close enough to target in the xy plane - // Otherwise fly towards the target bouncing around a 5 block altitude - let mut maintain_altitude = |altitude| { - if read_data + // Use a proportional controller with a coefficient of 1.0 to + // maintain altidude at the the provided set point + let mut maintain_altitude = |set_point| { + let alt = read_data .terrain - .ray(self.pos.0, self.pos.0 - (Vec3::unit_z() * altitude)) + .ray(self.pos.0, self.pos.0 - (Vec3::unit_z() * 7.0)) .until(Block::is_solid) .cast() - .1 - .map_or(true, |b| b.is_some()) - { - // Fly up - controller.inputs.move_z = 1.0; - } else { - // Fly down - controller.inputs.move_z = -1.0; - } + .0; + let error = set_point - alt; + controller.inputs.move_z = error; }; if (tgt_data.pos.0 - self.pos.0).xy().magnitude_squared() > (5.0_f32).powi(2) { - // If above 5 blocks, fly down maintain_altitude(5.0); } else { maintain_altitude(2.0); diff --git a/server/src/cmd.rs b/server/src/cmd.rs index eae4628a71..b9b81645e3 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -1221,7 +1221,7 @@ fn handle_spawn( pos, comp::Stats::new(get_npc_name(id, npc::BodyType::from_body(body))), comp::SkillSet::default(), - Some(comp::Health::new(body, 1)), + Some(comp::Health::new(body, 0)), comp::Poise::new(body), inventory, body, diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 2054f7b5b6..f906310adf 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -1405,12 +1405,14 @@ impl Scene { let positions = ecs.read_component::(); let colliders = ecs.read_component::(); let orientations = ecs.read_component::(); + let scales = ecs.read_component::(); let groups = ecs.read_component::(); - for (entity, pos, collider, ori, group) in ( + for (entity, pos, collider, ori, scale, group) in ( &ecs.entities(), &positions, &colliders, &orientations, + scales.maybe(), groups.maybe(), ) .join() @@ -1424,12 +1426,13 @@ impl Scene { z_max, } => { current_entities.insert(entity); + let s = scale.map_or(1.0, |sc| sc.0); let shape_id = hitboxes.entry(entity).or_insert_with(|| { self.debug.add_shape(DebugShape::CapsulePrism { - p0: *p0, - p1: *p1, - radius: *radius, - height: *z_max - *z_min, + p0: *p0 * s, + p1: *p1 * s, + radius: *radius * s, + height: (*z_max - *z_min) * s, }) }); let hb_pos = [pos.0.x, pos.0.y, pos.0.z + *z_min, 0.0];