Merge branch 'isse/rtsim_fixes' into 'master'

Somewhat fix airships

See merge request veloren/veloren!3869
This commit is contained in:
Isse 2023-04-14 18:25:43 +00:00
commit 913eda4b7f
16 changed files with 169 additions and 161 deletions

View File

@ -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,
}
}

View File

@ -110,6 +110,8 @@ impl Body {
matches!(self, Body::DefaultAirship | Body::AirBalloon | Body::Volume)
}
pub fn flying_height(&self) -> f32 { if self.can_fly() { 200.0 } else { 0.0 } }
pub fn has_water_thrust(&self) -> bool {
!self.can_fly() // TODO: Differentiate this more carefully
}

View File

@ -227,6 +227,7 @@ pub enum ServerEvent {
},
CreateShip {
pos: Pos,
ori: Ori,
ship: comp::ship::Body,
rtsim_entity: Option<RtSimVehicle>,
driver: Option<NpcBuilder>,

View File

@ -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

View File

@ -262,6 +262,7 @@ pub enum VehicleKind {
#[derive(Clone, Serialize, Deserialize)]
pub struct Vehicle {
pub wpos: Vec3<f32>,
pub dir: Vec2<f32>,
pub body: comp::ship::Body,
@ -283,6 +284,7 @@ impl Vehicle {
pub fn new(wpos: Vec3<f32>, body: comp::ship::Body) -> Self {
Self {
wpos,
dir: Vec2::unit_y(),
body,
chunk_pos: None,
driver: None,
@ -295,10 +297,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,
}
}

View File

@ -61,7 +61,7 @@ fn path_in_site(start: Vec2<i32>, end: Vec2<i32>, site: &site2::Site) -> PathRes
TileKind::Empty => 3.0,
TileKind::Hazard(_) => 50.0,
TileKind::Field => 8.0,
TileKind::Plaza | TileKind::Road { .. } | TileKind::Path => 1.0,
TileKind::Plaza | TileKind::Road { .. } | TileKind::Path | TileKind::Bridge => 1.0,
TileKind::Building
| TileKind::Castle
@ -95,13 +95,14 @@ fn path_in_site(start: Vec2<i32>, end: Vec2<i32>, 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<i32>| {
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(
@ -136,23 +137,23 @@ fn path_between_sites(
let neighbors = |site: &Id<civ::Site>| world.civs().neighbors(*site);
let track_between = |a: Id<civ::Site>, b: Id<civ::Site>| {
let transition = |a: &Id<civ::Site>, b: &Id<civ::Site>| {
world
.civs()
.tracks
.get(world.civs().track_between(a, b).unwrap().0)
.track_between(*a, *b)
.map(|(id, _)| world.civs().tracks.get(id).cost)
.unwrap_or(f32::INFINITY)
};
let transition = |a: &Id<civ::Site>, b: &Id<civ::Site>| track_between(*a, *b).cost;
let path = astar.poll(250, heuristic, neighbors, transition, |site| *site == end);
path.map(|path| {
let path = path
.into_iter()
.tuple_windows::<(_, _)>()
.map(|(a, b)| world.civs().track_between(a, b).unwrap())
.collect_vec();
// Since we get a, b from neighbors, track_between shouldn't return None.
.filter_map(|(a, b)| world.civs().track_between(a, b))
.collect();
Path { nodes: path }
})
}
@ -279,13 +280,9 @@ fn idle() -> impl Action { just(|ctx| ctx.controller.do_idle()).debug(|| "idle")
fn goto(wpos: Vec3<f32>, speed_factor: f32, goal_dist: f32) -> impl Action {
const STEP_DIST: f32 = 24.0;
const WAYPOINT_DIST: f32 = 12.0;
let mut waypoint = None;
just(move |ctx| {
let rpos = wpos - ctx.npc.wpos;
let len = rpos.magnitude();
// If we're close to the next waypoint, complete it
if waypoint.map_or(false, |waypoint: Vec3<f32>| {
ctx.npc.wpos.xy().distance_squared(waypoint.xy()) < WAYPOINT_DIST.powi(2)
@ -295,6 +292,8 @@ fn goto(wpos: Vec3<f32>, speed_factor: f32, goal_dist: f32) -> impl Action {
// Get the next waypoint on the route toward the goal
let waypoint = waypoint.get_or_insert_with(|| {
let rpos = wpos - ctx.npc.wpos;
let len = rpos.magnitude();
let wpos = ctx.npc.wpos + (rpos / len) * len.min(STEP_DIST);
wpos.with_z(
@ -313,6 +312,49 @@ fn goto(wpos: Vec3<f32>, speed_factor: f32, goal_dist: f32) -> impl Action {
.map(|_| {})
}
/// Try to walk fly a 3D position following the terrain altitude at an offset
/// without caring for obstacles.
fn goto_flying(
wpos: Vec3<f32>,
speed_factor: f32,
goal_dist: f32,
step_dist: f32,
waypoint_dist: f32,
height_offset: f32,
) -> impl Action {
let mut waypoint = None;
just(move |ctx| {
// If we're close to the next waypoint, complete it
if waypoint.map_or(false, |waypoint: Vec3<f32>| {
ctx.npc.wpos.distance_squared(waypoint) < waypoint_dist.powi(2)
}) {
waypoint = None;
}
// Get the next waypoint on the route toward the goal
let waypoint = waypoint.get_or_insert_with(|| {
let rpos = wpos - ctx.npc.wpos;
let len = rpos.magnitude();
let wpos = ctx.npc.wpos + (rpos / len) * len.min(step_dist);
wpos.with_z(
ctx.world
.sim()
.get_surface_alt_approx(wpos.xy().as_())
.map(|alt| alt + height_offset)
.unwrap_or(wpos.z),
)
});
ctx.controller.do_goto(*waypoint, speed_factor);
})
.repeat()
.stop_if(move |ctx| ctx.npc.wpos.xy().distance_squared(wpos.xy()) < goal_dist.powi(2))
.debug(move || format!("goto {}, {}, {}", wpos.x, wpos.y, wpos.z))
.map(|_| {})
}
/// Try to walk toward a 2D position on the terrain without caring for
/// obstacles.
fn goto_2d(wpos2d: Vec2<f32>, speed_factor: f32, goal_dist: f32) -> impl Action {
@ -322,6 +364,30 @@ fn goto_2d(wpos2d: Vec2<f32>, speed_factor: f32, goal_dist: f32) -> impl Action
})
}
/// Try to fly toward a 2D position following the terrain altitude at an offset
/// without caring for obstacles.
fn goto_2d_flying(
wpos2d: Vec2<f32>,
speed_factor: f32,
goal_dist: f32,
step_dist: f32,
waypoint_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) + height_offset);
goto_flying(
wpos,
speed_factor,
goal_dist,
step_dist,
waypoint_dist,
height_offset,
)
})
}
fn traverse_points<F>(mut next_point: F, speed_factor: f32) -> impl Action
where
F: FnMut(&mut NpcCtx) -> Option<Vec2<f32>> + Send + Sync + 'static,
@ -345,7 +411,7 @@ 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(
seq(path.into_iter().map(move |wpos| goto_2d(wpos, 1.0, 8.0))).then(goto_2d(
site_exit,
speed_factor,
8.0,
@ -821,53 +887,9 @@ fn follow(npc: NpcId, distance: f32) -> impl Action {
}
*/
fn chunk_path(
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 {
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 +902,19 @@ 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_flying(
site.wpos.as_(),
1.0,
50.0,
150.0,
110.0,
ship.flying_height(),
)
.then(goto_2d_flying(site.wpos.as_(), 1.0, 10.0, 32.0, 16.0, 10.0)),
)
} else {
finish().boxed()
Either::Left(finish())
}
})
.repeat()
@ -990,7 +1015,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())
},
@ -1024,6 +1049,16 @@ fn bird_large() -> impl Action {
let data = ctx.state.data();
if let Some(home) = ctx.npc.home {
let is_home = ctx.npc.current_site.map_or(false, |site| home == site);
let goto = |wpos| {
casual(goto_2d_flying(
wpos,
1.0,
20.0,
32.0,
22.0,
ctx.npc.body.flying_height(),
))
};
if is_home {
if let Some((_, site)) = data
.sites
@ -1036,32 +1071,12 @@ fn bird_large() -> impl Action {
})
.choose(&mut ctx.rng)
{
casual(goto(
site.wpos.as_::<f32>().with_z(
ctx.world
.sim()
.get_surface_alt_approx(site.wpos)
.unwrap_or(0.0)
+ ctx.npc.body.flying_height(),
),
1.0,
20.0,
))
goto(site.wpos.as_::<f32>())
} else {
casual(idle())
}
} else if let Some(site) = data.sites.get(home) {
casual(goto(
site.wpos.as_::<f32>().with_z(
ctx.world
.sim()
.get_surface_alt_approx(site.wpos)
.unwrap_or(0.0)
+ ctx.npc.body.flying_height(),
),
1.0,
20.0,
))
goto(site.wpos.as_::<f32>())
} else {
casual(idle())
}

View File

@ -11,6 +11,7 @@ use common::{
use rand::prelude::*;
use rand_chacha::ChaChaRng;
use tracing::{error, warn};
use vek::Vec2;
use world::site::SiteKind;
pub struct SimulateNpcs;
@ -166,12 +167,11 @@ fn on_tick(ctx: EventCtx<SimulateNpcs, OnTick>) {
let dist2 = diff.magnitude_squared();
if dist2 > 0.5f32.powi(2) {
let mut wpos = vehicle.wpos
let wpos = vehicle.wpos
+ (diff
* (vehicle.get_speed() * speed_factor * ctx.event.dt
/ dist2.sqrt())
.min(1.0))
.with_z(0.0);
.min(1.0));
let is_valid = match vehicle.body {
common::comp::ship::Body::DefaultAirship
@ -188,33 +188,11 @@ fn on_tick(ctx: EventCtx<SimulateNpcs, OnTick>) {
};
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.dir = (target.xy() - vehicle.wpos.xy())
.try_normalized()
.unwrap_or(Vec2::unit_y());
}
},
// When riding, other actions are disabled
@ -264,7 +242,6 @@ fn on_tick(ctx: EventCtx<SimulateNpcs, OnTick>) {
NpcAction::Attack(_) => {}, // TODO: Implement simulated combat
}
}
// Make sure NPCs remain on the surface
npc.wpos.z = ctx
.world

View File

@ -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
},
) {

View File

@ -1572,19 +1572,14 @@ fn handle_spawn_airship(
pos.0.z += 50.0;
const DESTINATION_RADIUS: f32 = 2000.0;
let angle = angle.map(|a| a * std::f32::consts::PI / 180.0);
let destination = angle.map(|a| {
pos.0
+ Vec3::new(
DESTINATION_RADIUS * a.cos(),
DESTINATION_RADIUS * a.sin(),
200.0,
)
});
let dir = angle.map(|a| Vec3::new(a.cos(), a.sin(), 0.0));
let destination = dir.map(|dir| pos.0 + dir * DESTINATION_RADIUS + Vec3::new(0.0, 0.0, 200.0));
let mut rng = thread_rng();
let ship = comp::ship::Body::random_airship_with(&mut rng);
let ori = comp::Ori::from(common::util::Dir::new(dir.unwrap_or(Vec3::unit_y())));
let mut builder = server
.state
.create_ship(pos, ship, |ship| ship.make_collider())
.create_ship(pos, ori, ship, |ship| ship.make_collider())
.with(LightEmitter {
col: Rgb::new(1.0, 0.65, 0.2),
strength: 2.0,
@ -1621,19 +1616,14 @@ fn handle_spawn_ship(
pos.0.z += 50.0;
const DESTINATION_RADIUS: f32 = 2000.0;
let angle = angle.map(|a| a * std::f32::consts::PI / 180.0);
let destination = angle.map(|a| {
pos.0
+ Vec3::new(
DESTINATION_RADIUS * a.cos(),
DESTINATION_RADIUS * a.sin(),
200.0,
)
});
let dir = angle.map(|a| Vec3::new(a.cos(), a.sin(), 0.0));
let destination = dir.map(|dir| pos.0 + dir * DESTINATION_RADIUS + Vec3::new(0.0, 0.0, 200.0));
let mut rng = thread_rng();
let ship = comp::ship::Body::random_ship_with(&mut rng);
let ori = comp::Ori::from(common::util::Dir::new(dir.unwrap_or(Vec3::unit_y())));
let mut builder = server
.state
.create_ship(pos, ship, |ship| ship.make_collider())
.create_ship(pos, ori, ship, |ship| ship.make_collider())
.with(LightEmitter {
col: Rgb::new(1.0, 0.65, 0.2),
strength: 2.0,
@ -1683,9 +1673,12 @@ fn handle_make_volume(
};
server
.state
.create_ship(comp::Pos(pos.0 + Vec3::unit_z() * 50.0), ship, move |_| {
collider
})
.create_ship(
comp::Pos(pos.0 + Vec3::unit_z() * 50.0),
comp::Ori::default(),
ship,
move |_| collider,
)
.build();
server.notify_client(

View File

@ -194,6 +194,7 @@ pub fn handle_create_npc(server: &mut Server, pos: Pos, mut npc: NpcBuilder) ->
pub fn handle_create_ship(
server: &mut Server,
pos: Pos,
ori: Ori,
ship: comp::ship::Body,
rtsim_vehicle: Option<RtSimVehicle>,
driver: Option<NpcBuilder>,
@ -201,7 +202,7 @@ pub fn handle_create_ship(
) {
let mut entity = server
.state
.create_ship(pos, ship, |ship| ship.make_collider());
.create_ship(pos, ori, ship, |ship| ship.make_collider());
/*
if let Some(mut agent) = agent {
let (kp, ki, kd) = pid_coefficients(&Body::Ship(ship));

View File

@ -193,10 +193,11 @@ impl Server {
},
ServerEvent::CreateShip {
pos,
ori,
ship,
rtsim_entity,
driver,
} => handle_create_ship(self, pos, ship, rtsim_entity, driver, Vec::new()),
} => handle_create_ship(self, pos, ori, ship, rtsim_entity, driver, Vec::new()),
ServerEvent::CreateWaypoint(pos) => handle_create_waypoint(self, pos),
ServerEvent::ClientDisconnect(entity, reason) => {
frontend_events.push(handle_client_disconnect(self, entity, reason, false))

View File

@ -11,6 +11,7 @@ use common::{
slowjob::SlowJobPool,
terrain::CoordinateConversions,
trade::{Good, SiteInformation},
util::Dir,
LoadoutBuilder,
};
use common_ecs::{Job, Origin, Phase, System};
@ -315,6 +316,7 @@ impl<'a> System<'a> for Sys {
emitter.emit(ServerEvent::CreateShip {
pos: comp::Pos(vehicle.wpos),
ori: comp::Ori::from(Dir::new(vehicle.dir.with_z(0.0))),
ship: vehicle.body,
rtsim_entity: Some(RtSimVehicle(vehicle_id)),
driver: vehicle.driver.and_then(&mut actor_info),
@ -330,6 +332,8 @@ impl<'a> System<'a> for Sys {
// loaded
if matches!(npc.mode, SimulationMode::Simulated)
&& chunk_states.0.get(chunk).map_or(false, |c| c.is_some())
// Riding npcs will be spawned by the vehicle.
&& npc.riding.is_none()
{
npc.mode = SimulationMode::Loaded;
let entity_info = get_npc_entity_info(npc, &data.sites, index.as_index_ref());

View File

@ -64,6 +64,7 @@ pub trait StateExt {
fn create_ship<F: FnOnce(comp::ship::Body) -> comp::Collider>(
&mut self,
pos: comp::Pos,
ori: comp::Ori,
ship: comp::ship::Body,
make_collider: F,
) -> EcsEntityBuilder;
@ -338,6 +339,7 @@ impl StateExt for State {
fn create_ship<F: FnOnce(comp::ship::Body) -> comp::Collider>(
&mut self,
pos: comp::Pos,
ori: comp::Ori,
ship: comp::ship::Body,
make_collider: F,
) -> EcsEntityBuilder {
@ -347,7 +349,7 @@ impl StateExt for State {
.create_entity_synced()
.with(pos)
.with(comp::Vel(Vec3::zero()))
.with(comp::Ori::default())
.with(ori)
.with(body.mass())
.with(body.density())
.with(make_collider(ship))

View File

@ -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()),
};

View File

@ -1067,7 +1067,7 @@ impl Site {
});
site.blit_aabr(aabr, Tile {
kind: TileKind::Building,
kind: TileKind::Bridge,
plot: Some(plot),
hard_alt: None,
});

View File

@ -191,6 +191,7 @@ pub enum TileKind {
Keep(KeepKind),
Gate,
GnarlingFortification,
Bridge,
}
#[derive(Clone, PartialEq)]