mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Began adding rtsim2 NPCs, scale command
This commit is contained in:
parent
9d3dadfaba
commit
87a6143375
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -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",
|
||||
|
@ -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",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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"] }
|
||||
|
@ -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>,
|
||||
}
|
@ -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))
|
||||
}
|
||||
|
@ -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
50
rtsim/src/data/npc.rs
Normal 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 }
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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() {
|
||||
|
Loading…
Reference in New Issue
Block a user