diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index c61081864b..470b747242 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -880,7 +880,7 @@ impl Body { Body::BirdLarge(_) => 50.0, Body::BirdMedium(_) => 40.0, Body::Dragon(_) => 60.0, - Body::Ship(ship) if ship.can_fly() => 60.0, + Body::Ship(ship) => ship.flying_height(), _ => 0.0, } } diff --git a/common/src/comp/body/ship.rs b/common/src/comp/body/ship.rs index 84bedf43aa..2eac0c4e1b 100644 --- a/common/src/comp/body/ship.rs +++ b/common/src/comp/body/ship.rs @@ -110,6 +110,8 @@ impl Body { matches!(self, Body::DefaultAirship | Body::AirBalloon | Body::Volume) } + pub fn flying_height(&self) -> f32 { if self.can_fly() { 60.0 } else { 0.0 } } + pub fn has_water_thrust(&self) -> bool { !self.can_fly() // TODO: Differentiate this more carefully } diff --git a/common/src/path.rs b/common/src/path.rs index a6dc55a5c4..4f5237a01b 100644 --- a/common/src/path.rs +++ b/common/src/path.rs @@ -466,7 +466,9 @@ impl Chaser { /*if traversal_cfg.can_fly { Some(((tgt - pos) , 1.0)) } else */ - if !walking_towards_edge || traversal_cfg.can_fly { + if traversal_cfg.can_fly { + Some(((tgt - pos) * Vec3::new(1.0, 1.0, 0.5), 1.0)) + } else if !walking_towards_edge { Some(((tgt - pos) * Vec3::new(1.0, 1.0, 0.0), 1.0)) } else { // This is unfortunately where an NPC will stare blankly diff --git a/rtsim/src/data/npc.rs b/rtsim/src/data/npc.rs index 8f2897f8cd..0709085a60 100644 --- a/rtsim/src/data/npc.rs +++ b/rtsim/src/data/npc.rs @@ -295,10 +295,10 @@ impl Vehicle { /// Max speed in block/s pub fn get_speed(&self) -> f32 { match self.body { - comp::ship::Body::DefaultAirship => 15.0, - comp::ship::Body::AirBalloon => 16.0, - comp::ship::Body::SailBoat => 12.0, - comp::ship::Body::Galleon => 13.0, + comp::ship::Body::DefaultAirship => 7.0, + comp::ship::Body::AirBalloon => 8.0, + comp::ship::Body::SailBoat => 5.0, + comp::ship::Body::Galleon => 6.0, _ => 10.0, } } diff --git a/rtsim/src/rule/npc_ai.rs b/rtsim/src/rule/npc_ai.rs index a487bf5b6f..dff731534a 100644 --- a/rtsim/src/rule/npc_ai.rs +++ b/rtsim/src/rule/npc_ai.rs @@ -95,13 +95,14 @@ fn path_in_site(start: Vec2, end: Vec2, site: &site2::Site) -> PathRes distance * terrain + building }; - astar.poll( - 1000, - heuristic, - |&tile| CARDINALS.iter().map(move |c| tile + *c), - transition, - |tile| *tile == end || site.tiles.get_known(*tile).is_none(), - ) + let neighbors = |tile: &Vec2| { + let tile = *tile; + CARDINALS.iter().map(move |c| tile + *c) + }; + + astar.poll(1000, heuristic, neighbors, transition, |tile| { + *tile == end || site.tiles.get_known(*tile).is_none() + }) } fn path_between_sites( @@ -276,7 +277,7 @@ impl Rule for NpcAi { fn idle() -> impl Action { just(|ctx| ctx.controller.do_idle()).debug(|| "idle") } /// Try to walk toward a 3D position without caring for obstacles. -fn goto(wpos: Vec3, speed_factor: f32, goal_dist: f32) -> impl Action { +fn goto(wpos: Vec3, speed_factor: f32, goal_dist: f32, height_offset: f32) -> impl Action { const STEP_DIST: f32 = 24.0; const WAYPOINT_DIST: f32 = 12.0; @@ -301,6 +302,7 @@ fn goto(wpos: Vec3, speed_factor: f32, goal_dist: f32) -> impl Action { ctx.world .sim() .get_surface_alt_approx(wpos.xy().as_()) + .map(|alt| alt + height_offset) .unwrap_or(wpos.z), ) }); @@ -315,14 +317,20 @@ fn goto(wpos: Vec3, speed_factor: f32, goal_dist: f32) -> impl Action { /// Try to walk toward a 2D position on the terrain without caring for /// obstacles. -fn goto_2d(wpos2d: Vec2, speed_factor: f32, goal_dist: f32) -> impl Action { +fn goto_2d( + wpos2d: Vec2, + speed_factor: f32, + goal_dist: f32, + height_offset: f32, +) -> impl Action { now(move |ctx| { - let wpos = wpos2d.with_z(ctx.world.sim().get_alt_approx(wpos2d.as_()).unwrap_or(0.0)); - goto(wpos, speed_factor, goal_dist) + let wpos = wpos2d + .with_z(ctx.world.sim().get_alt_approx(wpos2d.as_()).unwrap_or(0.0) + height_offset); + goto(wpos, speed_factor, goal_dist, height_offset) }) } -fn traverse_points(mut next_point: F, speed_factor: f32) -> impl Action +fn traverse_points(mut next_point: F, speed_factor: f32, height_offset: f32) -> impl Action where F: FnMut(&mut NpcCtx) -> Option> + Send + Sync + 'static, { @@ -345,17 +353,26 @@ where if let Some(path) = path_site(wpos, site_exit, site, ctx.index) { Some(Either::Left( - seq(path.into_iter().map(|wpos| goto_2d(wpos, 1.0, 8.0))).then(goto_2d( - site_exit, - speed_factor, - 8.0, - )), + seq(path + .into_iter() + .map(move |wpos| goto_2d(wpos, 1.0, 8.0, height_offset))) + .then(goto_2d(site_exit, speed_factor, 8.0, height_offset)), )) } else { - Some(Either::Right(goto_2d(site_exit, speed_factor, 8.0))) + Some(Either::Right(goto_2d( + site_exit, + speed_factor, + 8.0, + height_offset, + ))) } } else { - Some(Either::Right(goto_2d(wpos, speed_factor, 8.0))) + Some(Either::Right(goto_2d( + wpos, + speed_factor, + 8.0, + height_offset, + ))) } }) } @@ -368,7 +385,7 @@ fn travel_to_point(wpos: Vec2, speed_factor: f32) -> impl Action { let diff = wpos - start; let n = (diff.magnitude() / WAYPOINT).max(1.0); let mut points = (1..n as usize + 1).map(move |i| start + diff * (i as f32 / n)); - traverse_points(move |_| points.next(), speed_factor) + traverse_points(move |_| points.next(), speed_factor, 0.0) }) .debug(|| "travel to point") } @@ -414,7 +431,7 @@ fn travel_to_site(tgt_site: SiteId, speed_factor: f32) -> impl Action { } else { None } - }, speed_factor) + }, speed_factor, 0.0) .boxed() // For every track in the path we discovered between the sites... @@ -821,53 +838,9 @@ fn follow(npc: NpcId, distance: f32) -> impl Action { } */ -fn chunk_path( - from: Vec2, - to: Vec2, - chunk_height: impl Fn(Vec2) -> Option, -) -> Box { - let heuristics = - |(p, _): &(Vec2, i32), _: &(Vec2, i32)| p.distance_squared(to) as f32; - let start = (from, chunk_height(from).unwrap()); - let mut astar = Astar::new(1000, start, BuildHasherDefault::::default()); - - let path = astar.poll( - 1000, - heuristics, - |&(p, _)| { - NEIGHBORS - .into_iter() - .map(move |n| p + n) - .filter_map(|p| Some((p, chunk_height(p)?))) - }, - |(p0, h0), (p1, h1)| { - let diff = (p0 - p1).as_().cpos_to_wpos().with_z((h0 - h1) as f32); - - diff.magnitude_squared() - }, - |(e, _)| *e == to, - ); - let path = match path { - PathResult::Exhausted(p) | PathResult::Path(p) => p, - _ => return finish().boxed(), - }; - let len = path.len(); - seq(path - .into_iter() - .enumerate() - .map(move |(i, (chunk_pos, height))| { - let wpos = TerrainChunkSize::center_wpos(chunk_pos) - .with_z(height) - .as_(); - goto(wpos, 1.0, 5.0) - .debug(move || format!("chunk path {i}/{len} chunk: {chunk_pos}, height: {height}")) - })) - .boxed() -} - -fn pilot() -> impl Action { +fn pilot(ship: common::comp::ship::Body) -> impl Action { // Travel between different towns in a straight line - now(|ctx| { + now(move |ctx| { let data = &*ctx.state.data(); let site = data .sites @@ -880,16 +853,9 @@ fn pilot() -> impl Action { }) .choose(&mut ctx.rng); if let Some((_id, site)) = site { - let start_chunk = ctx.npc.wpos.xy().as_().wpos_to_cpos(); - let end_chunk = site.wpos.wpos_to_cpos(); - chunk_path(start_chunk, end_chunk, |chunk| { - ctx.world - .sim() - .get_alt_approx(TerrainChunkSize::center_wpos(chunk)) - .map(|f| (f + 150.0) as i32) - }) + Either::Right(goto_2d(site.wpos.as_(), 1.0, 20.0, ship.flying_height())) } else { - finish().boxed() + Either::Left(finish()) } }) .repeat() @@ -918,7 +884,7 @@ fn captain() -> impl Action { .get_interpolated(wpos, |chunk| chunk.water_alt) .unwrap_or(0.0), ); - goto(wpos, 0.7, 5.0).boxed() + goto(wpos, 0.7, 5.0, 0.0).boxed() } else { idle().boxed() } @@ -990,7 +956,7 @@ fn humanoid() -> impl Action { if let Some(vehicle) = ctx.state.data().npcs.vehicles.get(riding.vehicle) { match vehicle.body { common::comp::ship::Body::DefaultAirship - | common::comp::ship::Body::AirBalloon => important(pilot()), + | common::comp::ship::Body::AirBalloon => important(pilot(vehicle.body)), common::comp::ship::Body::SailBoat | common::comp::ship::Body::Galleon => { important(captain()) }, @@ -1036,31 +1002,21 @@ fn bird_large() -> impl Action { }) .choose(&mut ctx.rng) { - casual(goto( - site.wpos.as_::().with_z( - ctx.world - .sim() - .get_surface_alt_approx(site.wpos) - .unwrap_or(0.0) - + ctx.npc.body.flying_height(), - ), + casual(goto_2d( + site.wpos.as_::(), 1.0, 20.0, + ctx.npc.body.flying_height(), )) } else { casual(idle()) } } else if let Some(site) = data.sites.get(home) { - casual(goto( - site.wpos.as_::().with_z( - ctx.world - .sim() - .get_surface_alt_approx(site.wpos) - .unwrap_or(0.0) - + ctx.npc.body.flying_height(), - ), + casual(goto_2d( + site.wpos.as_::(), 1.0, 20.0, + ctx.npc.body.flying_height(), )) } else { casual(idle()) diff --git a/rtsim/src/rule/simulate_npcs.rs b/rtsim/src/rule/simulate_npcs.rs index f319b67985..a1531e979c 100644 --- a/rtsim/src/rule/simulate_npcs.rs +++ b/rtsim/src/rule/simulate_npcs.rs @@ -166,12 +166,11 @@ fn on_tick(ctx: EventCtx) { let dist2 = diff.magnitude_squared(); if dist2 > 0.5f32.powi(2) { - let mut wpos = vehicle.wpos - + (diff - * (vehicle.get_speed() * speed_factor * ctx.event.dt - / dist2.sqrt()) - .min(1.0)) - .with_z(0.0); + let wpos = vehicle.wpos + + (diff + * (vehicle.get_speed() * speed_factor * ctx.event.dt + / dist2.sqrt()) + .min(1.0)); let is_valid = match vehicle.body { common::comp::ship::Body::DefaultAirship @@ -188,31 +187,6 @@ fn on_tick(ctx: EventCtx) { }; if is_valid { - match vehicle.body { - common::comp::ship::Body::DefaultAirship - | common::comp::ship::Body::AirBalloon => { - if let Some(alt) = ctx - .world - .sim() - .get_alt_approx(wpos.xy().as_()) - .filter(|alt| wpos.z < *alt) - { - wpos.z = alt; - } - }, - common::comp::ship::Body::SailBoat - | common::comp::ship::Body::Galleon => { - wpos.z = ctx - .world - .sim() - .get_interpolated( - wpos.xy().map(|e| e as i32), - |chunk| chunk.water_alt, - ) - .unwrap_or(0.0); - }, - _ => {}, - } vehicle.wpos = wpos; } } @@ -226,6 +200,12 @@ fn on_tick(ctx: EventCtx) { ) => {}, None => {}, } + vehicle.wpos.z = ctx + .world + .sim() + .get_surface_alt_approx(npc.wpos.xy().map(|e| e as i32)) + .unwrap_or(0.0) + + vehicle.body.flying_height(); npc.wpos = vehicle.wpos; } else { // Vehicle doens't exist anymore @@ -264,7 +244,6 @@ fn on_tick(ctx: EventCtx) { NpcAction::Attack(_) => {}, // TODO: Implement simulated combat } } - // Make sure NPCs remain on the surface npc.wpos.z = ctx .world diff --git a/server/agent/src/action_nodes.rs b/server/agent/src/action_nodes.rs index af815f59ad..55377a5cf2 100644 --- a/server/agent/src/action_nodes.rs +++ b/server/agent/src/action_nodes.rs @@ -247,7 +247,7 @@ impl<'a> AgentData<'a> { self.vel.0, chase_tgt, TraversalConfig { - min_tgt_dist: 1.25, + min_tgt_dist: self.traversal_config.min_tgt_dist * 1.25, ..self.traversal_config }, ) { diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index 5151fbea23..b7ccafc870 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -107,6 +107,10 @@ impl<'a> System<'a> for Sys { .unwrap_or(entity); let moving_body = read_data.bodies.get(moving_entity); + let physics_state = read_data + .physics_states + .get(moving_entity) + .unwrap_or(physics_state); // Hack, replace with better system when groups are more sophisticated // Override alignment if in a group unless entity is owned already @@ -136,7 +140,10 @@ impl<'a> System<'a> for Sys { controller.inputs.look_dir = ori.look_dir(); } - let scale = read_data.scales.get(entity).map_or(1.0, |Scale(s)| *s); + let scale = read_data + .scales + .get(moving_entity) + .map_or(1.0, |Scale(s)| *s); let glider_equipped = inventory .equipped(EquipSlot::Glider) @@ -180,7 +187,7 @@ impl<'a> System<'a> for Sys { slow_factor, on_ground: physics_state.on_ground.is_some(), in_liquid: physics_state.in_liquid().is_some(), - min_tgt_dist: 1.0, + min_tgt_dist: scale * moving_body.map_or(1.0, |body| body.max_radius()), can_climb: moving_body.map_or(false, Body::can_climb), can_fly: moving_body.map_or(false, |b| b.fly_thrust().is_some()), };