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: Staff, material: Wood, hands: None)),
(1, ModularWeapon(tool: Sceptre, material: Wood, hands: None)), (1, ModularWeapon(tool: Sceptre, material: Wood, hands: None)),
]), None)), ]), None)),
glider: Item("common.items.glider.basic_white"),
)), )),
items: [ items: [
(5, "common.items.consumable.potion_minor"), (5, "common.items.consumable.potion_minor"),

View File

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

View File

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

View File

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

View File

@ -50,24 +50,29 @@ impl<'a> AgentData<'a> {
//////////////////////////////////////// ////////////////////////////////////////
// Action Nodes // Action Nodes
//////////////////////////////////////// ////////////////////////////////////////
pub fn glider_equip(&self, controller: &mut Controller, read_data: &ReadData) {
pub fn glider_fall(&self, controller: &mut Controller, read_data: &ReadData) {
self.dismount(controller, read_data); self.dismount(controller, read_data);
controller.push_action(ControlAction::GlideWield); controller.push_action(ControlAction::GlideWield);
}
let flight_direction = // TODO: add the ability to follow the target?
Vec3::from(self.vel.0.xy().try_normalized().unwrap_or_else(Vec2::zero)); pub fn glider_flight(&self, controller: &mut Controller, _read_data: &ReadData) {
let flight_ori = Quaternion::from_scalar_and_vec3((1.0, flight_direction)); let Some(fluid) = self.physics_state.in_fluid else {
return;
let ori = self.ori.look_vec(); };
let look_dir = if ori.z > 0.0 {
flight_ori.rotated_x(-0.1) let vel = self.vel;
} else {
flight_ori.rotated_x(0.1) 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); 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, scale,
loot, 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 let mut entity_builder = server
.state .state
.create_npc( .create_npc(
@ -760,7 +767,7 @@ fn handle_make_npc(
) )
.with(alignment) .with(alignment)
.with(scale) .with(scale)
.with(comp::Vel(Vec3::new(0.0, 0.0, 0.0))); .with(comp::Vel(vel));
if let Some(agent) = agent { if let Some(agent) = agent {
entity_builder = entity_builder.with(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 // Default to looking in orientation direction
// (can be overridden below) // (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? // probably not only that, do we really need this at all?
controller.reset(); controller.reset();
controller.inputs.look_dir = ori.look_dir(); controller.inputs.look_dir = ori.look_dir();

View File

@ -5,8 +5,8 @@ use common::{
TRADE_INTERACTION_TIME, TRADE_INTERACTION_TIME,
}, },
dialogue::Subject, dialogue::Subject,
Agent, Alignment, BehaviorCapability, BehaviorState, Body, BuffKind, ControlAction, Agent, Alignment, BehaviorCapability, BehaviorState, Body, BuffKind, CharacterState,
ControlEvent, Controller, InputKind, InventoryEvent, Pos, UtteranceKind, ControlAction, ControlEvent, Controller, InputKind, InventoryEvent, Pos, UtteranceKind,
}, },
path::TraversalConfig, path::TraversalConfig,
rtsim::{NpcAction, RtSimEntity}, rtsim::{NpcAction, RtSimEntity},
@ -79,6 +79,7 @@ impl BehaviorTree {
pub fn root() -> Self { pub fn root() -> Self {
Self { Self {
tree: vec![ tree: vec![
maintain_if_gliding,
react_on_dangerous_fall, react_on_dangerous_fall,
react_if_on_fire, react_if_on_fire,
target_if_attacked, 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 /// If falling velocity is critical, throw everything
/// and save yourself! /// and save yourself!
/// ///
@ -200,7 +230,7 @@ fn react_on_dangerous_fall(bdata: &mut BehaviorData) -> bool {
} else if bdata.agent_data.glider_equipped { } else if bdata.agent_data.glider_equipped {
bdata bdata
.agent_data .agent_data
.glider_fall(bdata.controller, bdata.read_data); .glider_equip(bdata.controller, bdata.read_data);
return true; return true;
} }
} }

View File

@ -5417,7 +5417,14 @@ pub fn angle_of_attack_text(
if v_sq.abs() > 0.0001 { if v_sq.abs() > 0.0001 {
let rel_flow_dir = Dir::new(rel_flow / v_sq.sqrt()); let rel_flow_dir = Dir::new(rel_flow / v_sq.sqrt());
let aoe = fluid_dynamics::angle_of_attack(&glider_ori, &rel_flow_dir); 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 { } else {
"Angle of Attack: Not moving".to_owned() "Angle of Attack: Not moving".to_owned()
} }

View File

@ -19,8 +19,8 @@ use common::{
inventory::slot::{EquipSlot, Slot}, inventory::slot::{EquipSlot, Slot},
invite::InviteKind, invite::InviteKind,
item::{tool::ToolKind, ItemDesc}, item::{tool::ToolKind, ItemDesc},
CharacterActivity, ChatType, Content, InputKind, InventoryUpdateEvent, Pos, PresenceKind, CharacterActivity, ChatType, Content, Fluid, InputKind, InventoryUpdateEvent, Pos,
Stats, UtteranceKind, Vel, PresenceKind, Stats, UtteranceKind, Vel,
}, },
consts::MAX_MOUNT_RANGE, consts::MAX_MOUNT_RANGE,
event::UpdateCharacterMetadata, event::UpdateCharacterMetadata,
@ -1331,33 +1331,12 @@ impl PlayState for SessionState {
.read_storage::<comp::PhysicsState>() .read_storage::<comp::PhysicsState>()
.get(entity)? .get(entity)?
.in_fluid?; .in_fluid?;
ecs.read_storage::<Vel>() let vel = *ecs.read_storage::<Vel>().get(entity)?;
.get(entity) let free_look = self.free_look;
.map(|vel| fluid.relative_flow(vel).0) let dir_forward_xy = self.scene.camera().forward_xy();
.map(|rel_flow| { let dir_right = self.scene.camera().right();
let is_wind_downwards =
rel_flow.dot(Vec3::unit_z()).is_sign_negative(); auto_glide(fluid, vel, free_look, dir_forward_xy, dir_right)
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)
}) })
{ {
self.key_state.auto_walk = false; 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 debug_info = global_state.settings.interface.toggle_debug.then(|| {
let client = self.client.borrow(); let client = self.client.borrow();
let ecs = client.state().ecs(); let ecs = client.state().ecs();
let entity = client.entity(); let client_entity = client.entity();
let coordinates = ecs.read_storage::<Pos>().get(entity).cloned(); let coordinates = ecs.read_storage::<Pos>().get(viewpoint_entity).cloned();
let velocity = ecs.read_storage::<Vel>().get(entity).cloned(); let velocity = ecs.read_storage::<Vel>().get(viewpoint_entity).cloned();
let ori = ecs.read_storage::<comp::Ori>().get(entity).cloned(); let ori = ecs
let look_dir = self.inputs.look_dir; .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 let in_fluid = ecs
.read_storage::<comp::PhysicsState>() .read_storage::<comp::PhysicsState>()
.get(entity) .get(viewpoint_entity)
.and_then(|state| state.in_fluid); .and_then(|state| state.in_fluid);
let character_state = ecs let character_state = ecs
.read_storage::<comp::CharacterState>() .read_storage::<comp::CharacterState>()
.get(entity) .get(viewpoint_entity)
.cloned(); .cloned();
DebugInfo { DebugInfo {
@ -2265,3 +2256,31 @@ fn find_shortest_distance(arr: &[Option<f32>]) -> Option<f32> {
.filter_map(|x| *x) .filter_map(|x| *x)
.min_by(|d1, d2| OrderedFloat(*d1).cmp(&OrderedFloat(*d2))) .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)
}