big birds!

This commit is contained in:
Isse 2023-03-09 10:20:05 +01:00 committed by Joshua Barretto
parent 259bb6fce4
commit dda1be58d4
16 changed files with 370 additions and 189 deletions

View File

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

View File

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

View File

@ -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<f32>,
pub body: comp::Body,
pub profession: Option<Profession>,
pub home: Option<SiteId>,
pub faction: Option<FactionId>,
@ -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<f32>) -> Self {
pub fn new(seed: u32, wpos: Vec3<f32>, 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<Option<VehicleId>>) -> 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<Option<VehicleId>>) -> 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<f32>,
pub kind: VehicleKind,
pub body: comp::ship::Body,
#[serde(skip_serializing, skip_deserializing)]
pub chunk_pos: Option<Vec2<i32>>,
@ -216,41 +210,36 @@ pub struct Vehicle {
// TODO: Find a way to detect riders when the vehicle is loaded
pub riders: Vec<Actor>,
/// 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<f32>, kind: VehicleKind) -> Self {
pub fn new(wpos: Vec3<f32>, 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<GridCell> { 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<f32>, radius: f32) -> impl Iterator<Item = NpcId> + '_ {
let chunk_pos = wpos.as_::<i32>() / common::terrain::TerrainChunkSize::RECT_SIZE.as_::<i32>();
let chunk_pos =
wpos.as_::<i32>() / common::terrain::TerrainChunkSize::RECT_SIZE.as_::<i32>();
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::<Vec<_>>()
})
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::<Vec<_>>()
})
})
.flatten()
}

View File

@ -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));
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).with_home(site_id).with_profession(Profession::Captain).steering(vehicle_id));
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

View File

@ -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<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(|| 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<i32>, to: Vec2<i32>, chunk_height: impl Fn(Vec2<i32>) -> Option<i32>) -> Box<dyn 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)| 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<i32>, to: Vec2<i32>, chunk_height: impl Fn(Vec2<i32>) -
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_::<i32>() / TerrainChunkSize::RECT_SIZE.as_::<i32>();
let start_chunk =
ctx.npc.wpos.xy().as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_::<i32>();
let end_chunk = site.wpos / TerrainChunkSize::RECT_SIZE.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
})
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_::<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,
))
} 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,
))
} 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

View File

@ -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_::<i32>() / TerrainChunkSize::RECT_SIZE.as_::<i32>();
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();
}
}

View File

@ -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<f32>, pv: Vec3<f32>) -> 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<f32>, pv: Vec3<f32>) -> 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(

View File

@ -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<RtSimVehicle>,
driver: Option<NpcBuilder>,
passangers: Vec<NpcBuilder>,
) {
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::<Uid>();
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");
}

View File

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

View File

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

View File

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

View File

@ -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<Self, RuleError> {
rtstate.bind::<Self, OnBlockChange>(|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::<ChunkStates>().0.get(key) {
let mut chunk_res = ctx.state.data().nature.get_chunk_resources(key);
// Remove resources

View File

@ -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<comp::agent:
}
fn get_npc_entity_info(npc: &Npc, sites: &Sites, index: IndexRef) -> 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| {

View File

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

View File

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

View File

@ -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<i32>) -> Option<f32> {
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<i32>) -> Option<f32> {
self.get_interpolated(wpos, |chunk| chunk.alt)
}