mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
somewhat fix airships
This commit is contained in:
parent
9c30d2018d
commit
d1f6e6bef6
@ -880,7 +880,7 @@ impl Body {
|
|||||||
Body::BirdLarge(_) => 50.0,
|
Body::BirdLarge(_) => 50.0,
|
||||||
Body::BirdMedium(_) => 40.0,
|
Body::BirdMedium(_) => 40.0,
|
||||||
Body::Dragon(_) => 60.0,
|
Body::Dragon(_) => 60.0,
|
||||||
Body::Ship(ship) if ship.can_fly() => 60.0,
|
Body::Ship(ship) => ship.flying_height(),
|
||||||
_ => 0.0,
|
_ => 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,6 +110,8 @@ impl Body {
|
|||||||
matches!(self, Body::DefaultAirship | Body::AirBalloon | Body::Volume)
|
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 {
|
pub fn has_water_thrust(&self) -> bool {
|
||||||
!self.can_fly() // TODO: Differentiate this more carefully
|
!self.can_fly() // TODO: Differentiate this more carefully
|
||||||
}
|
}
|
||||||
|
@ -466,7 +466,9 @@ impl Chaser {
|
|||||||
/*if traversal_cfg.can_fly {
|
/*if traversal_cfg.can_fly {
|
||||||
Some(((tgt - pos) , 1.0))
|
Some(((tgt - pos) , 1.0))
|
||||||
} else */
|
} 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))
|
Some(((tgt - pos) * Vec3::new(1.0, 1.0, 0.0), 1.0))
|
||||||
} else {
|
} else {
|
||||||
// This is unfortunately where an NPC will stare blankly
|
// This is unfortunately where an NPC will stare blankly
|
||||||
|
@ -295,10 +295,10 @@ impl Vehicle {
|
|||||||
/// Max speed in block/s
|
/// Max speed in block/s
|
||||||
pub fn get_speed(&self) -> f32 {
|
pub fn get_speed(&self) -> f32 {
|
||||||
match self.body {
|
match self.body {
|
||||||
comp::ship::Body::DefaultAirship => 15.0,
|
comp::ship::Body::DefaultAirship => 7.0,
|
||||||
comp::ship::Body::AirBalloon => 16.0,
|
comp::ship::Body::AirBalloon => 8.0,
|
||||||
comp::ship::Body::SailBoat => 12.0,
|
comp::ship::Body::SailBoat => 5.0,
|
||||||
comp::ship::Body::Galleon => 13.0,
|
comp::ship::Body::Galleon => 6.0,
|
||||||
_ => 10.0,
|
_ => 10.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,13 +95,14 @@ fn path_in_site(start: Vec2<i32>, end: Vec2<i32>, site: &site2::Site) -> PathRes
|
|||||||
distance * terrain + building
|
distance * terrain + building
|
||||||
};
|
};
|
||||||
|
|
||||||
astar.poll(
|
let neighbors = |tile: &Vec2<i32>| {
|
||||||
1000,
|
let tile = *tile;
|
||||||
heuristic,
|
CARDINALS.iter().map(move |c| tile + *c)
|
||||||
|&tile| CARDINALS.iter().map(move |c| tile + *c),
|
};
|
||||||
transition,
|
|
||||||
|tile| *tile == end || site.tiles.get_known(*tile).is_none(),
|
astar.poll(1000, heuristic, neighbors, transition, |tile| {
|
||||||
)
|
*tile == end || site.tiles.get_known(*tile).is_none()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_between_sites(
|
fn path_between_sites(
|
||||||
@ -276,7 +277,7 @@ impl Rule for NpcAi {
|
|||||||
fn idle() -> impl Action { just(|ctx| ctx.controller.do_idle()).debug(|| "idle") }
|
fn idle() -> impl Action { just(|ctx| ctx.controller.do_idle()).debug(|| "idle") }
|
||||||
|
|
||||||
/// Try to walk toward a 3D position without caring for obstacles.
|
/// Try to walk toward a 3D position without caring for obstacles.
|
||||||
fn goto(wpos: Vec3<f32>, speed_factor: f32, goal_dist: f32) -> impl Action {
|
fn goto(wpos: Vec3<f32>, speed_factor: f32, goal_dist: f32, height_offset: f32) -> impl Action {
|
||||||
const STEP_DIST: f32 = 24.0;
|
const STEP_DIST: f32 = 24.0;
|
||||||
const WAYPOINT_DIST: f32 = 12.0;
|
const WAYPOINT_DIST: f32 = 12.0;
|
||||||
|
|
||||||
@ -301,6 +302,7 @@ fn goto(wpos: Vec3<f32>, speed_factor: f32, goal_dist: f32) -> impl Action {
|
|||||||
ctx.world
|
ctx.world
|
||||||
.sim()
|
.sim()
|
||||||
.get_surface_alt_approx(wpos.xy().as_())
|
.get_surface_alt_approx(wpos.xy().as_())
|
||||||
|
.map(|alt| alt + height_offset)
|
||||||
.unwrap_or(wpos.z),
|
.unwrap_or(wpos.z),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@ -315,14 +317,20 @@ fn goto(wpos: Vec3<f32>, speed_factor: f32, goal_dist: f32) -> impl Action {
|
|||||||
|
|
||||||
/// Try to walk toward a 2D position on the terrain without caring for
|
/// Try to walk toward a 2D position on the terrain without caring for
|
||||||
/// obstacles.
|
/// obstacles.
|
||||||
fn goto_2d(wpos2d: Vec2<f32>, speed_factor: f32, goal_dist: f32) -> impl Action {
|
fn goto_2d(
|
||||||
|
wpos2d: Vec2<f32>,
|
||||||
|
speed_factor: f32,
|
||||||
|
goal_dist: f32,
|
||||||
|
height_offset: f32,
|
||||||
|
) -> impl Action {
|
||||||
now(move |ctx| {
|
now(move |ctx| {
|
||||||
let wpos = wpos2d.with_z(ctx.world.sim().get_alt_approx(wpos2d.as_()).unwrap_or(0.0));
|
let wpos = wpos2d
|
||||||
goto(wpos, speed_factor, goal_dist)
|
.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<F>(mut next_point: F, speed_factor: f32) -> impl Action
|
fn traverse_points<F>(mut next_point: F, speed_factor: f32, height_offset: f32) -> impl Action
|
||||||
where
|
where
|
||||||
F: FnMut(&mut NpcCtx) -> Option<Vec2<f32>> + Send + Sync + 'static,
|
F: FnMut(&mut NpcCtx) -> Option<Vec2<f32>> + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
@ -345,17 +353,26 @@ where
|
|||||||
|
|
||||||
if let Some(path) = path_site(wpos, site_exit, site, ctx.index) {
|
if let Some(path) = path_site(wpos, site_exit, site, ctx.index) {
|
||||||
Some(Either::Left(
|
Some(Either::Left(
|
||||||
seq(path.into_iter().map(|wpos| goto_2d(wpos, 1.0, 8.0))).then(goto_2d(
|
seq(path
|
||||||
site_exit,
|
.into_iter()
|
||||||
speed_factor,
|
.map(move |wpos| goto_2d(wpos, 1.0, 8.0, height_offset)))
|
||||||
8.0,
|
.then(goto_2d(site_exit, speed_factor, 8.0, height_offset)),
|
||||||
)),
|
|
||||||
))
|
))
|
||||||
} else {
|
} 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 {
|
} 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<f32>, speed_factor: f32) -> impl Action {
|
|||||||
let diff = wpos - start;
|
let diff = wpos - start;
|
||||||
let n = (diff.magnitude() / WAYPOINT).max(1.0);
|
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));
|
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")
|
.debug(|| "travel to point")
|
||||||
}
|
}
|
||||||
@ -414,7 +431,7 @@ fn travel_to_site(tgt_site: SiteId, speed_factor: f32) -> impl Action {
|
|||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}, speed_factor)
|
}, speed_factor, 0.0)
|
||||||
.boxed()
|
.boxed()
|
||||||
|
|
||||||
// For every track in the path we discovered between the sites...
|
// 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(
|
fn pilot(ship: common::comp::ship::Body) -> impl Action {
|
||||||
from: Vec2<i32>,
|
|
||||||
to: Vec2<i32>,
|
|
||||||
chunk_height: impl Fn(Vec2<i32>) -> Option<i32>,
|
|
||||||
) -> Box<dyn Action> {
|
|
||||||
let heuristics =
|
|
||||||
|(p, _): &(Vec2<i32>, i32), _: &(Vec2<i32>, i32)| p.distance_squared(to) as f32;
|
|
||||||
let start = (from, chunk_height(from).unwrap());
|
|
||||||
let mut astar = Astar::new(1000, start, BuildHasherDefault::<FxHasher64>::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 {
|
|
||||||
// Travel between different towns in a straight line
|
// Travel between different towns in a straight line
|
||||||
now(|ctx| {
|
now(move |ctx| {
|
||||||
let data = &*ctx.state.data();
|
let data = &*ctx.state.data();
|
||||||
let site = data
|
let site = data
|
||||||
.sites
|
.sites
|
||||||
@ -880,16 +853,9 @@ fn pilot() -> impl Action {
|
|||||||
})
|
})
|
||||||
.choose(&mut ctx.rng);
|
.choose(&mut ctx.rng);
|
||||||
if let Some((_id, site)) = site {
|
if let Some((_id, site)) = site {
|
||||||
let start_chunk = ctx.npc.wpos.xy().as_().wpos_to_cpos();
|
Either::Right(goto_2d(site.wpos.as_(), 1.0, 20.0, ship.flying_height()))
|
||||||
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)
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
finish().boxed()
|
Either::Left(finish())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.repeat()
|
.repeat()
|
||||||
@ -918,7 +884,7 @@ fn captain() -> impl Action {
|
|||||||
.get_interpolated(wpos, |chunk| chunk.water_alt)
|
.get_interpolated(wpos, |chunk| chunk.water_alt)
|
||||||
.unwrap_or(0.0),
|
.unwrap_or(0.0),
|
||||||
);
|
);
|
||||||
goto(wpos, 0.7, 5.0).boxed()
|
goto(wpos, 0.7, 5.0, 0.0).boxed()
|
||||||
} else {
|
} else {
|
||||||
idle().boxed()
|
idle().boxed()
|
||||||
}
|
}
|
||||||
@ -990,7 +956,7 @@ fn humanoid() -> impl Action {
|
|||||||
if let Some(vehicle) = ctx.state.data().npcs.vehicles.get(riding.vehicle) {
|
if let Some(vehicle) = ctx.state.data().npcs.vehicles.get(riding.vehicle) {
|
||||||
match vehicle.body {
|
match vehicle.body {
|
||||||
common::comp::ship::Body::DefaultAirship
|
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 => {
|
common::comp::ship::Body::SailBoat | common::comp::ship::Body::Galleon => {
|
||||||
important(captain())
|
important(captain())
|
||||||
},
|
},
|
||||||
@ -1036,31 +1002,21 @@ fn bird_large() -> impl Action {
|
|||||||
})
|
})
|
||||||
.choose(&mut ctx.rng)
|
.choose(&mut ctx.rng)
|
||||||
{
|
{
|
||||||
casual(goto(
|
casual(goto_2d(
|
||||||
site.wpos.as_::<f32>().with_z(
|
site.wpos.as_::<f32>(),
|
||||||
ctx.world
|
|
||||||
.sim()
|
|
||||||
.get_surface_alt_approx(site.wpos)
|
|
||||||
.unwrap_or(0.0)
|
|
||||||
+ ctx.npc.body.flying_height(),
|
|
||||||
),
|
|
||||||
1.0,
|
1.0,
|
||||||
20.0,
|
20.0,
|
||||||
|
ctx.npc.body.flying_height(),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
casual(idle())
|
casual(idle())
|
||||||
}
|
}
|
||||||
} else if let Some(site) = data.sites.get(home) {
|
} else if let Some(site) = data.sites.get(home) {
|
||||||
casual(goto(
|
casual(goto_2d(
|
||||||
site.wpos.as_::<f32>().with_z(
|
site.wpos.as_::<f32>(),
|
||||||
ctx.world
|
|
||||||
.sim()
|
|
||||||
.get_surface_alt_approx(site.wpos)
|
|
||||||
.unwrap_or(0.0)
|
|
||||||
+ ctx.npc.body.flying_height(),
|
|
||||||
),
|
|
||||||
1.0,
|
1.0,
|
||||||
20.0,
|
20.0,
|
||||||
|
ctx.npc.body.flying_height(),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
casual(idle())
|
casual(idle())
|
||||||
|
@ -166,12 +166,11 @@ fn on_tick(ctx: EventCtx<SimulateNpcs, OnTick>) {
|
|||||||
let dist2 = diff.magnitude_squared();
|
let dist2 = diff.magnitude_squared();
|
||||||
|
|
||||||
if dist2 > 0.5f32.powi(2) {
|
if dist2 > 0.5f32.powi(2) {
|
||||||
let mut wpos = vehicle.wpos
|
let wpos = vehicle.wpos
|
||||||
+ (diff
|
+ (diff
|
||||||
* (vehicle.get_speed() * speed_factor * ctx.event.dt
|
* (vehicle.get_speed() * speed_factor * ctx.event.dt
|
||||||
/ dist2.sqrt())
|
/ dist2.sqrt())
|
||||||
.min(1.0))
|
.min(1.0));
|
||||||
.with_z(0.0);
|
|
||||||
|
|
||||||
let is_valid = match vehicle.body {
|
let is_valid = match vehicle.body {
|
||||||
common::comp::ship::Body::DefaultAirship
|
common::comp::ship::Body::DefaultAirship
|
||||||
@ -188,31 +187,6 @@ fn on_tick(ctx: EventCtx<SimulateNpcs, OnTick>) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if is_valid {
|
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;
|
vehicle.wpos = wpos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -226,6 +200,12 @@ fn on_tick(ctx: EventCtx<SimulateNpcs, OnTick>) {
|
|||||||
) => {},
|
) => {},
|
||||||
None => {},
|
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;
|
npc.wpos = vehicle.wpos;
|
||||||
} else {
|
} else {
|
||||||
// Vehicle doens't exist anymore
|
// Vehicle doens't exist anymore
|
||||||
@ -264,7 +244,6 @@ fn on_tick(ctx: EventCtx<SimulateNpcs, OnTick>) {
|
|||||||
NpcAction::Attack(_) => {}, // TODO: Implement simulated combat
|
NpcAction::Attack(_) => {}, // TODO: Implement simulated combat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure NPCs remain on the surface
|
// Make sure NPCs remain on the surface
|
||||||
npc.wpos.z = ctx
|
npc.wpos.z = ctx
|
||||||
.world
|
.world
|
||||||
|
@ -247,7 +247,7 @@ impl<'a> AgentData<'a> {
|
|||||||
self.vel.0,
|
self.vel.0,
|
||||||
chase_tgt,
|
chase_tgt,
|
||||||
TraversalConfig {
|
TraversalConfig {
|
||||||
min_tgt_dist: 1.25,
|
min_tgt_dist: self.traversal_config.min_tgt_dist * 1.25,
|
||||||
..self.traversal_config
|
..self.traversal_config
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
@ -107,6 +107,10 @@ impl<'a> System<'a> for Sys {
|
|||||||
.unwrap_or(entity);
|
.unwrap_or(entity);
|
||||||
|
|
||||||
let moving_body = read_data.bodies.get(moving_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
|
// Hack, replace with better system when groups are more sophisticated
|
||||||
// Override alignment if in a group unless entity is owned already
|
// 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();
|
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
|
let glider_equipped = inventory
|
||||||
.equipped(EquipSlot::Glider)
|
.equipped(EquipSlot::Glider)
|
||||||
@ -180,7 +187,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
slow_factor,
|
slow_factor,
|
||||||
on_ground: physics_state.on_ground.is_some(),
|
on_ground: physics_state.on_ground.is_some(),
|
||||||
in_liquid: physics_state.in_liquid().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_climb: moving_body.map_or(false, Body::can_climb),
|
||||||
can_fly: moving_body.map_or(false, |b| b.fly_thrust().is_some()),
|
can_fly: moving_body.map_or(false, |b| b.fly_thrust().is_some()),
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user