mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge master and build
This commit is contained in:
commit
d82e93b39f
1
.gitignore
vendored
1
.gitignore
vendored
@ -38,3 +38,4 @@ todo.txt
|
||||
|
||||
# direnv
|
||||
/.envrc
|
||||
*.bat
|
||||
|
139
CHANGELOG.md
Normal file
139
CHANGELOG.md
Normal 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
713
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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]
|
||||
|
@ -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`
|
||||
|
8
assets/common/items/weapons/hammer_1.ron
Normal file
8
assets/common/items/weapons/hammer_1.ron
Normal file
@ -0,0 +1,8 @@
|
||||
Item(
|
||||
name: "Crude Mallet",
|
||||
description: "Breaks bones like sticks and stones.",
|
||||
kind: Tool(
|
||||
kind: Hammer,
|
||||
power: 20,
|
||||
),
|
||||
)
|
8
assets/common/items/weapons/staff_1.ron
Normal file
8
assets/common/items/weapons/staff_1.ron
Normal file
@ -0,0 +1,8 @@
|
||||
Item(
|
||||
name: "Humble Stick",
|
||||
description: "Walking stick with a sharpened end.",
|
||||
kind: Tool(
|
||||
kind: Hammer,
|
||||
power: 6,
|
||||
),
|
||||
)
|
@ -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",
|
||||
],
|
||||
|
BIN
assets/voxygen/audio/soundtrack/veloren_title_tune-3.ogg
(Stored with Git LFS)
BIN
assets/voxygen/audio/soundtrack/veloren_title_tune-3.ogg
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/audio/soundtrack/veloren_title_tune.ogg
(Stored with Git LFS)
Normal file
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
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
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
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
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
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
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
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
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
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)
BIN
assets/voxygen/background/map.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/buttons/indicator_mmap.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/buttons/indicator_mmap.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/buttons/indicator_mmap_2.vox
(Stored with Git LFS)
Normal file
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
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
BIN
assets/voxygen/element/buttons/min_plus/mmap_button-min.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/min_plus/mmap_button-min_hover.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/min_plus/mmap_button-min_hover.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/min_plus/mmap_button-min_press.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/min_plus/mmap_button-min_press.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/min_plus/mmap_button-plus.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/min_plus/mmap_button-plus.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/min_plus/mmap_button-plus_hover.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/min_plus/mmap_button-plus_hover.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/min_plus/mmap_button-plus_press.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/min_plus/mmap_button-plus_press.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/frames/enemybar-0.vox
(Stored with Git LFS)
Normal file
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
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)
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
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
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
BIN
assets/voxygen/element/skillbar/enemy_bar_content.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -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);
|
||||
|
@ -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
BIN
assets/voxygen/voxel/armor/back/short-0.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/fixture/selection_bg - Kopie.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/fixture/selection_bg - Kopie.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/npc/oger/belt.vox
(Stored with Git LFS)
Normal file
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
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
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
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
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
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
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
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
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
BIN
assets/voxygen/voxel/weapon/staff/wood-simple.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -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)
|
||||
),
|
||||
]
|
||||
)
|
||||
|
@ -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)
|
||||
),
|
||||
]
|
||||
)
|
||||
|
@ -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)
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)
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)
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)
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)
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
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
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
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
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
BIN
assets/world/tree/acacia_savannah/5.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -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);
|
||||
|
@ -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"] }
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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::*;
|
||||
|
22
common/benches/color_benchmark.rs
Normal file
22
common/benches/color_benchmark.rs
Normal 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);
|
@ -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 {
|
||||
|
69
common/src/comp/body/giant.rs
Normal file
69
common/src/comp/body/giant.rs
Normal 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,
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
54
common/src/comp/energy.rs
Normal 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>>;
|
||||
}
|
@ -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,
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::state::Uid;
|
||||
use crate::sync::Uid;
|
||||
use specs::{Component, FlaggedStorage, NullStorage};
|
||||
use specs_idvs::IDVStorage;
|
||||
use vek::*;
|
||||
|
@ -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>,
|
||||
|
@ -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 {
|
||||
|
@ -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
130
common/src/hierarchical.rs
Normal 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
|
||||
}
|
@ -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;
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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
14
common/src/sync/mod.rs
Normal 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
125
common/src/sync/packet.rs
Normal 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
164
common/src/sync/sync_ext.rs
Normal 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
128
common/src/sync/track.rs
Normal 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
92
common/src/sync/uid.rs
Normal 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();
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -2,6 +2,7 @@ use crate::{
|
||||
comp::{ControlEvent, Controller},
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
state::DeltaTime,
|
||||
sync::{Uid, UidAllocator},
|
||||
};
|
||||
use specs::{
|
||||
saveload::{Marker, MarkerAllocator},
|
||||
|
@ -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
89
common/src/sys/mount.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user