diff --git a/common/src/generation.rs b/common/src/generation.rs index 041f7a4b8e..9db9769c91 100644 --- a/common/src/generation.rs +++ b/common/src/generation.rs @@ -446,6 +446,12 @@ impl EntityInfo { self.no_flee = true; self } + + #[must_use] + pub fn with_loadout(mut self, loadout: LoadoutBuilder) -> Self { + self.loadout = loadout; + self + } } #[derive(Default)] diff --git a/common/systems/src/mount.rs b/common/systems/src/mount.rs index ee1945d7f1..b8d70b37b9 100644 --- a/common/systems/src/mount.rs +++ b/common/systems/src/mount.rs @@ -1,5 +1,5 @@ use common::{ - comp::{Body, Controller, InputKind, Ori, Pos, Scale, Vel, ControlAction}, + comp::{Body, ControlAction, Controller, InputKind, Ori, Pos, Scale, Vel}, link::Is, mounting::Mount, uid::UidAllocator, diff --git a/rtsim/src/data/npc.rs b/rtsim/src/data/npc.rs index ff6a513318..21ee9b4903 100644 --- a/rtsim/src/data/npc.rs +++ b/rtsim/src/data/npc.rs @@ -5,7 +5,8 @@ use common::{ grid::Grid, rtsim::{FactionId, RtSimController, SiteId, VehicleId}, store::Id, - uid::Uid, vol::RectVolSize, + uid::Uid, + vol::RectVolSize, }; use hashbrown::HashMap; use rand::prelude::*; @@ -21,7 +22,11 @@ use std::{ }, }; use vek::*; -use world::{civ::Track, site::Site as WorldSite, util::{RandomPerm, LOCALITY}}; +use world::{ + civ::Track, + site::Site as WorldSite, + util::{RandomPerm, LOCALITY}, +}; use super::Actor; @@ -78,6 +83,7 @@ pub struct Npc { pub seed: u32, pub wpos: Vec3, + pub body: comp::Body, pub profession: Option, pub home: Option, pub faction: Option, @@ -113,6 +119,7 @@ impl Clone for Npc { home: self.home, faction: self.faction, riding: self.riding.clone(), + body: self.body, // Not persisted chunk_pos: None, current_site: Default::default(), @@ -124,13 +131,11 @@ impl Clone for Npc { } impl Npc { - const PERM_BODY: u32 = 1; - const PERM_SPECIES: u32 = 0; - - pub fn new(seed: u32, wpos: Vec3) -> Self { + pub fn new(seed: u32, wpos: Vec3, body: comp::Body) -> Self { Self { seed, wpos, + body, profession: None, home: None, faction: None, @@ -154,21 +159,17 @@ impl Npc { } pub fn steering(mut self, vehicle: impl Into>) -> Self { - self.riding = vehicle.into().map(|vehicle| { - Riding { - vehicle, - steering: true, - } + self.riding = vehicle.into().map(|vehicle| Riding { + vehicle, + steering: true, }); self } pub fn riding(mut self, vehicle: impl Into>) -> Self { - self.riding = vehicle.into().map(|vehicle| { - Riding { - vehicle, - steering: false, - } + self.riding = vehicle.into().map(|vehicle| Riding { + vehicle, + steering: false, }); self } @@ -179,13 +180,6 @@ impl Npc { } pub fn rng(&self, perm: u32) -> impl Rng { RandomPerm::new(self.seed.wrapping_add(perm)) } - - pub fn get_body(&self) -> comp::Body { - let species = *(&comp::humanoid::ALL_SPECIES) - .choose(&mut self.rng(Self::PERM_SPECIES)) - .unwrap(); - comp::humanoid::Body::random_with(&mut self.rng(Self::PERM_BODY), &species).into() - } } #[derive(Clone, Serialize, Deserialize)] @@ -204,7 +198,7 @@ pub enum VehicleKind { pub struct Vehicle { pub wpos: Vec3, - pub kind: VehicleKind, + pub body: comp::ship::Body, #[serde(skip_serializing, skip_deserializing)] pub chunk_pos: Option>, @@ -216,41 +210,36 @@ pub struct Vehicle { // TODO: Find a way to detect riders when the vehicle is loaded pub riders: Vec, - /// Whether the Vehicle is in simulated or loaded mode (when rtsim is run on the - /// server, loaded corresponds to being within a loaded chunk). When in - /// loaded mode, the interactions of the Vehicle should not be simulated but - /// should instead be derived from the game. + /// Whether the Vehicle is in simulated or loaded mode (when rtsim is run on + /// the server, loaded corresponds to being within a loaded chunk). When + /// in loaded mode, the interactions of the Vehicle should not be + /// simulated but should instead be derived from the game. #[serde(skip_serializing, skip_deserializing)] pub mode: SimulationMode, } impl Vehicle { - pub fn new(wpos: Vec3, kind: VehicleKind) -> Self { + pub fn new(wpos: Vec3, body: comp::ship::Body) -> Self { Self { wpos, - kind, + body, chunk_pos: None, driver: None, riders: Vec::new(), mode: SimulationMode::Simulated, } } - pub fn get_ship(&self) -> comp::ship::Body { - match self.kind { - VehicleKind::Airship => comp::ship::Body::DefaultAirship, - VehicleKind::Boat => comp::ship::Body::Galleon, - } - } - pub fn get_body(&self) -> comp::Body { - comp::Body::Ship(self.get_ship()) - } + pub fn get_body(&self) -> comp::Body { comp::Body::Ship(self.body) } /// Max speed in block/s pub fn get_speed(&self) -> f32 { - match self.kind { - VehicleKind::Airship => 15.0, - VehicleKind::Boat => 13.0, + 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, + _ => 10.0, } } } @@ -274,27 +263,29 @@ fn construct_npc_grid() -> Grid { Grid::new(Vec2::zero(), Default::def impl Npcs { pub fn create_npc(&mut self, npc: Npc) -> NpcId { self.npcs.insert(npc) } - pub fn create_vehicle(&mut self, vehicle: Vehicle) -> VehicleId { self.vehicles.insert(vehicle) } + pub fn create_vehicle(&mut self, vehicle: Vehicle) -> VehicleId { + self.vehicles.insert(vehicle) + } /// Queries nearby npcs, not garantueed to work if radius > 32.0 pub fn nearby(&self, wpos: Vec2, radius: f32) -> impl Iterator + '_ { - let chunk_pos = wpos.as_::() / common::terrain::TerrainChunkSize::RECT_SIZE.as_::(); + let chunk_pos = + wpos.as_::() / common::terrain::TerrainChunkSize::RECT_SIZE.as_::(); let r_sqr = radius * radius; LOCALITY .into_iter() .filter_map(move |neighbor| { - self - .npc_grid - .get(chunk_pos + neighbor) - .map(|cell| { - cell.npcs.iter() - .copied() - .filter(|npc| { - self.npcs.get(*npc) - .map_or(false, |npc| npc.wpos.xy().distance_squared(wpos) < r_sqr) - }) - .collect::>() - }) + self.npc_grid.get(chunk_pos + neighbor).map(|cell| { + cell.npcs + .iter() + .copied() + .filter(|npc| { + self.npcs + .get(*npc) + .map_or(false, |npc| npc.wpos.xy().distance_squared(wpos) < r_sqr) + }) + .collect::>() + }) }) .flatten() } diff --git a/rtsim/src/gen/mod.rs b/rtsim/src/gen/mod.rs index 368c532697..37de8f34d4 100644 --- a/rtsim/src/gen/mod.rs +++ b/rtsim/src/gen/mod.rs @@ -8,7 +8,11 @@ use crate::data::{ Data, Nature, }; use common::{ - grid::Grid, resources::TimeOfDay, rtsim::WorldSettings, terrain::TerrainChunkSize, + comp::{self, Body}, + grid::Grid, + resources::TimeOfDay, + rtsim::WorldSettings, + terrain::TerrainChunkSize, vol::RectVolSize, }; use hashbrown::HashMap; @@ -72,12 +76,12 @@ impl Data { "Registering {} rtsim sites from world sites.", this.sites.len() ); - + /* // Spawn some test entities at the sites for (site_id, site) in this.sites.iter() // TODO: Stupid .filter(|(_, site)| site.world_site.map_or(false, |ws| - matches!(&index.sites.get(ws).kind, SiteKind::Refactor(_)))) .skip(1) + matches!(&index.sites.get(ws).kind, SiteKind::Refactor(_)))) { let Some(good_or_evil) = site .faction @@ -91,10 +95,17 @@ impl Data { .map(|e| e as f32 + 0.5) .with_z(world.sim().get_alt_approx(wpos2d).unwrap_or(0.0)) }; + let random_humanoid = |rng: &mut SmallRng| { + let species = comp::humanoid::ALL_SPECIES.choose(&mut *rng).unwrap(); + Body::Humanoid(comp::humanoid::Body::random_with( + rng, + species, + )) + }; if good_or_evil { for _ in 0..32 { this.npcs.create_npc( - Npc::new(rng.gen(), rand_wpos(&mut rng)) + Npc::new(rng.gen(), rand_wpos(&mut rng), random_humanoid(&mut rng)) .with_faction(site.faction) .with_home(site_id) .with_profession(match rng.gen_range(0..20) { @@ -112,7 +123,7 @@ impl Data { } else { for _ in 0..15 { this.npcs.create_npc( - Npc::new(rng.gen(), rand_wpos(&mut rng)) + Npc::new(rng.gen(), rand_wpos(&mut rng), random_humanoid(&mut rng)) .with_faction(site.faction) .with_home(site_id) .with_profession(match rng.gen_range(0..20) { @@ -122,17 +133,54 @@ impl Data { } } this.npcs.create_npc( - Npc::new(rng.gen(), rand_wpos(&mut rng)) + Npc::new(rng.gen(), rand_wpos(&mut rng), random_humanoid(&mut rng)) .with_home(site_id) .with_profession(Profession::Merchant), ); - let wpos = rand_wpos(&mut rng) + Vec3::unit_z() * 50.0; - let vehicle_id = this.npcs.create_vehicle(Vehicle::new(wpos, VehicleKind::Airship)); - - this.npcs.create_npc(Npc::new(rng.gen(), wpos).with_home(site_id).with_profession(Profession::Captain).steering(vehicle_id)); - } + if rng.gen_bool(0.4) { + let wpos = rand_wpos(&mut rng) + Vec3::unit_z() * 50.0; + let vehicle_id = this + .npcs + .create_vehicle(Vehicle::new(wpos, comp::body::ship::Body::DefaultAirship)); + this.npcs.create_npc( + Npc::new(rng.gen(), wpos, random_humanoid(&mut rng)) + .with_home(site_id) + .with_profession(Profession::Captain) + .steering(vehicle_id), + ); + } + } + */ + for (site_id, site) in this.sites.iter() + // TODO: Stupid + .filter(|(_, site)| site.world_site.map_or(false, |ws| + matches!(&index.sites.get(ws).kind, SiteKind::Dungeon(_)))) + { + let rand_wpos = |rng: &mut SmallRng| { + let wpos2d = site.wpos.map(|e| e + rng.gen_range(-10..10)); + wpos2d + .map(|e| e as f32 + 0.5) + .with_z(world.sim().get_alt_approx(wpos2d).unwrap_or(0.0)) + }; + + let species = [ + comp::body::bird_large::Species::Phoenix, + comp::body::bird_large::Species::Cockatrice, + comp::body::bird_large::Species::Roc, + ] + .choose(&mut rng) + .unwrap(); + this.npcs.create_npc( + Npc::new( + rng.gen(), + rand_wpos(&mut rng), + Body::BirdLarge(comp::body::bird_large::Body::random_with(&mut rng, species)), + ) + .with_home(site_id), + ); + } info!("Generated {} rtsim NPCs.", this.npcs.len()); this diff --git a/rtsim/src/rule/npc_ai.rs b/rtsim/src/rule/npc_ai.rs index c8e7ce4c53..99db92dd5e 100644 --- a/rtsim/src/rule/npc_ai.rs +++ b/rtsim/src/rule/npc_ai.rs @@ -14,7 +14,7 @@ use common::{ path::Path, rtsim::{Profession, SiteId}, store::Id, - terrain::{TerrainChunkSize, SiteKindMeta}, + terrain::{SiteKindMeta, TerrainChunkSize}, time::DayPeriod, vol::RectVolSize, }; @@ -32,7 +32,8 @@ use world::{ civ::{self, Track}, site::{Site as WorldSite, SiteKind}, site2::{self, PlotKind, TileKind}, - IndexRef, World, util::NEIGHBORS, + util::NEIGHBORS, + IndexRef, World, }; pub struct NpcAi; @@ -329,7 +330,11 @@ fn goto(wpos: Vec3, 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(|| ctx.npc.wpos + (rpos / len) * len.min(STEP_DIST)); + waypoint.get_or_insert_with(|| { + let wpos = ctx.npc.wpos + (rpos / len) * len.min(STEP_DIST); + + wpos.with_z(ctx.world.sim().get_surface_alt_approx(wpos.xy().as_()).unwrap_or(wpos.z)) + }); *ctx.controller = Controller::goto(*waypoint, speed_factor); }) @@ -635,7 +640,11 @@ fn follow(npc: NpcId, distance: f32) -> impl Action { .map(|_| {}) } -fn chunk_path(from: Vec2, to: Vec2, chunk_height: impl Fn(Vec2) -> Option) -> Box { +fn chunk_path( + from: Vec2, + to: Vec2, + chunk_height: impl Fn(Vec2) -> Option, +) -> Box { let heuristics = |(p, _): &(Vec2, i32)| p.distance_squared(to) as f32; let start = (from, chunk_height(from).unwrap()); let mut astar = Astar::new( @@ -648,35 +657,45 @@ fn chunk_path(from: Vec2, to: Vec2, chunk_height: impl Fn(Vec2) - let path = astar.poll( 1000, heuristics, - |&(p, _)| NEIGHBORS.into_iter().map(move |n| p + n).filter_map(|p| Some((p, chunk_height(p)?))), + |&(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_() * TerrainChunkSize::RECT_SIZE.as_()).with_z((h0 - h1) as f32); + let diff = + ((p0 - p1).as_() * TerrainChunkSize::RECT_SIZE.as_()).with_z((h0 - h1) as f32); diff.magnitude_squared() }, - |(e, _)| *e == to + |(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() + 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 now(|ctx| { let data = &*ctx.state.data(); - let site = data.sites.iter() + let site = data + .sites + .iter() .filter(|(id, _)| Some(*id) != ctx.npc.current_site) .filter(|(_, site)| { site.world_site @@ -685,12 +704,14 @@ fn pilot() -> impl Action { }) .choose(&mut thread_rng()); if let Some((_id, site)) = site { - let start_chunk = ctx.npc.wpos.xy().as_::() / TerrainChunkSize::RECT_SIZE.as_::(); + let start_chunk = + ctx.npc.wpos.xy().as_::() / TerrainChunkSize::RECT_SIZE.as_::(); let end_chunk = site.wpos / TerrainChunkSize::RECT_SIZE.as_::(); - chunk_path(start_chunk, end_chunk, |chunk| { - ctx.world.sim().get_alt_approx(TerrainChunkSize::center_wpos(chunk)).map(|f| { - (f + 150.0) as i32 - }) + 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 { finish().boxed() @@ -707,27 +728,42 @@ fn captain() -> impl Action { if let Some(chunk) = NEIGHBORS .into_iter() .map(|neighbor| chunk + neighbor) - .filter(|neighbor| ctx.world.sim().get(*neighbor).map_or(false, |c| c.river.river_kind.is_some())) + .filter(|neighbor| { + ctx.world + .sim() + .get(*neighbor) + .map_or(false, |c| c.river.river_kind.is_some()) + }) .choose(&mut thread_rng()) { let wpos = TerrainChunkSize::center_wpos(chunk); - let wpos = wpos.as_().with_z(ctx.world.sim().get_interpolated(wpos, |chunk| chunk.water_alt).unwrap_or(0.0)); + let wpos = wpos.as_().with_z( + ctx.world + .sim() + .get_interpolated(wpos, |chunk| chunk.water_alt) + .unwrap_or(0.0), + ); goto(wpos, 0.7, 5.0).boxed() } else { idle().boxed() } }) - .repeat().map(|_| ()) + .repeat() + .map(|_| ()) } -fn think() -> impl Action { +fn humanoid() -> impl Action { choose(|ctx| { if let Some(riding) = &ctx.npc.riding { if riding.steering { if let Some(vehicle) = ctx.state.data().npcs.vehicles.get(riding.vehicle) { - match vehicle.kind { - VehicleKind::Airship => important(pilot()), - VehicleKind::Boat => important(captain()), + match vehicle.body { + common::comp::ship::Body::DefaultAirship + | common::comp::ship::Body::AirBalloon => important(pilot()), + common::comp::ship::Body::SailBoat | common::comp::ship::Body::Galleon => { + important(captain()) + }, + _ => casual(idle()), } } else { casual(finish()) @@ -748,6 +784,66 @@ fn think() -> impl Action { }) } +fn bird_large() -> impl Action { + choose(|ctx| { + 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); + if is_home { + if let Some((id, site)) = data + .sites + .iter() + .filter(|(id, site)| { + *id != home + && site.world_site.map_or(false, |site| { + matches!(ctx.index.sites.get(site).kind, SiteKind::Dungeon(_)) + }) + }) + .choose(&mut thread_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(), + ), + 1.0, + 20.0, + )) + } 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(), + ), + 1.0, + 20.0, + )) + } else { + casual(idle()) + } + } else { + casual(idle()) + } + }) +} + +fn think() -> impl Action { + choose(|ctx| match ctx.npc.body { + common::comp::Body::Humanoid(_) => casual(humanoid()), + common::comp::Body::BirdLarge(_) => casual(bird_large()), + _ => casual(idle()), + }) +} + // if !matches!(stages.front(), Some(TravelStage::IntraSite { .. })) { // let data = ctx.state.data(); // if let Some((site2, site)) = npc diff --git a/rtsim/src/rule/simulate_npcs.rs b/rtsim/src/rule/simulate_npcs.rs index b65e5f5f1c..15674d6324 100644 --- a/rtsim/src/rule/simulate_npcs.rs +++ b/rtsim/src/rule/simulate_npcs.rs @@ -3,7 +3,7 @@ use crate::{ event::{OnSetup, OnTick}, RtState, Rule, RuleError, }; -use common::{terrain::TerrainChunkSize, vol::RectVolSize, grid::Grid}; +use common::{grid::Grid, terrain::TerrainChunkSize, vol::RectVolSize}; use tracing::info; use vek::*; @@ -77,8 +77,6 @@ impl Rule for SimulateNpcs { // Simulate the NPC's movement and interactions if matches!(npc.mode, SimulationMode::Simulated) { - let body = npc.get_body(); - if let Some(riding) = &npc.riding { if let Some(vehicle) = data.npcs.vehicles.get_mut(riding.vehicle) { if let Some(action) = npc.action && riding.steering { @@ -94,28 +92,30 @@ impl Rule for SimulateNpcs { .min(1.0)) .with_z(0.0); - let is_valid = match vehicle.kind { - crate::data::npc::VehicleKind::Airship => true, - crate::data::npc::VehicleKind::Boat => { + let is_valid = match vehicle.body { + common::comp::ship::Body::DefaultAirship | common::comp::ship::Body::AirBalloon => true, + common::comp::ship::Body::SailBoat | common::comp::ship::Body::Galleon => { let chunk_pos = wpos.xy().as_::() / TerrainChunkSize::RECT_SIZE.as_::(); ctx.world.sim().get(chunk_pos).map_or(true, |f| f.river.river_kind.is_some()) }, + _ => false, }; if is_valid { - match vehicle.kind { - crate::data::npc::VehicleKind::Airship => { + 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; } }, - crate::data::npc::VehicleKind::Boat => { + 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; } @@ -138,7 +138,7 @@ impl Rule for SimulateNpcs { if dist2 > 0.5f32.powi(2) { npc.wpos += (diff - * (body.max_speed_approx() * speed_factor * ctx.event.dt + * (npc.body.max_speed_approx() * speed_factor * ctx.event.dt / dist2.sqrt()) .min(1.0)) .with_z(0.0); @@ -150,8 +150,8 @@ impl Rule for SimulateNpcs { npc.wpos.z = ctx .world .sim() - .get_alt_approx(npc.wpos.xy().map(|e| e as i32)) - .unwrap_or(0.0); + .get_surface_alt_approx(npc.wpos.xy().map(|e| e as i32)) + .unwrap_or(0.0) + npc.body.flying_height(); } } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 8ad10cdfc3..0c310ce1b9 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -1486,7 +1486,8 @@ fn handle_spawn_airship( animated: true, }); if let Some(pos) = destination { - let (kp, ki, kd) = comp::agent::pid_coefficients(&comp::Body::Ship(ship)).unwrap_or((1.0, 0.0, 0.0)); + let (kp, ki, kd) = + comp::agent::pid_coefficients(&comp::Body::Ship(ship)).unwrap_or((1.0, 0.0, 0.0)); fn pure_z(sp: Vec3, pv: Vec3) -> f32 { (sp - pv).z } let agent = comp::Agent::from_body(&comp::Body::Ship(ship)) .with_destination(pos) @@ -1534,7 +1535,8 @@ fn handle_spawn_ship( animated: true, }); if let Some(pos) = destination { - let (kp, ki, kd) = comp::agent::pid_coefficients(&comp::Body::Ship(ship)).unwrap_or((1.0, 0.0, 0.0)); + let (kp, ki, kd) = + comp::agent::pid_coefficients(&comp::Body::Ship(ship)).unwrap_or((1.0, 0.0, 0.0)); fn pure_z(sp: Vec3, pv: Vec3) -> f32 { (sp - pv).z } let agent = comp::Agent::from_body(&comp::Body::Ship(ship)) .with_destination(pos) @@ -1575,11 +1577,9 @@ 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), ship, move |_| { + collider + }) .build(); server.notify_client( diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index 5601d755b9..f399b3d3ec 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -14,14 +14,15 @@ use common::{ LightEmitter, Object, Ori, PidController, Poise, Pos, Projectile, Scale, SkillSet, Stats, TradingBehavior, Vel, WaypointArea, }, - event::{EventBus, UpdateCharacterMetadata, NpcBuilder}, + event::{EventBus, NpcBuilder, UpdateCharacterMetadata}, lottery::LootSpec, + mounting::Mounting, outcome::Outcome, resources::{Secs, Time}, rtsim::{RtSimEntity, RtSimVehicle}, uid::Uid, util::Dir, - ViewDistances, mounting::Mounting, + ViewDistances, }; use common_net::{msg::ServerGeneral, sync::WorldSyncExt}; use specs::{Builder, Entity as EcsEntity, WorldExt}; @@ -91,14 +92,18 @@ pub fn handle_loaded_character_data( server.notify_client(entity, ServerGeneral::CharacterDataLoadResult(Ok(metadata))); } -pub fn handle_create_npc( - server: &mut Server, - pos: Pos, - mut npc: NpcBuilder, -) -> EcsEntity { +pub fn handle_create_npc(server: &mut Server, pos: Pos, mut npc: NpcBuilder) -> EcsEntity { let entity = server .state - .create_npc(pos, npc.stats, npc.skill_set, npc.health, npc.poise, npc.inventory, npc.body) + .create_npc( + pos, + npc.stats, + npc.skill_set, + npc.health, + npc.poise, + npc.inventory, + npc.body, + ) .with(npc.scale); if let Some(agent) = &mut npc.agent { @@ -194,7 +199,6 @@ pub fn handle_create_ship( rtsim_vehicle: Option, driver: Option, passangers: Vec, - ) { let mut entity = server .state @@ -213,19 +217,21 @@ pub fn handle_create_ship( } let entity = entity.build(); - if let Some(driver) = driver { let npc_entity = handle_create_npc(server, pos, driver); let uids = server.state.ecs().read_storage::(); if let (Some(rider_uid), Some(mount_uid)) = - (uids.get(npc_entity).copied(), uids.get(entity).copied()) + (uids.get(npc_entity).copied(), uids.get(entity).copied()) { drop(uids); - server.state.link(Mounting { - mount: mount_uid, - rider: rider_uid, - }).expect("Failed to link driver to ship"); + server + .state + .link(Mounting { + mount: mount_uid, + rider: rider_uid, + }) + .expect("Failed to link driver to ship"); } else { panic!("Couldn't get Uid from newly created ship and npc"); } diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs index c1edd8a6af..8a56355aad 100644 --- a/server/src/events/interaction.rs +++ b/server/src/events/interaction.rs @@ -9,8 +9,9 @@ use common::{ dialogue::Subject, inventory::slot::EquipSlot, loot_owner::LootOwnerKind, + pet::is_mountable, tool::ToolKind, - Inventory, LootOwner, Pos, SkillGroupKind, pet::is_mountable, + Inventory, LootOwner, Pos, SkillGroupKind, }, consts::{MAX_MOUNT_RANGE, SOUND_TRAVEL_DIST_PER_VOLUME}, event::EventBus, @@ -119,10 +120,13 @@ pub fn handle_mount(server: &mut Server, rider: EcsEntity, mount: EcsEntity) { Some(comp::Alignment::Owned(owner)) if *owner == rider_uid, ); - let can_ride = state.ecs() + let can_ride = state + .ecs() .read_storage() .get(mount) - .map_or(false, |mount_body| is_mountable(mount_body, state.ecs().read_storage().get(rider))); + .map_or(false, |mount_body| { + is_mountable(mount_body, state.ecs().read_storage().get(rider)) + }); if is_pet && can_ride { drop(uids); diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index 8637890b87..a3b23044d4 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -188,15 +188,8 @@ impl Server { ServerEvent::ExitIngame { entity } => { handle_exit_ingame(self, entity, false); }, - ServerEvent::CreateNpc { - pos, - npc, - } => { - handle_create_npc( - self, - pos, - npc, - ); + ServerEvent::CreateNpc { pos, npc } => { + handle_create_npc(self, pos, npc); }, ServerEvent::CreateShip { pos, diff --git a/server/src/rtsim2/mod.rs b/server/src/rtsim2/mod.rs index 1bbb35e863..3073939e6f 100644 --- a/server/src/rtsim2/mod.rs +++ b/server/src/rtsim2/mod.rs @@ -4,7 +4,7 @@ pub mod tick; use common::{ grid::Grid, - rtsim::{ChunkResource, RtSimEntity, WorldSettings, RtSimVehicle}, + rtsim::{ChunkResource, RtSimEntity, RtSimVehicle, WorldSettings}, slowjob::SlowJobPool, terrain::{Block, TerrainChunk}, vol::RectRasterableVol, diff --git a/server/src/rtsim2/rule/deplete_resources.rs b/server/src/rtsim2/rule/deplete_resources.rs index 1bc0667b40..ebea23e721 100644 --- a/server/src/rtsim2/rule/deplete_resources.rs +++ b/server/src/rtsim2/rule/deplete_resources.rs @@ -1,5 +1,8 @@ use crate::rtsim2::{event::OnBlockChange, ChunkStates}; -use common::{terrain::{TerrainChunk, CoordinateConversions}, vol::RectRasterableVol}; +use common::{ + terrain::{CoordinateConversions, TerrainChunk}, + vol::RectRasterableVol, +}; use rtsim2::{RtState, Rule, RuleError}; pub struct DepleteResources; @@ -7,10 +10,7 @@ pub struct DepleteResources; impl Rule for DepleteResources { fn start(rtstate: &mut RtState) -> Result { rtstate.bind::(|ctx| { - let key = ctx - .event - .wpos - .xy().wpos_to_cpos(); + let key = ctx.event.wpos.xy().wpos_to_cpos(); if let Some(Some(chunk_state)) = ctx.state.resource_mut::().0.get(key) { let mut chunk_res = ctx.state.data().nature.get_chunk_resources(key); // Remove resources diff --git a/server/src/rtsim2/tick.rs b/server/src/rtsim2/tick.rs index fb53d80eb5..2c2e498332 100644 --- a/server/src/rtsim2/tick.rs +++ b/server/src/rtsim2/tick.rs @@ -3,19 +3,20 @@ use super::*; use crate::sys::terrain::NpcData; use common::{ - comp::{self, inventory::loadout::Loadout, skillset::skills, Body, Agent}, - event::{EventBus, ServerEvent, NpcBuilder}, + comp::{self, inventory::loadout::Loadout, skillset::skills, Agent, Body}, + event::{EventBus, NpcBuilder, ServerEvent}, generation::{BodyBuilder, EntityConfig, EntityInfo}, resources::{DeltaTime, Time, TimeOfDay}, rtsim::{RtSimController, RtSimEntity, RtSimVehicle}, slowjob::SlowJobPool, + terrain::CoordinateConversions, trade::{Good, SiteInformation}, - LoadoutBuilder, SkillSetBuilder, terrain::CoordinateConversions, + LoadoutBuilder, SkillSetBuilder, lottery::LootSpec, }; use common_ecs::{Job, Origin, Phase, System}; use rtsim2::data::{ - npc::{SimulationMode, Profession}, - Npc, Sites, Actor, + npc::{Profession, SimulationMode}, + Actor, Npc, Sites, }; use specs::{Join, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage}; use std::{sync::Arc, time::Duration}; @@ -126,9 +127,9 @@ fn profession_agent_mark(profession: Option<&Profession>) -> Option EntityInfo { - let body = npc.get_body(); let pos = comp::Pos(npc.wpos); + let mut rng = npc.rng(3); if let Some(ref profession) = npc.profession { let economy = npc.home.and_then(|home| { let site = sites.get(home)?.world_site?; @@ -137,9 +138,8 @@ fn get_npc_entity_info(npc: &Npc, sites: &Sites, index: IndexRef) -> EntityInfo let config_asset = humanoid_config(profession); - let entity_config = - EntityConfig::from_asset_expect_owned(config_asset).with_body(BodyBuilder::Exact(body)); - let mut rng = npc.rng(3); + let entity_config = EntityConfig::from_asset_expect_owned(config_asset) + .with_body(BodyBuilder::Exact(npc.body)); EntityInfo::at(pos.0) .with_entity_config(entity_config, Some(config_asset), &mut rng) .with_alignment(if matches!(profession, Profession::Cultist) { @@ -151,10 +151,23 @@ fn get_npc_entity_info(npc: &Npc, sites: &Sites, index: IndexRef) -> EntityInfo .with_lazy_loadout(profession_extra_loadout(npc.profession.as_ref())) .with_agent_mark(profession_agent_mark(npc.profession.as_ref())) } else { + let config_asset = match npc.body { + Body::BirdLarge(body) => match body.species { + comp::bird_large::Species::Phoenix => "common.entity.wild.peaceful.phoenix", + comp::bird_large::Species::Cockatrice => "common.entity.wild.aggressive.cockatrice", + comp::bird_large::Species::Roc => "common.entity.wild.aggressive.roc", + // Wildcard match used here as there is an array above + // which limits what species are used + _ => unimplemented!(), + }, + _ => unimplemented!(), + }; + let entity_config = EntityConfig::from_asset_expect_owned(config_asset) + .with_body(BodyBuilder::Exact(npc.body)); + EntityInfo::at(pos.0) - .with_body(body) + .with_entity_config(entity_config, Some(config_asset), &mut rng) .with_alignment(comp::Alignment::Wild) - .with_name("Rtsim NPC") } } @@ -228,7 +241,8 @@ impl<'a> System<'a> for Sys { let npc = data.npcs.npcs.get_mut(npc_id)?; if matches!(npc.mode, SimulationMode::Simulated) { npc.mode = SimulationMode::Loaded; - let entity_info = get_npc_entity_info(npc, &data.sites, index.as_index_ref()); + let entity_info = + get_npc_entity_info(npc, &data.sites, index.as_index_ref()); Some(match NpcData::from_entity_info(entity_info) { NpcData::Data { @@ -244,14 +258,14 @@ impl<'a> System<'a> for Sys { scale, loot, } => NpcBuilder::new(stats, body, alignment) - .with_skill_set(skill_set) - .with_health(health) - .with_poise(poise) - .with_inventory(inventory) - .with_agent(agent) - .with_scale(scale) - .with_loot(loot) - .with_rtsim(RtSimEntity(npc_id)), + .with_skill_set(skill_set) + .with_health(health) + .with_poise(poise) + .with_inventory(inventory) + .with_agent(agent) + .with_scale(scale) + .with_loot(loot) + .with_rtsim(RtSimEntity(npc_id)), // EntityConfig can't represent Waypoints at all // as of now, and if someone will try to spawn // rtsim waypoint it is definitely error. @@ -265,11 +279,17 @@ impl<'a> System<'a> for Sys { emitter.emit(ServerEvent::CreateShip { pos: comp::Pos(vehicle.wpos), - ship: vehicle.get_ship(), + ship: vehicle.body, // agent: None,//Some(Agent::from_body(&Body::Ship(ship))), rtsim_entity: Some(RtSimVehicle(vehicle_id)), driver: vehicle.driver.and_then(&mut actor_info), - passangers: vehicle.riders.iter().copied().filter(|actor| vehicle.driver != Some(*actor)).filter_map(actor_info).collect(), + passangers: vehicle + .riders + .iter() + .copied() + .filter(|actor| vehicle.driver != Some(*actor)) + .filter_map(actor_info) + .collect(), }); } } @@ -301,14 +321,14 @@ impl<'a> System<'a> for Sys { } => ServerEvent::CreateNpc { pos, npc: NpcBuilder::new(stats, body, alignment) - .with_skill_set(skill_set) - .with_health(health) - .with_poise(poise) - .with_inventory(inventory) - .with_agent(agent) - .with_scale(scale) - .with_loot(loot) - .with_rtsim(RtSimEntity(npc_id)), + .with_skill_set(skill_set) + .with_health(health) + .with_poise(poise) + .with_inventory(inventory) + .with_agent(agent) + .with_scale(scale) + .with_loot(loot) + .with_rtsim(RtSimEntity(npc_id)), }, // EntityConfig can't represent Waypoints at all // as of now, and if someone will try to spawn @@ -319,10 +339,9 @@ impl<'a> System<'a> for Sys { } // Synchronise rtsim NPC with entity data - for (pos, rtsim_vehicle) in - (&positions, &rtsim_vehicles).join() - { - data.npcs.vehicles + for (pos, rtsim_vehicle) in (&positions, &rtsim_vehicles).join() { + data.npcs + .vehicles .get_mut(rtsim_vehicle.0) .filter(|npc| matches!(npc.mode, SimulationMode::Loaded)) .map(|vehicle| { diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index dde39a5319..9fbd288978 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -19,7 +19,7 @@ use common_base::prof_span; use common_ecs::{Job, Origin, ParMode, Phase, System}; use rand::thread_rng; use rayon::iter::ParallelIterator; -use specs::{Join, ParJoin, Read, WriteExpect, WriteStorage, saveload::MarkerAllocator}; +use specs::{saveload::MarkerAllocator, Join, ParJoin, Read, WriteExpect, WriteStorage}; /// This system will allow NPCs to modify their controller #[derive(Default)] @@ -101,7 +101,13 @@ impl<'a> System<'a> for Sys { let mut rng = thread_rng(); // The entity that is moving, if riding it's the mount, otherwise it's itself - let moving_entity = is_rider.and_then(|is_rider| read_data.uid_allocator.retrieve_entity_internal(is_rider.mount.into())).unwrap_or(entity); + let moving_entity = is_rider + .and_then(|is_rider| { + read_data + .uid_allocator + .retrieve_entity_internal(is_rider.mount.into()) + }) + .unwrap_or(entity); let moving_body = read_data.bodies.get(moving_entity); @@ -147,8 +153,13 @@ impl<'a> System<'a> for Sys { Some(CharacterState::GlideWield(_) | CharacterState::Glide(_)) ) && physics_state.on_ground.is_none(); - if let Some((kp, ki, kd)) = moving_body.and_then(comp::agent::pid_coefficients) { - if agent.position_pid_controller.as_ref().map_or(false, |pid| (pid.kp, pid.ki, pid.kd) != (kp, ki, kd)) { + if let Some((kp, ki, kd)) = moving_body.and_then(comp::agent::pid_coefficients) + { + if agent + .position_pid_controller + .as_ref() + .map_or(false, |pid| (pid.kp, pid.ki, pid.kd) != (kp, ki, kd)) + { agent.position_pid_controller = None; } let pid = agent.position_pid_controller.get_or_insert_with(|| { diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index c864352fcd..c9c5e8be78 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -19,7 +19,7 @@ use common::{ comp::{ self, agent, bird_medium, skillset::skills, BehaviorCapability, ForceUpdate, Pos, Waypoint, }, - event::{EventBus, ServerEvent, NpcBuilder}, + event::{EventBus, NpcBuilder, ServerEvent}, generation::EntityInfo, lottery::LootSpec, resources::{Time, TimeOfDay}, @@ -225,7 +225,7 @@ impl<'a> System<'a> for Sys { .with_agent(agent) .with_scale(scale) .with_anchor(comp::Anchor::Chunk(key)) - .with_loot(loot) + .with_loot(loot), }); }, } diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index fa499f46ae..cc8173d168 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -1914,6 +1914,13 @@ impl WorldSim { } } + /// Get the altitude of the surface, could be water or ground. + pub fn get_surface_alt_approx(&self, wpos: Vec2) -> Option { + self.get_interpolated(wpos, |chunk| chunk.alt) + .zip(self.get_interpolated(wpos, |chunk| chunk.water_alt)) + .map(|(alt, water_alt)| alt.max(water_alt)) + } + pub fn get_alt_approx(&self, wpos: Vec2) -> Option { self.get_interpolated(wpos, |chunk| chunk.alt) }