mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Make bats easier to combat and fix hitbox scaling bug
This commit is contained in:
parent
696b575611
commit
9d31baf500
@ -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
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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::<f32>() - 0.5, rng.gen::<f32>() - 0.5) * 0.1
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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];
|
||||
|
Loading…
Reference in New Issue
Block a user