Merge master and build

This commit is contained in:
Adam Whitehurst 2020-01-16 05:27:30 -08:00
commit d82e93b39f
158 changed files with 5172 additions and 2247 deletions

1
.gitignore vendored
View File

@ -38,3 +38,4 @@ todo.txt
# direnv
/.envrc
*.bat

139
CHANGELOG.md Normal file
View File

@ -0,0 +1,139 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- Added new debug item
- Bows give experience by projectiles having an owner
- Allow cancelling chunk generation
- Include licence in assets
- Added dropping items
- Added initial region system implementation
- Added /giveitem command
- Strip Linux executables
- Added moon
- Added clouds
- Added tarpaulin coverage
- Added ability to jump while underwater
- Added proper SFX system
- Added changelog
- Added animated Map and Minimap position indicator
- Added visuals to indicate strength compared to the player
- Added Scrolling Combat Text (SCT) & Settings for it
- Added a Death Screen and Hurt Screen
- Added randomly selected Loading Screen background images
### Changed
- Fixed near and far view planes
- Improvements to armor names
- Animation fixes to line up with true positions
- Proper message for command permission check failure
- Improved meshing
- Improved dusk
- Improved movement and climbing
- Improved water rendering and chunk render order
- Moved computations to terrain fragment shaders
- Fixed title music
- Made rolling less violent when changing directions
- Fixed singleplayer crash
- Improved error information in client and server
- Store items as RON files
- Updated download info in readme
- Fixed cloud performance
- Fixed region display name
- Fixed the bow fire rate
- Healthbars now flash on critical health
- Fixed ghosts when going back to character screen
- Fixed not being able to unmount
- Fixed non-humanoids being able to climb and glide
- Made shadows and lights use interpolated positions
### Removed
- Remove heaptrack as it is now deprecated
## [0.4.0] - 2019-10-10
### Added
- Added adjustable FOV slider
- Added /explosion command
- Added first person switch
- Added singleplayer server settings
- Added admin check for commands
- Started asset reloading system
- Added SRGB conversion in shaders
- Added adminify to give temp admin privilages
### Changed
- Collision and fall damage fixes
- Switched to eventbus system
- Improved seed generation, diffusion function
- Switch to hashbrown in server/client
- Improved colors and lighting
- Replaced view distance culling with frustum culling
## [0.3.0] - 2019-08-04
### Added
- Added enemies
- Added player info to debug window
- Added server info
- Game settings persist after closing
- Added caves
- Added random NPC names
- Added tree roots, houses, basic lights
- Added XP and leveling
- Added build mode
- Character customization, multiple races
- Inventories (WIP)
- Day/night, better shaders, voxel shadows
### Changed
- Fixed attack delay
- Fixed disclaimer to show only once
- Only send physics updates for entities within view distance
- Fix for headphones and invalid device parameters
- Fixed asset names for consistancy
- Fixes animals jumping after their target no matter how far\
- Improved SFX in caves
- Better combat, movement, and animations
- Many performance optimizations
- Better world generation, more biomes
## [0.2.0] - 2019-05-28
### Added
- Hang Gliding
- Pets: Pig and Wolf. They can be spawned with /pig and /wolf commands.
- Name tags: You can finally know who is this guy with the blue shirt!
- Singleplayer: No need to start a server just to play alone
- Character customization: It isn't fully complete but still allows you to look different than others
- Music!
- Major performance improvements related to the fact that we rewrote the entire game
- 0% chance to get a deadlock
- Animations: You finally can move your limbs!
- Combat: You can finally swing your sword that has been on your back. Enemies are coming soon, but you can always fight with other players
- When a server dies the game no longer crashes - you will be just kicked to the main menu
## [0.1.0] - 2018-XX-XX
_0.1.0 was part of the legacy engine_
[unreleased]: https://gitlab.com/veloren/veloren/compare?from=v0.4.0&to=master
[0.0.4]: https://gitlab.com/veloren/veloren/compare?from=v0.3.0&to=v0.4.0
[0.0.3]: https://gitlab.com/veloren/veloren/compare?from=v0.2.0&to=v0.3.0
[0.0.2]: https://gitlab.com/veloren/veloren/compare?from=7d17f8b67a2a6d5aa00730f028cedc430fd5075a&to=v0.2.0
[0.0.1]: https://gitlab.com/veloren/game

713
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -14,10 +14,10 @@ members = [
# default profile for devs, fast to compile, okay enough to run, no debug information
[profile.dev]
opt-level = 2
overflow-checks = true
overflow-checks = true
debug-assertions = true
panic = "abort"
debug = false
debug = false
codegen-units = 8
lto = false
incremental = true
@ -44,7 +44,7 @@ opt-level = 2
inherits= 'dev'
debug = true
# this profil is used for veloren releases, compile time doesn't matter
# this profile is used for veloren releases, compile time doesn't matter
# we need stacktraces, light debug information, as much checks as possible
# I would like to put it in a seperate `official_release` target, but that doesnt share caches with `cargo test` and `cargo bench`
[profile.release]

View File

@ -47,6 +47,8 @@ If you want to compile Veloren yourself, take a look at the [How to Compile Guid
#### Arch
[AUR Airshipper](https://aur.archlinux.org/packages/airshipper-git): `yay -Sy airshipper-git`
[AUR latest binary release](https://aur.archlinux.org/packages/veloren-bin/): `yay -Sy veloren-bin`
[AUR latest release](https://aur.archlinux.org/packages/veloren/): `yay -Sy veloren`

View File

@ -0,0 +1,8 @@
Item(
name: "Crude Mallet",
description: "Breaks bones like sticks and stones.",
kind: Tool(
kind: Hammer,
power: 20,
),
)

View File

@ -0,0 +1,8 @@
Item(
name: "Humble Stick",
description: "Walking stick with a sharpened end.",
kind: Tool(
kind: Hammer,
power: 6,
),
)

View File

@ -1,7 +1,6 @@
(
items: [
(
trigger: Run,
{
Run: (
files: [
"voxygen.audio.sfx.footsteps.stepgrass_1",
"voxygen.audio.sfx.footsteps.stepgrass_2",
@ -12,15 +11,13 @@
],
threshold: 0.25,
),
(
trigger: GliderOpen,
GliderOpen: (
files: [
"voxygen.audio.sfx.glider_open",
],
threshold: 0.5,
),
(
trigger: GliderClose,
GliderClose: (
files: [
"voxygen.audio.sfx.glider_close",
],

Binary file not shown.

BIN
assets/voxygen/audio/soundtrack/veloren_title_tune.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/background/bg_1.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/background/bg_3.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/background/bg_4.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/background/bg_5.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/background/bg_6.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/background/bg_7.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/background/bg_8.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/background/death.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/background/hurt.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/background/map.png (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/element/buttons/indicator_mmap_2.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/buttons/indicator_mmap_3.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/buttons/min_plus/mmap_button-min.vox (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/element/buttons/min_plus/mmap_button-plus.vox (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/element/frames/enemybar-0.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/frames/enemybar.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/help.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/element/icons/skull.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/icons/skull_2.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/skillbar/enemy_bar_content.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -27,7 +27,7 @@ void main() {
// Increase array access by 3 to access positive values
uint norm_dir = ((f_pos_norm >> 29) & 0x1u) * 3u;
// Use an array to avoid conditional branching
vec3 f_norm = normals[norm_axis + norm_dir];
vec3 f_norm = normals[(f_pos_norm >> 29) & 0x7u];
vec3 light, diffuse_light, ambient_light;
get_sun_diffuse(f_norm, time_of_day.x, light, diffuse_light, ambient_light, 1.0);

View File

@ -25,6 +25,10 @@ void main() {
if (w_pos.w == 1.0) {
// In-game element
gl_Position = proj_mat * (view_mat * w_pos + vec4(v_pos, 0.0, 0.0));
} else if (w_pos.w == -1.0 ) {
// Fixed scale In-game element
vec4 projected_pos = proj_mat * view_mat * vec4(w_pos.xyz, 1.0);
gl_Position = vec4(projected_pos.xy / projected_pos.w + v_pos, 0.0, 1.0);
} else {
// Interface element
gl_Position = vec4(v_pos, 0.0, 1.0);

BIN
assets/voxygen/voxel/armor/back/short-0.vox (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

BIN
assets/voxygen/voxel/npc/oger/belt.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/npc/oger/chest.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/npc/oger/foot.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/npc/oger/hand-l.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/npc/oger/hand-r.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/npc/oger/head.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/npc/oger/legs.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/npc/oger/shoulder.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/weapon/hammer/hammer_1_2h.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/weapon/staff/wood-simple.vox (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -2,23 +2,23 @@
[
(
specifier: "world.tree.acacia.1",
center: (16, 17, 1)
center: (17, 18, 4)
),
(
specifier: "world.tree.acacia.2",
center: (5, 6, 1)
center: (5, 5, 4)
),
(
specifier: "world.tree.acacia.3",
center: (5, 6, 1)
center: (6, 6, 3)
),
(
specifier: "world.tree.acacia.4",
center: (15, 16, 1)
center: (12, 14, 4)
),
(
specifier: "world.tree.acacia.5",
center: (19, 18, 1)
center: (19, 19, 4)
),
]
)

View File

@ -2,7 +2,7 @@
[
(
specifier: "world.tree.mangroves.1",
center: (18, 18, 8)
center: (19, 18, 8)
),
(
specifier: "world.tree.mangroves.2",
@ -10,11 +10,11 @@
),
(
specifier: "world.tree.mangroves.3",
center: (18, 18, 8)
center: (18, 19, 8)
),
(
specifier: "world.tree.mangroves.4",
center: (18, 16, 8)
center: (19, 18, 8)
),
(
specifier: "world.tree.mangroves.5",
@ -22,15 +22,15 @@
),
(
specifier: "world.tree.mangroves.6",
center: (18, 18, 9)
center: (18, 21, 9)
),
(
specifier: "world.tree.mangroves.7",
center: (18, 17, 9)
center: (20, 17, 9)
),
(
specifier: "world.tree.mangroves.8",
center: (18, 18, 9)
center: (18, 19, 9)
),
]
)

View File

@ -2,19 +2,19 @@
[
(
specifier: "world.tree.oak_green.1",
center: (15, 18, 14)
center: (15, 17, 14)
),
(
specifier: "world.tree.oak_green.2",
center: (15, 18, 14)
center: (18, 17, 14)
),
(
specifier: "world.tree.oak_green.3",
center: (16, 20, 14)
center: (19, 20, 14)
),
(
specifier: "world.tree.oak_green.4",
center: (18, 21, 14)
center: (19, 20, 14)
),
(
specifier: "world.tree.oak_green.5",
@ -22,19 +22,19 @@
),
(
specifier: "world.tree.oak_green.6",
center: (16, 21, 14)
center: (18, 21, 14)
),
(
specifier: "world.tree.oak_green.7",
center: (20, 19, 14)
center: (20, 21, 14)
),
(
specifier: "world.tree.oak_green.8",
center: (22, 20, 14)
center: (22, 21, 14)
),
(
specifier: "world.tree.oak_green.9",
center:(26, 26, 14)
center:(21, 21, 14)
),
]
)

BIN
assets/world/tree/acacia/1.vox (Stored with Git LFS)

Binary file not shown.

BIN
assets/world/tree/acacia/2.vox (Stored with Git LFS)

Binary file not shown.

BIN
assets/world/tree/acacia/3.vox (Stored with Git LFS)

Binary file not shown.

BIN
assets/world/tree/acacia/4.vox (Stored with Git LFS)

Binary file not shown.

BIN
assets/world/tree/acacia/5.vox (Stored with Git LFS)

Binary file not shown.

BIN
assets/world/tree/acacia_savannah/1.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/world/tree/acacia_savannah/2.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/world/tree/acacia_savannah/3.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/world/tree/acacia_savannah/4.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/world/tree/acacia_savannah/5.vox (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -65,7 +65,11 @@ fn main() {
client.send_chat(msg)
}
let events = match client.tick(comp::ControllerInputs::default(), clock.get_last_delta()) {
let events = match client.tick(
comp::ControllerInputs::default(),
clock.get_last_delta(),
|_| {},
) {
Ok(events) => events,
Err(err) => {
error!("Error: {:?}", err);

View File

@ -12,6 +12,6 @@ uvth = "3.1.1"
image = "0.22.3"
num_cpus = "1.10.1"
log = "0.4.8"
specs = "0.14.2"
specs = "0.15.1"
vek = { version = "0.9.9", features = ["serde"] }
hashbrown = { version = "0.6.2", features = ["serde", "nightly"] }

View File

@ -5,16 +5,21 @@ pub mod error;
// Reexports
pub use crate::error::Error;
pub use specs::{join::Join, saveload::Marker, Entity as EcsEntity, ReadStorage};
pub use specs::{
join::Join,
saveload::{Marker, MarkerAllocator},
Builder, DispatcherBuilder, Entity as EcsEntity, ReadStorage, WorldExt,
};
use common::{
comp::{self, ControlEvent, Controller, ControllerInputs, InventoryManip},
msg::{
validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, RequestStateError,
ServerError, ServerInfo, ServerMsg, MAX_BYTES_CHAT_MSG,
validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, PlayerListUpdate,
RequestStateError, ServerError, ServerInfo, ServerMsg, MAX_BYTES_CHAT_MSG,
},
net::PostBox,
state::{State, Uid},
state::State,
sync::{Uid, UidAllocator, WorldSyncExt},
terrain::{block::Block, TerrainChunk, TerrainChunkSize},
vol::RectVolSize,
ChatType,
@ -51,6 +56,7 @@ pub struct Client {
thread_pool: ThreadPool,
pub server_info: ServerInfo,
pub world_map: Arc<DynamicImage>,
pub player_list: HashMap<u64, String>,
postbox: PostBox<ClientMsg, ServerMsg>,
@ -70,7 +76,6 @@ pub struct Client {
impl Client {
/// Create a new `Client`.
#[allow(dead_code)]
pub fn new<A: Into<SocketAddr>>(addr: A, view_distance: Option<u32>) -> Result<Self, Error> {
let client_state = ClientState::Connected;
let mut postbox = PostBox::to(addr)?;
@ -78,12 +83,12 @@ impl Client {
// Wait for initial sync
let (state, entity, server_info, world_map) = match postbox.next_message() {
Some(ServerMsg::InitialSync {
ecs_state,
entity_uid,
entity_package,
server_info,
time_of_day,
// world_map: /*(map_size, world_map)*/map_size,
}) => {
// TODO: Voxygen should display this.
// TODO: Display that versions don't match in Voxygen
if server_info.git_hash != common::util::GIT_HASH.to_string() {
log::warn!(
"Server is running {}[{}], you are running {}[{}], versions might be incompatible!",
@ -94,11 +99,10 @@ impl Client {
);
}
let state = State::from_state_package(ecs_state);
let entity = state
.ecs()
.entity_from_uid(entity_uid)
.ok_or(Error::ServerWentMad)?;
// Initialize `State`
let mut state = State::default();
let entity = state.ecs_mut().apply_entity_package(entity_package);
*state.ecs_mut().write_resource() = time_of_day;
// assert_eq!(world_map.len(), map_size.x * map_size.y);
let map_size = Vec2::new(1024, 1024);
@ -137,6 +141,7 @@ impl Client {
thread_pool,
server_info,
world_map,
player_list: HashMap::new(),
postbox,
@ -154,7 +159,6 @@ impl Client {
})
}
#[allow(dead_code)]
pub fn with_thread_pool(mut self, thread_pool: ThreadPool) -> Self {
self.thread_pool = thread_pool;
self
@ -183,17 +187,15 @@ impl Client {
self.client_state = ClientState::Pending;
}
/// Request a state transition to `ClientState::Character`.
/// Send disconnect message to the server
pub fn request_logout(&mut self) {
self.postbox
.send_message(ClientMsg::RequestState(ClientState::Connected));
self.postbox.send_message(ClientMsg::Disconnect);
self.client_state = ClientState::Pending;
}
/// Request a state transition to `ClientState::Character`.
/// Request a state transition to `ClientState::Registered` from an ingame state.
pub fn request_remove_character(&mut self) {
self.postbox
.send_message(ClientMsg::RequestState(ClientState::Registered));
self.postbox.send_message(ClientMsg::ExitIngame);
self.client_state = ClientState::Pending;
}
@ -282,10 +284,9 @@ impl Client {
}
/// Send a chat message to the server.
#[allow(dead_code)]
pub fn send_chat(&mut self, msg: String) {
match validate_chat_msg(&msg) {
Ok(()) => self.postbox.send_message(ClientMsg::chat(msg)),
pub fn send_chat(&mut self, message: String) {
match validate_chat_msg(&message) {
Ok(()) => self.postbox.send_message(ClientMsg::ChatMsg { message }),
Err(ChatMsgValidationError::TooLong) => log::warn!(
"Attempted to send a message that's too long (Over {} bytes)",
MAX_BYTES_CHAT_MSG
@ -294,7 +295,6 @@ impl Client {
}
/// Remove all cached terrain
#[allow(dead_code)]
pub fn clear_terrain(&mut self) {
self.state.clear_terrain();
self.pending_chunks.clear();
@ -316,8 +316,12 @@ impl Client {
}
/// Execute a single client tick, handle input and update the game state by the given duration.
#[allow(dead_code)]
pub fn tick(&mut self, inputs: ControllerInputs, dt: Duration) -> Result<Vec<Event>, Error> {
pub fn tick(
&mut self,
inputs: ControllerInputs,
dt: Duration,
add_foreign_systems: impl Fn(&mut DispatcherBuilder),
) -> Result<Vec<Event>, Error> {
// This tick function is the centre of the Veloren universe. Most client-side things are
// managed from here, and as such it's important that it stays organised. Please consult
// the core developers before making significant changes to this code. Here is the
@ -334,7 +338,7 @@ impl Client {
// 1) Handle input from frontend.
// Pass character actions from frontend input to the player's entity.
if let ClientState::Character | ClientState::Dead = self.client_state {
if let ClientState::Character = self.client_state {
self.state.write_component(
self.entity,
Controller {
@ -375,7 +379,7 @@ impl Client {
// 3) Update client local data
// 4) Tick the client's LocalState
self.state.tick(dt, |_| {});
self.state.tick(dt, add_foreign_systems);
// 5) Terrain
let pos = self
@ -389,6 +393,10 @@ impl Client {
// Remove chunks that are too far from the player.
let mut chunks_to_remove = Vec::new();
self.state.terrain().iter().for_each(|(key, _)| {
// Subtract 2 from the offset before computing squared magnitude
// 1 for the chunks needed bordering other chunks for meshing
// 1 as a buffer so that if the player moves back in that direction the chunks
// don't need to be reloaded
if (chunk_pos - key)
.map(|e: i32| (e.abs() as u32).checked_sub(2).unwrap_or(0))
.magnitude_squared()
@ -403,7 +411,7 @@ impl Client {
// Request chunks from the server.
let mut all_loaded = true;
'outer: for dist in 0..=view_distance as i32 {
'outer: for dist in 0..(view_distance as i32) + 1 {
// Only iterate through chunks that need to be loaded for circular vd
// The (dist - 2) explained:
// -0.5 because a chunk is visible if its corner is within the view distance
@ -420,7 +428,7 @@ impl Client {
dist
};
for i in -top..=top {
for i in -top..top + 1 {
let keys = [
chunk_pos + Vec2::new(dist, i),
chunk_pos + Vec2::new(i, dist),
@ -492,7 +500,6 @@ impl Client {
}
/// Clean up the client after a tick.
#[allow(dead_code)]
pub fn cleanup(&mut self) {
// Cleanup the local state
self.state.cleanup();
@ -531,16 +538,37 @@ impl Client {
},
ServerMsg::Shutdown => return Err(Error::ServerShutdown),
ServerMsg::InitialSync { .. } => return Err(Error::ServerWentMad),
ServerMsg::PlayerListUpdate(PlayerListUpdate::Init(list)) => {
self.player_list = list
}
ServerMsg::PlayerListUpdate(PlayerListUpdate::Add(uid, name)) => {
if let Some(old_name) = self.player_list.insert(uid, name.clone()) {
warn!("Received msg to insert {} with uid {} into the player list but there was already an entry for {} with the same uid that was overwritten!", name, uid, old_name);
}
}
ServerMsg::PlayerListUpdate(PlayerListUpdate::Remove(uid)) => {
if self.player_list.remove(&uid).is_none() {
warn!("Received msg to remove uid {} from the player list by they weren't in the list!", uid);
}
}
ServerMsg::PlayerListUpdate(PlayerListUpdate::Alias(uid, new_name)) => {
if let Some(name) = self.player_list.get_mut(&uid) {
*name = new_name;
} else {
warn!("Received msg to alias player with uid {} to {} but this uid is not in the player list", uid, new_name);
}
}
ServerMsg::Ping => self.postbox.send_message(ClientMsg::Pong),
ServerMsg::Pong => {
self.last_server_pong = Instant::now();
self.last_ping_delta = Instant::now()
.duration_since(self.last_server_ping)
.as_secs_f64()
.as_secs_f64();
}
ServerMsg::ChatMsg { chat_type, message } => {
frontend_events.push(Event::Chat { chat_type, message })
ServerMsg::ChatMsg { message, chat_type } => {
frontend_events.push(Event::Chat { message, chat_type })
}
ServerMsg::SetPlayerEntity(uid) => {
if let Some(entity) = self.state.ecs().entity_from_uid(uid) {
@ -549,16 +577,47 @@ impl Client {
return Err(Error::Other("Failed to find entity from uid.".to_owned()));
}
}
ServerMsg::TimeOfDay(time_of_day) => {
*self.state.ecs_mut().write_resource() = time_of_day;
}
ServerMsg::EcsSync(sync_package) => {
self.state.ecs_mut().sync_with_package(sync_package)
self.state.ecs_mut().apply_sync_package(sync_package);
}
ServerMsg::CreateEntity(entity_package) => {
self.state.ecs_mut().apply_entity_package(entity_package);
}
ServerMsg::DeleteEntity(entity) => {
if let Some(entity) = self.state.ecs().entity_from_uid(entity) {
if entity != self.entity {
let _ = self.state.ecs_mut().delete_entity(entity);
}
if self
.state
.read_component_cloned::<Uid>(self.entity)
.map(|u| u.into())
!= Some(entity)
{
self.state
.ecs_mut()
.delete_entity_and_clear_from_uid_allocator(entity);
}
}
// Cleanup for when the client goes back to the `Registered` state
ServerMsg::ExitIngameCleanup => {
// Get client entity Uid
let client_uid = self
.state
.read_component_cloned::<Uid>(self.entity)
.map(|u| u.into())
.expect("Client doesn't have a Uid!!!");
// Clear ecs of all entities
self.state.ecs_mut().delete_all();
self.state.ecs_mut().maintain();
self.state.ecs_mut().insert(UidAllocator::default());
// Recreate client entity with Uid
let entity_builder = self.state.ecs_mut().create_entity();
let uid = entity_builder
.world
.write_resource::<UidAllocator>()
.allocate(entity_builder.entity, Some(client_uid));
self.entity = entity_builder.with(uid).build();
}
ServerMsg::EntityPos { entity, pos } => {
if let Some(entity) = self.state.ecs().entity_from_uid(entity) {
self.state.write_component(entity, pos);
@ -591,9 +650,11 @@ impl Client {
}
self.pending_chunks.remove(&key);
}
ServerMsg::TerrainBlockUpdates(mut blocks) => blocks
.drain()
.for_each(|(pos, block)| self.state.set_block(pos, block)),
ServerMsg::TerrainBlockUpdates(mut blocks) => {
blocks.drain().for_each(|(pos, block)| {
self.state.set_block(pos, block);
});
}
ServerMsg::StateAnswer(Ok(state)) => {
self.client_state = state;
}
@ -607,9 +668,6 @@ impl Client {
error, state
);
}
ServerMsg::ForceState(state) => {
self.client_state = state;
}
ServerMsg::Disconnect => {
frontend_events.push(Event::Disconnect);
}
@ -625,24 +683,20 @@ impl Client {
}
/// Get the player's entity.
#[allow(dead_code)]
pub fn entity(&self) -> EcsEntity {
self.entity
}
/// Get the client state
#[allow(dead_code)]
pub fn get_client_state(&self) -> ClientState {
self.client_state
}
/// Get the current tick number.
#[allow(dead_code)]
pub fn get_tick(&self) -> u64 {
self.tick
}
#[allow(dead_code)]
pub fn get_ping_ms(&self) -> f64 {
self.last_ping_delta * 1000.0
}
@ -650,19 +704,16 @@ impl Client {
/// Get a reference to the client's worker thread pool. This pool should be used for any
/// computationally expensive operations that run outside of the main thread (i.e., threads that
/// block on I/O operations are exempt).
#[allow(dead_code)]
pub fn thread_pool(&self) -> &ThreadPool {
&self.thread_pool
}
/// Get a reference to the client's game state.
#[allow(dead_code)]
pub fn state(&self) -> &State {
&self.state
}
/// Get a mutable reference to the client's game state.
#[allow(dead_code)]
pub fn state_mut(&mut self) -> &mut State {
&mut self.state
}

View File

@ -5,10 +5,9 @@ authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>", "Maciej Ćwięka <mc
edition = "2018"
[dependencies]
sphynx = { git = "https://gitlab.com/veloren/sphynx.git", features = ["serde1"], rev = "ac4adf54d181339a789907acd27f61fe81daa455" }
specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git" }
specs = { version = "0.14.2", features = ["serde", "nightly"] }
specs = { version = "0.15.1", features = ["serde", "nightly", "storage-event-control"] }
vek = { version = "0.9.9", features = ["serde"] }
dot_vox = "4.0.0"
image = "0.22.3"
@ -30,8 +29,7 @@ parking_lot = "0.9.0"
crossbeam = "=0.7.2"
notify = "5.0.0-pre.1"
indexmap = "1.3.0"
# TODO: remove when upgrading to specs 0.15
hibitset = "0.5.3"
sum_type = "0.2.0"
[dev-dependencies]
criterion = "0.3"
@ -39,3 +37,7 @@ criterion = "0.3"
[[bench]]
name = "chonk_benchmark"
harness = false
[[bench]]
name = "color_benchmark"
harness = false

View File

@ -1,7 +1,6 @@
#[macro_use]
extern crate criterion;
use criterion::black_box;
use criterion::criterion_group;
use criterion::criterion_main;
use criterion::Criterion;
use vek::*;

View File

@ -0,0 +1,22 @@
use criterion::black_box;
use criterion::criterion_group;
use criterion::criterion_main;
use criterion::Criterion;
use vek::*;
use veloren_common::util::{linear_to_srgb, srgb_to_linear};
fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("color: srgb to linear (0.5, 0.1, 0.5)", |b| {
b.iter(|| {
black_box(srgb_to_linear(black_box(Rgb::new(0.5, 0.1, 0.5))));
})
});
c.bench_function("color: linear to srgb (0.5, 0.1, 0.5)", |b| {
b.iter(|| {
black_box(linear_to_srgb(black_box(Rgb::new(0.5, 0.1, 0.5))));
})
});
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View File

@ -33,6 +33,23 @@ impl Body {
_ => false,
}
}
// Note: this might need to be refined to something more complex for realistic
// behavior with less cylindrical bodies (e.g. wolfs)
pub fn radius(&self) -> f32 {
// TODO: Improve these values (some might be reliant on more info in inner type)
match self {
Body::Humanoid(_) => 0.5,
Body::QuadrupedSmall(_) => 0.6,
Body::QuadrupedMedium(_) => 0.9,
Body::BirdMedium(_) => 0.5,
Body::FishMedium(_) => 0.5,
Body::Dragon(_) => 2.5,
Body::BirdSmall(_) => 0.2,
Body::FishSmall(_) => 0.2,
Body::BipedLarge(_) => 1.0,
Body::Object(_) => 0.3,
}
}
}
impl Component for Body {

View File

@ -0,0 +1,69 @@
use rand::{seq::SliceRandom, thread_rng};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Body {
pub head: Head,
pub shoulder: Shoulder,
pub chest: Chest,
pub hand: Hand
pub belt: Belt,
pub pants: Pants,
pub foot: Foot,
}
impl Body {
pub fn random() -> Self {
let mut rng = thread_rng();
Self {
head: *(&ALL_HEADS).choose(&mut rng).unwrap(),
shoulder: *(&ALL_SHOULDERS).choose(&mut rng).unwrap(),
chest: *(&ALL_CHESTS).choose(&mut rng).unwrap(),
hand: *(&ALL_HANDS).choose(&mut rng).unwrap(),
belt: *(&ALL_BELTS).choose(&mut rng).unwrap(),
pants: *(&ALL_PANTS).choose(&mut rng).unwrap(),
foot: *(&ALL_FEET).choose(&mut rng).unwrap(),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Head {
Default,
}
const ALL_HEADS: [Head; 1] = [Head::Default];
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Shoulder {
Default,
}
const ALL_SHOULDERS: [Shoulder; 1] = [Shoulder::Default];
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Chest {
Default,
}
const ALL_CHESTS: [Chest; 1] = [Chest::Default];
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Hand {
Default,
}
const ALL_HANDS: [Hand; 1] = [Hand::Default];
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Belt {
Default,
}
const ALL_BELTS: [Belt; 1] = [Belt::Default];
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Pants {
Default,
}
const ALL_FEET: [Foot; 1] = [Foot::Default];
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Foot {
Default,
}

View File

@ -1,6 +1,6 @@
use crate::sync::Uid;
use specs::{Component, FlaggedStorage};
use specs_idvs::IDVStorage;
use sphynx::Uid;
use std::time::Duration;
use vek::*;

54
common/src/comp/energy.rs Normal file
View File

@ -0,0 +1,54 @@
use specs::{Component, FlaggedStorage};
use specs_idvs::IDVStorage;
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Energy {
current: u32,
maximum: u32,
pub last_change: Option<(i32, f64, EnergySource)>,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum EnergySource {
CastSpell,
LevelUp,
Unknown,
}
impl Energy {
pub fn new(amount: u32) -> Energy {
Energy {
current: amount,
maximum: amount,
last_change: None,
}
}
pub fn current(&self) -> u32 {
self.current
}
pub fn maximum(&self) -> u32 {
self.maximum
}
pub fn set_to(&mut self, amount: u32, cause: EnergySource) {
let amount = amount.min(self.maximum);
self.last_change = Some((amount as i32 - self.current as i32, 0.0, cause));
self.current = amount;
}
pub fn change_by(&mut self, amount: i32, cause: EnergySource) {
self.current = ((self.current as i32 + amount).max(0) as u32).min(self.maximum);
self.last_change = Some((amount, 0.0, cause));
}
pub fn set_maximum(&mut self, amount: u32) {
self.maximum = amount;
self.current = self.current.min(self.maximum);
}
}
impl Component for Energy {
type Storage = FlaggedStorage<Self, IDVStorage<Self>>;
}

View File

@ -4,6 +4,7 @@ mod agent;
mod body;
mod character_state;
mod controller;
mod energy;
mod inputs;
mod inventory;
mod last;
@ -30,6 +31,7 @@ pub use controller::{
ControlEvent, Controller, ControllerInputs, Input, InputState, InventoryManip, MountState,
Mounting,
};
pub use energy::Energy;
pub use inputs::CanBuild;
pub use inventory::{
item, Inventory, InventoryUpdate, Item, ItemKind, SwordKind, ToolData, ToolKind,

View File

@ -1,4 +1,4 @@
use crate::state::Uid;
use crate::sync::Uid;
use specs::{Component, FlaggedStorage, NullStorage};
use specs_idvs::IDVStorage;
use vek::*;

View File

@ -1,5 +1,4 @@
use crate::comp;
use crate::state::Uid;
use crate::{comp, sync::Uid};
use specs::{Component, FlaggedStorage};
use specs_idvs::IDVStorage;
use std::time::Duration;
@ -15,6 +14,7 @@ pub enum Effect {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Projectile {
pub owner: Uid,
// TODO: use SmallVec for these effects
pub hit_ground: Vec<Effect>,
pub hit_wall: Vec<Effect>,
pub hit_entity: Vec<Effect>,

View File

@ -1,4 +1,4 @@
use crate::{comp, state::Uid};
use crate::{comp, sync::Uid};
use specs::{Component, FlaggedStorage};
use specs_idvs::IDVStorage;
@ -19,12 +19,6 @@ pub enum HealthSource {
Item,
Unknown,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum EnergySource {
CastSpell,
LevelUp,
Unknown,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Health {
@ -33,13 +27,6 @@ pub struct Health {
pub last_change: (f64, HealthChange),
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Energy {
current: u32,
maximum: u32,
pub last_change: Option<(i32, f64, EnergySource)>,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Exp {
current: u32,
@ -90,32 +77,6 @@ impl Health {
}
}
impl Energy {
pub fn current(&self) -> u32 {
self.current
}
pub fn maximum(&self) -> u32 {
self.maximum
}
pub fn set_to(&mut self, amount: u32, cause: EnergySource) {
let amount = amount.min(self.maximum);
self.last_change = Some((amount as i32 - self.current as i32, 0.0, cause));
self.current = amount;
}
pub fn change_by(&mut self, amount: i32, cause: EnergySource) {
self.current = ((self.current as i32 + amount).max(0) as u32).min(self.maximum);
self.last_change = Some((amount, 0.0, cause));
}
pub fn set_maximum(&mut self, amount: u32) {
self.maximum = amount;
self.current = self.current.min(self.maximum);
}
}
impl Exp {
pub fn current(&self) -> u32 {
self.current
@ -161,7 +122,6 @@ impl Level {
pub struct Stats {
pub name: String,
pub health: Health,
pub energy: Energy,
pub level: Level,
pub exp: Exp,
pub equipment: Equipment,
@ -204,11 +164,6 @@ impl Stats {
current: 0,
maximum: 50,
},
energy: Energy {
current: 200,
maximum: 200,
last_change: None,
},
equipment: Equipment {
main: main,
alt: None,
@ -229,12 +184,6 @@ impl Stats {
self.health.current = amount;
self
}
pub fn with_max_energy(mut self, amount: u32) -> Self {
self.energy.maximum = amount;
self.energy.current = amount;
self
}
}
impl Component for Stats {

View File

@ -1,9 +1,8 @@
use crate::comp;
use crate::{comp, sync::Uid};
use comp::item::ToolKind;
use parking_lot::Mutex;
use serde::Deserialize;
use specs::Entity as EcsEntity;
use sphynx::Uid;
use std::{collections::VecDeque, ops::DerefMut};
use vek::*;
@ -30,18 +29,17 @@ pub enum SfxEvent {
OpenChest,
ChatTellReceived,
OpenBag,
LevelUp,
Run,
Roll,
Climb,
Swim,
Run,
GliderOpen,
Glide,
GliderClose,
Jump,
Fall,
InventoryAdd,
InventoryDrop,
ExperienceGained,
LevelUp,
LightLantern,
ExtinguishLantern,
Attack(ToolKind),
@ -90,12 +88,15 @@ pub enum ServerEvent {
Mount(EcsEntity, EcsEntity),
Unmount(EcsEntity),
Possess(Uid, Uid),
CreatePlayer {
CreateCharacter {
entity: EcsEntity,
name: String,
body: comp::Body,
main: Option<String>,
},
ExitIngame {
entity: EcsEntity,
},
CreateNpc {
pos: comp::Pos,
stats: comp::Stats,

130
common/src/hierarchical.rs Normal file
View File

@ -0,0 +1,130 @@
use crate::{
astar::astar,
pathfinding::WorldPath,
vol::{ReadVol, RectRasterableVol},
volumes::vol_grid_2d::VolGrid2d,
};
use std::fmt::Debug;
use vek::*;
#[derive(Clone, Debug, Default)]
pub struct ChunkPath {
pub from: Vec3<f32>,
pub dest: Vec3<f32>,
pub chunk_path: Option<Vec<Vec2<i32>>>,
}
impl ChunkPath {
pub fn new<V: RectRasterableVol + ReadVol + Debug>(
vol: &VolGrid2d<V>,
from: Vec3<f32>,
dest: Vec3<f32>,
) -> Self {
let ifrom: Vec3<i32> = Vec3::from(from.map(|e| e.floor() as i32));
let idest: Vec3<i32> = Vec3::from(dest.map(|e| e.floor() as i32));
let start_chunk = vol.pos_key(ifrom);
let end_chunk = vol.pos_key(idest);
let chunk_path = astar(
start_chunk,
end_chunk,
chunk_euclidean_distance,
|pos| ChunkPath::chunk_get_neighbors(vol, pos),
chunk_transition_cost,
);
Self {
from,
dest,
chunk_path,
}
}
pub fn chunk_get_neighbors<V: RectRasterableVol + ReadVol + Debug>(
_vol: &VolGrid2d<V>,
pos: &Vec2<i32>,
) -> impl IntoIterator<Item = Vec2<i32>> {
let directions = vec![
Vec2::new(1, 0), // Right chunk
Vec2::new(-1, 0), // Left chunk
Vec2::new(0, 1), // Top chunk
Vec2::new(0, -1), // Bottom chunk
];
let neighbors: Vec<Vec2<i32>> = directions.into_iter().map(|dir| dir + pos).collect();
neighbors.into_iter()
}
pub fn worldpath_get_neighbors<V: RectRasterableVol + ReadVol + Debug>(
&mut self,
vol: &VolGrid2d<V>,
pos: Vec3<i32>,
) -> impl IntoIterator<Item = Vec3<i32>> {
let directions = vec![
Vec3::new(0, 1, 0), // Forward
Vec3::new(0, 1, 1), // Forward upward
Vec3::new(0, 1, 2), // Forward Upwardx2
Vec3::new(0, 1, -1), // Forward downward
Vec3::new(1, 0, 0), // Right
Vec3::new(1, 0, 1), // Right upward
Vec3::new(1, 0, 2), // Right Upwardx2
Vec3::new(1, 0, -1), // Right downward
Vec3::new(0, -1, 0), // Backwards
Vec3::new(0, -1, 1), // Backward Upward
Vec3::new(0, -1, 2), // Backward Upwardx2
Vec3::new(0, -1, -1), // Backward downward
Vec3::new(-1, 0, 0), // Left
Vec3::new(-1, 0, 1), // Left upward
Vec3::new(-1, 0, 2), // Left Upwardx2
Vec3::new(-1, 0, -1), // Left downward
];
let neighbors: Vec<Vec3<i32>> = directions
.into_iter()
.map(|dir| dir + pos)
.filter(|new_pos| self.is_valid_space(vol, *new_pos))
.collect();
neighbors.into_iter()
}
pub fn is_valid_space<V: RectRasterableVol + ReadVol + Debug>(
&mut self,
vol: &VolGrid2d<V>,
pos: Vec3<i32>,
) -> bool {
let is_walkable_position = WorldPath::is_walkable_space(vol, pos);
let mut is_within_chunk = false;
match self.chunk_path.clone() {
Some(chunk_path) => {
is_within_chunk = chunk_path
.iter()
.any(|new_pos| new_pos.cmpeq(&vol.pos_key(pos)).iter().all(|e| *e));
}
_ => {
println!("No chunk path");
}
}
return is_walkable_position && is_within_chunk;
}
pub fn get_worldpath<V: RectRasterableVol + ReadVol + Debug>(
&mut self,
vol: &VolGrid2d<V>,
) -> WorldPath {
let wp = WorldPath::new(vol, self.from, self.dest, |vol, pos| {
self.worldpath_get_neighbors(vol, *pos)
});
println!("Fetching world path from hierarchical path: {:?}", wp);
wp
}
}
pub fn chunk_euclidean_distance(start: &Vec2<i32>, end: &Vec2<i32>) -> f32 {
let istart = start.map(|e| e as f32);
let iend = end.map(|e| e as f32);
istart.distance(iend)
}
pub fn chunk_transition_cost(_start: &Vec2<i32>, _end: &Vec2<i32>) -> f32 {
1.0f32
}

View File

@ -14,6 +14,7 @@ pub mod comp;
pub mod effect;
pub mod event;
pub mod figure;
pub mod hierarchical;
pub mod msg;
pub mod npc;
pub mod pathfinding;
@ -21,6 +22,7 @@ pub mod ray;
pub mod region;
pub mod state;
pub mod states;
pub mod sync;
pub mod sys;
pub mod terrain;
pub mod util;

View File

@ -1,6 +1,4 @@
use super::ClientState;
use crate::terrain::block::Block;
use crate::{comp, ChatType};
use crate::{comp, terrain::block::Block};
use vek::*;
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -14,16 +12,18 @@ pub enum ClientMsg {
body: comp::Body,
main: Option<String>, // Specifier for the weapon
},
/// Request `ClientState::Registered` from an ingame state
ExitIngame,
/// Request `ClientState::Spectator` from a registered or ingame state
Spectate,
ControllerInputs(comp::ControllerInputs),
ControlEvent(comp::ControlEvent),
RequestState(ClientState),
SetViewDistance(u32),
BreakBlock(Vec3<i32>),
PlaceBlock(Vec3<i32>, Block),
Ping,
Pong,
ChatMsg {
chat_type: ChatType,
message: String,
},
PlayerPhysics {
@ -36,42 +36,3 @@ pub enum ClientMsg {
},
Disconnect,
}
impl ClientMsg {
pub fn chat(message: String) -> ClientMsg {
ClientMsg::ChatMsg {
chat_type: ChatType::Chat,
message,
}
}
pub fn tell(message: String) -> ClientMsg {
ClientMsg::ChatMsg {
chat_type: ChatType::Tell,
message,
}
}
pub fn game(message: String) -> ClientMsg {
ClientMsg::ChatMsg {
chat_type: ChatType::GameUpdate,
message,
}
}
pub fn broadcast(message: String) -> ClientMsg {
ClientMsg::ChatMsg {
chat_type: ChatType::Broadcast,
message,
}
}
pub fn private(message: String) -> ClientMsg {
ClientMsg::ChatMsg {
chat_type: ChatType::Private,
message,
}
}
pub fn kill(message: String) -> ClientMsg {
ClientMsg::ChatMsg {
chat_type: ChatType::Private,
message,
}
}
}

View File

@ -1,36 +1,24 @@
use crate::{comp, state};
use crate::{comp, sync};
use serde_derive::{Deserialize, Serialize};
use std::marker::PhantomData;
use sum_type::sum_type;
// Automatically derive From<T> for EcsResPacket
// for each variant EcsResPacket::T(T).
sphynx::sum_type! {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum EcsResPacket {
Time(state::Time),
TimeOfDay(state::TimeOfDay),
}
}
impl sphynx::ResPacket for EcsResPacket {}
// Automatically derive From<T> for EcsCompPacket
// for each variant EcsCompPacket::T(T.)
sphynx::sum_type! {
sum_type! {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum EcsCompPacket {
Pos(comp::Pos),
Vel(comp::Vel),
Ori(comp::Ori),
Body(comp::Body),
Player(comp::Player),
CanBuild(comp::CanBuild),
Stats(comp::Stats),
Energy(comp::Energy),
LightEmitter(comp::LightEmitter),
Item(comp::Item),
Scale(comp::Scale),
MountState(comp::MountState),
Mounting(comp::Mounting),
Mass(comp::Mass),
Projectile(comp::Projectile),
Gravity(comp::Gravity),
Sticky(comp::Sticky),
OverrideAction(comp::OverrideAction),
@ -42,23 +30,20 @@ sphynx::sum_type! {
}
// Automatically derive From<T> for EcsCompPhantom
// for each variant EcsCompPhantom::T(PhantomData<T>).
sphynx::sum_type! {
sum_type! {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum EcsCompPhantom {
Pos(PhantomData<comp::Pos>),
Vel(PhantomData<comp::Vel>),
Ori(PhantomData<comp::Ori>),
Body(PhantomData<comp::Body>),
Player(PhantomData<comp::Player>),
CanBuild(PhantomData<comp::CanBuild>),
Stats(PhantomData<comp::Stats>),
Energy(PhantomData<comp::Energy>),
LightEmitter(PhantomData<comp::LightEmitter>),
Item(PhantomData<comp::Item>),
Scale(PhantomData<comp::Scale>),
MountState(PhantomData<comp::MountState>),
Mounting(PhantomData<comp::Mounting>),
Mass(PhantomData<comp::Mass>),
Projectile(PhantomData<comp::Projectile>),
Gravity(PhantomData<comp::Gravity>),
Sticky(PhantomData<comp::Sticky>),
OverrideAction(PhantomData<comp::OverrideAction>),
@ -68,6 +53,59 @@ sphynx::sum_type! {
AbilityPool(PhantomData<comp::AbilityPool>),
}
}
impl sphynx::CompPacket for EcsCompPacket {
impl sync::CompPacket for EcsCompPacket {
type Phantom = EcsCompPhantom;
fn apply_insert(self, entity: specs::Entity, world: &specs::World) {
match self {
EcsCompPacket::Body(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Player(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::CanBuild(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Stats(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Energy(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::LightEmitter(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Item(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Scale(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::MountState(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Mounting(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Mass(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Gravity(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Sticky(comp) => sync::handle_insert(comp, entity, world),
}
}
fn apply_modify(self, entity: specs::Entity, world: &specs::World) {
match self {
EcsCompPacket::Body(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Player(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::CanBuild(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Stats(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Energy(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::LightEmitter(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Item(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Scale(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::MountState(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Mounting(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Mass(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Gravity(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Sticky(comp) => sync::handle_modify(comp, entity, world),
}
}
fn apply_remove(phantom: Self::Phantom, entity: specs::Entity, world: &specs::World) {
match phantom {
EcsCompPhantom::Body(_) => sync::handle_remove::<comp::Body>(entity, world),
EcsCompPhantom::Player(_) => sync::handle_remove::<comp::Player>(entity, world),
EcsCompPhantom::CanBuild(_) => sync::handle_remove::<comp::CanBuild>(entity, world),
EcsCompPhantom::Stats(_) => sync::handle_remove::<comp::Stats>(entity, world),
EcsCompPhantom::Energy(_) => sync::handle_remove::<comp::Energy>(entity, world),
EcsCompPhantom::LightEmitter(_) => {
sync::handle_remove::<comp::LightEmitter>(entity, world)
}
EcsCompPhantom::Item(_) => sync::handle_remove::<comp::Item>(entity, world),
EcsCompPhantom::Scale(_) => sync::handle_remove::<comp::Scale>(entity, world),
EcsCompPhantom::MountState(_) => sync::handle_remove::<comp::MountState>(entity, world),
EcsCompPhantom::Mounting(_) => sync::handle_remove::<comp::Mounting>(entity, world),
EcsCompPhantom::Mass(_) => sync::handle_remove::<comp::Mass>(entity, world),
EcsCompPhantom::Gravity(_) => sync::handle_remove::<comp::Gravity>(entity, world),
EcsCompPhantom::Sticky(_) => sync::handle_remove::<comp::Sticky>(entity, world),
}
}
}

View File

@ -4,8 +4,8 @@ pub mod server;
// Reexports
pub use self::client::ClientMsg;
pub use self::ecs_packet::{EcsCompPacket, EcsResPacket};
pub use self::server::{RequestStateError, ServerError, ServerInfo, ServerMsg};
pub use self::ecs_packet::EcsCompPacket;
pub use self::server::{PlayerListUpdate, RequestStateError, ServerError, ServerInfo, ServerMsg};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum ClientState {
@ -13,7 +13,6 @@ pub enum ClientState {
Connected,
Registered,
Spectator,
Dead,
Character,
}

View File

@ -1,6 +1,6 @@
use super::{ClientState, EcsCompPacket, EcsResPacket};
use super::{ClientState, EcsCompPacket};
use crate::{
comp,
comp, state, sync,
terrain::{Block, TerrainChunk},
ChatType,
};
@ -23,16 +23,27 @@ pub struct ServerInfo {
pub git_date: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PlayerListUpdate {
Init(HashMap<u64, String>),
Add(u64, String),
Remove(u64),
Alias(u64, String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ServerMsg {
InitialSync {
ecs_state: sphynx::StatePackage<EcsCompPacket, EcsResPacket>,
entity_uid: u64,
entity_package: sync::EntityPackage<EcsCompPacket>,
server_info: ServerInfo,
time_of_day: state::TimeOfDay,
// world_map: Vec2<usize>, /*, Vec<u32>)*/
},
PlayerListUpdate(PlayerListUpdate),
StateAnswer(Result<ClientState, (RequestStateError, ClientState)>),
ForceState(ClientState),
/// Trigger cleanup for when the client goes back to the `Registered` state from an ingame
/// state
ExitIngameCleanup,
Ping,
Pong,
ChatMsg {
@ -40,7 +51,9 @@ pub enum ServerMsg {
message: String,
},
SetPlayerEntity(u64),
EcsSync(sphynx::SyncPackage<EcsCompPacket, EcsResPacket>),
TimeOfDay(state::TimeOfDay),
EcsSync(sync::SyncPackage<EcsCompPacket>),
CreateEntity(sync::EntityPackage<EcsCompPacket>),
DeleteEntity(u64),
EntityPos {
entity: u64,

View File

@ -13,19 +13,31 @@ pub struct WorldPath {
}
impl WorldPath {
pub fn new<V: ReadVol>(vol: &V, from: Vec3<f32>, dest: Vec3<f32>) -> Self {
pub fn new<V: ReadVol, I>(
vol: &V,
from: Vec3<f32>,
dest: Vec3<f32>,
get_neighbors: impl FnMut(&V, &Vec3<i32>) -> I,
) -> Self
where
I: IntoIterator<Item = Vec3<i32>>,
{
let ifrom: Vec3<i32> = Vec3::from(from.map(|e| e.floor() as i32));
let idest: Vec3<i32> = Vec3::from(dest.map(|e| e.floor() as i32));
let path = WorldPath::get_path(vol, ifrom, idest);
let path = WorldPath::get_path(vol, ifrom, idest, get_neighbors);
Self { from, dest, path }
}
pub fn get_path<V: ReadVol>(
pub fn get_path<V: ReadVol, I>(
vol: &V,
from: Vec3<i32>,
dest: Vec3<i32>,
) -> Option<Vec<Vec3<i32>>> {
mut get_neighbors: impl FnMut(&V, &Vec3<i32>) -> I,
) -> Option<Vec<Vec3<i32>>>
where
I: IntoIterator<Item = Vec3<i32>>,
{
let new_start = WorldPath::get_z_walkable_space(vol, from);
let new_dest = WorldPath::get_z_walkable_space(vol, dest);
@ -34,7 +46,7 @@ impl WorldPath {
new_start,
new_dest,
euclidean_distance,
|pos| WorldPath::get_neighbors(vol, pos),
|pos| get_neighbors(vol, pos),
transition_cost,
)
} else {
@ -136,7 +148,7 @@ impl WorldPath {
if let Some(mut block_path) = self.path.clone() {
if let Some(next_pos) = block_path.clone().last() {
if self.path_is_blocked(vol) {
self.path = WorldPath::get_path(vol, ipos, idest)
self.path = WorldPath::get_path(vol, ipos, idest, WorldPath::get_neighbors);
}
if Vec2::<i32>::from(ipos) == Vec2::<i32>::from(*next_pos) {

View File

@ -1,8 +1,7 @@
use crate::comp::{Pos, Vel};
use hashbrown::{hash_map::DefaultHashBuilder, HashSet};
use hibitset::BitSetLike;
use indexmap::IndexMap;
use specs::{BitSet, Entities, Join, ReadStorage};
use specs::{hibitset::BitSetLike, BitSet, Entities, Join, ReadStorage};
use vek::*;
pub enum Event {
@ -104,6 +103,16 @@ impl RegionMap {
// TODO special case large entities
pub fn tick(&mut self, pos: ReadStorage<Pos>, vel: ReadStorage<Vel>, entities: Entities) {
self.tick += 1;
// Clear events within each region
for i in 0..self.regions.len() {
self.regions
.get_index_mut(i)
.map(|(_, v)| v)
.unwrap()
.events
.clear();
}
// Add any untracked entites
for (pos, id) in (&pos, &entities, !&self.tracked_entities)
.join()
@ -118,14 +127,6 @@ impl RegionMap {
let mut regions_to_remove = Vec::new();
for i in 0..self.regions.len() {
// Clear events within each region
self.regions
.get_index_mut(i)
.map(|(_, v)| v)
.unwrap()
.events
.clear();
for (maybe_pos, _maybe_vel, id) in (
pos.maybe(),
vel.maybe(),
@ -215,6 +216,47 @@ impl RegionMap {
pub fn key_pos(key: Vec2<i32>) -> Vec2<i32> {
key.map(|e| e << REGION_LOG2)
}
/// Finds the region where a given entity is located using a given position to speed up the search
pub fn find_region(&self, entity: specs::Entity, pos: Vec3<f32>) -> Option<Vec2<i32>> {
let id = entity.id();
// Compute key for most likely region
let key = Self::pos_key(pos.map(|e| e as i32));
// Get region
if let Some(region) = self.regions.get(&key) {
if region.entities().contains(id) {
return Some(key);
} else {
// Check neighbors
for i in 0..8 {
if let Some(idx) = region.neighbors[i] {
let (key, region) = self.regions.get_index(idx).unwrap();
if region.entities().contains(id) {
return Some(*key);
}
}
}
}
} else {
// Check neighbors
for i in 0..8 {
let key = key + NEIGHBOR_OFFSETS[i];
if let Some(region) = self.regions.get(&key) {
if region.entities().contains(id) {
return Some(key);
}
}
}
}
// Scan though all regions
for (key, region) in self.iter() {
if region.entities().contains(id) {
return Some(key);
}
}
None
}
fn key_index(&self, key: Vec2<i32>) -> Option<usize> {
self.regions.get_full(&key).map(|(i, _, _)| i)
}
@ -330,8 +372,8 @@ pub fn regions_in_vd(pos: Vec3<f32>, vd: f32) -> HashSet<Vec2<i32>> {
let max = RegionMap::pos_key(pos_xy.map(|e| (e + vd_extended) as i32));
let min = RegionMap::pos_key(pos_xy.map(|e| (e - vd_extended) as i32));
for x in min.x..=max.x {
for y in min.y..=max.y {
for x in min.x..max.x + 1 {
for y in min.y..max.y + 1 {
let key = Vec2::new(x, y);
if region_in_vd(key, pos, vd) {

View File

@ -1,11 +1,8 @@
// Reexports
pub use sphynx::Uid;
use crate::{
comp,
event::{EventBus, LocalEvent, ServerEvent, SfxEventItem},
msg::{EcsCompPacket, EcsResPacket},
region::RegionMap,
sync::WorldSyncExt,
sys,
terrain::{Block, TerrainChunk, TerrainGrid},
vol::WriteVol,
@ -14,12 +11,10 @@ use hashbrown::{HashMap, HashSet};
use rayon::{ThreadPool, ThreadPoolBuilder};
use serde_derive::{Deserialize, Serialize};
use specs::{
saveload::Marker,
shred::{Fetch, FetchMut},
storage::{MaskedStorage as EcsMaskedStorage, Storage as EcsStorage},
Component, DispatcherBuilder, Entity as EcsEntity, Join,
Component, DispatcherBuilder, Entity as EcsEntity, WorldExt,
};
use sphynx;
use std::{sync::Arc, time::Duration};
use vek::*;
@ -28,7 +23,7 @@ use vek::*;
const DAY_CYCLE_FACTOR: f64 = 24.0 * 2.0;
/// A resource that stores the time of day.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct TimeOfDay(pub f64);
/// A resource that stores the tick (i.e: physics) time.
@ -89,7 +84,7 @@ impl TerrainChanges {
/// A type used to represent game state stored on both the client and the server. This includes
/// things like entity components, terrain data, and global states like weather, time of day, etc.
pub struct State {
ecs: sphynx::World<EcsCompPacket, EcsResPacket>,
ecs: specs::World,
// Avoid lifetime annotation by storing a thread pool instead of the whole dispatcher
thread_pool: Arc<ThreadPool>,
}
@ -98,49 +93,39 @@ impl Default for State {
/// Create a new `State`.
fn default() -> Self {
Self {
ecs: sphynx::World::new(specs::World::new(), Self::setup_sphynx_world),
ecs: Self::setup_ecs_world(),
thread_pool: Arc::new(ThreadPoolBuilder::new().build().unwrap()),
}
}
}
impl State {
/// Create a new `State` from an ECS state package.
pub fn from_state_package(
state_package: sphynx::StatePackage<EcsCompPacket, EcsResPacket>,
) -> Self {
Self {
ecs: sphynx::World::from_state_package(
specs::World::new(),
Self::setup_sphynx_world,
state_package,
),
thread_pool: Arc::new(ThreadPoolBuilder::new().build().unwrap()),
}
}
// Create a new Sphynx ECS world.
/// Creates ecs world and registers all the common components and resources
// TODO: Split up registering into server and client (e.g. move EventBus<ServerEvent> to the server)
fn setup_sphynx_world(ecs: &mut sphynx::World<EcsCompPacket, EcsResPacket>) {
fn setup_ecs_world() -> specs::World {
let mut ecs = specs::World::new();
// Uids for sync
ecs.register_sync_marker();
// Register server -> all clients synced components.
ecs.register_synced::<comp::AbilityPool>();
ecs.register_synced::<comp::AbilityAction>();
ecs.register_synced::<comp::Body>();
ecs.register_synced::<comp::Player>();
ecs.register_synced::<comp::Stats>();
ecs.register_synced::<comp::CanBuild>();
ecs.register_synced::<comp::LightEmitter>();
ecs.register_synced::<comp::Item>();
ecs.register_synced::<comp::Scale>();
ecs.register_synced::<comp::Mounting>();
ecs.register_synced::<comp::MountState>();
ecs.register_synced::<comp::Mass>();
ecs.register_synced::<comp::Sticky>();
ecs.register_synced::<comp::Gravity>();
ecs.register_synced::<comp::Projectile>();
ecs.register_synced::<comp::OverrideAction>();
ecs.register_synced::<comp::OverrideMove>();
ecs.register_synced::<comp::OverrideState>();
ecs.register::<comp::AbilityPool>();
ecs.register::<comp::AbilityAction>();
ecs.register::<comp::Projectile>();
ecs.register::<comp::OverrideAction>();
ecs.register::<comp::OverrideMove>();
ecs.register::<comp::OverrideState>();
ecs.register::<comp::Body>();
ecs.register::<comp::Player>();
ecs.register::<comp::Stats>();
ecs.register::<comp::Energy>();
ecs.register::<comp::CanBuild>();
ecs.register::<comp::LightEmitter>();
ecs.register::<comp::Item>();
ecs.register::<comp::Scale>();
ecs.register::<comp::Mounting>();
ecs.register::<comp::MountState>();
ecs.register::<comp::Mass>();
ecs.register::<comp::Sticky>();
ecs.register::<comp::Gravity>();
// Register components send from clients -> server
ecs.register::<comp::Controller>();
@ -156,6 +141,7 @@ impl State {
ecs.register::<comp::Inventory>();
// Register server-local components
// TODO: only register on the server
ecs.register::<comp::Last<comp::Pos>>();
ecs.register::<comp::Last<comp::Vel>>();
ecs.register::<comp::Last<comp::Ori>>();
@ -163,23 +149,26 @@ impl State {
ecs.register::<comp::Agent>();
ecs.register::<comp::ForceUpdate>();
ecs.register::<comp::InventoryUpdate>();
ecs.register::<comp::Inventory>();
ecs.register::<comp::Admin>();
ecs.register::<comp::Waypoint>();
ecs.register::<comp::Projectile>();
// Register synced resources used by the ECS.
ecs.insert_synced(TimeOfDay(0.0));
ecs.insert(TimeOfDay(0.0));
// Register unsynced resources used by the ECS.
ecs.add_resource(Time(0.0));
ecs.add_resource(DeltaTime(0.0));
ecs.add_resource(TerrainGrid::new().unwrap());
ecs.add_resource(BlockChange::default());
ecs.add_resource(TerrainChanges::default());
ecs.add_resource(EventBus::<ServerEvent>::default());
ecs.add_resource(EventBus::<LocalEvent>::default());
ecs.add_resource(EventBus::<SfxEventItem>::default());
ecs.add_resource(RegionMap::new());
ecs.insert(Time(0.0));
ecs.insert(DeltaTime(0.0));
ecs.insert(TerrainGrid::new().unwrap());
ecs.insert(BlockChange::default());
ecs.insert(TerrainChanges::default());
// TODO: only register on the server
ecs.insert(EventBus::<ServerEvent>::default());
ecs.insert(EventBus::<LocalEvent>::default());
ecs.insert(EventBus::<SfxEventItem>::default());
ecs.insert(RegionMap::new());
ecs
}
/// Register a component with the state's ECS.
@ -212,12 +201,12 @@ impl State {
}
/// Get a reference to the internal ECS world.
pub fn ecs(&self) -> &sphynx::World<EcsCompPacket, EcsResPacket> {
pub fn ecs(&self) -> &specs::World {
&self.ecs
}
/// Get a mutable reference to the internal ECS world.
pub fn ecs_mut(&mut self) -> &mut sphynx::World<EcsCompPacket, EcsResPacket> {
pub fn ecs_mut(&mut self) -> &mut specs::World {
&mut self.ecs
}
@ -324,68 +313,6 @@ impl State {
// Beyond a delta time of MAX_DELTA_TIME, start lagging to avoid skipping important physics events.
self.ecs.write_resource::<DeltaTime>().0 = dt.as_secs_f32().min(MAX_DELTA_TIME);
// Mounted entities. We handle this here because we need access to the Uid registry and I
// forgot how to access that within a system. Anyhow, here goes.
for (entity, mount_state) in (
&self.ecs.entities(),
&mut self.ecs.write_storage::<comp::MountState>(),
)
.join()
{
match mount_state {
comp::MountState::Unmounted => {}
comp::MountState::MountedBy(mounter) => {
if let Some((controller, mounter)) =
self.ecs.entity_from_uid(mounter.id()).and_then(|mounter| {
self.ecs
.read_storage::<comp::Controller>()
.get(mounter)
.cloned()
.map(|x| (x, mounter))
})
{
let pos = self.ecs.read_storage::<comp::Pos>().get(entity).copied();
let ori = self.ecs.read_storage::<comp::Ori>().get(entity).copied();
let vel = self.ecs.read_storage::<comp::Vel>().get(entity).copied();
if let (Some(pos), Some(ori), Some(vel)) = (pos, ori, vel) {
let _ = self
.ecs
.write_storage()
.insert(mounter, comp::Pos(pos.0 + Vec3::unit_z() * 1.0));
let _ = self.ecs.write_storage().insert(mounter, ori);
let _ = self.ecs.write_storage().insert(mounter, vel);
}
let _ = self
.ecs
.write_storage::<comp::Controller>()
.insert(entity, controller);
} else {
*mount_state = comp::MountState::Unmounted;
}
}
}
}
let mut to_unmount = Vec::new();
for (entity, comp::Mounting(mountee)) in (
&self.ecs.entities(),
&self.ecs.read_storage::<comp::Mounting>(),
)
.join()
{
if self
.ecs
.entity_from_uid(mountee.id())
.filter(|mountee| self.ecs.is_alive(*mountee))
.is_none()
{
to_unmount.push(entity);
}
}
for entity in to_unmount {
self.ecs.write_storage::<comp::Mounting>().remove(entity);
}
// Run RegionMap tick to update entity region occupancy
self.ecs.write_resource::<RegionMap>().tick(
self.ecs.read_storage::<comp::Pos>(),
@ -400,23 +327,20 @@ impl State {
// TODO: Consider alternative ways to do this
add_foreign_systems(&mut dispatch_builder);
// This dispatches all the systems in parallel.
dispatch_builder.build().dispatch(&self.ecs.res);
dispatch_builder.build().dispatch(&self.ecs);
self.ecs.maintain();
// Apply terrain changes
let mut terrain = self.ecs.write_resource::<TerrainGrid>();
self.ecs
.read_resource::<BlockChange>()
.blocks
.iter()
.for_each(|(pos, block)| {
let _ = terrain.set(*pos, *block);
});
self.ecs.write_resource::<TerrainChanges>().modified_blocks = std::mem::replace(
let mut modified_blocks = std::mem::replace(
&mut self.ecs.write_resource::<BlockChange>().blocks,
Default::default(),
);
// Apply block modifications
// Only include in `TerrainChanges` if successful
modified_blocks.retain(|pos, block| terrain.set(*pos, *block).is_ok());
self.ecs.write_resource::<TerrainChanges>().modified_blocks = modified_blocks;
// Process local events
let events = self.ecs.read_resource::<EventBus<LocalEvent>>().recv_all();

14
common/src/sync/mod.rs Normal file
View File

@ -0,0 +1,14 @@
// Note: Currently only one-way sync is supported until a usecase for two-way sync arises
mod packet;
mod sync_ext;
mod track;
mod uid;
// Reexports
pub use packet::{
handle_insert, handle_modify, handle_remove, CompPacket, EntityPackage, StatePackage,
SyncPackage,
};
pub use sync_ext::WorldSyncExt;
pub use track::UpdateTracker;
pub use uid::{Uid, UidAllocator};

125
common/src/sync/packet.rs Normal file
View File

@ -0,0 +1,125 @@
use super::{track::UpdateTracker, uid::Uid};
use log::error;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use specs::{Component, Entity, Join, ReadStorage, World, WorldExt};
use std::{
convert::{TryFrom, TryInto},
fmt::Debug,
marker::PhantomData,
};
/// Implemented by type that carries component data for insertion and modification
/// The assocatied `Phantom` type only carries information about which component type is of
/// interest and is used to transmit deletion events
pub trait CompPacket: Clone + Debug + Send + 'static {
type Phantom: Clone + Debug + Serialize + DeserializeOwned;
fn apply_insert(self, entity: Entity, world: &World);
fn apply_modify(self, entity: Entity, world: &World);
fn apply_remove(phantom: Self::Phantom, entity: Entity, world: &World);
}
/// Useful for implementing CompPacket trait
pub fn handle_insert<C: Component>(comp: C, entity: Entity, world: &World) {
if let Err(err) = world.write_storage::<C>().insert(entity, comp) {
error!("Error inserting component: {:?}", err);
};
}
/// Useful for implementing CompPacket trait
pub fn handle_modify<C: Component>(comp: C, entity: Entity, world: &World) {
let _ = world
.write_storage::<C>()
.get_mut(entity)
.map(|c| *c = comp);
}
/// Useful for implementing CompPacket trait
pub fn handle_remove<C: Component>(entity: Entity, world: &World) {
let _ = world.write_storage::<C>().remove(entity);
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub enum CompUpdateKind<P: CompPacket> {
Inserted(P),
Modified(P),
Removed(P::Phantom),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EntityPackage<P: CompPacket> {
pub uid: u64,
pub comps: Vec<P>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StatePackage<P: CompPacket> {
pub entities: Vec<EntityPackage<P>>,
}
impl<P: CompPacket> Default for StatePackage<P> {
fn default() -> Self {
Self {
entities: Vec::new(),
}
}
}
impl<P: CompPacket> StatePackage<P> {
pub fn new() -> Self {
Self::default()
}
pub fn with_entities<C: Component + Clone + Send + Sync>(
mut self,
mut entities: Vec<EntityPackage<P>>,
) -> Self {
self.entities.append(&mut entities);
self
}
pub fn with_entity(mut self, entry: EntityPackage<P>) -> Self {
self.entities.push(entry);
self
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SyncPackage<P: CompPacket> {
pub comp_updates: Vec<(u64, CompUpdateKind<P>)>,
pub created_entities: Vec<u64>,
pub deleted_entities: Vec<u64>,
}
impl<P: CompPacket> SyncPackage<P> {
pub fn new<'a>(
uids: &ReadStorage<'a, Uid>,
uid_tracker: &UpdateTracker<Uid>,
filter: impl Join + Copy,
deleted_entities: Vec<u64>,
) -> Self {
// Add created and deleted entities
let created_entities = (uids, filter, uid_tracker.inserted())
.join()
.map(|(uid, _, _)| (*uid).into())
.collect();
Self {
comp_updates: Vec::new(),
created_entities,
deleted_entities,
}
}
pub fn with_component<'a, C: Component + Clone + Send + Sync>(
mut self,
uids: &ReadStorage<'a, Uid>,
tracker: &UpdateTracker<C>,
storage: &ReadStorage<'a, C>,
filter: impl Join + Copy,
) -> Self
where
P: From<C>,
C: TryFrom<P>,
P::Phantom: From<PhantomData<C>>,
P::Phantom: TryInto<PhantomData<C>>,
C::Storage: specs::storage::Tracked,
{
tracker.get_updates_for(uids, storage, filter, &mut self.comp_updates);
self
}
}

164
common/src/sync/sync_ext.rs Normal file
View File

@ -0,0 +1,164 @@
use super::{
packet::{CompPacket, CompUpdateKind, EntityPackage, StatePackage, SyncPackage},
track::UpdateTracker,
uid::{Uid, UidAllocator},
};
use log::error;
use specs::{
saveload::{MarkedBuilder, MarkerAllocator},
world::Builder,
WorldExt,
};
pub trait WorldSyncExt {
fn register_sync_marker(&mut self);
fn register_synced<C: specs::Component + Clone + Send + Sync>(&mut self)
where
C::Storage: Default + specs::storage::Tracked;
fn register_tracker<C: specs::Component + Clone + Send + Sync>(&mut self)
where
C::Storage: Default + specs::storage::Tracked;
fn create_entity_synced(&mut self) -> specs::EntityBuilder;
fn delete_entity_and_clear_from_uid_allocator(&mut self, uid: u64);
fn uid_from_entity(&self, entity: specs::Entity) -> Option<Uid>;
fn entity_from_uid(&self, uid: u64) -> Option<specs::Entity>;
fn apply_entity_package<P: CompPacket>(
&mut self,
entity_package: EntityPackage<P>,
) -> specs::Entity;
fn apply_state_package<P: CompPacket>(&mut self, state_package: StatePackage<P>);
fn apply_sync_package<P: CompPacket>(&mut self, package: SyncPackage<P>);
}
impl WorldSyncExt for specs::World {
fn register_sync_marker(&mut self) {
self.register_synced::<Uid>();
// TODO: Consider only having allocator server side for now
self.insert(UidAllocator::new());
}
fn register_synced<C: specs::Component + Clone + Send + Sync>(&mut self)
where
C::Storage: Default + specs::storage::Tracked,
{
self.register::<C>();
self.register_tracker::<C>();
}
fn register_tracker<C: specs::Component + Clone + Send + Sync>(&mut self)
where
C::Storage: Default + specs::storage::Tracked,
{
let tracker = UpdateTracker::<C>::new(self);
self.insert(tracker);
}
fn create_entity_synced(&mut self) -> specs::EntityBuilder {
self.create_entity().marked::<super::Uid>()
}
/// Get the UID of an entity
fn uid_from_entity(&self, entity: specs::Entity) -> Option<Uid> {
self.read_storage::<Uid>().get(entity).copied()
}
/// Get the UID of an entity
fn entity_from_uid(&self, uid: u64) -> Option<specs::Entity> {
self.read_resource::<UidAllocator>()
.retrieve_entity_internal(uid)
}
fn apply_entity_package<P: CompPacket>(
&mut self,
entity_package: EntityPackage<P>,
) -> specs::Entity {
let EntityPackage { uid, comps } = entity_package;
let entity = create_entity_with_uid(self, uid);
for packet in comps {
packet.apply_insert(entity, self)
}
entity
}
fn delete_entity_and_clear_from_uid_allocator(&mut self, uid: u64) {
// Clear from uid allocator
let maybe_entity = self.write_resource::<UidAllocator>().remove_entity(uid);
if let Some(entity) = maybe_entity {
if let Err(err) = self.delete_entity(entity) {
error!("Failed to delete entity: {:?}", err);
}
}
}
fn apply_state_package<P: CompPacket>(&mut self, state_package: StatePackage<P>) {
let StatePackage { entities } = state_package;
// Apply state package entities
for entity_package in entities {
self.apply_entity_package(entity_package);
}
// TODO: determine if this is needed
// Initialize entities
//self.maintain();
}
fn apply_sync_package<P: CompPacket>(&mut self, package: SyncPackage<P>) {
// Take ownership of the fields
let SyncPackage {
comp_updates,
created_entities,
deleted_entities,
} = package;
// Attempt to create entities
for entity_uid in created_entities {
create_entity_with_uid(self, entity_uid);
}
// Update components
for (entity_uid, update) in comp_updates {
if let Some(entity) = self
.read_resource::<UidAllocator>()
.retrieve_entity_internal(entity_uid)
{
match update {
CompUpdateKind::Inserted(packet) => {
packet.apply_insert(entity, self);
}
CompUpdateKind::Modified(packet) => {
packet.apply_modify(entity, self);
}
CompUpdateKind::Removed(phantom) => {
P::apply_remove(phantom, entity, self);
}
}
}
}
// Attempt to delete entities that were marked for deletion
for entity_uid in deleted_entities {
self.delete_entity_and_clear_from_uid_allocator(entity_uid);
}
}
}
// Private utilities
fn create_entity_with_uid(specs_world: &mut specs::World, entity_uid: u64) -> specs::Entity {
let existing_entity = specs_world
.read_resource::<UidAllocator>()
.retrieve_entity_internal(entity_uid);
match existing_entity {
Some(entity) => entity,
None => {
let entity_builder = specs_world.create_entity();
let uid = entity_builder
.world
.write_resource::<UidAllocator>()
.allocate(entity_builder.entity, Some(entity_uid));
entity_builder.with(uid).build()
}
}
}

128
common/src/sync/track.rs Normal file
View File

@ -0,0 +1,128 @@
use super::{
packet::{CompPacket, CompUpdateKind},
uid::Uid,
};
use specs::{BitSet, Component, Entity, Join, ReadStorage, World, WorldExt};
use std::{
convert::{TryFrom, TryInto},
marker::PhantomData,
};
pub struct UpdateTracker<C: Component> {
reader_id: specs::ReaderId<specs::storage::ComponentEvent>,
inserted: BitSet,
modified: BitSet,
removed: BitSet,
phantom: PhantomData<C>,
}
impl<C: Component> UpdateTracker<C>
where
C::Storage: specs::storage::Tracked,
{
pub fn new(specs_world: &mut World) -> Self {
Self {
reader_id: specs_world.write_storage::<C>().register_reader(),
inserted: BitSet::new(),
modified: BitSet::new(),
removed: BitSet::new(),
phantom: PhantomData,
}
}
pub fn inserted(&self) -> &BitSet {
&self.inserted
}
pub fn modified(&self) -> &BitSet {
&self.modified
}
pub fn removed(&self) -> &BitSet {
&self.removed
}
pub fn record_changes<'a>(&mut self, storage: &specs::ReadStorage<'a, C>) {
self.inserted.clear();
self.modified.clear();
self.removed.clear();
for event in storage.channel().read(&mut self.reader_id) {
match event {
specs::storage::ComponentEvent::Inserted(id) => {
// If previously removed/modified we don't need to know that anymore
self.removed.remove(*id);
self.modified.remove(*id);
self.inserted.add(*id);
}
specs::storage::ComponentEvent::Modified(id) => {
// We don't care about modification if the component was just added
if !self.inserted.contains(*id) {
debug_assert!(!self.removed.contains(*id)); // Theoretically impossible
self.modified.add(*id);
}
}
specs::storage::ComponentEvent::Removed(id) => {
// Don't need to know that it was inserted/modified if it was subsequently removed
self.inserted.remove(*id);
self.modified.remove(*id);
self.removed.add(*id);
}
};
}
}
}
impl<C: Component + Clone + Send + Sync> UpdateTracker<C> {
pub fn add_packet_for<'a, P>(
&self,
storage: &ReadStorage<'a, C>,
entity: Entity,
packets: &mut Vec<P>,
) where
P: CompPacket,
P: From<C>,
C: TryFrom<P>,
P::Phantom: From<PhantomData<C>>,
P::Phantom: TryInto<PhantomData<C>>,
C::Storage: specs::storage::Tracked,
{
if let Some(comp) = storage.get(entity) {
packets.push(P::from(comp.clone()));
}
}
pub fn get_updates_for<'a, P>(
&self,
uids: &specs::ReadStorage<'a, Uid>,
storage: &specs::ReadStorage<'a, C>,
entity_filter: impl Join + Copy,
buf: &mut Vec<(u64, CompUpdateKind<P>)>,
) where
P: CompPacket,
P: From<C>,
C: TryFrom<P>,
P::Phantom: From<PhantomData<C>>,
P::Phantom: TryInto<PhantomData<C>>,
C::Storage: specs::storage::Tracked,
{
// Generate inserted updates
for (uid, comp, _, _) in (uids, storage, &self.inserted, entity_filter).join() {
buf.push((
(*uid).into(),
CompUpdateKind::Inserted(P::from(comp.clone())),
));
}
// Generate modified updates
for (uid, comp, _, _) in (uids, storage, &self.modified, entity_filter).join() {
buf.push((
(*uid).into(),
CompUpdateKind::Modified(P::from(comp.clone())),
));
}
// Generate removed updates
for (uid, _, _) in (uids, &self.removed, entity_filter).join() {
buf.push((
(*uid).into(),
CompUpdateKind::Removed(P::Phantom::from(PhantomData::<C>)),
));
}
}
}

92
common/src/sync/uid.rs Normal file
View File

@ -0,0 +1,92 @@
use serde_derive::{Deserialize, Serialize};
use specs::{
saveload::{Marker, MarkerAllocator},
world::EntitiesRes,
Component, Entity, FlaggedStorage, Join, ReadStorage, VecStorage,
};
use std::{collections::HashMap, fmt, u64};
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct Uid(pub u64);
impl Into<u64> for Uid {
fn into(self) -> u64 {
self.0
}
}
impl From<u64> for Uid {
fn from(uid: u64) -> Self {
Self(uid)
}
}
impl fmt::Display for Uid {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl Component for Uid {
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
}
impl Marker for Uid {
type Identifier = u64;
type Allocator = UidAllocator;
fn id(&self) -> u64 {
self.0
}
fn update(&mut self, update: Self) {
assert_eq!(self.0, update.0);
}
}
pub struct UidAllocator {
index: u64,
mapping: HashMap<u64, Entity>,
}
impl UidAllocator {
pub fn new() -> Self {
Self {
index: 0,
mapping: HashMap::new(),
}
}
// Useful for when a single entity is deleted because it doesn't reconstruct the entire hashmap
pub fn remove_entity(&mut self, id: u64) -> Option<Entity> {
self.mapping.remove(&id)
}
}
impl Default for UidAllocator {
fn default() -> Self {
Self::new()
}
}
impl MarkerAllocator<Uid> for UidAllocator {
fn allocate(&mut self, entity: Entity, id: Option<u64>) -> Uid {
let id = id.unwrap_or_else(|| {
let id = self.index;
self.index += 1;
id
});
self.mapping.insert(id, entity);
Uid(id)
}
fn retrieve_entity_internal(&self, id: u64) -> Option<Entity> {
self.mapping.get(&id).copied()
}
fn maintain(&mut self, entities: &EntitiesRes, storage: &ReadStorage<Uid>) {
self.mapping = (entities, storage)
.join()
.map(|(e, m)| (m.id(), e))
.collect();
}
}

View File

@ -1,6 +1,7 @@
use crate::comp::{
Agent, CharacterState, Controller, ControllerInputs, MountState, MoveState::Glide, Pos, Stats,
};
use crate::hierarchical::ChunkPath;
use crate::pathfinding::WorldPath;
use crate::terrain::TerrainGrid;
use rand::{seq::SliceRandom, thread_rng};
@ -59,7 +60,7 @@ impl<'a> System<'a> for Sys {
controller.reset();
let mut inputs = ControllerInputs::default();
let mut inputs = &mut controller.inputs;
match agent {
Agent::Traveler { path } => {
@ -72,7 +73,10 @@ impl<'a> System<'a> for Sys {
const MAX_TRAVEL_DIST: f32 = 200.0;
let new_dest = Vec3::new(rand::random::<f32>(), rand::random::<f32>(), 0.0)
* MAX_TRAVEL_DIST;
new_path = Some(WorldPath::new(&*terrain, pos.0, pos.0 + new_dest));
new_path = Some(
ChunkPath::new(&*terrain, pos.0, pos.0 + new_dest)
.get_worldpath(&*terrain),
);
};
path.move_along_path(
@ -146,10 +150,13 @@ impl<'a> System<'a> for Sys {
let dist = Vec2::<f32>::from(target_pos.0 - pos.0).magnitude();
if target_stats.is_dead {
choose_new = true;
} else if dist < MIN_ATTACK_DIST
&& dist > 0.001
&& rand::random::<f32>() < 0.3
{
} else if dist < 0.001 {
// Probably can only happen when entities are at a different z-level
// since at the same level repulsion would keep them apart.
// Distinct from the first if block since we may want to change the
// behavior for this case.
choose_new = true;
} else if dist < MIN_ATTACK_DIST {
// Fight (and slowly move closer)
inputs.move_dir =
Vec2::<f32>::from(target_pos.0 - pos.0).normalized() * 0.01;
@ -203,7 +210,8 @@ impl<'a> System<'a> for Sys {
}
}
controller.inputs = inputs;
debug_assert!(inputs.move_dir.map(|e| !e.is_nan()).reduce_and());
debug_assert!(inputs.look_dir.map(|e| !e.is_nan()).reduce_and());
}
}
}

View File

@ -1,10 +1,11 @@
use crate::{
comp::{
ActionState::*, CharacterState, Controller, HealthChange, HealthSource, Item, ItemKind,
Ori, Pos, Stats,
ActionState::*, Body, CharacterState, Controller, HealthChange, HealthSource, Item,
ItemKind, Ori, Pos, Scale, Stats,
},
event::{EventBus, LocalEvent, ServerEvent},
state::{DeltaTime, Uid},
state::DeltaTime,
sync::Uid,
};
use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage};
use std::time::Duration;
@ -12,7 +13,8 @@ use vek::*;
const BLOCK_EFFICIENCY: f32 = 0.9;
const ATTACK_RANGE: f32 = 4.0;
const ATTACK_RANGE: f32 = 3.5;
const ATTACK_ANGLE: f32 = 45.0;
const BLOCK_ANGLE: f32 = 180.0;
/// This system is responsible for handling accepted inputs like moving or attacking
@ -26,9 +28,11 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Uid>,
ReadStorage<'a, Pos>,
ReadStorage<'a, Ori>,
ReadStorage<'a, Scale>,
ReadStorage<'a, Controller>,
ReadStorage<'a, Body>,
ReadStorage<'a, Stats>,
WriteStorage<'a, CharacterState>,
WriteStorage<'a, Stats>,
);
fn run(
@ -41,34 +45,37 @@ impl<'a> System<'a> for Sys {
uids,
positions,
orientations,
scales,
controllers,
mut character_states,
bodies,
stats,
mut character_states,
): Self::SystemData,
) {
// let mut server_emitter = server_bus.emitter();
// let mut _local_emitter = local_bus.emitter();
// // Attacks
// for (entity, uid, pos, ori, _, stat) in (
// &entities,
// &uids,
// &positions,
// &orientations,
// &controllers,
// &stats,
// )
// .join()
// {
// let recover_duration = if let Some(Item {
// kind: ItemKind::Tool { kind, .. },
// ..
// }) = stat.equipment.main
// {
// kind.attack_recover_duration()
// } else {
// Duration::from_secs(1)
// };
// Attacks
for (entity, uid, pos, ori, scale_maybe, _, attacker_stats) in (
&entities,
&uids,
&positions,
&orientations,
scales.maybe(),
&controllers,
&stats,
)
.join()
{
let recover_duration = if let Some(Item {
kind: ItemKind::Tool { kind, .. },
..
}) = attacker_stats.equipment.main
{
kind.attack_recover_duration()
} else {
Duration::from_secs(1)
};
// let (deal_damage, should_end) = if let Some(Attack { time_left, applied }) =
// &mut character_states.get_mut(entity).map(|c| &mut c.action)
@ -88,47 +95,54 @@ impl<'a> System<'a> for Sys {
// (false, false)
// };
// if deal_damage {
// if let Some(Attack { .. }) = &character_states.get(entity).map(|c| c.action) {
// // Go through all other entities
// for (b, uid_b, pos_b, ori_b, character_b, stat_b) in (
// &entities,
// &uids,
// &positions,
// &orientations,
// &character_states,
// &stats,
// )
// .join()
// if deal_damage {
// if let Some(Attack { .. }) = &character_states.get(entity).map(|c| c.action) {
// // Go through all other entities
// for (b, uid_b, pos_b, ori_b, scale_b_maybe, character_b, stats_b, body_b) in (
// &entities,
// &uids,
// &positions,
// &orientations,
// scales.maybe(),
// &character_states,
// &stats,
// &bodies,
// )
// .join()
// {
// // 2D versions
// let pos2 = Vec2::from(pos.0);
// let pos_b2: Vec2<f32> = Vec2::from(pos_b.0);
// let ori2 = Vec2::from(ori.0);
// // Scales
// let scale = scale_maybe.map_or(1.0, |s| s.0);
// let scale_b = scale_b_maybe.map_or(1.0, |s| s.0);
// let rad_b = body_b.radius() * scale_b;
// // Check if it is a hit
// if entity != b
// && !stats_b.is_dead
// // Spherical wedge shaped attack field
// && pos.0.distance_squared(pos_b.0) < (rad_b + scale * ATTACK_RANGE).powi(2)
// && ori2.angle_between(pos_b2 - pos2) < ATTACK_ANGLE.to_radians() / 2.0 + (rad_b / pos2.distance(pos_b2)).atan()
// {
// // 2D versions
// let pos2 = Vec2::from(pos.0);
// let pos_b2: Vec2<f32> = Vec2::from(pos_b.0);
// let ori2 = Vec2::from(ori.0);
// // Check if it is a hit
// if entity != b
// && !stat_b.is_dead
// && pos.0.distance_squared(pos_b.0) < ATTACK_RANGE.powi(2)
// // TODO: Use size instead of 1.0
// && ori2.angle_between(pos_b2 - pos2) < (2.0 / pos2.distance(pos_b2)).atan()
// // Weapon gives base damage
// let mut dmg = if let Some(ItemKind::Tool { power, .. }) =
// attacker_stats.equipment.main.as_ref().map(|i| &i.kind)
// {
// // Weapon gives base damage
// let mut dmg = if let Some(ItemKind::Tool { power, .. }) =
// stat.equipment.main.as_ref().map(|i| &i.kind)
// {
// *power as i32
// } else {
// 1
// };
// *power as i32
// } else {
// 1
// };
// // Block
// if character_b.action.is_block()
// && ori_b.0.angle_between(pos.0 - pos_b.0).to_degrees()
// < BLOCK_ANGLE / 2.0
// {
// dmg = (dmg as f32 * (1.0 - BLOCK_EFFICIENCY)) as i32
// }
// // Block
// if character_b.action.is_block()
// && ori_b.0.angle_between(pos.0 - pos_b.0)
// < BLOCK_ANGLE.to_radians() / 2.0
// {
// dmg = (dmg as f32 * (1.0 - BLOCK_EFFICIENCY)) as i32
// }
// server_emitter.emit(ServerEvent::Damage {
// uid: *uid_b,

View File

@ -2,6 +2,7 @@ use crate::{
comp::{ControlEvent, Controller},
event::{EventBus, LocalEvent, ServerEvent},
state::DeltaTime,
sync::{Uid, UidAllocator},
};
use specs::{
saveload::{Marker, MarkerAllocator},

View File

@ -2,6 +2,8 @@ mod ability;
pub mod agent;
pub mod character_state;
pub mod controller;
mod mount;
pub mod movement;
pub mod phys;
mod projectile;
mod stats;
@ -10,20 +12,28 @@ mod stats;
use specs::DispatcherBuilder;
// System names
const ABILITY_SYS: &str = "ability_sys";
const AGENT_SYS: &str = "agent_sys";
const CHARACTER_STATE_SYS: &str = "character_state_sys";
const CONTROLLER_SYS: &str = "controller_sys";
const PHYS_SYS: &str = "phys_sys";
const PROJECTILE_SYS: &str = "projectile_sys";
const STATS_SYS: &str = "stats_sys";
pub const ABILITY_SYS: &str = "ability_sys";
pub const CHARACTER_STATE_SYS: &str = "character_state_sys";
pub const AGENT_SYS: &str = "agent_sys";
pub const CONTROLLER_SYS: &str = "controller_sys";
pub const MOUNT_SYS: &str = "mount_sys";
pub const PHYS_SYS: &str = "phys_sys";
pub const MOVEMENT_SYS: &str = "movement_sys";
pub const PROJECTILE_SYS: &str = "projectile_sys";
pub const STATS_SYS: &str = "stats_sys";
pub const CLEANUP_SYS: &str = "cleanup_sys";
pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
dispatch_builder.add(agent::Sys, AGENT_SYS, &[]);
dispatch_builder.add(controller::Sys, CONTROLLER_SYS, &[AGENT_SYS]);
dispatch_builder.add(mount::Sys, MOUNT_SYS, &[AGENT_SYS]);
dispatch_builder.add(controller::Sys, CONTROLLER_SYS, &[AGENT_SYS, MOUNT_SYS]);
dispatch_builder.add(character_state::Sys, CHARACTER_STATE_SYS, &[CONTROLLER_SYS]);
dispatch_builder.add(ability::Sys, ABILITY_SYS, &[CHARACTER_STATE_SYS]);
dispatch_builder.add(stats::Sys, STATS_SYS, &[]);
dispatch_builder.add(phys::Sys, PHYS_SYS, &[CONTROLLER_SYS, STATS_SYS]);
dispatch_builder.add(ability::Sys, ABILITY_SYS, &[CHARACTER_STATE_SYS]);
dispatch_builder.add(
phys::Sys,
PHYS_SYS,
&[CONTROLLER_SYS, MOUNT_SYS, MOVEMENT_SYS, STATS_SYS],
);
dispatch_builder.add(projectile::Sys, PROJECTILE_SYS, &[PHYS_SYS]);
}

89
common/src/sys/mount.rs Normal file
View File

@ -0,0 +1,89 @@
use crate::{
comp::{Controller, MountState, Mounting, Ori, Pos, Vel},
sync::UidAllocator,
};
use specs::{
saveload::{Marker, MarkerAllocator},
Entities, Join, Read, System, WriteStorage,
};
use vek::*;
/// This system is responsible for controlling mounts
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Read<'a, UidAllocator>,
Entities<'a>,
WriteStorage<'a, Controller>,
WriteStorage<'a, MountState>,
WriteStorage<'a, Mounting>,
WriteStorage<'a, Pos>,
WriteStorage<'a, Vel>,
WriteStorage<'a, Ori>,
);
fn run(
&mut self,
(
uid_allocator,
entities,
mut controllers,
mut mount_state,
mut mountings,
mut positions,
mut velocities,
mut orientations,
): Self::SystemData,
) {
// Mounted entities.
for (entity, mut mount_states) in (&entities, &mut mount_state.restrict_mut()).join() {
match mount_states.get_unchecked() {
MountState::Unmounted => {}
MountState::MountedBy(mounter_uid) => {
// Note: currently controller events are not passed through since none of them
// are currently relevant to controlling the mounted entity
if let Some((inputs, mounter)) = uid_allocator
.retrieve_entity_internal(mounter_uid.id())
.and_then(|mounter| {
controllers
.get(mounter)
.map(|c| (c.inputs.clone(), mounter))
})
{
// TODO: consider joining on these? (remember we can use .maybe())
let pos = positions.get(entity).copied();
let ori = orientations.get(entity).copied();
let vel = velocities.get(entity).copied();
if let (Some(pos), Some(ori), Some(vel)) = (pos, ori, vel) {
let _ = positions.insert(mounter, Pos(pos.0 + Vec3::unit_z() * 1.0));
let _ = orientations.insert(mounter, ori);
let _ = velocities.insert(mounter, vel);
}
controllers.get_mut(entity).map(|controller| {
*controller = Controller {
inputs,
..Default::default()
}
});
} else {
*(mount_states.get_mut_unchecked()) = MountState::Unmounted;
}
}
}
}
let mut to_unmount = Vec::new();
for (entity, Mounting(mountee_uid)) in (&entities, &mountings).join() {
if uid_allocator
.retrieve_entity_internal(mountee_uid.id())
.filter(|mountee| entities.is_alive(*mountee))
.is_none()
{
to_unmount.push(entity);
}
}
for entity in to_unmount {
mountings.remove(entity);
}
}
}

View File

@ -3,11 +3,11 @@ use {
comp::{Body, Gravity, Mass, Mounting, Ori, PhysicsState, Pos, Scale, Sticky, Vel},
event::{EventBus, ServerEvent},
state::DeltaTime,
sync::Uid,
terrain::{Block, TerrainGrid},
vol::ReadVol,
},
specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage},
sphynx::Uid,
vek::*,
};
@ -102,6 +102,7 @@ impl<'a> System<'a> for Sys {
let scale = scale.map(|s| s.0).unwrap_or(1.0);
// Basic collision with terrain
// TODO: rename this, not just the player entity
let player_rad = 0.3 * scale; // half-width of the player's AABB
let player_height = 1.5 * scale;
@ -109,8 +110,10 @@ impl<'a> System<'a> for Sys {
let hdist = player_rad.ceil() as i32;
let vdist = player_height.ceil() as i32;
// Neighbouring blocks iterator
let near_iter = (-hdist..=hdist)
.map(move |i| (-hdist..=hdist).map(move |j| (0..=vdist).map(move |k| (i, j, k))))
let near_iter = (-hdist..hdist + 1)
.map(move |i| {
(-hdist..hdist + 1).map(move |j| (0..vdist + 1).map(move |k| (i, j, k)))
})
.flatten()
.flatten();
@ -342,16 +345,20 @@ impl<'a> System<'a> for Sys {
}
// Apply pushback
for (pos, scale, mass, vel, _, _, physics) in (
for (pos, scale, mass, vel, _, _, _, physics) in (
&positions,
scales.maybe(),
masses.maybe(),
&mut velocities,
&bodies,
!&mountings,
stickies.maybe(),
&mut physics_states,
)
.join()
.filter(|(_, _, _, _, _, _, sticky, physics)| {
sticky.is_none() || (physics.on_wall.is_none() && !physics.on_ground)
})
{
physics.touch_entity = None;

View File

@ -1,6 +1,6 @@
use crate::{
comp::{HealthSource, Stats},
event::{EventBus, ServerEvent, SfxEvent, SfxEventItem},
event::{EventBus, ServerEvent},
state::DeltaTime,
};
use specs::{Entities, Join, Read, System, WriteStorage};
@ -13,18 +13,31 @@ impl<'a> System<'a> for Sys {
Entities<'a>,
Read<'a, DeltaTime>,
Read<'a, EventBus<ServerEvent>>,
Read<'a, EventBus<SfxEventItem>>,
WriteStorage<'a, Stats>,
);
fn run(
&mut self,
(entities, dt, server_event_bus, audio_event_bus, mut stats): Self::SystemData,
) {
fn run(&mut self, (entities, dt, server_event_bus, mut stats): Self::SystemData) {
let mut server_event_emitter = server_event_bus.emitter();
for (entity, mut stat) in (&entities, &mut stats).join() {
if stat.should_die() && !stat.is_dead {
// Increment last change timer
stats.set_event_emission(false); // avoid unnecessary syncing
for stat in (&mut stats).join() {
stat.health.last_change.0 += f64::from(dt.0);
}
stats.set_event_emission(true);
// Mutates all stats every tick causing the server to resend this component for every entity every tick
for (entity, mut stats) in (&entities, &mut stats.restrict_mut()).join() {
let (set_dead, level_up) = {
let stat = stats.get_unchecked();
(
stat.should_die() && !stat.is_dead,
stat.exp.current() >= stat.exp.maximum(),
)
};
if set_dead {
let stat = stats.get_mut_unchecked();
server_event_emitter.emit(ServerEvent::Destroy {
entity,
cause: stat.health.last_change.1.cause,
@ -33,19 +46,14 @@ impl<'a> System<'a> for Sys {
stat.is_dead = true;
}
stat.health.last_change.0 += f64::from(dt.0);
if stat.exp.current() >= stat.exp.maximum() {
if level_up {
let stat = stats.get_mut_unchecked();
while stat.exp.current() >= stat.exp.maximum() {
stat.exp.change_by(-(stat.exp.maximum() as i64));
stat.exp.change_maximum_by(25);
stat.level.change_by(1);
}
audio_event_bus
.emitter()
.emit(SfxEventItem::at_player_position(SfxEvent::LevelUp));
stat.update_max_hp();
stat.health
.set_to(stat.health.maximum(), HealthSource::LevelUp)

View File

@ -7,81 +7,41 @@ lazy_static::lazy_static! {
use vek::{Mat3, Rgb, Rgba, Vec3};
/// TODO: Move these to a named utils folder. Are they even being used? I couldnt find references.
/// This is a fast approximation of powf. This should only be used when minor accuracy loss is acceptable.
#[inline(always)]
#[allow(unsafe_code)]
fn approx_powf(b: f32, e: f32) -> f32 {
unsafe {
let b = b as f64;
let e = e as f64;
union Swagger {
f: f64,
a: [i32; 2],
}
let mut b = Swagger { f: b };
b.a[1] = (e * (b.a[1] as f64 - 1072632447.0) + 1072632447.0) as i32;
b.a[0] = 0;
b.f as f32
}
}
#[cfg(test)]
mod approx_powf_tests {
fn close_ei(a: f32, b: f32) -> bool {
(a - b < 1.0 && a - b > 0.0) || (b - a < 1.0 && b - a > 0.0)
}
#[test]
fn accuracy_1() {
let test_values: Vec<f32> = vec![3.0, 2.5, 1.5, 2.2];
test_values.windows(2).for_each(|a| {
assert!(close_ei(a[0].powf(a[1]), super::approx_powf(a[0], a[1])));
});
}
}
/// TODO: Move these to a named utils folder. Are they even being used? I couldnt find references.
#[inline(always)]
pub fn srgb_to_linear(col: Rgb<f32>) -> Rgb<f32> {
#[inline(always)]
fn to_linear(x: f32) -> f32 {
if x <= 0.04045 {
x / 12.92
col.map(|c| {
if c <= 0.104 {
c * 0.08677088
} else {
approx_powf((x + 0.055) / 1.055, 2.4)
0.012522878 * c + 0.682171111 * c * c + 0.305306011 * c * c * c
}
}
col.map(to_linear)
})
}
/// TODO: Move these to a named utils folder. Are they even being used? I couldnt find references.
#[inline(always)]
pub fn linear_to_srgb(col: Rgb<f32>) -> Rgb<f32> {
#[inline(always)]
fn to_srgb(x: f32) -> f32 {
if x <= 0.0031308 {
x * 12.92
col.map(|c| {
if c <= 0.0060 {
c * 11.500726
} else {
approx_powf(x, 1.0 / 2.4) * 1.055 - 0.055
let s1 = c.sqrt();
let s2 = s1.sqrt();
let s3 = s2.sqrt();
0.585122381 * s1 + 0.783140355 * s2 - 0.368262736 * s3
}
}
col.map(to_srgb)
})
}
/// TODO: Move these to a named utils folder. Are they even being used? I couldnt find references.
#[inline(always)]
pub fn srgba_to_linear(col: Rgba<f32>) -> Rgba<f32> {
Rgba::from_translucent(srgb_to_linear(Rgb::from(col)), col.a)
}
/// TODO: Move these to a named utils folder. Are they even being used? I couldnt find references.
#[inline(always)]
pub fn linear_to_srgba(col: Rgba<f32>) -> Rgba<f32> {
Rgba::from_translucent(linear_to_srgb(Rgb::from(col)), col.a)
}
/// TODO: Move these to a named utils folder. Are they even being used? I couldnt find references.
/// Convert rgb to hsv. Expects rgb to be [0, 1].
#[inline(always)]
pub fn rgb_to_hsv(rgb: Rgb<f32>) -> Vec3<f32> {
@ -114,7 +74,6 @@ pub fn rgb_to_hsv(rgb: Rgb<f32>) -> Vec3<f32> {
Vec3::new(h, s, v)
}
/// TODO: Move these to a named utils folder. Are they even being used? I couldnt find references.
/// Convert hsv to rgb. Expects h [0, 360], s [0, 1], v [0, 1]
#[inline(always)]
pub fn hsv_to_rgb(hsv: Vec3<f32>) -> Rgb<f32> {
@ -141,7 +100,6 @@ pub fn hsv_to_rgb(hsv: Vec3<f32>) -> Rgb<f32> {
Rgb::new(r + m, g + m, b + m)
}
/// TODO: Move these to a named utils folder. Are they even being used? I couldnt find references.
/// Convert linear rgb to CIExyY
#[inline(always)]
pub fn rgb_to_xyy(rgb: Rgb<f32>) -> Vec3<f32> {
@ -154,7 +112,6 @@ pub fn rgb_to_xyy(rgb: Rgb<f32>) -> Vec3<f32> {
Vec3::new(xyz.x / sum, xyz.y / sum, xyz.y)
}
/// TODO: Move these to a named utils folder. Are they even being used? I couldnt find references.
/// Convert to CIExyY to linear rgb
#[inline(always)]
pub fn xyy_to_rgb(xyy: Vec3<f32>) -> Rgb<f32> {
@ -171,7 +128,6 @@ pub fn xyy_to_rgb(xyy: Vec3<f32>) -> Rgb<f32> {
)
}
/// TODO: Move these to a named utils folder. Are they even being used? I couldnt find references.
// TO-DO: speed this up
#[inline(always)]
pub fn saturate_srgb(col: Rgb<f32>, value: f32) -> Rgb<f32> {
@ -180,7 +136,6 @@ pub fn saturate_srgb(col: Rgb<f32>, value: f32) -> Rgb<f32> {
linear_to_srgb(hsv_to_rgb(hsv).map(|e| e.min(1.0).max(0.0)))
}
/// TODO: Move these to a named utils folder. Are they even being used? I couldnt find references.
/// Preserves the luma of one color while changing its chromaticty to match the other
#[inline(always)]
pub fn chromify_srgb(luma: Rgb<f32>, chroma: Rgb<f32>) -> Rgb<f32> {

Some files were not shown because too many files have changed in this diff Show More