From 87a6143375d9d837a685b20a7039b3afe434bd92 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 11 Aug 2022 00:40:30 +0100 Subject: [PATCH] Began adding rtsim2 NPCs, scale command --- Cargo.lock | 2 + common/src/cmd.rs | 5 ++ common/src/rtsim.rs | 3 + common/src/states/behavior.rs | 5 +- common/src/states/climb.rs | 4 +- common/src/states/utils.rs | 24 +++++--- common/systems/src/character_behavior.rs | 7 ++- common/systems/src/phys.rs | 26 ++++---- rtsim/Cargo.toml | 2 + rtsim/src/data/actor.rs | 16 ----- rtsim/src/data/mod.rs | 76 +++++++++++++++++++++--- rtsim/src/data/nature.rs | 15 +++++ rtsim/src/data/npc.rs | 50 ++++++++++++++++ rtsim/src/gen/mod.rs | 42 ++++++++++--- server/src/cmd.rs | 24 ++++++++ server/src/lib.rs | 2 +- server/src/rtsim2/mod.rs | 66 ++++++++++---------- server/src/rtsim2/tick.rs | 36 ++++++++++- voxygen/src/scene/figure/mod.rs | 19 +++--- voxygen/src/scene/mod.rs | 29 ++++++++- 20 files changed, 354 insertions(+), 99 deletions(-) delete mode 100644 rtsim/src/data/actor.rs create mode 100644 rtsim/src/data/npc.rs diff --git a/Cargo.lock b/Cargo.lock index efd30ed9ae..237da3cbc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/common/src/cmd.rs b/common/src/cmd.rs index ddc319baf3..7431e7e271 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -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", } } diff --git a/common/src/rtsim.rs b/common/src/rtsim.rs index 1dc03a0250..19990a9d5b 100644 --- a/common/src/rtsim.rs +++ b/common/src/rtsim.rs @@ -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, } diff --git a/common/src/states/behavior.rs b/common/src/states/behavior.rs index 766a1738bc..a117ad19a0 100644 --- a/common/src/states/behavior.rs +++ b/common/src/states/behavior.rs @@ -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, diff --git a/common/src/states/climb.rs b/common/src/states/climb.rs index 68c2dd0be0..77ffc8d69d 100644 --- a/common/src/states/climb.rs +++ b/common/src/states/climb.rs @@ -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, } diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 4457f0df51..e266db3864 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -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() diff --git a/common/systems/src/character_behavior.rs b/common/systems/src/character_behavior.rs index 133c66d869..d4a8e6ca70 100644 --- a/common/systems/src/character_behavior.rs +++ b/common/systems/src/character_behavior.rs @@ -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, diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index a11bbcc053..2e9bf97adc 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -1350,19 +1350,15 @@ fn box_voxel_collision + 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, radius: f32, z_range: Range) -> Aabb { + fn player_aabb(pos: Vec3, radius: f32, z_range: Range, scale: f32) -> Aabb { 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 + ReadVol>( near_aabb: Aabb, radius: f32, z_range: Range, + 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 + 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 + 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 + 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 + ReadVol>( near_aabb, radius, z_range.clone(), + scale, ) } { // ...block-hop! @@ -1613,6 +1612,7 @@ fn box_voxel_collision + ReadVol>( near_aabb, radius, z_range.clone(), + scale, ) } { //prof_span!("snap!!"); @@ -1630,7 +1630,7 @@ fn box_voxel_collision + 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); diff --git a/rtsim/Cargo.toml b/rtsim/Cargo.toml index 1ffcb48f0d..da380b2066 100644 --- a/rtsim/Cargo.toml +++ b/rtsim/Cargo.toml @@ -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"] } diff --git a/rtsim/src/data/actor.rs b/rtsim/src/data/actor.rs deleted file mode 100644 index 88d3e58ee3..0000000000 --- a/rtsim/src/data/actor.rs +++ /dev/null @@ -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, -} diff --git a/rtsim/src/data/mod.rs b/rtsim/src/data/mod.rs index 4e8622f294..c68797db4f 100644 --- a/rtsim/src/data/mod.rs +++ b/rtsim/src/data/mod.rs @@ -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(&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 + Serialize, V: PartialEq + Default + Serialize, S: ser::Serializer>(map: &EnumMap, mut ser: S) -> Result { +// ser.collect_map(map +// .iter() +// .filter(|(k, v)| v != &&V::default()) +// .map(|(k, v)| (k, v))) +// } + +fn rugged_ser_enum_map< + K: EnumArray + Serialize, + V: From + PartialEq + Serialize, + S: ser::Serializer, + const DEFAULT: i16, +>(map: &EnumMap, ser: S) -> Result { + 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 + EnumArray> + Deserialize<'a>, + V: From + Deserialize<'a>, + D: de::Deserializer<'a>, + const DEFAULT: i16, +>(mut de: D) -> Result, D::Error> { + struct Visitor(PhantomData<(K, V)>); + + impl<'de, K, V, const DEFAULT: i16> de::Visitor<'de> for Visitor + where + K: EnumArray + EnumArray> + Deserialize<'de>, + V: From + Deserialize<'de>, + { + type Value = EnumMap; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "a map") + } + + fn visit_map>(self, mut access: M) -> Result { + 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)) +} diff --git a/rtsim/src/data/nature.rs b/rtsim/src/data/nature.rs index 5cb9bea7ef..2a7abf8b0f 100644 --- a/rtsim/src/data/nature.rs +++ b/rtsim/src/data/nature.rs @@ -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, @@ -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, } diff --git a/rtsim/src/data/npc.rs b/rtsim/src/data/npc.rs new file mode 100644 index 0000000000..ce8e0054fa --- /dev/null +++ b/rtsim/src/data/npc.rs @@ -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, + #[serde(skip_serializing, skip_deserializing)] + pub mode: NpcMode, +} + +impl Npc { + pub fn at(wpos: Vec3) -> 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, +} + +impl Npcs { + pub fn spawn(&mut self, npc: Npc) -> NpcId { + self.npcs.insert(npc) + } +} + +impl Deref for Npcs { + type Target = HopSlotMap; + fn deref(&self) -> &Self::Target { &self.npcs } +} + +impl DerefMut for Npcs { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.npcs } +} diff --git a/rtsim/src/gen/mod.rs b/rtsim/src/gen/mod.rs index f1ce167d1d..fb2d3829a8 100644 --- a/rtsim/src/gen/mod.rs +++ b/rtsim/src/gen/mod.rs @@ -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 } } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 9bbc73a86d..b1413ef4f8 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -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, + action: &ServerChatCommand, +) -> CmdResult<()> { + if let Some(scale) = parse_cmd_args!(args, f32) { + let _ = server + .state + .ecs_mut() + .write_storage::() + .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()) + } +} diff --git a/server/src/lib.rs b/server/src/lib.rs index ec39cab251..c1b8fd37a9 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -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); diff --git a/server/src/rtsim2/mod.rs b/server/src/rtsim2/mod.rs index f71555afaa..388ba1b6dc 100644 --- a/server/src/rtsim2/mod.rs +++ b/server/src/rtsim2/mod.rs @@ -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 { + pub fn new(index: IndexRef, world: &World, data_dir: PathBuf) -> Result { 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 }; diff --git a/server/src/rtsim2/tick.rs b/server/src/rtsim2/tick.rs index bf07399e7c..a1a2b906aa 100644 --- a/server/src/rtsim2/tick.rs +++ b/server/src/rtsim2/tick.rs @@ -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, - (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::(); + 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 diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 3dcbdef1b2..2340e40b5c 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -776,7 +776,7 @@ impl FigureMgr { .enumerate() { // Velocity relative to the current ground - let rel_vel = anim::vek::Vec3::::from(vel.0 - physics.ground_vel); + let rel_vel = anim::vek::Vec3::::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::(); - if let (Some(pos), Some(body)) = ( + if let (Some(pos), Some(body), scale) = ( ecs.read_storage::().get(player_entity), ecs.read_storage::().get(player_entity), + ecs.read_storage::().get(player_entity), ) { let healths = state.read_storage::(); 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, inventory: Option<&Inventory>, is_viewpoint: bool, pos: Vec3, @@ -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 FigureState { 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 FigureState { // 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; } diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index d5d81f10a7..ca10372782 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -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::() + .get(scene_data.viewpoint_entity) + .map_or(1.0, |scale| scale.0); + + let viewpoint_rolling = ecs + .read_storage::() + .get(scene_data.viewpoint_entity) + .map_or(false, |cs| cs.is_dodge()); + + let is_running = ecs + .read_storage::() + .get(scene_data.viewpoint_entity) + .map(|v| v.0.magnitude_squared() > RUNNING_THRESHOLD.powi(2)) + .unwrap_or(false); + + let on_ground = ecs + .read_storage::() + .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::() .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() {