mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
big birds!
This commit is contained in:
parent
259bb6fce4
commit
dda1be58d4
@ -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)]
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
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 {
|
||||
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,23 +263,25 @@ 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()
|
||||
self.npc_grid.get(chunk_pos + neighbor).map(|cell| {
|
||||
cell.npcs
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|npc| {
|
||||
self.npcs.get(*npc)
|
||||
self.npcs
|
||||
.get(*npc)
|
||||
.map_or(false, |npc| npc.wpos.xy().distance_squared(wpos) < r_sqr)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
|
@ -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),
|
||||
);
|
||||
|
||||
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, VehicleKind::Airship));
|
||||
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
|
||||
|
@ -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
|
||||
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()
|
||||
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
|
||||
})
|
||||
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
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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,7 +217,6 @@ pub fn handle_create_ship(
|
||||
}
|
||||
let entity = entity.build();
|
||||
|
||||
|
||||
if let Some(driver) = driver {
|
||||
let npc_entity = handle_create_npc(server, pos, driver);
|
||||
|
||||
@ -222,10 +225,13 @@ pub fn handle_create_ship(
|
||||
(uids.get(npc_entity).copied(), uids.get(entity).copied())
|
||||
{
|
||||
drop(uids);
|
||||
server.state.link(Mounting {
|
||||
server
|
||||
.state
|
||||
.link(Mounting {
|
||||
mount: mount_uid,
|
||||
rider: rider_uid,
|
||||
}).expect("Failed to link driver to ship");
|
||||
})
|
||||
.expect("Failed to link driver to ship");
|
||||
} else {
|
||||
panic!("Couldn't get Uid from newly created ship and npc");
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
@ -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(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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| {
|
||||
|
@ -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(|| {
|
||||
|
@ -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),
|
||||
});
|
||||
},
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user