From e861aae8360ce0e2f3ab367ecb90440faf4d0a87 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Thu, 29 Feb 2024 18:40:11 +0200 Subject: [PATCH 1/4] Add gliders to travelers --- assets/common/entity/world/traveler0.ron | 1 + assets/common/entity/world/traveler1.ron | 1 + assets/common/entity/world/traveler2.ron | 1 + assets/common/entity/world/traveler3.ron | 1 + 4 files changed, 4 insertions(+) diff --git a/assets/common/entity/world/traveler0.ron b/assets/common/entity/world/traveler0.ron index 8540b7ab17..6bb0e0b47d 100644 --- a/assets/common/entity/world/traveler0.ron +++ b/assets/common/entity/world/traveler0.ron @@ -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"), diff --git a/assets/common/entity/world/traveler1.ron b/assets/common/entity/world/traveler1.ron index 9f903b5c50..80583ffc58 100644 --- a/assets/common/entity/world/traveler1.ron +++ b/assets/common/entity/world/traveler1.ron @@ -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"), diff --git a/assets/common/entity/world/traveler2.ron b/assets/common/entity/world/traveler2.ron index 6c6d88a32c..42dfc3b404 100644 --- a/assets/common/entity/world/traveler2.ron +++ b/assets/common/entity/world/traveler2.ron @@ -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"), diff --git a/assets/common/entity/world/traveler3.ron b/assets/common/entity/world/traveler3.ron index 3f81160a5d..f3408a870c 100644 --- a/assets/common/entity/world/traveler3.ron +++ b/assets/common/entity/world/traveler3.ron @@ -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"), From 60d47326bd723cf584f3576886de2a2a19b5353e Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Fri, 1 Mar 2024 00:01:17 +0200 Subject: [PATCH 2/4] 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 --- server/agent/src/action_nodes.rs | 31 +++++---- server/src/sys/agent.rs | 7 ++- server/src/sys/agent/behavior_tree.rs | 29 ++++++++- voxygen/src/hud/mod.rs | 9 ++- voxygen/src/session/mod.rs | 91 ++++++++++++++++----------- 5 files changed, 112 insertions(+), 55 deletions(-) diff --git a/server/agent/src/action_nodes.rs b/server/agent/src/action_nodes.rs index 02819ead16..79201e785b 100644 --- a/server/agent/src/action_nodes.rs +++ b/server/agent/src/action_nodes.rs @@ -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); } diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index 9b5a6caf20..42ee585091 100755 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -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(); diff --git a/server/src/sys/agent/behavior_tree.rs b/server/src/sys/agent/behavior_tree.rs index 4ea400893f..a334df19e2 100755 --- a/server/src/sys/agent/behavior_tree.rs +++ b/server/src/sys/agent/behavior_tree.rs @@ -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; } } diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 76e0c9ff16..a5d5ec6a61 100755 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -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() } diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 8a21ee5661..a9c27bf53c 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -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::() .get(entity)? .in_fluid?; - ecs.read_storage::() - .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::().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::().get(entity).cloned(); - let velocity = ecs.read_storage::().get(entity).cloned(); - let ori = ecs.read_storage::().get(entity).cloned(); - let look_dir = self.inputs.look_dir; + let client_entity = client.entity(); + let coordinates = ecs.read_storage::().get(viewpoint_entity).cloned(); + let velocity = ecs.read_storage::().get(viewpoint_entity).cloned(); + let ori = ecs + .read_storage::() + .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::() + .get(viewpoint_entity) + .map(|c| c.inputs.look_dir) + .unwrap_or_default() + }; let in_fluid = ecs .read_storage::() - .get(entity) + .get(viewpoint_entity) .and_then(|state| state.in_fluid); let character_state = ecs .read_storage::() - .get(entity) + .get(viewpoint_entity) .cloned(); DebugInfo { @@ -2265,3 +2256,31 @@ fn find_shortest_distance(arr: &[Option]) -> Option { .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, + dir_right: Vec3, +) -> Option { + 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) +} From 29ca171256d0c9ce8b7c2f91490e94fd0dc3e8af Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Fri, 1 Mar 2024 20:23:23 +0200 Subject: [PATCH 3/4] Spread out /make_npc starting velocities --- server/src/cmd.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 895b683b02..5d49f8b680 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -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); From 3eee002fa3757f398ac20337bacff06743cc22ee Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Sat, 2 Mar 2024 22:24:48 +0200 Subject: [PATCH 4/4] Made behaviour during GlideWield more clean --- server/src/sys/agent/behavior_tree.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/server/src/sys/agent/behavior_tree.rs b/server/src/sys/agent/behavior_tree.rs index a334df19e2..97ec2293e2 100755 --- a/server/src/sys/agent/behavior_tree.rs +++ b/server/src/sys/agent/behavior_tree.rs @@ -192,8 +192,15 @@ fn maintain_if_gliding(bdata: &mut BehaviorData) -> bool { .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); + 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,