Implement reliable gliding AI

- Wield glider when falling, but do nothing else
- Safe auto glide when in gliding state
- Agent unwield glider if on ground
This commit is contained in:
juliancoffee 2024-03-01 00:01:17 +02:00
parent e861aae836
commit 60d47326bd
5 changed files with 112 additions and 55 deletions

View File

@ -50,24 +50,29 @@ impl<'a> AgentData<'a> {
////////////////////////////////////////
// Action Nodes
////////////////////////////////////////
pub fn glider_fall(&self, controller: &mut Controller, read_data: &ReadData) {
pub fn glider_equip(&self, controller: &mut Controller, read_data: &ReadData) {
self.dismount(controller, read_data);
controller.push_action(ControlAction::GlideWield);
}
let flight_direction =
Vec3::from(self.vel.0.xy().try_normalized().unwrap_or_else(Vec2::zero));
let flight_ori = Quaternion::from_scalar_and_vec3((1.0, flight_direction));
let ori = self.ori.look_vec();
let look_dir = if ori.z > 0.0 {
flight_ori.rotated_x(-0.1)
} else {
flight_ori.rotated_x(0.1)
// TODO: add the ability to follow the target?
pub fn glider_flight(&self, controller: &mut Controller, _read_data: &ReadData) {
let Some(fluid) = self.physics_state.in_fluid else {
return;
};
let vel = self.vel;
let comp::Vel(rel_flow) = fluid.relative_flow(vel);
let is_wind_downwards = rel_flow.z.is_sign_negative();
let look_dir = if is_wind_downwards {
Vec3::from(-rel_flow.xy())
} else {
-rel_flow
};
let (_, look_dir) = look_dir.into_scalar_and_vec3();
controller.inputs.look_dir = Dir::from_unnormalized(look_dir).unwrap_or_else(Dir::forward);
}

View File

@ -137,11 +137,14 @@ impl<'a> System<'a> for Sys {
)
};
if !matches!(char_state, CharacterState::LeapMelee(_)) {
if !matches!(
char_state,
CharacterState::LeapMelee(_) | CharacterState::Glide(_)
) {
// Default to looking in orientation direction
// (can be overridden below)
//
// This definitely breaks LeapMelee and
// This definitely breaks LeapMelee, Glide and
// probably not only that, do we really need this at all?
controller.reset();
controller.inputs.look_dir = ori.look_dir();

View File

@ -5,8 +5,8 @@ use common::{
TRADE_INTERACTION_TIME,
},
dialogue::Subject,
Agent, Alignment, BehaviorCapability, BehaviorState, Body, BuffKind, ControlAction,
ControlEvent, Controller, InputKind, InventoryEvent, Pos, UtteranceKind,
Agent, Alignment, BehaviorCapability, BehaviorState, Body, BuffKind, CharacterState,
ControlAction, ControlEvent, Controller, InputKind, InventoryEvent, Pos, UtteranceKind,
},
path::TraversalConfig,
rtsim::{NpcAction, RtSimEntity},
@ -79,6 +79,7 @@ impl BehaviorTree {
pub fn root() -> Self {
Self {
tree: vec![
maintain_if_gliding,
react_on_dangerous_fall,
react_if_on_fire,
target_if_attacked,
@ -177,6 +178,28 @@ impl BehaviorTree {
}
}
/// If in gliding, properly maintain it
/// If on ground, unwield glider
fn maintain_if_gliding(bdata: &mut BehaviorData) -> bool {
let Some(char_state) = bdata.read_data.char_states.get(*bdata.agent_data.entity) else {
return false;
};
match char_state {
CharacterState::Glide(_) => {
bdata
.agent_data
.glider_flight(bdata.controller, bdata.read_data);
true
},
CharacterState::GlideWield(_) if bdata.agent_data.physics_state.on_ground.is_some() => {
bdata.controller.push_action(ControlAction::Unwield);
true
},
_ => false,
}
}
/// If falling velocity is critical, throw everything
/// and save yourself!
///
@ -200,7 +223,7 @@ fn react_on_dangerous_fall(bdata: &mut BehaviorData) -> bool {
} else if bdata.agent_data.glider_equipped {
bdata
.agent_data
.glider_fall(bdata.controller, bdata.read_data);
.glider_equip(bdata.controller, bdata.read_data);
return true;
}
}

View File

@ -5417,7 +5417,14 @@ pub fn angle_of_attack_text(
if v_sq.abs() > 0.0001 {
let rel_flow_dir = Dir::new(rel_flow / v_sq.sqrt());
let aoe = fluid_dynamics::angle_of_attack(&glider_ori, &rel_flow_dir);
format!("Angle of Attack: {:.1}", aoe.to_degrees())
let (rel_x, rel_y, rel_z) = (rel_flow.x, rel_flow.y, rel_flow.z);
format!(
"Angle of Attack: {:.1} ({:.1},{:.1},{:.1})",
aoe.to_degrees(),
rel_x,
rel_y,
rel_z
)
} else {
"Angle of Attack: Not moving".to_owned()
}

View File

@ -19,8 +19,8 @@ use common::{
inventory::slot::{EquipSlot, Slot},
invite::InviteKind,
item::{tool::ToolKind, ItemDesc},
CharacterActivity, ChatType, Content, InputKind, InventoryUpdateEvent, Pos, PresenceKind,
Stats, UtteranceKind, Vel,
CharacterActivity, ChatType, Content, Fluid, InputKind, InventoryUpdateEvent, Pos,
PresenceKind, Stats, UtteranceKind, Vel,
},
consts::MAX_MOUNT_RANGE,
event::UpdateCharacterMetadata,
@ -1331,33 +1331,12 @@ impl PlayState for SessionState {
.read_storage::<comp::PhysicsState>()
.get(entity)?
.in_fluid?;
ecs.read_storage::<Vel>()
.get(entity)
.map(|vel| fluid.relative_flow(vel).0)
.map(|rel_flow| {
let is_wind_downwards =
rel_flow.dot(Vec3::unit_z()).is_sign_negative();
if !self.free_look {
if is_wind_downwards {
self.scene.camera().forward_xy().into()
} else {
let windwards = rel_flow
* self
.scene
.camera()
.forward_xy()
.dot(rel_flow.xy())
.signum();
Plane::from(Dir::new(self.scene.camera().right()))
.projection(windwards)
}
} else if is_wind_downwards {
Vec3::from(-rel_flow.xy())
} else {
-rel_flow
}
})
.and_then(Dir::from_unnormalized)
let vel = *ecs.read_storage::<Vel>().get(entity)?;
let free_look = self.free_look;
let dir_forward_xy = self.scene.camera().forward_xy();
let dir_right = self.scene.camera().right();
auto_glide(fluid, vel, free_look, dir_forward_xy, dir_right)
})
{
self.key_state.auto_walk = false;
@ -1567,18 +1546,30 @@ impl PlayState for SessionState {
let debug_info = global_state.settings.interface.toggle_debug.then(|| {
let client = self.client.borrow();
let ecs = client.state().ecs();
let entity = client.entity();
let coordinates = ecs.read_storage::<Pos>().get(entity).cloned();
let velocity = ecs.read_storage::<Vel>().get(entity).cloned();
let ori = ecs.read_storage::<comp::Ori>().get(entity).cloned();
let look_dir = self.inputs.look_dir;
let client_entity = client.entity();
let coordinates = ecs.read_storage::<Pos>().get(viewpoint_entity).cloned();
let velocity = ecs.read_storage::<Vel>().get(viewpoint_entity).cloned();
let ori = ecs
.read_storage::<comp::Ori>()
.get(viewpoint_entity)
.cloned();
// NOTE: at the time of writing, it will always output default
// look_dir in Specate mode, because Controller isn't synced
let look_dir = if viewpoint_entity == client_entity {
self.inputs.look_dir
} else {
ecs.read_storage::<comp::Controller>()
.get(viewpoint_entity)
.map(|c| c.inputs.look_dir)
.unwrap_or_default()
};
let in_fluid = ecs
.read_storage::<comp::PhysicsState>()
.get(entity)
.get(viewpoint_entity)
.and_then(|state| state.in_fluid);
let character_state = ecs
.read_storage::<comp::CharacterState>()
.get(entity)
.get(viewpoint_entity)
.cloned();
DebugInfo {
@ -2265,3 +2256,31 @@ fn find_shortest_distance(arr: &[Option<f32>]) -> Option<f32> {
.filter_map(|x| *x)
.min_by(|d1, d2| OrderedFloat(*d1).cmp(&OrderedFloat(*d2)))
}
// TODO: Can probably be exported in some way for AI, somehow
fn auto_glide(
fluid: Fluid,
vel: Vel,
free_look: bool,
dir_forward_xy: Vec2<f32>,
dir_right: Vec3<f32>,
) -> Option<Dir> {
let Vel(rel_flow) = fluid.relative_flow(&vel);
let is_wind_downwards = rel_flow.z.is_sign_negative();
let dir = if free_look {
if is_wind_downwards {
Vec3::from(-rel_flow.xy())
} else {
-rel_flow
}
} else if is_wind_downwards {
dir_forward_xy.into()
} else {
let windwards = rel_flow * dir_forward_xy.dot(rel_flow.xy()).signum();
Plane::from(Dir::new(dir_right)).projection(windwards)
};
Dir::from_unnormalized(dir)
}