Make bats easier to combat and fix hitbox scaling bug

This commit is contained in:
James Melkonian 2023-01-14 18:03:18 -08:00
parent 696b575611
commit 9d31baf500
7 changed files with 88 additions and 64 deletions

View File

@ -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

View File

@ -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,

View File

@ -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<f32> {
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,15 +1110,21 @@ 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)
@ -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,
});
}
}
}

View File

@ -348,24 +348,21 @@ 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;
.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);
}
} else {
// Fly down
controller.inputs.move_z = -1.0;
}
}
agent.bearing += Vec2::new(rng.gen::<f32>() - 0.5, rng.gen::<f32>() - 0.5) * 0.1
- agent.bearing * 0.003

View File

@ -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);

View File

@ -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,

View File

@ -1405,12 +1405,14 @@ impl Scene {
let positions = ecs.read_component::<comp::Pos>();
let colliders = ecs.read_component::<comp::Collider>();
let orientations = ecs.read_component::<comp::Ori>();
let scales = ecs.read_component::<comp::Scale>();
let groups = ecs.read_component::<comp::Group>();
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];