Merge branch 'juliancoffee/gliding-travelers' into 'master'

Better Gliding for NPCs

See merge request veloren/veloren!4348
This commit is contained in:
Illia Denysenko 2024-03-02 23:27:16 +00:00
commit ef5a9663ba
10 changed files with 131 additions and 56 deletions

View File

@ -19,6 +19,7 @@
(1, ModularWeapon(tool: Staff, material: Wood, hands: None)),
(1, ModularWeapon(tool: Sceptre, material: Wood, hands: None)),
]), None)),
glider: Item("common.items.glider.basic_white"),
)),
items: [
(5, "common.items.consumable.potion_minor"),

View File

@ -19,6 +19,7 @@
(1, ModularWeapon(tool: Staff, material: Bamboo, hands: None)),
(1, ModularWeapon(tool: Sceptre, material: Bamboo, hands: None)),
]), None)),
glider: Item("common.items.glider.leaves"),
)),
items: [
(25, "common.items.consumable.potion_minor"),

View File

@ -30,6 +30,7 @@
(1, ModularWeapon(tool: Staff, material: Ironwood, hands: None)),
(1, ModularWeapon(tool: Sceptre, material: Ironwood, hands: None)),
]), None)),
glider: Item("common.items.glider.butterfly3"),
)),
items: [
(50, "common.items.consumable.potion_med"),

View File

@ -30,6 +30,7 @@
(2, Item("common.items.weapons.staff.laevateinn")),
(1, Item("common.items.weapons.sceptre.caduceus")),
]), None)),
glider: Item("common.items.glider.sunset"),
)),
items: [
(50, "common.items.consumable.potion_big"),

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

@ -746,6 +746,13 @@ fn handle_make_npc(
scale,
loot,
} => {
// Spread about spawned npcs
let vel = Vec3::new(
thread_rng().gen_range(-2.0..3.0),
thread_rng().gen_range(-2.0..3.0),
10.0,
);
let mut entity_builder = server
.state
.create_npc(
@ -760,7 +767,7 @@ fn handle_make_npc(
)
.with(alignment)
.with(scale)
.with(comp::Vel(Vec3::new(0.0, 0.0, 0.0)));
.with(comp::Vel(vel));
if let Some(agent) = agent {
entity_builder = entity_builder.with(agent);

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,35 @@ 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);
}
// Always stop execution if during GlideWield.
// - If on ground, the line above will unwield the glider on next
// tick
// - If in air, we probably wouldn't want to do anything anyway, as
// character state code will shift itself to glide on next tick
true
},
_ => false,
}
}
/// If falling velocity is critical, throw everything
/// and save yourself!
///
@ -200,7 +230,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)
}