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.
|
- Command to toggle experimental shaders.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
- Bats move slower and use a simple proportional controller to maintain altitude
|
||||||
|
- Bats now have less health
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Doors
|
- Doors
|
||||||
|
- Debug hitboxes now scale with the `Scale` component
|
||||||
|
|
||||||
## [0.14.0] - 2023-01-07
|
## [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::Duck => Vec3::new(0.9, 1.0, 1.4),
|
||||||
bird_medium::Species::Goose => Vec3::new(1.0, 1.2, 1.5),
|
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::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),
|
_ => Vec3::new(2.0, 1.0, 1.5),
|
||||||
},
|
},
|
||||||
Body::BirdLarge(body) => match body.species {
|
Body::BirdLarge(body) => match body.species {
|
||||||
@ -717,7 +717,7 @@ impl Body {
|
|||||||
bird_medium::Species::Eagle => 45,
|
bird_medium::Species::Eagle => 45,
|
||||||
bird_medium::Species::Owl => 45,
|
bird_medium::Species::Owl => 45,
|
||||||
bird_medium::Species::Duck => 10,
|
bird_medium::Species::Duck => 10,
|
||||||
bird_medium::Species::Bat => 20,
|
bird_medium::Species::Bat => 1,
|
||||||
_ => 15,
|
_ => 15,
|
||||||
},
|
},
|
||||||
Body::FishMedium(_) => 15,
|
Body::FishMedium(_) => 15,
|
||||||
|
@ -3,14 +3,14 @@ use crate::{
|
|||||||
combat,
|
combat,
|
||||||
comp::{
|
comp::{
|
||||||
ability::{Ability, AbilityInput, AbilityMeta, Capability},
|
ability::{Ability, AbilityInput, AbilityMeta, Capability},
|
||||||
arthropod, biped_large, biped_small,
|
arthropod, biped_large, biped_small, bird_medium,
|
||||||
character_state::OutputEvents,
|
character_state::OutputEvents,
|
||||||
inventory::slot::{ArmorSlot, EquipSlot, Slot},
|
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,
|
quadruped_low, quadruped_medium, quadruped_small,
|
||||||
skills::{Skill, SwimSkill, SKILL_MODIFIERS},
|
skills::{Skill, SwimSkill, SKILL_MODIFIERS},
|
||||||
theropod, Body, CharacterAbility, CharacterState, Density, InputAttr, InputKind,
|
theropod, Body, CharacterAbility, CharacterState, Density, InputAttr, InputKind,
|
||||||
InventoryAction, StateUpdate, Melee,
|
InventoryAction, Melee, StateUpdate,
|
||||||
},
|
},
|
||||||
consts::{FRIC_GROUND, GRAVITY, MAX_PICKUP_RANGE},
|
consts::{FRIC_GROUND, GRAVITY, MAX_PICKUP_RANGE},
|
||||||
event::{LocalEvent, ServerEvent},
|
event::{LocalEvent, ServerEvent},
|
||||||
@ -250,7 +250,10 @@ impl Body {
|
|||||||
/// Returns thrust force if the body type can fly, otherwise None
|
/// Returns thrust force if the body type can fly, otherwise None
|
||||||
pub fn fly_thrust(&self) -> Option<f32> {
|
pub fn fly_thrust(&self) -> Option<f32> {
|
||||||
match self {
|
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::BirdLarge(_) => Some(GRAVITY * self.mass().0 * 0.5),
|
||||||
Body::Dragon(_) => Some(200_000.0),
|
Body::Dragon(_) => Some(200_000.0),
|
||||||
Body::Ship(ship) if ship.can_fly() => Some(300_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::Roll(roll) = &mut update.character {
|
||||||
if let CharacterState::ComboMelee(c) = data.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;
|
roll.was_wielded = true;
|
||||||
} else {
|
} else {
|
||||||
if data.character.is_wield() {
|
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
|
/// Returns whether an interrupt occurred
|
||||||
pub fn handle_interrupts(
|
pub fn handle_interrupts(data: &JoinData, update: &mut StateUpdate) -> bool {
|
||||||
data: &JoinData,
|
|
||||||
update: &mut StateUpdate,
|
|
||||||
) -> bool {
|
|
||||||
let can_dodge = {
|
let can_dodge = {
|
||||||
let in_buildup = data
|
let in_buildup = data
|
||||||
.character
|
.character
|
||||||
@ -1106,15 +1110,21 @@ pub fn handle_interrupts(
|
|||||||
.map_or(true, |stage_section| {
|
.map_or(true, |stage_section| {
|
||||||
matches!(stage_section, StageSection::Buildup)
|
matches!(stage_section, StageSection::Buildup)
|
||||||
});
|
});
|
||||||
let interruptible = data.character.ability_info().and_then(|info| info.ability_meta).map_or(false, |meta| {
|
let interruptible = data
|
||||||
meta.capabilities
|
.character
|
||||||
.contains(Capability::ROLL_INTERRUPT)
|
.ability_info()
|
||||||
|
.and_then(|info| info.ability_meta)
|
||||||
|
.map_or(false, |meta| {
|
||||||
|
meta.capabilities.contains(Capability::ROLL_INTERRUPT)
|
||||||
});
|
});
|
||||||
in_buildup || interruptible
|
in_buildup || interruptible
|
||||||
};
|
};
|
||||||
let can_block = data.character.ability_info().and_then(|info| info.ability_meta).map_or(false, |meta| {
|
let can_block = data
|
||||||
meta.capabilities
|
.character
|
||||||
.contains(Capability::BLOCK_INTERRUPT)
|
.ability_info()
|
||||||
|
.and_then(|info| info.ability_meta)
|
||||||
|
.map_or(false, |meta| {
|
||||||
|
meta.capabilities.contains(Capability::BLOCK_INTERRUPT)
|
||||||
});
|
});
|
||||||
if can_dodge {
|
if can_dodge {
|
||||||
handle_dodge_input(data, update)
|
handle_dodge_input(data, update)
|
||||||
@ -1348,8 +1358,13 @@ impl AbilityInfo {
|
|||||||
None
|
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
|
// If this ability should not be returned to, check if this ability was going to
|
||||||
let return_ability = return_ability.or_else(|| char_state.ability_info().and_then(|info| info.return_ability));
|
// 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 {
|
Self {
|
||||||
tool: None,
|
tool: None,
|
||||||
@ -1364,19 +1379,32 @@ impl AbilityInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn end_ability(data: &JoinData<'_>, update: &mut StateUpdate) {
|
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
|
// If an ability has a return ability specified, and is not itself an ability
|
||||||
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() {
|
// 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)
|
handle_ability(data, update, return_ability)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
if !returned {
|
if !returned {
|
||||||
if data.character.is_wield() || data.character.was_wielded() {
|
if data.character.is_wield() || data.character.was_wielded() {
|
||||||
update.character =
|
update.character = CharacterState::Wielding(wielding::Data {
|
||||||
CharacterState::Wielding(wielding::Data { is_sneaking: data.character.is_stealthy() });
|
is_sneaking: data.character.is_stealthy(),
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
update.character =
|
update.character = CharacterState::Idle(idle::Data {
|
||||||
CharacterState::Idle(idle::Data { is_sneaking: data.character.is_stealthy(), footwear: None });
|
is_sneaking: data.character.is_stealthy(),
|
||||||
|
footwear: None,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -348,24 +348,21 @@ impl<'a> AgentData<'a> {
|
|||||||
{
|
{
|
||||||
// Bats don't like the ground, so make sure they are always flying
|
// Bats don't like the ground, so make sure they are always flying
|
||||||
controller.push_basic_input(InputKind::Fly);
|
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
|
.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)
|
.until(Block::is_solid)
|
||||||
.cast()
|
.cast()
|
||||||
.1
|
.0;
|
||||||
.map_or(true, |b| b.is_some())
|
let set_point = 5.0;
|
||||||
{
|
let error = set_point - alt;
|
||||||
// Fly up
|
controller.inputs.move_z = error;
|
||||||
controller.inputs.move_z = 1.0;
|
|
||||||
// If on the ground, jump
|
// If on the ground, jump
|
||||||
if self.physics_state.on_ground.is_some() {
|
if self.physics_state.on_ground.is_some() {
|
||||||
controller.push_basic_input(InputKind::Jump);
|
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 += Vec2::new(rng.gen::<f32>() - 0.5, rng.gen::<f32>() - 0.5) * 0.1
|
||||||
- agent.bearing * 0.003
|
- agent.bearing * 0.003
|
||||||
|
@ -73,31 +73,24 @@ impl<'a> AgentData<'a> {
|
|||||||
// Always fly! If the floor can't touch you, it can't hurt you...
|
// Always fly! If the floor can't touch you, it can't hurt you...
|
||||||
controller.push_basic_input(InputKind::Fly);
|
controller.push_basic_input(InputKind::Fly);
|
||||||
// Flee from the ground! The internet told me it was lava!
|
// 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
|
// If on the ground, jump with every last ounce of energy, holding onto
|
||||||
// is dear in life and straining for the wide open skies.
|
// all that is dear in life and straining for the wide open skies.
|
||||||
if self.physics_state.on_ground.is_some() {
|
if self.physics_state.on_ground.is_some() {
|
||||||
controller.push_basic_input(InputKind::Jump);
|
controller.push_basic_input(InputKind::Jump);
|
||||||
} else {
|
} else {
|
||||||
// Only fly down if close enough to target in the xy plane
|
// Use a proportional controller with a coefficient of 1.0 to
|
||||||
// Otherwise fly towards the target bouncing around a 5 block altitude
|
// maintain altidude at the the provided set point
|
||||||
let mut maintain_altitude = |altitude| {
|
let mut maintain_altitude = |set_point| {
|
||||||
if read_data
|
let alt = read_data
|
||||||
.terrain
|
.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)
|
.until(Block::is_solid)
|
||||||
.cast()
|
.cast()
|
||||||
.1
|
.0;
|
||||||
.map_or(true, |b| b.is_some())
|
let error = set_point - alt;
|
||||||
{
|
controller.inputs.move_z = error;
|
||||||
// Fly up
|
|
||||||
controller.inputs.move_z = 1.0;
|
|
||||||
} else {
|
|
||||||
// Fly down
|
|
||||||
controller.inputs.move_z = -1.0;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
if (tgt_data.pos.0 - self.pos.0).xy().magnitude_squared() > (5.0_f32).powi(2) {
|
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);
|
maintain_altitude(5.0);
|
||||||
} else {
|
} else {
|
||||||
maintain_altitude(2.0);
|
maintain_altitude(2.0);
|
||||||
|
@ -1221,7 +1221,7 @@ fn handle_spawn(
|
|||||||
pos,
|
pos,
|
||||||
comp::Stats::new(get_npc_name(id, npc::BodyType::from_body(body))),
|
comp::Stats::new(get_npc_name(id, npc::BodyType::from_body(body))),
|
||||||
comp::SkillSet::default(),
|
comp::SkillSet::default(),
|
||||||
Some(comp::Health::new(body, 1)),
|
Some(comp::Health::new(body, 0)),
|
||||||
comp::Poise::new(body),
|
comp::Poise::new(body),
|
||||||
inventory,
|
inventory,
|
||||||
body,
|
body,
|
||||||
|
@ -1405,12 +1405,14 @@ impl Scene {
|
|||||||
let positions = ecs.read_component::<comp::Pos>();
|
let positions = ecs.read_component::<comp::Pos>();
|
||||||
let colliders = ecs.read_component::<comp::Collider>();
|
let colliders = ecs.read_component::<comp::Collider>();
|
||||||
let orientations = ecs.read_component::<comp::Ori>();
|
let orientations = ecs.read_component::<comp::Ori>();
|
||||||
|
let scales = ecs.read_component::<comp::Scale>();
|
||||||
let groups = ecs.read_component::<comp::Group>();
|
let groups = ecs.read_component::<comp::Group>();
|
||||||
for (entity, pos, collider, ori, group) in (
|
for (entity, pos, collider, ori, scale, group) in (
|
||||||
&ecs.entities(),
|
&ecs.entities(),
|
||||||
&positions,
|
&positions,
|
||||||
&colliders,
|
&colliders,
|
||||||
&orientations,
|
&orientations,
|
||||||
|
scales.maybe(),
|
||||||
groups.maybe(),
|
groups.maybe(),
|
||||||
)
|
)
|
||||||
.join()
|
.join()
|
||||||
@ -1424,12 +1426,13 @@ impl Scene {
|
|||||||
z_max,
|
z_max,
|
||||||
} => {
|
} => {
|
||||||
current_entities.insert(entity);
|
current_entities.insert(entity);
|
||||||
|
let s = scale.map_or(1.0, |sc| sc.0);
|
||||||
let shape_id = hitboxes.entry(entity).or_insert_with(|| {
|
let shape_id = hitboxes.entry(entity).or_insert_with(|| {
|
||||||
self.debug.add_shape(DebugShape::CapsulePrism {
|
self.debug.add_shape(DebugShape::CapsulePrism {
|
||||||
p0: *p0,
|
p0: *p0 * s,
|
||||||
p1: *p1,
|
p1: *p1 * s,
|
||||||
radius: *radius,
|
radius: *radius * s,
|
||||||
height: *z_max - *z_min,
|
height: (*z_max - *z_min) * s,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
let hb_pos = [pos.0.x, pos.0.y, pos.0.z + *z_min, 0.0];
|
let hb_pos = [pos.0.x, pos.0.y, pos.0.z + *z_min, 0.0];
|
||||||
|
Loading…
Reference in New Issue
Block a user