Began adding rtsim2 NPCs, scale command

This commit is contained in:
Joshua Barretto 2022-08-11 00:40:30 +01:00
parent 9d3dadfaba
commit 87a6143375
20 changed files with 354 additions and 99 deletions

2
Cargo.lock generated
View File

@ -6949,9 +6949,11 @@ dependencies = [
"atomic_refcell",
"enum-map",
"hashbrown 0.12.3",
"rand 0.8.5",
"rmp-serde",
"ron 0.8.0",
"serde",
"slotmap 1.0.6",
"tracing",
"vek 0.15.8",
"veloren-common",

View File

@ -298,6 +298,7 @@ pub enum ServerChatCommand {
RevokeBuildAll,
Safezone,
Say,
Scale,
ServerPhysics,
SetMotd,
Ship,
@ -727,6 +728,9 @@ impl ServerChatCommand {
ServerChatCommand::Lightning => {
cmd(vec![], "Lightning strike at current position", Some(Admin))
},
ServerChatCommand::Scale => {
cmd(vec![Float("factor", 1.0, Required)], "Scale your character", Some(Admin))
},
}
}
@ -808,6 +812,7 @@ impl ServerChatCommand {
ServerChatCommand::DeleteLocation => "delete_location",
ServerChatCommand::WeatherZone => "weather_zone",
ServerChatCommand::Lightning => "lightning",
ServerChatCommand::Scale => "scale",
}
}

View File

@ -86,7 +86,10 @@ impl RtSimController {
#[derive(Copy, Clone, Debug, Serialize, Deserialize, enum_map::Enum)]
pub enum ChunkResource {
#[serde(rename = "0")]
Grass,
#[serde(rename = "1")]
Flax,
#[serde(rename = "2")]
Cotton,
}

View File

@ -6,7 +6,7 @@ use crate::{
ActiveAbilities, Beam, Body, CharacterState, Combo, ControlAction, Controller,
ControllerInputs, Density, Energy, Health, InputAttr, InputKind, Inventory,
InventoryAction, Mass, Melee, Ori, PhysicsState, Pos, SkillSet, Stance, StateUpdate, Stats,
Vel,
Vel, Scale,
},
link::Is,
mounting::Rider,
@ -123,6 +123,7 @@ pub struct JoinData<'a> {
pub pos: &'a Pos,
pub vel: &'a Vel,
pub ori: &'a Ori,
pub scale: Option<&'a Scale>,
pub mass: &'a Mass,
pub density: &'a Density,
pub dt: &'a DeltaTime,
@ -155,6 +156,7 @@ pub struct JoinStruct<'a> {
pub pos: &'a mut Pos,
pub vel: &'a mut Vel,
pub ori: &'a mut Ori,
pub scale: Option<&'a Scale>,
pub mass: &'a Mass,
pub density: FlaggedAccessMut<'a, &'a mut Density, Density>,
pub energy: FlaggedAccessMut<'a, &'a mut Energy, Energy>,
@ -191,6 +193,7 @@ impl<'a> JoinData<'a> {
pos: j.pos,
vel: j.vel,
ori: j.ori,
scale: j.scale,
mass: j.mass,
density: &j.density,
energy: &j.energy,

View File

@ -122,10 +122,10 @@ impl CharacterBehavior for Data {
// Apply Vertical Climbing Movement
match climb {
Climb::Down => {
update.vel.0.z += data.dt.0 * (GRAVITY - self.static_data.movement_speed.powi(2))
update.vel.0.z += data.dt.0 * (GRAVITY - self.static_data.movement_speed.powi(2) * data.scale.map_or(1.0, |s| s.0))
},
Climb::Up => {
update.vel.0.z += data.dt.0 * (GRAVITY + self.static_data.movement_speed.powi(2))
update.vel.0.z += data.dt.0 * (GRAVITY + self.static_data.movement_speed.powi(2) * data.scale.map_or(1.0, |s| s.0))
},
Climb::Hold => update.vel.0.z += data.dt.0 * GRAVITY,
}

View File

@ -385,7 +385,10 @@ fn basic_move(data: &JoinData<'_>, update: &mut StateUpdate, efficiency: f32) {
let accel = if let Some(block) = data.physics.on_ground {
// FRIC_GROUND temporarily used to normalize things around expected values
data.body.base_accel() * block.get_traction() * block.get_friction() / FRIC_GROUND
data.body.base_accel()
* data.scale.map_or(1.0, |s| s.0)
* block.get_traction()
* block.get_friction() / FRIC_GROUND
} else {
data.body.air_accel()
} * efficiency;
@ -435,7 +438,7 @@ pub fn handle_forced_movement(
data.body.base_accel() * block.get_traction() * block.get_friction() / FRIC_GROUND
}) {
update.vel.0 +=
Vec2::broadcast(data.dt.0) * accel * Vec2::from(*data.ori) * strength;
Vec2::broadcast(data.dt.0) * accel * data.scale.map_or(1.0, |s| s.0) * Vec2::from(*data.ori) * strength;
}
},
ForcedMovement::Reverse(strength) => {
@ -445,7 +448,7 @@ pub fn handle_forced_movement(
data.body.base_accel() * block.get_traction() * block.get_friction() / FRIC_GROUND
}) {
update.vel.0 +=
Vec2::broadcast(data.dt.0) * accel * -Vec2::from(*data.ori) * strength;
Vec2::broadcast(data.dt.0) * accel * data.scale.map_or(1.0, |s| s.0) * -Vec2::from(*data.ori) * strength;
}
},
ForcedMovement::Sideways(strength) => {
@ -467,7 +470,7 @@ pub fn handle_forced_movement(
}
};
update.vel.0 += Vec2::broadcast(data.dt.0) * accel * direction * strength;
update.vel.0 += Vec2::broadcast(data.dt.0) * accel * data.scale.map_or(1.0, |s| s.0) * direction * strength;
}
},
ForcedMovement::DirectedReverse(strength) => {
@ -516,6 +519,7 @@ pub fn handle_forced_movement(
dir.y,
vertical,
)
* data.scale.map_or(1.0, |s| s.0)
// Multiply decreasing amount linearly over time (with average of 1)
* 2.0 * progress
// Apply direction
@ -528,8 +532,9 @@ pub fn handle_forced_movement(
* (1.0 - data.inputs.look_dir.z.abs());
},
ForcedMovement::Hover { move_input } => {
update.vel.0 = Vec3::new(data.vel.0.x, data.vel.0.y, 0.0)
+ move_input * data.inputs.move_dir.try_normalized().unwrap_or_default();
update.vel.0 = Vec3::new(data.vel.0.x, data.vel.0.y, 0.0) + move_input
* data.scale.map_or(1.0, |s| s.0)
* data.inputs.move_dir.try_normalized().unwrap_or_default();
},
}
}
@ -570,6 +575,7 @@ pub fn handle_orientation(
};
// unit is multiples of 180°
let half_turns_per_tick = data.body.base_ori_rate()
/ data.scale.map_or(1.0, |s| s.0)
* efficiency
* if data.physics.on_ground.is_some() {
1.0
@ -605,7 +611,7 @@ fn swim_move(
) -> bool {
let efficiency = efficiency * data.stats.move_speed_modifier * data.stats.friction_modifier;
if let Some(force) = data.body.swim_thrust() {
let force = efficiency * force;
let force = efficiency * force * data.scale.map_or(1.0, |s| s.0);
let mut water_accel = force / data.mass.0;
if let Ok(level) = data.skill_set.skill_level(Skill::Swim(SwimSkill::Speed)) {
@ -1068,7 +1074,9 @@ pub fn handle_jump(
.map(|impulse| {
output_events.emit_local(LocalEvent::Jump(
data.entity,
strength * impulse / data.mass.0 * data.stats.move_speed_modifier,
strength * impulse / data.mass.0
* data.scale.map_or(1.0, |s| s.0.sqrt())
* data.stats.move_speed_modifier,
));
})
.is_some()

View File

@ -10,7 +10,7 @@ use common::{
inventory::item::{tool::AbilityMap, MaterialStatManifest},
ActiveAbilities, Beam, Body, CharacterState, Combo, Controller, Density, Energy, Health,
Inventory, InventoryManip, Mass, Melee, Ori, PhysicsState, Poise, Pos, SkillSet, Stance,
StateUpdate, Stats, Vel,
StateUpdate, Stats, Vel, Scale,
},
event::{EventBus, LocalEvent, ServerEvent},
link::Is,
@ -37,6 +37,7 @@ pub struct ReadData<'a> {
healths: ReadStorage<'a, Health>,
bodies: ReadStorage<'a, Body>,
masses: ReadStorage<'a, Mass>,
scales: ReadStorage<'a, Scale>,
physics_states: ReadStorage<'a, PhysicsState>,
melee_attacks: ReadStorage<'a, Melee>,
beams: ReadStorage<'a, Beam>,
@ -116,7 +117,7 @@ impl<'a> System<'a> for Sys {
health,
body,
physics,
(stat, skill_set, active_abilities, is_rider),
(scale, stat, skill_set, active_abilities, is_rider),
combo,
) in (
&read_data.entities,
@ -134,6 +135,7 @@ impl<'a> System<'a> for Sys {
&read_data.bodies,
&read_data.physics_states,
(
read_data.scales.maybe(),
&read_data.stats,
&read_data.skill_sets,
read_data.active_abilities.maybe(),
@ -183,6 +185,7 @@ impl<'a> System<'a> for Sys {
pos,
vel,
ori,
scale,
mass,
density,
energy,

View File

@ -1350,19 +1350,15 @@ fn box_voxel_collision<T: BaseVol<Vox = Block> + ReadVol>(
read: &PhysicsRead,
ori: &Ori,
) {
// FIXME: Review these
#![allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
let scale = read.scales.get(entity).map_or(1.0, |s| s.0);
//prof_span!("box_voxel_collision");
// Convience function to compute the player aabb
fn player_aabb(pos: Vec3<f32>, radius: f32, z_range: Range<f32>) -> Aabb<f32> {
fn player_aabb(pos: Vec3<f32>, radius: f32, z_range: Range<f32>, scale: f32) -> Aabb<f32> {
Aabb {
min: pos + Vec3::new(-radius, -radius, z_range.start),
max: pos + Vec3::new(radius, radius, z_range.end),
min: pos + Vec3::new(-radius, -radius, z_range.start) * scale,
max: pos + Vec3::new(radius, radius, z_range.end) * scale,
}
}
@ -1383,8 +1379,9 @@ fn box_voxel_collision<T: BaseVol<Vox = Block> + ReadVol>(
near_aabb: Aabb<i32>,
radius: f32,
z_range: Range<f32>,
scale: f32,
) -> bool {
let player_aabb = player_aabb(pos, radius, z_range);
let player_aabb = player_aabb(pos, radius, z_range, scale);
// Calculate the world space near aabb
let near_aabb = move_aabb(near_aabb, pos);
@ -1451,7 +1448,7 @@ fn box_voxel_collision<T: BaseVol<Vox = Block> + ReadVol>(
let try_colliding_block = |pos: &Pos| {
//prof_span!("most colliding check");
// Calculate the player's AABB
let player_aabb = player_aabb(pos.0, radius, z_range.clone());
let player_aabb = player_aabb(pos.0, radius, z_range.clone(), scale);
// Determine the block that we are colliding with most
// (based on minimum collision axis)
@ -1501,7 +1498,7 @@ fn box_voxel_collision<T: BaseVol<Vox = Block> + ReadVol>(
.flatten()
{
// Calculate the player's AABB
let player_aabb = player_aabb(pos.0, radius, z_range.clone());
let player_aabb = player_aabb(pos.0, radius, z_range.clone(), scale);
// Find the intrusion vector of the collision
let dir = player_aabb.collision_vector_with_aabb(block_aabb);
@ -1547,6 +1544,7 @@ fn box_voxel_collision<T: BaseVol<Vox = Block> + ReadVol>(
near_aabb,
radius,
z_range.clone(),
scale,
)
}
// ...and there is a collision with a block beneath our current hitbox...
@ -1559,6 +1557,7 @@ fn box_voxel_collision<T: BaseVol<Vox = Block> + ReadVol>(
near_aabb,
radius,
z_range.clone(),
scale,
)
} {
// ...block-hop!
@ -1613,6 +1612,7 @@ fn box_voxel_collision<T: BaseVol<Vox = Block> + ReadVol>(
near_aabb,
radius,
z_range.clone(),
scale,
)
} {
//prof_span!("snap!!");
@ -1630,7 +1630,7 @@ fn box_voxel_collision<T: BaseVol<Vox = Block> + ReadVol>(
}
// Find liquid immersion and wall collision all in one round of iteration
let player_aabb = player_aabb(pos.0, radius, z_range.clone());
let player_aabb = player_aabb(pos.0, radius, z_range.clone(), scale);
// Calculate the world space near_aabb
let near_aabb = move_aabb(near_aabb, pos.0);

View File

@ -15,3 +15,5 @@ rmp-serde = "1.1.0"
anymap2 = "0.13"
tracing = "0.1"
atomic_refcell = "0.1"
slotmap = { version = "1.0.6", features = ["serde"] }
rand = { version = "0.8", features = ["small_rng"] }

View File

@ -1,16 +0,0 @@
use hashbrown::HashMap;
use serde::{Serialize, Deserialize};
#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct ActorId {
pub idx: u32,
pub gen: u32,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Actor {}
#[derive(Clone, Serialize, Deserialize)]
pub struct Actors {
pub actors: HashMap<ActorId, Actor>,
}

View File

@ -1,19 +1,30 @@
pub mod actor;
pub mod npc;
pub mod nature;
pub use self::{
actor::{Actor, ActorId, Actors},
npc::{Npc, NpcId, Npcs},
nature::Nature,
};
use self::helper::Latest;
use serde::{Serialize, Deserialize};
use std::io::{Read, Write};
use enum_map::{EnumMap, EnumArray, enum_map};
use serde::{Serialize, Deserialize, ser, de};
use std::{
io::{Read, Write},
marker::PhantomData,
cmp::PartialEq,
fmt,
};
#[derive(Copy, Clone, Serialize, Deserialize)]
pub enum Actor {
Npc(NpcId),
Character(common::character::CharacterId),
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Data {
pub nature: Nature,
pub actors: Actors,
pub npcs: Npcs,
}
pub type ReadError = rmp_serde::decode::Error;
@ -25,6 +36,57 @@ impl Data {
}
pub fn write_to<W: Write>(&self, mut writer: W) -> Result<(), WriteError> {
rmp_serde::encode::write(&mut writer, self)
rmp_serde::encode::write_named(&mut writer, self)
}
}
// fn rugged_ser_enum_map<K: EnumArray<V> + Serialize, V: PartialEq + Default + Serialize, S: ser::Serializer>(map: &EnumMap<K, V>, mut ser: S) -> Result<S::Ok, S::Error> {
// ser.collect_map(map
// .iter()
// .filter(|(k, v)| v != &&V::default())
// .map(|(k, v)| (k, v)))
// }
fn rugged_ser_enum_map<
K: EnumArray<V> + Serialize,
V: From<i16> + PartialEq + Serialize,
S: ser::Serializer,
const DEFAULT: i16,
>(map: &EnumMap<K, V>, ser: S) -> Result<S::Ok, S::Error> {
ser.collect_map(map
.iter()
.filter(|(k, v)| v != &&V::from(DEFAULT))
.map(|(k, v)| (k, v)))
}
fn rugged_de_enum_map<
'a,
K: EnumArray<V> + EnumArray<Option<V>> + Deserialize<'a>,
V: From<i16> + Deserialize<'a>,
D: de::Deserializer<'a>,
const DEFAULT: i16,
>(mut de: D) -> Result<EnumMap<K, V>, D::Error> {
struct Visitor<K, V, const DEFAULT: i16>(PhantomData<(K, V)>);
impl<'de, K, V, const DEFAULT: i16> de::Visitor<'de> for Visitor<K, V, DEFAULT>
where
K: EnumArray<V> + EnumArray<Option<V>> + Deserialize<'de>,
V: From<i16> + Deserialize<'de>,
{
type Value = EnumMap<K, V>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "a map")
}
fn visit_map<M: de::MapAccess<'de>>(self, mut access: M) -> Result<Self::Value, M::Error> {
let mut entries = EnumMap::default();
while let Some((key, value)) = access.next_entry()? {
entries[key] = Some(value);
}
Ok(enum_map! { key => entries[key].take().unwrap_or_else(|| V::from(DEFAULT)) })
}
}
de.deserialize_map(Visitor::<_, _, DEFAULT>(PhantomData))
}

View File

@ -9,6 +9,9 @@ use vek::*;
/// Represents the state of 'natural' elements of the world such as plant/animal/resource populations, weather systems,
/// etc.
///
/// Where possible, this data does not define the state of natural aspects of the world, but instead defines
/// 'modifications' that sit on top of the world data generated by initial generation.
#[derive(Clone, Serialize, Deserialize)]
pub struct Nature {
chunks: Grid<Chunk>,
@ -43,5 +46,17 @@ impl Nature {
#[derive(Clone, Serialize, Deserialize)]
pub struct Chunk {
/// Represent the 'naturally occurring' resource proportion that exists in this chunk.
///
/// 0.0 => None of the resources generated by terrain generation should be present
/// 1.0 => All of the resources generated by terrain generation should be present
///
/// It's important to understand this this number does not represent the total amount of a resource present in a
/// chunk, nor is it even proportional to the amount of the resource present. To get the total amount of the
/// resource in a chunk, one must first multiply this factor by the amount of 'natural' resources given by terrain
/// generation. This value represents only the variable 'depletion' factor of that resource, which shall change
/// over time as the world evolves and players interact with it.
#[serde(rename = "r")]
#[serde(serialize_with = "crate::data::rugged_ser_enum_map::<_, _, _, 1>")]
#[serde(deserialize_with = "crate::data::rugged_de_enum_map::<_, _, _, 1>")]
res: EnumMap<ChunkResource, f32>,
}

50
rtsim/src/data/npc.rs Normal file
View File

@ -0,0 +1,50 @@
use hashbrown::HashMap;
use serde::{Serialize, Deserialize};
use slotmap::HopSlotMap;
use vek::*;
use std::ops::{Deref, DerefMut};
use common::uid::Uid;
slotmap::new_key_type! { pub struct NpcId; }
#[derive(Clone, Serialize, Deserialize)]
pub struct Npc {
pub wpos: Vec3<f32>,
#[serde(skip_serializing, skip_deserializing)]
pub mode: NpcMode,
}
impl Npc {
pub fn at(wpos: Vec3<f32>) -> Self {
Self { wpos, mode: NpcMode::Simulated }
}
}
#[derive(Copy, Clone, Default)]
pub enum NpcMode {
/// The NPC is unloaded and is being simulated via rtsim.
#[default]
Simulated,
/// The NPC has been loaded into the game world as an ECS entity.
Loaded,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Npcs {
pub npcs: HopSlotMap<NpcId, Npc>,
}
impl Npcs {
pub fn spawn(&mut self, npc: Npc) -> NpcId {
self.npcs.insert(npc)
}
}
impl Deref for Npcs {
type Target = HopSlotMap<NpcId, Npc>;
fn deref(&self) -> &Self::Target { &self.npcs }
}
impl DerefMut for Npcs {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.npcs }
}

View File

@ -1,14 +1,42 @@
use crate::data::{Actors, Data, Nature};
use crate::data::{Npcs, Npc, Data, Nature};
use hashbrown::HashMap;
use world::World;
use rand::prelude::*;
use world::{
site::SiteKind,
IndexRef,
World,
};
impl Data {
pub fn generate(world: &World) -> Self {
Self {
pub fn generate(index: IndexRef, world: &World) -> Self {
let mut seed = [0; 32];
seed.iter_mut().zip(&mut index.seed.to_le_bytes()).for_each(|(dst, src)| *dst = *src);
let mut rng = SmallRng::from_seed(seed);
let mut this = Self {
nature: Nature::generate(world),
actors: Actors {
actors: HashMap::default(),
},
npcs: Npcs { npcs: Default::default() },
};
for (site_id, site) in world
.civs()
.sites
.iter()
.filter_map(|(site_id, site)| site.site_tmp.map(|id| (site_id, &index.sites[id])))
{
match &site.kind {
SiteKind::Refactor(site2) => {
let wpos = site.get_origin()
.map(|e| e as f32 + 0.5)
.with_z(world.sim().get_alt_approx(site.get_origin()).unwrap_or(0.0));
// TODO: Better API
this.npcs.spawn(Npc::at(wpos));
println!("Spawned rtsim NPC at {:?}", wpos);
}
_ => {},
}
}
this
}
}

View File

@ -196,6 +196,7 @@ fn do_command(
ServerChatCommand::DeleteLocation => handle_delete_location,
ServerChatCommand::WeatherZone => handle_weather_zone,
ServerChatCommand::Lightning => handle_lightning,
ServerChatCommand::Scale => handle_scale,
};
handler(server, client, target, args, cmd)
@ -3835,3 +3836,26 @@ fn handle_body(
Err(action.help_string())
}
}
fn handle_scale(
server: &mut Server,
client: EcsEntity,
target: EcsEntity,
args: Vec<String>,
action: &ServerChatCommand,
) -> CmdResult<()> {
if let Some(scale) = parse_cmd_args!(args, f32) {
let _ = server
.state
.ecs_mut()
.write_storage::<comp::Scale>()
.insert(target, comp::Scale(scale));
server.notify_client(
client,
ServerGeneral::server_msg(ChatType::CommandInfo, format!("Set scale to {}", scale)),
);
Ok(())
} else {
Err(action.help_string())
}
}

View File

@ -564,7 +564,7 @@ impl Server {
// Init rtsim, loading it from disk if possible
#[cfg(feature = "worldgen")]
{
match rtsim2::RtSim::new(&world, data_dir.to_owned()) {
match rtsim2::RtSim::new(index.as_index_ref(), &world, data_dir.to_owned()) {
Ok(rtsim) => state.ecs_mut().insert(rtsim),
Err(err) => {
error!("Failed to load rtsim: {}", err);

View File

@ -27,7 +27,7 @@ use std::{
use enum_map::EnumMap;
use tracing::{error, warn, info, debug};
use vek::*;
use world::World;
use world::{IndexRef, World};
pub struct RtSim {
file_path: PathBuf,
@ -36,43 +36,47 @@ pub struct RtSim {
}
impl RtSim {
pub fn new(world: &World, data_dir: PathBuf) -> Result<Self, ron::Error> {
pub fn new(index: IndexRef, world: &World, data_dir: PathBuf) -> Result<Self, ron::Error> {
let file_path = Self::get_file_path(data_dir);
info!("Looking for rtsim data in {}...", file_path.display());
let data = 'load: {
match File::open(&file_path) {
Ok(file) => {
info!("Rtsim data found. Attempting to load...");
match Data::from_reader(io::BufReader::new(file)) {
Ok(data) => { info!("Rtsim data loaded."); break 'load data },
Err(e) => {
error!("Rtsim data failed to load: {}", e);
let mut i = 0;
loop {
let mut backup_path = file_path.clone();
backup_path.set_extension(if i == 0 {
format!("backup_{}", i)
} else {
"ron_backup".to_string()
});
if !backup_path.exists() {
fs::rename(&file_path, &backup_path)?;
warn!("Failed rtsim data was moved to {}", backup_path.display());
info!("A fresh rtsim data will now be generated.");
break;
if std::env::var("RTSIM_NOLOAD").map_or(true, |v| v != "1") {
match File::open(&file_path) {
Ok(file) => {
info!("Rtsim data found. Attempting to load...");
match Data::from_reader(io::BufReader::new(file)) {
Ok(data) => { info!("Rtsim data loaded."); break 'load data },
Err(e) => {
error!("Rtsim data failed to load: {}", e);
let mut i = 0;
loop {
let mut backup_path = file_path.clone();
backup_path.set_extension(if i == 0 {
format!("backup_{}", i)
} else {
"ron_backup".to_string()
});
if !backup_path.exists() {
fs::rename(&file_path, &backup_path)?;
warn!("Failed rtsim data was moved to {}", backup_path.display());
info!("A fresh rtsim data will now be generated.");
break;
}
i += 1;
}
i += 1;
}
},
}
},
Err(e) if e.kind() == io::ErrorKind::NotFound =>
info!("No rtsim data found. Generating from world..."),
Err(e) => return Err(e.into()),
},
}
},
Err(e) if e.kind() == io::ErrorKind::NotFound =>
info!("No rtsim data found. Generating from world..."),
Err(e) => return Err(e.into()),
}
} else {
warn!("'RTSIM_NOLOAD' is set, skipping loading of rtsim state (old state will be overwritten).");
}
let data = Data::generate(&world);
let data = Data::generate(index, &world);
info!("Rtsim data generated.");
data
};

View File

@ -10,6 +10,7 @@ use common::{
slowjob::SlowJobPool,
};
use common_ecs::{Job, Origin, Phase, System};
use rtsim2::data::npc::NpcMode;
use specs::{Join, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage};
use std::{sync::Arc, time::Duration};
@ -32,8 +33,9 @@ impl<'a> System<'a> for Sys {
fn run(
_job: &mut Job<Self>,
(dt, time, server_event_bus, mut rtsim, world, index, slow_jobs): Self::SystemData,
(dt, time, mut server_event_bus, mut rtsim, world, index, slow_jobs): Self::SystemData,
) {
let mut emitter = server_event_bus.emitter();
let rtsim = &mut *rtsim;
rtsim.state.tick(dt.0);
@ -42,6 +44,38 @@ impl<'a> System<'a> for Sys {
rtsim.save(&slow_jobs);
}
let chunk_states = rtsim.state.resource::<ChunkStates>();
for (npc_id, npc) in rtsim.state.data_mut().npcs.iter_mut() {
let chunk = npc.wpos
.xy()
.map2(TerrainChunk::RECT_SIZE, |e, sz| (e as i32).div_euclid(sz as i32));
// Load the NPC into the world if it's in a loaded chunk and is not already loaded
if matches!(npc.mode, NpcMode::Simulated) && chunk_states.0.get(chunk).map_or(false, |c| c.is_some()) {
npc.mode = NpcMode::Loaded;
println!("Loading in rtsim NPC at {:?}", npc.wpos);
let body = comp::Body::Object(comp::object::Body::Scarecrow);
emitter.emit(ServerEvent::CreateNpc {
pos: comp::Pos(npc.wpos),
stats: comp::Stats::new("Rtsim NPC".to_string()),
skill_set: comp::SkillSet::default(),
health: None,
poise: comp::Poise::new(body),
inventory: comp::Inventory::with_empty(),
body,
agent: None,
alignment: comp::Alignment::Wild,
scale: comp::Scale(10.0),
anchor: None,
loot: Default::default(),
rtsim_entity: None, // For now, the old one is used!
projectile: None,
});
}
}
// rtsim.tick += 1;
// Update unloaded rtsim entities, in groups at a time

View File

@ -776,7 +776,7 @@ impl FigureMgr {
.enumerate()
{
// Velocity relative to the current ground
let rel_vel = anim::vek::Vec3::<f32>::from(vel.0 - physics.ground_vel);
let rel_vel = anim::vek::Vec3::<f32>::from(vel.0 - physics.ground_vel) / scale.map_or(1.0, |s| s.0);
let look_dir = controller.map(|c| c.inputs.look_dir).unwrap_or_default();
let is_viewpoint = scene_data.viewpoint_entity == entity;
@ -1005,7 +1005,7 @@ impl FigureMgr {
});
// Average velocity relative to the current ground
let rel_avg_vel = state.avg_vel - physics.ground_vel;
let rel_avg_vel = (state.avg_vel - physics.ground_vel) / scale;
let (character, last_character) = match (character, last_character) {
(Some(c), Some(l)) => (c, l),
@ -6157,6 +6157,7 @@ impl FigureMgr {
None,
entity,
body,
scale.copied(),
inventory,
false,
pos.0,
@ -6238,6 +6239,7 @@ impl FigureMgr {
character_state,
entity,
body,
scale.copied(),
inventory,
false,
pos.0,
@ -6273,9 +6275,10 @@ impl FigureMgr {
let character_state = character_state_storage.get(player_entity);
let items = ecs.read_storage::<Item>();
if let (Some(pos), Some(body)) = (
if let (Some(pos), Some(body), scale) = (
ecs.read_storage::<Pos>().get(player_entity),
ecs.read_storage::<Body>().get(player_entity),
ecs.read_storage::<Scale>().get(player_entity),
) {
let healths = state.read_storage::<Health>();
let health = healths.get(player_entity);
@ -6292,6 +6295,7 @@ impl FigureMgr {
character_state,
player_entity,
body,
scale.copied(),
inventory,
true,
pos.0,
@ -6325,6 +6329,7 @@ impl FigureMgr {
character_state: Option<&CharacterState>,
entity: EcsEntity,
body: &Body,
scale: Option<Scale>,
inventory: Option<&Inventory>,
is_viewpoint: bool,
pos: Vec3<f32>,
@ -6702,8 +6707,8 @@ impl FigureMgr {
} {
let model_entry = model_entry?;
let figure_low_detail_distance = figure_lod_render_distance * 0.75;
let figure_mid_detail_distance = figure_lod_render_distance * 0.5;
let figure_low_detail_distance = figure_lod_render_distance * scale.map_or(1.0, |s| s.0) * 0.75;
let figure_mid_detail_distance = figure_lod_render_distance * scale.map_or(1.0, |s| s.0) * 0.5;
let model = if pos.distance_squared(cam_pos) > figure_low_detail_distance.powi(2) {
model_entry.lod_model(2)
@ -7092,7 +7097,7 @@ impl<S: Skeleton> FigureState<S> {
self.last_ori = Lerp::lerp(self.last_ori, *ori, 15.0 * dt).normalized();
self.state_time += dt * state_animation_rate;
self.state_time += dt * state_animation_rate / scale;
let mat = {
let scale_mat = anim::vek::Mat4::scaling_3d(anim::vek::Vec3::from(*scale));
@ -7254,7 +7259,7 @@ impl<S: Skeleton> FigureState<S> {
// Can potentially overflow
if self.avg_vel.magnitude_squared() != 0.0 {
self.acc_vel += (self.avg_vel - *ground_vel).magnitude() * dt;
self.acc_vel += (self.avg_vel - *ground_vel).magnitude() * dt / scale;
} else {
self.acc_vel = 0.0;
}

View File

@ -526,7 +526,30 @@ impl Scene {
.get(scene_data.viewpoint_entity)
.map_or(Quaternion::identity(), |ori| ori.to_quat());
let (is_humanoid, viewpoint_height, viewpoint_eye_height) = ecs
let viewpoint_scale = ecs
.read_storage::<comp::Scale>()
.get(scene_data.viewpoint_entity)
.map_or(1.0, |scale| scale.0);
let viewpoint_rolling = ecs
.read_storage::<comp::CharacterState>()
.get(scene_data.viewpoint_entity)
.map_or(false, |cs| cs.is_dodge());
let is_running = ecs
.read_storage::<comp::Vel>()
.get(scene_data.viewpoint_entity)
.map(|v| v.0.magnitude_squared() > RUNNING_THRESHOLD.powi(2))
.unwrap_or(false);
let on_ground = ecs
.read_storage::<comp::PhysicsState>()
.get(scene_data.viewpoint_entity)
.map(|p| p.on_ground.is_some());
let (is_humanoid, viewpoint_height, viewpoint_eye_height) = scene_data
.state
.ecs()
.read_storage::<comp::Body>()
.get(scene_data.viewpoint_entity)
.map_or((false, 1.0, 0.0), |b| {
@ -602,10 +625,10 @@ impl Scene {
let tilt = self.camera.get_orientation().y;
let dist = self.camera.get_distance();
Vec3::unit_z() * (up - tilt.min(0.0).sin() * dist * 0.6)
Vec3::unit_z() * (up * viewpoint_scale - tilt.min(0.0).sin() * dist * 0.6)
} else {
self.figure_mgr
.viewpoint_offset(scene_data, scene_data.viewpoint_entity)
.viewpoint_offset(scene_data, scene_data.viewpoint_entity) * viewpoint_scale
};
match self.camera.get_mode() {