mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge remote-tracking branch 'origin/master' into sharp/map-colors
This commit is contained in:
commit
f8926a5737
@ -8,7 +8,8 @@ variables:
|
|||||||
stages:
|
stages:
|
||||||
- optional-builds
|
- optional-builds
|
||||||
- check-compile
|
- check-compile
|
||||||
- post
|
- build-post
|
||||||
|
- publish
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- source $HOME/.cargo/env
|
- source $HOME/.cargo/env
|
||||||
@ -70,7 +71,7 @@ check:
|
|||||||
tags:
|
tags:
|
||||||
- veloren-docker
|
- veloren-docker
|
||||||
script:
|
script:
|
||||||
- RUSTFLAGS="-D warnings" cargo check
|
- RUSTFLAGS="-D warnings" cargo check --locked
|
||||||
|
|
||||||
code-quality:
|
code-quality:
|
||||||
stage: check-compile
|
stage: check-compile
|
||||||
@ -90,29 +91,30 @@ security:
|
|||||||
|
|
||||||
# --
|
# --
|
||||||
|
|
||||||
# -- post build
|
# -- build-post
|
||||||
|
|
||||||
unittests:
|
unittests:
|
||||||
stage: post
|
stage: build-post
|
||||||
when: delayed
|
when: delayed
|
||||||
start_in: 5 seconds
|
start_in: 5 seconds
|
||||||
tags:
|
tags:
|
||||||
- veloren-docker
|
- veloren-docker
|
||||||
script:
|
script:
|
||||||
- echo "Workaround, cargo tests fails due some rust files are already deleted, so we just stack cargo test. if its the os error, it wont appear on them all, if its a real error, it will retry and then fail"
|
- echo "Workaround, cargo tests fails due some rust files are already deleted, so we just stack cargo test. if its the os error, it wont appear on them all, if its a real error, it will retry and then fail"
|
||||||
- cargo test || cargo test || cargo test || cargo test
|
- cargo test || ( sleep 10 && cargo test ) || ( sleep 10 && cargo test ) || cargo test || cargo test || cargo test || cargo test || cargo test || cargo test || cargo test || cargo test || cargo test || cargo test
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
stage: post
|
stage: build-post
|
||||||
when: delayed
|
when: delayed
|
||||||
start_in: 5 seconds
|
start_in: 5 seconds
|
||||||
tags:
|
tags:
|
||||||
- veloren-docker
|
- veloren-docker
|
||||||
script:
|
script:
|
||||||
- cargo tarpaulin -v
|
- echo "Workaround, tarpaulin fails due some rust files are already deleted, so we just stack tarpaulin. if its the os error, it wont appear on them all, if its a real error, it will retry and then fail"
|
||||||
|
- cargo tarpaulin -v || ( sleep 10 && cargo tarpaulin -v ) || ( sleep 10 && cargo tarpaulin -v ) || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v || cargo tarpaulin -v
|
||||||
|
|
||||||
benchmarks:
|
benchmarks:
|
||||||
stage: post
|
stage: build-post
|
||||||
when: delayed
|
when: delayed
|
||||||
start_in: 5 seconds
|
start_in: 5 seconds
|
||||||
tags:
|
tags:
|
||||||
@ -121,14 +123,26 @@ benchmarks:
|
|||||||
- unset DISABLE_GIT_LFS_CHECK
|
- unset DISABLE_GIT_LFS_CHECK
|
||||||
- cargo bench
|
- cargo bench
|
||||||
|
|
||||||
|
localization-status:
|
||||||
|
variables:
|
||||||
|
GIT_DEPTH: 0
|
||||||
|
stage: build-post
|
||||||
|
when: delayed
|
||||||
|
start_in: 5 seconds
|
||||||
|
allow_failure: true
|
||||||
|
tags:
|
||||||
|
- veloren-docker
|
||||||
|
script:
|
||||||
|
- cargo test -q test_all_localizations -- --nocapture --ignored
|
||||||
|
|
||||||
linux:
|
linux:
|
||||||
stage: post
|
stage: build-post
|
||||||
when: delayed
|
when: delayed
|
||||||
start_in: 5 seconds
|
start_in: 5 seconds
|
||||||
only:
|
only:
|
||||||
refs:
|
refs:
|
||||||
- /^r[0-9]+\.[0-9]+\.[0-9]+/
|
- /^r[0-9]+\.[0-9]+\.[0-9]+/
|
||||||
- /^v[0-9]+\.[0-9]+\.[0-9]+/
|
- /^v[0-9]+\.[0-9]+/
|
||||||
- /^master$/
|
- /^master$/
|
||||||
tags:
|
tags:
|
||||||
- veloren-docker
|
- veloren-docker
|
||||||
@ -147,13 +161,13 @@ linux:
|
|||||||
expire_in: 1 week
|
expire_in: 1 week
|
||||||
|
|
||||||
windows:
|
windows:
|
||||||
stage: post
|
stage: build-post
|
||||||
when: delayed
|
when: delayed
|
||||||
start_in: 5 seconds
|
start_in: 5 seconds
|
||||||
only:
|
only:
|
||||||
refs:
|
refs:
|
||||||
- /^r[0-9]+\.[0-9]+\.[0-9]+/
|
- /^r[0-9]+\.[0-9]+\.[0-9]+/
|
||||||
- /^v[0-9]+\.[0-9]+\.[0-9]+/
|
- /^v[0-9]+\.[0-9]+/
|
||||||
- /^master$/
|
- /^master$/
|
||||||
tags:
|
tags:
|
||||||
- veloren-docker
|
- veloren-docker
|
||||||
@ -169,4 +183,52 @@ windows:
|
|||||||
- LICENSE
|
- LICENSE
|
||||||
expire_in: 1 week
|
expire_in: 1 week
|
||||||
|
|
||||||
|
macos:
|
||||||
|
stage: build-post
|
||||||
|
when: delayed
|
||||||
|
start_in: 5 seconds
|
||||||
|
only:
|
||||||
|
refs:
|
||||||
|
- /^r[0-9]+\.[0-9]+\.[0-9]+/
|
||||||
|
- /^v[0-9]+\.[0-9]+/
|
||||||
|
- /^master$/
|
||||||
|
tags:
|
||||||
|
- veloren-docker
|
||||||
|
script:
|
||||||
|
- PATH="/dockercache/osxcross/target/bin:$PATH" COREAUDIO_SDK_PATH=/dockercache/osxcross/target/SDK/MacOSX10.13.sdk CC=o64-clang CXX=o64-clang++ cargo build --target x86_64-apple-darwin --release
|
||||||
|
- cp -r target/x86_64-apple-darwin/release/veloren-server-cli $CI_PROJECT_DIR
|
||||||
|
- cp -r target/x86_64-apple-darwin/release/veloren-voxygen $CI_PROJECT_DIR
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- veloren-server-cli
|
||||||
|
- veloren-voxygen
|
||||||
|
- assets/
|
||||||
|
- LICENSE
|
||||||
|
expire_in: 1 week
|
||||||
|
# --
|
||||||
|
|
||||||
|
# -- publish
|
||||||
|
|
||||||
|
docker:
|
||||||
|
stage: publish
|
||||||
|
when: delayed
|
||||||
|
start_in: 5 seconds
|
||||||
|
image:
|
||||||
|
name: gcr.io/kaniko-project/executor:debug
|
||||||
|
entrypoint: [""]
|
||||||
|
dependencies:
|
||||||
|
- linux
|
||||||
|
before_script:
|
||||||
|
- ls "$CI_PROJECT_DIR/server-cli/"
|
||||||
|
only:
|
||||||
|
refs:
|
||||||
|
- /^r[0-9]+\.[0-9]+\.[0-9]+/
|
||||||
|
- /^v[0-9]+\.[0-9]+/
|
||||||
|
- /^master$/
|
||||||
|
tags:
|
||||||
|
- veloren-docker
|
||||||
|
script:
|
||||||
|
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
|
||||||
|
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/server-cli/Dockerfile --destination "${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_NAME}-server"
|
||||||
|
|
||||||
# --
|
# --
|
||||||
|
28
.gitlab/CODEOWNERS
Normal file
28
.gitlab/CODEOWNERS
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Defines people who should approve to certain parts of the codebase
|
||||||
|
|
||||||
|
/assets/ @assetsandvisualdesign @frontend
|
||||||
|
/chat-cli/ @frontend
|
||||||
|
/client/ @backend @networking
|
||||||
|
/common/ @backend @networking
|
||||||
|
/server/ @backend @networking
|
||||||
|
/server-cli/ @frontend
|
||||||
|
#/voxygen/ @someone
|
||||||
|
/voxygen/anim/ @animation
|
||||||
|
/voxygen/audio/ @audio
|
||||||
|
#/voxygen/hud/ @someone
|
||||||
|
#/voxygen/menu / @someone
|
||||||
|
#/voxygen/mesh/ @someone
|
||||||
|
/voxygen/render/ @rendering
|
||||||
|
#/voxygen/scene/ @someone
|
||||||
|
#/voxygen/ui/ @someone
|
||||||
|
/world/ @worldgen
|
||||||
|
|
||||||
|
# All files related to documentation or game unrelated content needs to be approved by the meta group
|
||||||
|
*.md @meta
|
||||||
|
*.nix @meta
|
||||||
|
.gitignore @meta
|
||||||
|
.gitattributes @meta
|
||||||
|
.gitlab-ci.yml @meta
|
||||||
|
rust-toolchain @meta
|
||||||
|
LICENSE @meta
|
||||||
|
.cargo/ @meta
|
17
CHANGELOG.md
17
CHANGELOG.md
@ -14,10 +14,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Added rotating orientation marker to main-map
|
- Added rotating orientation marker to main-map
|
||||||
- Added daily Mac builds
|
- Added daily Mac builds
|
||||||
- Allow spawning individual pet species, not just generic body kinds.
|
- Allow spawning individual pet species, not just generic body kinds.
|
||||||
|
- Configurable fonts
|
||||||
|
- Tanslation status tracking
|
||||||
|
- Added gamma setting
|
||||||
|
- Added new orc hairstyles
|
||||||
|
- Added sfx for wielding/unwielding weapons
|
||||||
|
- Fixed NPCs attacking the player forever after killing them
|
||||||
|
- Added sfx for collecting, dropping and using inventory items
|
||||||
|
- New attack animation
|
||||||
|
- weapon control system
|
||||||
|
- Game pauses when in singleplayer and pause menu
|
||||||
|
- Added authentication system (to play on the official server register on https://account.veloren.net)
|
||||||
|
- Added gamepad/controller support
|
||||||
|
- Added player feedback when attempting to pickup an item with a full inventory
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Brighter / higher contrast main-map
|
- Brighter / higher contrast main-map
|
||||||
- Removed highlighting of non-collectible sprites
|
- Removed highlighting of non-collectible sprites
|
||||||
|
- Fixed /give_exp ignoring player argument
|
||||||
|
- Extend run sfx to small animals to prevent sneak attacks by geese.
|
||||||
|
- Decreased clientside latency of ServerEvent mediated effects (e.g. projectiles, inventory operations, etc)
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
1013
Cargo.lock
generated
1013
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -18,6 +18,9 @@ Currently the communication of contributors happens mainly on our [official Disc
|
|||||||
|
|
||||||
## Useful Links
|
## Useful Links
|
||||||
|
|
||||||
|
[Sign Up](https://account.veloren.net) - Here you can create an online account for Veloren.
|
||||||
|
This will be needed to play on auth-enabled servers, including the official server.
|
||||||
|
|
||||||
[The Book](https://book.veloren.net) - A collection of all important information relating to Veloren. It includes information on how to compile Veloren and how to contribute.
|
[The Book](https://book.veloren.net) - A collection of all important information relating to Veloren. It includes information on how to compile Veloren and how to contribute.
|
||||||
|
|
||||||
[Future Plans](https://gitlab.com/veloren/veloren/milestones) - Go here for information about Veloren's development roadmap and what we're currently working on.
|
[Future Plans](https://gitlab.com/veloren/veloren/milestones) - Go here for information about Veloren's development roadmap and what we're currently working on.
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
"Colborn",
|
"Colborn",
|
||||||
"Dagfinn",
|
"Dagfinn",
|
||||||
"Dagrod",
|
"Dagrod",
|
||||||
|
"Digbod",
|
||||||
"Dimian",
|
"Dimian",
|
||||||
"Domnhar",
|
"Domnhar",
|
||||||
"Ebraheim",
|
"Ebraheim",
|
||||||
@ -458,6 +459,10 @@
|
|||||||
"peacock": {
|
"peacock": {
|
||||||
"keyword": "peacock",
|
"keyword": "peacock",
|
||||||
"generic": "Peacock"
|
"generic": "Peacock"
|
||||||
|
},
|
||||||
|
"eagle": {
|
||||||
|
"keyword": "eagle",
|
||||||
|
"generic": "Eagle"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -23,5 +23,77 @@
|
|||||||
],
|
],
|
||||||
threshold: 0.5,
|
threshold: 0.5,
|
||||||
),
|
),
|
||||||
|
Wield(Sword): (
|
||||||
|
files: [
|
||||||
|
"voxygen.audio.sfx.weapon.sword_out",
|
||||||
|
],
|
||||||
|
threshold: 0.5,
|
||||||
|
),
|
||||||
|
Unwield(Sword): (
|
||||||
|
files: [
|
||||||
|
"voxygen.audio.sfx.weapon.sword_in",
|
||||||
|
],
|
||||||
|
threshold: 0.5,
|
||||||
|
),
|
||||||
|
Inventory(Collected): (
|
||||||
|
files: [
|
||||||
|
"voxygen.audio.sfx.inventory.add_item",
|
||||||
|
],
|
||||||
|
threshold: 0.5,
|
||||||
|
),
|
||||||
|
Inventory(Swapped): (
|
||||||
|
files: [
|
||||||
|
"voxygen.audio.sfx.inventory.add_item",
|
||||||
|
],
|
||||||
|
threshold: 0.5,
|
||||||
|
),
|
||||||
|
Inventory(Given): (
|
||||||
|
files: [
|
||||||
|
"voxygen.audio.sfx.inventory.add_item",
|
||||||
|
],
|
||||||
|
threshold: 0.5,
|
||||||
|
),
|
||||||
|
Inventory(Dropped): (
|
||||||
|
files: [
|
||||||
|
"voxygen.audio.sfx.footsteps.stepgrass_4",
|
||||||
|
],
|
||||||
|
threshold: 0.5,
|
||||||
|
),
|
||||||
|
Inventory(Consumed(Potion)): (
|
||||||
|
files: [
|
||||||
|
"voxygen.audio.sfx.inventory.consumable.liquid",
|
||||||
|
],
|
||||||
|
threshold: 0.3,
|
||||||
|
),
|
||||||
|
Inventory(Consumed(PotionMinor)): (
|
||||||
|
files: [
|
||||||
|
"voxygen.audio.sfx.inventory.consumable.liquid",
|
||||||
|
],
|
||||||
|
threshold: 0.3,
|
||||||
|
),
|
||||||
|
Inventory(Consumed(Apple)): (
|
||||||
|
files: [
|
||||||
|
"voxygen.audio.sfx.inventory.consumable.apple",
|
||||||
|
],
|
||||||
|
threshold: 0.3,
|
||||||
|
),
|
||||||
|
Inventory(Consumed(Mushroom)): (
|
||||||
|
files: [
|
||||||
|
"voxygen.audio.sfx.inventory.consumable.food",
|
||||||
|
],
|
||||||
|
threshold: 0.3,
|
||||||
|
),
|
||||||
|
Inventory(Consumed(Cheese)): (
|
||||||
|
files: [
|
||||||
|
"voxygen.audio.sfx.inventory.consumable.food",
|
||||||
|
],
|
||||||
|
threshold: 0.3,
|
||||||
|
),
|
||||||
|
Inventory(CollectFailed): (
|
||||||
|
files: [
|
||||||
|
"voxygen.audio.sfx.inventory.add_failed",
|
||||||
|
],
|
||||||
|
threshold: 0.3,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
BIN
assets/voxygen/audio/sfx/inventory/add_failed.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/inventory/add_failed.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/sfx/inventory/add_item.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/inventory/add_item.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/sfx/inventory/consumable/apple.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/inventory/consumable/apple.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/sfx/inventory/consumable/food.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/inventory/consumable/food.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/sfx/inventory/consumable/liquid.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/inventory/consumable/liquid.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/sfx/weapon/sword_in.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/weapon/sword_in.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/audio/sfx/weapon/sword_out.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/weapon/sword_out.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/button.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/button.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/button_hover.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/button_hover.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/button_press.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/button_press.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/frames/banner.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/frames/banner.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/frames/banner_small_top.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/frames/banner_small_top.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/frames/banner_top.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/frames/banner_top.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/frames/enemybar-0.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/frames/enemybar-0.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/frames/enemybar.png
(Stored with Git LFS)
BIN
assets/voxygen/element/frames/enemybar.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/frames/enemybar_bg.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/frames/enemybar_bg.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/frames/esc_menu.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/frames/esc_menu.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/icons/elf_m.png
(Stored with Git LFS)
BIN
assets/voxygen/element/icons/elf_m.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/misc_bg/textbox_bot.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/misc_bg/textbox_bot.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/misc_bg/textbox_mid.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/misc_bg/textbox_mid.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/misc_bg/textbox_top.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/misc_bg/textbox_top.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/font/haxrcorp_4089_cyrillic_altgr_extended.ttf
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/font/haxrcorp_4089_cyrillic_altgr_extended.ttf
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -17,6 +17,28 @@ VoxygenLocalization(
|
|||||||
language_identifier: "en",
|
language_identifier: "en",
|
||||||
),
|
),
|
||||||
convert_utf8_to_ascii: false,
|
convert_utf8_to_ascii: false,
|
||||||
|
fonts: {
|
||||||
|
"opensans": Font (
|
||||||
|
asset_key: "voxygen.font.OpenSans-Regular",
|
||||||
|
scale_ratio: 1.0,
|
||||||
|
),
|
||||||
|
"metamorph": Font (
|
||||||
|
asset_key: "voxygen.font.Metamorphous-Regular",
|
||||||
|
scale_ratio: 1.0,
|
||||||
|
),
|
||||||
|
"alkhemi": Font (
|
||||||
|
asset_key: "voxygen.font.Alkhemikal",
|
||||||
|
scale_ratio: 1.0,
|
||||||
|
),
|
||||||
|
"wizard": Font (
|
||||||
|
asset_key: "voxygen.font.wizard",
|
||||||
|
scale_ratio: 1.0,
|
||||||
|
),
|
||||||
|
"cyri": Font (
|
||||||
|
asset_key: "voxygen.font.haxrcorp_4089_cyrillic_altgr_extended",
|
||||||
|
scale_ratio: 1.0,
|
||||||
|
),
|
||||||
|
},
|
||||||
string_map: {
|
string_map: {
|
||||||
/// Start Common section
|
/// Start Common section
|
||||||
// Texts used in multiple locations with the same formatting
|
// Texts used in multiple locations with the same formatting
|
||||||
@ -43,6 +65,8 @@ VoxygenLocalization(
|
|||||||
"common.disclaimer": "Disclaimer",
|
"common.disclaimer": "Disclaimer",
|
||||||
"common.cancel": "Cancel",
|
"common.cancel": "Cancel",
|
||||||
"common.none": "None",
|
"common.none": "None",
|
||||||
|
"common.error": "Error",
|
||||||
|
"common.fatal_error": "Fatal Error",
|
||||||
|
|
||||||
// Message when connection to the server is lost
|
// Message when connection to the server is lost
|
||||||
"common.connection_lost": r#"Connection lost!
|
"common.connection_lost": r#"Connection lost!
|
||||||
@ -91,12 +115,25 @@ Thanks for taking the time to read this notice, we hope you enjoy the game!
|
|||||||
// Login process description
|
// Login process description
|
||||||
"main.login_process": r#"Information on the Login Process:
|
"main.login_process": r#"Information on the Login Process:
|
||||||
|
|
||||||
Put in any username. No Account needed yet.
|
If you are having issues signing in:
|
||||||
|
|
||||||
Character names and appearances will be saved locally.
|
Please note that you now need an account
|
||||||
|
to play on auth-enabled servers.
|
||||||
|
|
||||||
Levels/Items are not saved yet."#,
|
You can create an account over at
|
||||||
|
|
||||||
|
https://account.veloren.net."#,
|
||||||
|
"main.login.server_not_found": "Server not found",
|
||||||
|
"main.login.authentication_error": "Auth error on server",
|
||||||
|
"main.login.server_full": "Server is full",
|
||||||
|
"main.login.untrusted_auth_server": "Auth server not trusted",
|
||||||
|
"main.login.outdated_client_or_server": "ServerWentMad: Probably versions are incompatible, check for updates.",
|
||||||
|
"main.login.timeout": "Timeout: Server did not respond in time. (Overloaded or network issues).",
|
||||||
|
"main.login.server_shut_down": "Server shut down",
|
||||||
|
"main.login.already_logged_in": "You are already logged into the server.",
|
||||||
|
"main.login.network_error": "Network error",
|
||||||
|
"main.login.failed_sending_request": "Request to Auth server failed",
|
||||||
|
"main.login.client_crashed": "Client crashed",
|
||||||
|
|
||||||
/// End Main screen section
|
/// End Main screen section
|
||||||
|
|
||||||
@ -186,6 +223,7 @@ Enjoy your stay in the World of Veloren."#,
|
|||||||
"hud.settings.view_distance": "View Distance",
|
"hud.settings.view_distance": "View Distance",
|
||||||
"hud.settings.maximum_fps": "Maximum FPS",
|
"hud.settings.maximum_fps": "Maximum FPS",
|
||||||
"hud.settings.fov": "Field of View (deg)",
|
"hud.settings.fov": "Field of View (deg)",
|
||||||
|
"hud.settings.gamma": "Gamma",
|
||||||
"hud.settings.antialiasing_mode": "AntiAliasing Mode",
|
"hud.settings.antialiasing_mode": "AntiAliasing Mode",
|
||||||
"hud.settings.cloud_rendering_mode": "Cloud Rendering Mode",
|
"hud.settings.cloud_rendering_mode": "Cloud Rendering Mode",
|
||||||
"hud.settings.fluid_rendering_mode": "Fluid Rendering Mode",
|
"hud.settings.fluid_rendering_mode": "Fluid Rendering Mode",
|
||||||
|
@ -4,7 +4,29 @@ VoxygenLocalization(
|
|||||||
language_name: "Français",
|
language_name: "Français",
|
||||||
language_identifier: "fr_FR",
|
language_identifier: "fr_FR",
|
||||||
),
|
),
|
||||||
convert_utf8_to_ascii: true,
|
convert_utf8_to_ascii: false,
|
||||||
|
fonts: {
|
||||||
|
"opensans": Font (
|
||||||
|
asset_key: "voxygen.font.OpenSans-Regular",
|
||||||
|
scale_ratio: 1.0,
|
||||||
|
),
|
||||||
|
"metamorph": Font (
|
||||||
|
asset_key: "voxygen.font.Metamorphous-Regular",
|
||||||
|
scale_ratio: 1.0,
|
||||||
|
),
|
||||||
|
"alkhemi": Font (
|
||||||
|
asset_key: "voxygen.font.Alkhemikal",
|
||||||
|
scale_ratio: 1.0,
|
||||||
|
),
|
||||||
|
"wizard": Font (
|
||||||
|
asset_key: "voxygen.font.wizard",
|
||||||
|
scale_ratio: 1.0,
|
||||||
|
),
|
||||||
|
"cyri": Font (
|
||||||
|
asset_key: "voxygen.font.haxrcorp_4089_cyrillic_altgr_extended",
|
||||||
|
scale_ratio: 0.9,
|
||||||
|
),
|
||||||
|
},
|
||||||
string_map: {
|
string_map: {
|
||||||
// Common texts used in multiple locations
|
// Common texts used in multiple locations
|
||||||
"common.username": "pseudo",
|
"common.username": "pseudo",
|
||||||
@ -16,7 +38,7 @@ VoxygenLocalization(
|
|||||||
"common.languages": "Langues",
|
"common.languages": "Langues",
|
||||||
"common.interface": "Interface",
|
"common.interface": "Interface",
|
||||||
"common.gameplay": "Gameplay",
|
"common.gameplay": "Gameplay",
|
||||||
"common.controls": "Controles",
|
"common.controls": "Contrôles",
|
||||||
"common.video": "Video",
|
"common.video": "Video",
|
||||||
"common.sound": "Audio",
|
"common.sound": "Audio",
|
||||||
"common.resume": "Reprendre",
|
"common.resume": "Reprendre",
|
||||||
|
61
assets/voxygen/net.veloren.veloren.appdata.xml
Normal file
61
assets/voxygen/net.veloren.veloren.appdata.xml
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Copyright 2019 Artem Polishchuk <ego.cordatus@gmail.com> -->
|
||||||
|
<component type="desktop-application">
|
||||||
|
<id>net.veloren.veloren.desktop</id>
|
||||||
|
<metadata_license>CC0-1.0</metadata_license>
|
||||||
|
<project_license>GPL-3.0-or-later</project_license>
|
||||||
|
<content_rating type="oars-1.0">
|
||||||
|
<content_attribute id="social-chat">intense</content_attribute>
|
||||||
|
<content_attribute id="social-info">mild</content_attribute>
|
||||||
|
<content_attribute id="violence-cartoon">mild</content_attribute>
|
||||||
|
<content_attribute id="violence-fantasy">mild</content_attribute>
|
||||||
|
</content_rating>
|
||||||
|
<name>Veloren</name>
|
||||||
|
<summary>
|
||||||
|
Veloren is a multiplayer voxel RPG written in Rust. It is inspired
|
||||||
|
by games such as Cube World, Legend of Zelda: Breath of the Wild,
|
||||||
|
Dwarf Fortress and Minecraft.
|
||||||
|
</summary>
|
||||||
|
<description>
|
||||||
|
<p>
|
||||||
|
Welcome To Veloren!
|
||||||
|
</p><p>
|
||||||
|
Veloren is a multiplayer voxel RPG written in Rust. Veloren takes
|
||||||
|
inspiration from games such as Cube World, Minecraft and Dwarf
|
||||||
|
Fortress. The game is currently under heavy development, but is
|
||||||
|
playable.
|
||||||
|
</p><p>
|
||||||
|
Development
|
||||||
|
</p><p>
|
||||||
|
Currently the communication of contributors happens mainly on our
|
||||||
|
official Discord server (https://discord.gg/kjwJwjK). You can join
|
||||||
|
it to keep up with the development, talk to us or contribute
|
||||||
|
something yourself. Anyone who shows genuine effort to help is
|
||||||
|
welcome in our team. You don't have to know how to program to
|
||||||
|
contribute!
|
||||||
|
</p>
|
||||||
|
</description>
|
||||||
|
<screenshots>
|
||||||
|
<screenshot type="default">
|
||||||
|
<image>https://media.discordapp.net/attachments/634860358623821835/643034796548947968/screenshot_1573381825305.png</image>
|
||||||
|
</screenshot>
|
||||||
|
<screenshot>
|
||||||
|
<image>https://media.discordapp.net/attachments/597826574095613962/643102462781423616/screenshot_1573397958545.png</image>
|
||||||
|
</screenshot>
|
||||||
|
<screenshot>
|
||||||
|
<image>https://cdn.discordapp.com/attachments/634860358623821835/646518917577310219/screenshot_1574211401431.png</image>
|
||||||
|
</screenshot>
|
||||||
|
</screenshots>
|
||||||
|
<keywords>
|
||||||
|
<keyword>sandbox</keyword>
|
||||||
|
<keyword>world</keyword>
|
||||||
|
<keyword>multiplayer</keyword>
|
||||||
|
</keywords>
|
||||||
|
<url type="homepage">https://veloren.net</url>
|
||||||
|
<url type="bugtracker">https://gitlab.com/veloren/veloren/issues</url>
|
||||||
|
<url type="faq">https://gitlab.com/veloren/veloren#faq</url>
|
||||||
|
<url type="help">https://book.veloren.net/</url>
|
||||||
|
<provides>
|
||||||
|
<binary>veloren-voxygen</binary>
|
||||||
|
</provides>
|
||||||
|
</component>
|
9
assets/voxygen/net.veloren.veloren.desktop
Normal file
9
assets/voxygen/net.veloren.veloren.desktop
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Veloren
|
||||||
|
Comment=Veloren is a multiplayer voxel RPG written in Rust
|
||||||
|
Exec=veloren-voxygen
|
||||||
|
Categories=Game;Simulation;
|
||||||
|
Keywords=veloren;sandbox;world;blocks;nodes;multiplayer;roleplaying;
|
||||||
|
Icon=net.veloren.veloren.png
|
||||||
|
Terminal=false
|
BIN
assets/voxygen/net.veloren.veloren.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/net.veloren.veloren.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -12,4 +12,5 @@ uniform u_globals {
|
|||||||
uvec4 light_shadow_count;
|
uvec4 light_shadow_count;
|
||||||
uvec4 medium;
|
uvec4 medium;
|
||||||
ivec4 select_pos;
|
ivec4 select_pos;
|
||||||
|
vec4 gamma;
|
||||||
};
|
};
|
||||||
|
@ -45,7 +45,7 @@ void main() {
|
|||||||
//hsva_color.z = 1.0 - 1.0 / (1.0 * hsva_color.z + 1.0);
|
//hsva_color.z = 1.0 - 1.0 / (1.0 * hsva_color.z + 1.0);
|
||||||
//vec4 final_color = vec4(hsv2rgb(hsva_color.rgb), hsva_color.a);
|
//vec4 final_color = vec4(hsv2rgb(hsva_color.rgb), hsva_color.a);
|
||||||
|
|
||||||
vec4 final_color = aa_color;
|
vec4 final_color = pow(aa_color, gamma);
|
||||||
|
|
||||||
if (medium.x == 1u) {
|
if (medium.x == 1u) {
|
||||||
final_color *= vec4(0.2, 0.2, 0.8, 1.0);
|
final_color *= vec4(0.2, 0.2, 0.8, 1.0);
|
||||||
|
@ -111,4 +111,32 @@
|
|||||||
center: ("npc.peacock.female.tail"),
|
center: ("npc.peacock.female.tail"),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
(Eagle, Male): (
|
||||||
|
head: (
|
||||||
|
offset: (-2.0, -2.0, -3.5),
|
||||||
|
center: ("npc.eagle.female.head"),
|
||||||
|
),
|
||||||
|
torso: (
|
||||||
|
offset: (-3.0, -4.5, -4.5),
|
||||||
|
center: ("npc.eagle.female.torso"),
|
||||||
|
),
|
||||||
|
tail: (
|
||||||
|
offset: (-2.0, -3.5, -3.5),
|
||||||
|
center: ("npc.eagle.female.tail"),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
(Eagle, Female): (
|
||||||
|
head: (
|
||||||
|
offset: (-2.0, -2.0, -3.5),
|
||||||
|
center: ("npc.eagle.female.head"),
|
||||||
|
),
|
||||||
|
torso: (
|
||||||
|
offset: (-3.0, -4.5, -4.5),
|
||||||
|
center: ("npc.eagle.female.torso"),
|
||||||
|
),
|
||||||
|
tail: (
|
||||||
|
offset: (-2.0, -3.5, -3.5),
|
||||||
|
center: ("npc.eagle.female.tail"),
|
||||||
|
)
|
||||||
|
),
|
||||||
})
|
})
|
@ -143,4 +143,40 @@
|
|||||||
lateral: ("npc.peacock.female.leg_r"),
|
lateral: ("npc.peacock.female.leg_r"),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
(Eagle, Male): (
|
||||||
|
wing_l: (
|
||||||
|
offset: (-1.0, -3.5, -13.0),
|
||||||
|
lateral: ("npc.eagle.male.wing_l"),
|
||||||
|
),
|
||||||
|
wing_r: (
|
||||||
|
offset: (-1.0, -3.5, -13.0),
|
||||||
|
lateral: ("npc.eagle.male.wing_r"),
|
||||||
|
),
|
||||||
|
foot_l: (
|
||||||
|
offset: (-1.5, 0.0, -8.0),
|
||||||
|
lateral: ("npc.eagle.male.leg_l"),
|
||||||
|
),
|
||||||
|
foot_r: (
|
||||||
|
offset: (-1.5, 0.0, -8.0),
|
||||||
|
lateral: ("npc.eagle.male.leg_r"),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
(Eagle, Female): (
|
||||||
|
wing_l: (
|
||||||
|
offset: (-1.0, -3.5, -13.0),
|
||||||
|
lateral: ("npc.eagle.female.wing_l"),
|
||||||
|
),
|
||||||
|
wing_r: (
|
||||||
|
offset: (-1.0, -3.5, -13.0),
|
||||||
|
lateral: ("npc.eagle.female.wing_r"),
|
||||||
|
),
|
||||||
|
foot_l: (
|
||||||
|
offset: (-1.5, 0.0, -8.0),
|
||||||
|
lateral: ("npc.eagle.female.leg_l"),
|
||||||
|
),
|
||||||
|
foot_r: (
|
||||||
|
offset: (-1.5, 0.0, -8.0),
|
||||||
|
lateral: ("npc.eagle.female.leg_r"),
|
||||||
|
)
|
||||||
|
),
|
||||||
})
|
})
|
BIN
assets/voxygen/voxel/figure/accessory/orc/warpaint-female-0.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/figure/accessory/orc/warpaint-female-0.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/figure/accessory/orc/warpaint-female-1.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/figure/accessory/orc/warpaint-female-1.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/figure/beard/human/human-0.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/figure/beard/human/human-0.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/figure/hair/elf/male-2.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/figure/hair/elf/male-2.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/figure/hair/human/male-20.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/figure/hair/human/male-20.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/figure/hair/orc/female-0.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/figure/hair/orc/female-0.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/figure/hair/orc/female-1.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/figure/hair/orc/female-1.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/figure/hair/orc/female-2.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/figure/hair/orc/female-2.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/figure/hair/orc/female-3.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/figure/hair/orc/female-3.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/figure/hair/orc/female-4.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/figure/hair/orc/female-4.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/figure/hair/orc/female-5.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/figure/hair/orc/female-5.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/figure/hair/orc/female-6.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/figure/hair/orc/female-6.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/figure/hair/orc/female.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/figure/hair/orc/female.vox
(Stored with Git LFS)
Binary file not shown.
@ -27,6 +27,7 @@
|
|||||||
Some(("figure.hair.human.male-20", (-3, -4, -7))),
|
Some(("figure.hair.human.male-20", (-3, -4, -7))),
|
||||||
],
|
],
|
||||||
beard: [
|
beard: [
|
||||||
|
None,
|
||||||
Some(("figure.beard.human.human-0", (4, 6, -2))),
|
Some(("figure.beard.human.human-0", (4, 6, -2))),
|
||||||
Some(("figure.beard.human.human-1", (5, 10, -2))),
|
Some(("figure.beard.human.human-1", (5, 10, -2))),
|
||||||
Some(("figure.beard.human.human-2", (3, 7, -3))),
|
Some(("figure.beard.human.human-2", (3, 7, -3))),
|
||||||
@ -92,17 +93,24 @@
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
(Orc, Female): (
|
(Orc, Female): (
|
||||||
offset: (-8.0, -3.0, -6.0),
|
offset: (-8.0, -2.5, -6.0),
|
||||||
head: ("figure.head.orc.female", (0, 2, 0)),
|
head: ("figure.head.orc.female", (0, 1, 0)),
|
||||||
eyes: ("figure.eyes.orc.female-0", (3, 9, 2)),
|
eyes: ("figure.eyes.orc.female-0", (3, 8, 2)),
|
||||||
hair: [
|
hair: [
|
||||||
Some(("figure.hair.orc.female", (5, -2, 0))),
|
Some(("figure.hair.orc.female-0", (-2, -8, 0))),
|
||||||
|
Some(("figure.hair.orc.female-1", (-2, -8, 0))),
|
||||||
|
Some(("figure.hair.orc.female-2", (-2, -8, 0))),
|
||||||
|
Some(("figure.hair.orc.female-3", (-2, -8, -4))),
|
||||||
|
Some(("figure.hair.orc.female-4", (-2, -8, 0))),
|
||||||
|
Some(("figure.hair.orc.female-5", (-2, -8, -4))),
|
||||||
|
Some(("figure.hair.orc.female-6", (-2, -8, -4))),
|
||||||
],
|
],
|
||||||
beard: [None],
|
beard: [None],
|
||||||
accessory: [
|
accessory: [
|
||||||
None,
|
None,
|
||||||
Some(("figure.accessory.orc.earring-female-0", (2, 5, 1))),
|
Some(("figure.accessory.orc.earring-female-0", (2, 4, 1))),
|
||||||
Some(("figure.accessory.orc.warpaint-female-0", (3, 5, 1))),
|
Some(("figure.accessory.orc.warpaint-female-0", (-2, -4, -7))),
|
||||||
|
Some(("figure.accessory.orc.warpaint-female-1", (-2, -4, -7))),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
(Elf, Male): (
|
(Elf, Male): (
|
||||||
@ -239,6 +247,7 @@
|
|||||||
Some(("figure.hair.danari.male-1", (3, 1, 2))),
|
Some(("figure.hair.danari.male-1", (3, 1, 2))),
|
||||||
],
|
],
|
||||||
beard: [
|
beard: [
|
||||||
|
None,
|
||||||
Some(("figure.beard.danari.danari-0", (4, 6, -1))),
|
Some(("figure.beard.danari.danari-0", (4, 6, -1))),
|
||||||
],
|
],
|
||||||
accessory: [
|
accessory: [
|
||||||
|
BIN
assets/voxygen/voxel/npc/eagle/female/head.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/npc/eagle/female/head.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/npc/eagle/female/leg_l.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/npc/eagle/female/leg_l.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/npc/eagle/female/leg_r.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/npc/eagle/female/leg_r.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/npc/eagle/female/tail.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/npc/eagle/female/tail.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/npc/eagle/female/torso.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/npc/eagle/female/torso.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/npc/eagle/female/wing_l.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/npc/eagle/female/wing_l.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/npc/eagle/female/wing_r.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/npc/eagle/female/wing_r.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/npc/eagle/male/head.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/npc/eagle/male/head.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/npc/eagle/male/leg_l.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/npc/eagle/male/leg_l.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/npc/eagle/male/leg_r.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/npc/eagle/male/leg_r.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/npc/eagle/male/tail.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/npc/eagle/male/tail.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/npc/eagle/male/torso.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/npc/eagle/male/torso.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/npc/eagle/male/wing_l.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/npc/eagle/male/wing_l.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/npc/eagle/male/wing_r.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/npc/eagle/male/wing_r.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -386,7 +386,7 @@
|
|||||||
central: ("npc.lion.male.ears"),
|
central: ("npc.lion.male.ears"),
|
||||||
),
|
),
|
||||||
tail: (
|
tail: (
|
||||||
offset: (-0.5, -1.0, -8.0),
|
offset: (-0.5, -1.0, -1.0),
|
||||||
central: ("npc.lion.male.tail"),
|
central: ("npc.lion.male.tail"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -417,7 +417,7 @@
|
|||||||
),
|
),
|
||||||
tail: (
|
tail: (
|
||||||
offset: (-0.5, -1.0, -1.0),
|
offset: (-0.5, -1.0, -1.0),
|
||||||
central: ("npc.lion.male.tail"),
|
central: ("npc.lion.female.tail"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(Tarasque, Male): (
|
(Tarasque, Male): (
|
||||||
|
BIN
assets/world/structure/dungeon/meso_sewer_temple.vox
(Stored with Git LFS)
BIN
assets/world/structure/dungeon/meso_sewer_temple.vox
(Stored with Git LFS)
Binary file not shown.
@ -51,7 +51,9 @@ fn main() {
|
|||||||
println!("Players online: {:?}", client.get_players());
|
println!("Players online: {:?}", client.get_players());
|
||||||
|
|
||||||
client
|
client
|
||||||
.register(comp::Player::new(username, None), password)
|
.register(username, password, |provider| {
|
||||||
|
provider == "https://auth.veloren.net"
|
||||||
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
|
@ -15,3 +15,4 @@ log = "0.4.8"
|
|||||||
specs = "0.15.1"
|
specs = "0.15.1"
|
||||||
vek = { version = "0.9.9", features = ["serde"] }
|
vek = { version = "0.9.9", features = ["serde"] }
|
||||||
hashbrown = { version = "0.6.2", features = ["rayon", "serde", "nightly"] }
|
hashbrown = { version = "0.6.2", features = ["rayon", "serde", "nightly"] }
|
||||||
|
authc = { git = "https://gitlab.com/veloren/auth.git", rev = "65571ade0d954a0e0bd995fdb314854ff146ab97" }
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use authc::AuthClientError;
|
||||||
use common::net::PostError;
|
use common::net::PostError;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -7,11 +8,18 @@ pub enum Error {
|
|||||||
ServerTimeout,
|
ServerTimeout,
|
||||||
ServerShutdown,
|
ServerShutdown,
|
||||||
TooManyPlayers,
|
TooManyPlayers,
|
||||||
InvalidAuth,
|
AlreadyLoggedIn,
|
||||||
|
AuthErr(String),
|
||||||
|
AuthClientError(AuthClientError),
|
||||||
|
AuthServerNotTrusted,
|
||||||
//TODO: InvalidAlias,
|
//TODO: InvalidAlias,
|
||||||
Other(String),
|
Other(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PostError> for Error {
|
impl From<PostError> for Error {
|
||||||
fn from(err: PostError) -> Self { Error::Network(err) }
|
fn from(err: PostError) -> Self { Self::Network(err) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AuthClientError> for Error {
|
||||||
|
fn from(err: AuthClientError) -> Self { Self::AuthClientError(err) }
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ pub mod error;
|
|||||||
|
|
||||||
// Reexports
|
// Reexports
|
||||||
pub use crate::error::Error;
|
pub use crate::error::Error;
|
||||||
|
pub use authc::AuthClientError;
|
||||||
pub use specs::{
|
pub use specs::{
|
||||||
join::Join,
|
join::Join,
|
||||||
saveload::{Marker, MarkerAllocator},
|
saveload::{Marker, MarkerAllocator},
|
||||||
@ -13,10 +14,13 @@ pub use specs::{
|
|||||||
|
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{self, ControlEvent, Controller, ControllerInputs, InventoryManip},
|
comp::{
|
||||||
|
self, ControlEvent, Controller, ControllerInputs, InventoryManip, InventoryUpdateEvent,
|
||||||
|
},
|
||||||
|
event::{EventBus, SfxEvent, SfxEventItem},
|
||||||
msg::{
|
msg::{
|
||||||
validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, PlayerListUpdate,
|
validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, PlayerListUpdate,
|
||||||
RequestStateError, ServerError, ServerInfo, ServerMsg, MAX_BYTES_CHAT_MSG,
|
RegisterError, RequestStateError, ServerInfo, ServerMsg, MAX_BYTES_CHAT_MSG,
|
||||||
},
|
},
|
||||||
net::PostBox,
|
net::PostBox,
|
||||||
state::State,
|
state::State,
|
||||||
@ -39,11 +43,11 @@ use vek::*;
|
|||||||
// The duration of network inactivity until the player is kicked
|
// The duration of network inactivity until the player is kicked
|
||||||
// @TODO: in the future, this should be configurable on the server
|
// @TODO: in the future, this should be configurable on the server
|
||||||
// and be provided to the client
|
// and be provided to the client
|
||||||
const SERVER_TIMEOUT: Duration = Duration::from_secs(20);
|
const SERVER_TIMEOUT: f64 = 20.0;
|
||||||
|
|
||||||
// After this duration has elapsed, the user will begin getting kick warnings in
|
// After this duration has elapsed, the user will begin getting kick warnings in
|
||||||
// their chat window
|
// their chat window
|
||||||
const SERVER_TIMEOUT_GRACE_PERIOD: Duration = Duration::from_secs(14);
|
const SERVER_TIMEOUT_GRACE_PERIOD: f64 = 14.0;
|
||||||
|
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
Chat {
|
Chat {
|
||||||
@ -63,8 +67,8 @@ pub struct Client {
|
|||||||
|
|
||||||
postbox: PostBox<ClientMsg, ServerMsg>,
|
postbox: PostBox<ClientMsg, ServerMsg>,
|
||||||
|
|
||||||
last_server_ping: Instant,
|
last_server_ping: f64,
|
||||||
last_server_pong: Instant,
|
last_server_pong: f64,
|
||||||
last_ping_delta: f64,
|
last_ping_delta: f64,
|
||||||
|
|
||||||
tick: u64,
|
tick: u64,
|
||||||
@ -85,13 +89,13 @@ impl Client {
|
|||||||
let mut postbox = PostBox::to(addr)?;
|
let mut postbox = PostBox::to(addr)?;
|
||||||
|
|
||||||
// Wait for initial sync
|
// Wait for initial sync
|
||||||
let (state, entity, server_info, world_map) = match postbox.next_message() {
|
let (state, entity, server_info, world_map) = match postbox.next_message()? {
|
||||||
Some(ServerMsg::InitialSync {
|
ServerMsg::InitialSync {
|
||||||
entity_package,
|
entity_package,
|
||||||
server_info,
|
server_info,
|
||||||
time_of_day,
|
time_of_day,
|
||||||
world_map: (map_size, world_map),
|
world_map: (map_size, world_map),
|
||||||
}) => {
|
} => {
|
||||||
// TODO: Display that versions don't match in Voxygen
|
// TODO: Display that versions don't match in Voxygen
|
||||||
if server_info.git_hash != common::util::GIT_HASH.to_string() {
|
if server_info.git_hash != common::util::GIT_HASH.to_string() {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
@ -104,6 +108,8 @@ impl Client {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log::debug!("Auth Server: {:?}", server_info.auth_provider);
|
||||||
|
|
||||||
// Initialize `State`
|
// Initialize `State`
|
||||||
let mut state = State::default();
|
let mut state = State::default();
|
||||||
let entity = state.ecs_mut().apply_entity_package(entity_package);
|
let entity = state.ecs_mut().apply_entity_package(entity_package);
|
||||||
@ -128,9 +134,7 @@ impl Client {
|
|||||||
|
|
||||||
(state, entity, server_info, (world_map, map_size))
|
(state, entity, server_info, (world_map, map_size))
|
||||||
},
|
},
|
||||||
Some(ServerMsg::Error(ServerError::TooManyPlayers)) => {
|
ServerMsg::TooManyPlayers => return Err(Error::TooManyPlayers),
|
||||||
return Err(Error::TooManyPlayers);
|
|
||||||
},
|
|
||||||
_ => return Err(Error::ServerWentMad),
|
_ => return Err(Error::ServerWentMad),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -151,8 +155,8 @@ impl Client {
|
|||||||
|
|
||||||
postbox,
|
postbox,
|
||||||
|
|
||||||
last_server_ping: Instant::now(),
|
last_server_ping: 0.0,
|
||||||
last_server_pong: Instant::now(),
|
last_server_pong: 0.0,
|
||||||
last_ping_delta: 0.0,
|
last_ping_delta: 0.0,
|
||||||
|
|
||||||
tick: 0,
|
tick: 0,
|
||||||
@ -171,16 +175,40 @@ impl Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Request a state transition to `ClientState::Registered`.
|
/// Request a state transition to `ClientState::Registered`.
|
||||||
pub fn register(&mut self, player: comp::Player, password: String) -> Result<(), Error> {
|
pub fn register(
|
||||||
self.postbox
|
&mut self,
|
||||||
.send_message(ClientMsg::Register { player, password });
|
username: String,
|
||||||
|
password: String,
|
||||||
|
mut auth_trusted: impl FnMut(&str) -> bool,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// Authentication
|
||||||
|
let token_or_username = self.server_info.auth_provider.as_ref().map(|addr|
|
||||||
|
// Query whether this is a trusted auth server
|
||||||
|
if auth_trusted(&addr) {
|
||||||
|
Ok(authc::AuthClient::new(addr)
|
||||||
|
.sign_in(&username, &password)?
|
||||||
|
.serialize())
|
||||||
|
} else {
|
||||||
|
Err(Error::AuthServerNotTrusted)
|
||||||
|
}
|
||||||
|
).unwrap_or(Ok(username))?;
|
||||||
|
|
||||||
|
self.postbox.send_message(ClientMsg::Register {
|
||||||
|
view_distance: self.view_distance,
|
||||||
|
token_or_username,
|
||||||
|
});
|
||||||
self.client_state = ClientState::Pending;
|
self.client_state = ClientState::Pending;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match self.postbox.next_message() {
|
match self.postbox.next_message()? {
|
||||||
Some(ServerMsg::StateAnswer(Err((RequestStateError::Denied, _)))) => {
|
ServerMsg::StateAnswer(Err((RequestStateError::RegisterDenied(err), state))) => {
|
||||||
break Err(Error::InvalidAuth);
|
self.client_state = state;
|
||||||
|
break Err(match err {
|
||||||
|
RegisterError::AlreadyLoggedIn => Error::AlreadyLoggedIn,
|
||||||
|
RegisterError::AuthError(err) => Error::AuthErr(err),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
Some(ServerMsg::StateAnswer(Ok(ClientState::Registered))) => break Ok(()),
|
ServerMsg::StateAnswer(Ok(ClientState::Registered)) => break Ok(()),
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -194,10 +222,7 @@ impl Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Send disconnect message to the server
|
/// Send disconnect message to the server
|
||||||
pub fn request_logout(&mut self) {
|
pub fn request_logout(&mut self) { self.postbox.send_message(ClientMsg::Disconnect); }
|
||||||
self.postbox.send_message(ClientMsg::Disconnect);
|
|
||||||
self.client_state = ClientState::Pending;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Request a state transition to `ClientState::Registered` from an ingame
|
/// Request a state transition to `ClientState::Registered` from an ingame
|
||||||
/// state.
|
/// state.
|
||||||
@ -377,13 +402,14 @@ impl Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle new messages from the server.
|
// Handle new messages from the server.
|
||||||
frontend_events.append(&mut self.handle_new_messages()?);
|
frontend_events.append(&mut self.handle_new_messages()?);
|
||||||
|
|
||||||
// 3) Update client local data
|
// 3) Update client local data
|
||||||
|
|
||||||
// 4) Tick the client's LocalState
|
// 4) Tick the client's LocalState
|
||||||
self.state.tick(dt, add_foreign_systems);
|
self.state.tick(dt, add_foreign_systems, true);
|
||||||
|
|
||||||
// 5) Terrain
|
// 5) Terrain
|
||||||
let pos = self
|
let pos = self
|
||||||
@ -479,9 +505,9 @@ impl Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send a ping to the server once every second
|
// Send a ping to the server once every second
|
||||||
if Instant::now().duration_since(self.last_server_ping) > Duration::from_secs(1) {
|
if self.state.get_time() - self.last_server_ping > 1. {
|
||||||
self.postbox.send_message(ClientMsg::Ping);
|
self.postbox.send_message(ClientMsg::Ping);
|
||||||
self.last_server_ping = Instant::now();
|
self.last_server_ping = self.state.get_time();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6) Update the server about the player's physics attributes.
|
// 6) Update the server about the player's physics attributes.
|
||||||
@ -526,16 +552,14 @@ impl Client {
|
|||||||
// Check that we have an valid connection.
|
// Check that we have an valid connection.
|
||||||
// Use the last ping time as a 1s rate limiter, we only notify the user once per
|
// Use the last ping time as a 1s rate limiter, we only notify the user once per
|
||||||
// second
|
// second
|
||||||
if Instant::now().duration_since(self.last_server_ping) > Duration::from_secs(1) {
|
if self.state.get_time() - self.last_server_ping > 1. {
|
||||||
let duration_since_last_pong = Instant::now().duration_since(self.last_server_pong);
|
let duration_since_last_pong = self.state.get_time() - self.last_server_pong;
|
||||||
|
|
||||||
// Dispatch a notification to the HUD warning they will be kicked in {n} seconds
|
// Dispatch a notification to the HUD warning they will be kicked in {n} seconds
|
||||||
if duration_since_last_pong.as_secs() >= SERVER_TIMEOUT_GRACE_PERIOD.as_secs() {
|
if duration_since_last_pong >= SERVER_TIMEOUT_GRACE_PERIOD {
|
||||||
if let Some(seconds_until_kick) =
|
if self.state.get_time() - duration_since_last_pong > 0. {
|
||||||
SERVER_TIMEOUT.checked_sub(duration_since_last_pong)
|
|
||||||
{
|
|
||||||
frontend_events.push(Event::DisconnectionNotification(
|
frontend_events.push(Event::DisconnectionNotification(
|
||||||
seconds_until_kick.as_secs(),
|
(self.state.get_time() - duration_since_last_pong).round() as u64,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -546,10 +570,8 @@ impl Client {
|
|||||||
if new_msgs.len() > 0 {
|
if new_msgs.len() > 0 {
|
||||||
for msg in new_msgs {
|
for msg in new_msgs {
|
||||||
match msg {
|
match msg {
|
||||||
ServerMsg::Error(e) => match e {
|
ServerMsg::TooManyPlayers => {
|
||||||
ServerError::TooManyPlayers => return Err(Error::ServerWentMad),
|
return Err(Error::ServerWentMad);
|
||||||
ServerError::InvalidAuth => return Err(Error::InvalidAuth),
|
|
||||||
//TODO: ServerError::InvalidAlias => return Err(Error::InvalidAlias),
|
|
||||||
},
|
},
|
||||||
ServerMsg::Shutdown => return Err(Error::ServerShutdown),
|
ServerMsg::Shutdown => return Err(Error::ServerShutdown),
|
||||||
ServerMsg::InitialSync { .. } => return Err(Error::ServerWentMad),
|
ServerMsg::InitialSync { .. } => return Err(Error::ServerWentMad),
|
||||||
@ -589,11 +611,10 @@ impl Client {
|
|||||||
|
|
||||||
ServerMsg::Ping => self.postbox.send_message(ClientMsg::Pong),
|
ServerMsg::Ping => self.postbox.send_message(ClientMsg::Pong),
|
||||||
ServerMsg::Pong => {
|
ServerMsg::Pong => {
|
||||||
self.last_server_pong = Instant::now();
|
self.last_server_pong = self.state.get_time();
|
||||||
|
|
||||||
self.last_ping_delta = Instant::now()
|
self.last_ping_delta =
|
||||||
.duration_since(self.last_server_ping)
|
(self.state.get_time() - self.last_server_ping).round();
|
||||||
.as_secs_f64();
|
|
||||||
},
|
},
|
||||||
ServerMsg::ChatMsg { message, chat_type } => {
|
ServerMsg::ChatMsg { message, chat_type } => {
|
||||||
frontend_events.push(Event::Chat { message, chat_type })
|
frontend_events.push(Event::Chat { message, chat_type })
|
||||||
@ -669,8 +690,26 @@ impl Client {
|
|||||||
self.state.write_component(entity, character_state);
|
self.state.write_component(entity, character_state);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ServerMsg::InventoryUpdate(inventory) => {
|
ServerMsg::InventoryUpdate(inventory, event) => {
|
||||||
self.state.write_component(self.entity, inventory)
|
match event {
|
||||||
|
InventoryUpdateEvent::CollectFailed => {
|
||||||
|
frontend_events.push(Event::Chat {
|
||||||
|
message: String::from(
|
||||||
|
"Failed to collect item. Your inventory may be full!",
|
||||||
|
),
|
||||||
|
chat_type: ChatType::Meta,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
self.state.write_component(self.entity, inventory);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
self.state
|
||||||
|
.ecs()
|
||||||
|
.read_resource::<EventBus<SfxEventItem>>()
|
||||||
|
.emitter()
|
||||||
|
.emit(SfxEventItem::at_player_position(SfxEvent::Inventory(event)));
|
||||||
},
|
},
|
||||||
ServerMsg::TerrainChunkUpdate { key, chunk } => {
|
ServerMsg::TerrainChunkUpdate { key, chunk } => {
|
||||||
if let Ok(chunk) = chunk {
|
if let Ok(chunk) = chunk {
|
||||||
@ -687,10 +726,6 @@ impl Client {
|
|||||||
self.client_state = state;
|
self.client_state = state;
|
||||||
},
|
},
|
||||||
ServerMsg::StateAnswer(Err((error, state))) => {
|
ServerMsg::StateAnswer(Err((error, state))) => {
|
||||||
if error == RequestStateError::Denied {
|
|
||||||
warn!("Connection denied!");
|
|
||||||
return Err(Error::InvalidAuth);
|
|
||||||
}
|
|
||||||
warn!(
|
warn!(
|
||||||
"StateAnswer: {:?}. Server thinks client is in state {:?}.",
|
"StateAnswer: {:?}. Server thinks client is in state {:?}.",
|
||||||
error, state
|
error, state
|
||||||
@ -698,13 +733,14 @@ impl Client {
|
|||||||
},
|
},
|
||||||
ServerMsg::Disconnect => {
|
ServerMsg::Disconnect => {
|
||||||
frontend_events.push(Event::Disconnect);
|
frontend_events.push(Event::Disconnect);
|
||||||
|
self.postbox.send_message(ClientMsg::Terminate);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let Some(err) = self.postbox.error() {
|
} else if let Some(err) = self.postbox.error() {
|
||||||
return Err(err.into());
|
return Err(err.into());
|
||||||
// We regularily ping in the tick method
|
// We regularily ping in the tick method
|
||||||
} else if Instant::now().duration_since(self.last_server_pong) > SERVER_TIMEOUT {
|
} else if self.state.get_time() - self.last_server_pong > SERVER_TIMEOUT {
|
||||||
return Err(Error::ServerTimeout);
|
return Err(Error::ServerTimeout);
|
||||||
}
|
}
|
||||||
Ok(frontend_events)
|
Ok(frontend_events)
|
||||||
|
@ -30,9 +30,10 @@ hashbrown = { version = "0.6.2", features = ["rayon", "serde", "nightly"] }
|
|||||||
find_folder = "0.3.0"
|
find_folder = "0.3.0"
|
||||||
parking_lot = "0.9.0"
|
parking_lot = "0.9.0"
|
||||||
crossbeam = "=0.7.2"
|
crossbeam = "=0.7.2"
|
||||||
notify = "5.0.0-pre.1"
|
notify = "5.0.0-pre.2"
|
||||||
indexmap = "1.3.0"
|
indexmap = "1.3.0"
|
||||||
sum_type = "0.2.0"
|
sum_type = "0.2.0"
|
||||||
|
authc = { git = "https://gitlab.com/veloren/auth.git", rev = "65571ade0d954a0e0bd995fdb314854ff146ab97" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.3"
|
criterion = "0.3"
|
||||||
|
@ -31,6 +31,7 @@ pub enum Species {
|
|||||||
Chicken = 1,
|
Chicken = 1,
|
||||||
Goose = 2,
|
Goose = 2,
|
||||||
Peacock = 3,
|
Peacock = 3,
|
||||||
|
Eagle = 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Data representing per-species generic data.
|
/// Data representing per-species generic data.
|
||||||
@ -42,6 +43,7 @@ pub struct AllSpecies<SpeciesMeta> {
|
|||||||
pub chicken: SpeciesMeta,
|
pub chicken: SpeciesMeta,
|
||||||
pub goose: SpeciesMeta,
|
pub goose: SpeciesMeta,
|
||||||
pub peacock: SpeciesMeta,
|
pub peacock: SpeciesMeta,
|
||||||
|
pub eagle: SpeciesMeta,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies<SpeciesMeta> {
|
impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies<SpeciesMeta> {
|
||||||
@ -54,15 +56,17 @@ impl<'a, SpeciesMeta> core::ops::Index<&'a Species> for AllSpecies<SpeciesMeta>
|
|||||||
Species::Chicken => &self.chicken,
|
Species::Chicken => &self.chicken,
|
||||||
Species::Goose => &self.goose,
|
Species::Goose => &self.goose,
|
||||||
Species::Peacock => &self.peacock,
|
Species::Peacock => &self.peacock,
|
||||||
|
Species::Eagle => &self.eagle,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const ALL_SPECIES: [Species; 4] = [
|
pub const ALL_SPECIES: [Species; 5] = [
|
||||||
Species::Duck,
|
Species::Duck,
|
||||||
Species::Chicken,
|
Species::Chicken,
|
||||||
Species::Goose,
|
Species::Goose,
|
||||||
Species::Peacock,
|
Species::Peacock,
|
||||||
|
Species::Eagle,
|
||||||
];
|
];
|
||||||
|
|
||||||
impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies<SpeciesMeta> {
|
impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies<SpeciesMeta> {
|
||||||
|
@ -131,10 +131,10 @@ pub const DANARI_HAIR_COLORS: [(u8, u8, u8); 11] = [
|
|||||||
//(228, 208, 147), // Gold Blonde
|
//(228, 208, 147), // Gold Blonde
|
||||||
//(228, 223, 141), // Platinum Blonde
|
//(228, 223, 141), // Platinum Blonde
|
||||||
(199, 131, 58), // Summer Blonde
|
(199, 131, 58), // Summer Blonde
|
||||||
(107, 76, 51), // Oak Brown
|
(107, 76, 51), // Oak Skin4
|
||||||
//(203, 154, 98), // Light Brown
|
//(203, 154, 98), // Light Skin4
|
||||||
(64, 32, 18), // Chocolate Brown
|
(64, 32, 18), // Skin7 Skin4
|
||||||
(86, 72, 71), // Ash Brown
|
(86, 72, 71), // Ash Skin4
|
||||||
(57, 56, 61), // Raven Black
|
(57, 56, 61), // Raven Black
|
||||||
(101, 83, 95), // Matte Purple
|
(101, 83, 95), // Matte Purple
|
||||||
(101, 57, 90), // Witch Purple
|
(101, 57, 90), // Witch Purple
|
||||||
@ -148,10 +148,10 @@ pub const DWARF_HAIR_COLORS: [(u8, u8, u8); 20] = [
|
|||||||
(228, 208, 147), // Gold Blonde
|
(228, 208, 147), // Gold Blonde
|
||||||
(228, 223, 141), // Platinum Blonde
|
(228, 223, 141), // Platinum Blonde
|
||||||
(199, 131, 58), // Summer Blonde
|
(199, 131, 58), // Summer Blonde
|
||||||
(107, 76, 51), // Oak Brown
|
(107, 76, 51), // Oak Skin4
|
||||||
(203, 154, 98), // Light Brown
|
(203, 154, 98), // Light Skin4
|
||||||
(64, 32, 18), // Chocolate Brown
|
(64, 32, 18), // Skin7 Skin4
|
||||||
(86, 72, 71), // Ash Brown
|
(86, 72, 71), // Ash Skin4
|
||||||
(57, 56, 61), // Raven Black
|
(57, 56, 61), // Raven Black
|
||||||
(101, 83, 95), // Matte Purple
|
(101, 83, 95), // Matte Purple
|
||||||
(101, 57, 90), // Witch Purple
|
(101, 57, 90), // Witch Purple
|
||||||
@ -172,10 +172,10 @@ pub const ELF_HAIR_COLORS: [(u8, u8, u8); 23] = [
|
|||||||
(228, 208, 147), // Gold Blonde
|
(228, 208, 147), // Gold Blonde
|
||||||
(228, 223, 141), // Platinum Blonde
|
(228, 223, 141), // Platinum Blonde
|
||||||
(199, 131, 58), // Summer Blonde
|
(199, 131, 58), // Summer Blonde
|
||||||
(107, 76, 51), // Oak Brown
|
(107, 76, 51), // Oak Skin4
|
||||||
(203, 154, 98), // Light Brown
|
(203, 154, 98), // Light Skin4
|
||||||
(64, 32, 18), // Chocolate Brown
|
(64, 32, 18), // Skin7 Skin4
|
||||||
(86, 72, 71), // Ash Brown
|
(86, 72, 71), // Ash Skin4
|
||||||
(57, 56, 61), // Raven Black
|
(57, 56, 61), // Raven Black
|
||||||
(101, 83, 95), // Matte Purple
|
(101, 83, 95), // Matte Purple
|
||||||
(101, 57, 90), // Witch Purple
|
(101, 57, 90), // Witch Purple
|
||||||
@ -195,10 +195,10 @@ pub const HUMAN_HAIR_COLORS: [(u8, u8, u8); 21] = [
|
|||||||
(228, 208, 147), // Gold Blonde
|
(228, 208, 147), // Gold Blonde
|
||||||
(228, 223, 141), // Platinum Blonde
|
(228, 223, 141), // Platinum Blonde
|
||||||
(199, 131, 58), // Summer Blonde
|
(199, 131, 58), // Summer Blonde
|
||||||
(107, 76, 51), // Oak Brown
|
(107, 76, 51), // Oak Skin4
|
||||||
(203, 154, 98), // Light Brown
|
(203, 154, 98), // Light Skin4
|
||||||
(64, 32, 18), // Chocolate Brown
|
(64, 32, 18), // Skin7 Skin4
|
||||||
(86, 72, 71), // Ash Brown
|
(86, 72, 71), // Ash Skin4
|
||||||
(57, 56, 61), // Raven Black
|
(57, 56, 61), // Raven Black
|
||||||
(101, 83, 95), // Matte Purple
|
(101, 83, 95), // Matte Purple
|
||||||
(101, 57, 90), // Witch Purple
|
(101, 57, 90), // Witch Purple
|
||||||
@ -215,11 +215,11 @@ pub const HUMAN_HAIR_COLORS: [(u8, u8, u8); 21] = [
|
|||||||
];
|
];
|
||||||
pub const ORC_HAIR_COLORS: [(u8, u8, u8); 10] = [
|
pub const ORC_HAIR_COLORS: [(u8, u8, u8); 10] = [
|
||||||
(66, 66, 59), // Wise Grey
|
(66, 66, 59), // Wise Grey
|
||||||
//(107, 76, 51), // Oak Brown
|
//(107, 76, 51), // Oak Skin4
|
||||||
//(203, 154, 98), // Light Brown
|
//(203, 154, 98), // Light Skin4
|
||||||
(64, 32, 18), // Chocolate Brown
|
(64, 32, 18), // Skin7 Skin4
|
||||||
(54, 30, 26), // Dark Chocolate
|
(54, 30, 26), // Dark Skin7
|
||||||
(86, 72, 71), // Ash Brown
|
(86, 72, 71), // Ash Skin4
|
||||||
(57, 56, 61), // Raven Black
|
(57, 56, 61), // Raven Black
|
||||||
(101, 83, 95), // Matte Purple
|
(101, 83, 95), // Matte Purple
|
||||||
(101, 57, 90), // Witch Purple
|
(101, 57, 90), // Witch Purple
|
||||||
@ -232,10 +232,10 @@ pub const UNDEAD_HAIR_COLORS: [(u8, u8, u8); 21] = [
|
|||||||
(228, 208, 147), // Gold Blonde
|
(228, 208, 147), // Gold Blonde
|
||||||
//(228, 223, 141), // Platinum Blonde
|
//(228, 223, 141), // Platinum Blonde
|
||||||
(199, 131, 58), // Summer Blonde
|
(199, 131, 58), // Summer Blonde
|
||||||
(107, 76, 51), // Oak Brown
|
(107, 76, 51), // Oak Skin4
|
||||||
(203, 154, 98), // Light Brown
|
(203, 154, 98), // Light Skin4
|
||||||
(64, 32, 18), // Chocolate Brown
|
(64, 32, 18), // Skin7 Skin4
|
||||||
(86, 72, 71), // Ash Brown
|
(86, 72, 71), // Ash Skin4
|
||||||
(57, 56, 61), // Raven Black
|
(57, 56, 61), // Raven Black
|
||||||
(101, 83, 95), // Matte Purple
|
(101, 83, 95), // Matte Purple
|
||||||
(101, 57, 90), // Witch Purple
|
(101, 57, 90), // Witch Purple
|
||||||
@ -261,30 +261,59 @@ pub const DANARI_SKIN_COLORS: [Skin; 4] = [
|
|||||||
Skin::DanariThree,
|
Skin::DanariThree,
|
||||||
Skin::DanariFour,
|
Skin::DanariFour,
|
||||||
];
|
];
|
||||||
pub const DWARF_SKIN_COLORS: [Skin; 5] = [
|
pub const DWARF_SKIN_COLORS: [Skin; 14] = [
|
||||||
Skin::Pale,
|
Skin::Skin1,
|
||||||
Skin::White,
|
Skin::Skin2,
|
||||||
Skin::Tanned,
|
Skin::Skin3,
|
||||||
|
Skin::Skin4,
|
||||||
|
Skin::Skin5,
|
||||||
|
Skin::Skin6,
|
||||||
|
Skin::Skin7,
|
||||||
|
Skin::Skin8,
|
||||||
|
Skin::Skin9,
|
||||||
|
Skin::Skin10,
|
||||||
|
Skin::Skin11,
|
||||||
|
Skin::Skin12,
|
||||||
Skin::Iron,
|
Skin::Iron,
|
||||||
Skin::Steel,
|
Skin::Steel,
|
||||||
];
|
];
|
||||||
pub const ELF_SKIN_COLORS: [Skin; 7] = [
|
pub const ELF_SKIN_COLORS: [Skin; 14] = [
|
||||||
Skin::Pale,
|
Skin::Skin1,
|
||||||
|
Skin::Skin2,
|
||||||
|
Skin::Skin3,
|
||||||
|
Skin::Skin5,
|
||||||
|
Skin::Skin6,
|
||||||
|
Skin::Skin7,
|
||||||
|
Skin::Skin8,
|
||||||
|
Skin::Skin9,
|
||||||
|
Skin::Skin10,
|
||||||
|
Skin::Skin11,
|
||||||
|
Skin::Skin12,
|
||||||
Skin::ElfOne,
|
Skin::ElfOne,
|
||||||
Skin::ElfTwo,
|
Skin::ElfTwo,
|
||||||
Skin::ElfThree,
|
Skin::ElfThree,
|
||||||
Skin::White,
|
|
||||||
Skin::Tanned,
|
|
||||||
Skin::TannedBrown,
|
|
||||||
];
|
];
|
||||||
pub const HUMAN_SKIN_COLORS: [Skin; 5] = [
|
pub const HUMAN_SKIN_COLORS: [Skin; 18] = [
|
||||||
Skin::Pale,
|
Skin::Skin1,
|
||||||
Skin::White,
|
Skin::Skin2,
|
||||||
Skin::Tanned,
|
Skin::Skin3,
|
||||||
Skin::TannedBrown,
|
Skin::Skin4,
|
||||||
Skin::TannedDarkBrown,
|
Skin::Skin5,
|
||||||
|
Skin::Skin6,
|
||||||
|
Skin::Skin7,
|
||||||
|
Skin::Skin8,
|
||||||
|
Skin::Skin9,
|
||||||
|
Skin::Skin10,
|
||||||
|
Skin::Skin11,
|
||||||
|
Skin::Skin12,
|
||||||
|
Skin::Skin13,
|
||||||
|
Skin::Skin14,
|
||||||
|
Skin::Skin15,
|
||||||
|
Skin::Skin16,
|
||||||
|
Skin::Skin17,
|
||||||
|
Skin::Skin18,
|
||||||
];
|
];
|
||||||
pub const ORC_SKIN_COLORS: [Skin; 4] = [Skin::OrcOne, Skin::OrcTwo, Skin::OrcThree, Skin::Brown];
|
pub const ORC_SKIN_COLORS: [Skin; 4] = [Skin::OrcOne, Skin::OrcTwo, Skin::OrcThree, Skin::OrcFour];
|
||||||
pub const UNDEAD_SKIN_COLORS: [Skin; 3] = [Skin::UndeadOne, Skin::UndeadTwo, Skin::UndeadThree];
|
pub const UNDEAD_SKIN_COLORS: [Skin; 3] = [Skin::UndeadOne, Skin::UndeadTwo, Skin::UndeadThree];
|
||||||
|
|
||||||
// Eye colors
|
// Eye colors
|
||||||
@ -293,22 +322,31 @@ pub const DANARI_EYE_COLORS: [EyeColor; 3] = [
|
|||||||
EyeColor::LoyalBrown,
|
EyeColor::LoyalBrown,
|
||||||
EyeColor::ViciousRed,
|
EyeColor::ViciousRed,
|
||||||
];
|
];
|
||||||
pub const DWARF_EYE_COLORS: [EyeColor; 3] = [
|
pub const DWARF_EYE_COLORS: [EyeColor; 4] = [
|
||||||
EyeColor::CuriousGreen,
|
EyeColor::CuriousGreen,
|
||||||
EyeColor::LoyalBrown,
|
EyeColor::LoyalBrown,
|
||||||
EyeColor::NobleBlue,
|
EyeColor::NobleBlue,
|
||||||
|
EyeColor::CornflowerBlue,
|
||||||
];
|
];
|
||||||
pub const ELF_EYE_COLORS: [EyeColor; 3] = [
|
pub const ELF_EYE_COLORS: [EyeColor; 4] = [
|
||||||
EyeColor::NobleBlue,
|
EyeColor::NobleBlue,
|
||||||
|
EyeColor::CornflowerBlue,
|
||||||
EyeColor::CuriousGreen,
|
EyeColor::CuriousGreen,
|
||||||
EyeColor::LoyalBrown,
|
EyeColor::LoyalBrown,
|
||||||
];
|
];
|
||||||
pub const HUMAN_EYE_COLORS: [EyeColor; 3] = [
|
pub const HUMAN_EYE_COLORS: [EyeColor; 4] = [
|
||||||
EyeColor::NobleBlue,
|
EyeColor::NobleBlue,
|
||||||
|
EyeColor::CornflowerBlue,
|
||||||
EyeColor::CuriousGreen,
|
EyeColor::CuriousGreen,
|
||||||
EyeColor::LoyalBrown,
|
EyeColor::LoyalBrown,
|
||||||
];
|
];
|
||||||
pub const ORC_EYE_COLORS: [EyeColor; 2] = [EyeColor::LoyalBrown, EyeColor::ExoticPurple];
|
pub const ORC_EYE_COLORS: [EyeColor; 5] = [
|
||||||
|
EyeColor::LoyalBrown,
|
||||||
|
EyeColor::ExoticPurple,
|
||||||
|
EyeColor::AmberOrange,
|
||||||
|
EyeColor::PineGreen,
|
||||||
|
EyeColor::CornflowerBlue,
|
||||||
|
];
|
||||||
pub const UNDEAD_EYE_COLORS: [EyeColor; 5] = [
|
pub const UNDEAD_EYE_COLORS: [EyeColor; 5] = [
|
||||||
EyeColor::ViciousRed,
|
EyeColor::ViciousRed,
|
||||||
EyeColor::PumpkinOrange,
|
EyeColor::PumpkinOrange,
|
||||||
@ -365,7 +403,7 @@ impl Race {
|
|||||||
self.skin_colors()
|
self.skin_colors()
|
||||||
.get(val as usize)
|
.get(val as usize)
|
||||||
.copied()
|
.copied()
|
||||||
.unwrap_or(Skin::Tanned)
|
.unwrap_or(Skin::Skin3)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn num_skin_colors(self) -> u8 { self.skin_colors().len() as u8 }
|
pub fn num_skin_colors(self) -> u8 { self.skin_colors().len() as u8 }
|
||||||
@ -389,7 +427,7 @@ impl Race {
|
|||||||
(Race::Elf, BodyType::Male) => 4,
|
(Race::Elf, BodyType::Male) => 4,
|
||||||
(Race::Human, BodyType::Female) => 19,
|
(Race::Human, BodyType::Female) => 19,
|
||||||
(Race::Human, BodyType::Male) => 17,
|
(Race::Human, BodyType::Male) => 17,
|
||||||
(Race::Orc, BodyType::Female) => 1,
|
(Race::Orc, BodyType::Female) => 7,
|
||||||
(Race::Orc, BodyType::Male) => 8,
|
(Race::Orc, BodyType::Male) => 8,
|
||||||
(Race::Undead, BodyType::Female) => 4,
|
(Race::Undead, BodyType::Female) => 4,
|
||||||
(Race::Undead, BodyType::Male) => 3,
|
(Race::Undead, BodyType::Male) => 3,
|
||||||
@ -406,7 +444,7 @@ impl Race {
|
|||||||
(Race::Elf, BodyType::Male) => 1,
|
(Race::Elf, BodyType::Male) => 1,
|
||||||
(Race::Human, BodyType::Female) => 1,
|
(Race::Human, BodyType::Female) => 1,
|
||||||
(Race::Human, BodyType::Male) => 1,
|
(Race::Human, BodyType::Male) => 1,
|
||||||
(Race::Orc, BodyType::Female) => 3,
|
(Race::Orc, BodyType::Female) => 4,
|
||||||
(Race::Orc, BodyType::Male) => 5,
|
(Race::Orc, BodyType::Male) => 5,
|
||||||
(Race::Undead, BodyType::Female) => 1,
|
(Race::Undead, BodyType::Female) => 1,
|
||||||
(Race::Undead, BodyType::Male) => 1,
|
(Race::Undead, BodyType::Male) => 1,
|
||||||
@ -534,6 +572,10 @@ pub enum EyeColor {
|
|||||||
MagicPurple = 7,
|
MagicPurple = 7,
|
||||||
ToxicGreen = 8,
|
ToxicGreen = 8,
|
||||||
ExoticPurple = 9,
|
ExoticPurple = 9,
|
||||||
|
SulfurYellow = 10,
|
||||||
|
AmberOrange = 11,
|
||||||
|
PineGreen = 12,
|
||||||
|
CornflowerBlue = 13,
|
||||||
}
|
}
|
||||||
impl EyeColor {
|
impl EyeColor {
|
||||||
pub fn light_rgb(self) -> Rgb<u8> {
|
pub fn light_rgb(self) -> Rgb<u8> {
|
||||||
@ -548,6 +590,10 @@ impl EyeColor {
|
|||||||
EyeColor::MagicPurple => Rgb::new(137, 4, 177),
|
EyeColor::MagicPurple => Rgb::new(137, 4, 177),
|
||||||
EyeColor::ToxicGreen => Rgb::new(1, 223, 1),
|
EyeColor::ToxicGreen => Rgb::new(1, 223, 1),
|
||||||
EyeColor::ExoticPurple => Rgb::new(95, 32, 111),
|
EyeColor::ExoticPurple => Rgb::new(95, 32, 111),
|
||||||
|
EyeColor::SulfurYellow => Rgb::new(235, 198, 94),
|
||||||
|
EyeColor::AmberOrange => Rgb::new(137, 46, 1),
|
||||||
|
EyeColor::PineGreen => Rgb::new(0, 78, 56),
|
||||||
|
EyeColor::CornflowerBlue => Rgb::new(18, 66, 90),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -563,6 +609,10 @@ impl EyeColor {
|
|||||||
EyeColor::MagicPurple => Rgb::new(110, 3, 143),
|
EyeColor::MagicPurple => Rgb::new(110, 3, 143),
|
||||||
EyeColor::ToxicGreen => Rgb::new(1, 185, 1),
|
EyeColor::ToxicGreen => Rgb::new(1, 185, 1),
|
||||||
EyeColor::ExoticPurple => Rgb::new(69, 23, 80),
|
EyeColor::ExoticPurple => Rgb::new(69, 23, 80),
|
||||||
|
EyeColor::SulfurYellow => Rgb::new(209, 176, 84),
|
||||||
|
EyeColor::AmberOrange => Rgb::new(112, 40, 1),
|
||||||
|
EyeColor::PineGreen => Rgb::new(0, 54, 38),
|
||||||
|
EyeColor::CornflowerBlue => Rgb::new(13, 47, 64),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -580,12 +630,12 @@ pub const ALL_ACCESSORIES: [Accessory; 2] = [Accessory::Nothing, Accessory::Some
|
|||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum Skin {
|
pub enum Skin {
|
||||||
Pale = 0,
|
Skin1 = 0,
|
||||||
White = 1,
|
Skin2 = 1,
|
||||||
Tanned = 2,
|
Skin3 = 2,
|
||||||
Brown = 3,
|
Skin4 = 3,
|
||||||
TannedBrown = 4,
|
Skin5 = 4,
|
||||||
TannedDarkBrown = 5,
|
Skin6 = 5,
|
||||||
Iron = 6,
|
Iron = 6,
|
||||||
Steel = 7,
|
Steel = 7,
|
||||||
DanariOne = 8,
|
DanariOne = 8,
|
||||||
@ -601,16 +651,41 @@ pub enum Skin {
|
|||||||
UndeadOne = 18,
|
UndeadOne = 18,
|
||||||
UndeadTwo = 19,
|
UndeadTwo = 19,
|
||||||
UndeadThree = 20,
|
UndeadThree = 20,
|
||||||
|
Skin7 = 21,
|
||||||
|
Skin8 = 22,
|
||||||
|
Skin9 = 23,
|
||||||
|
Skin10 = 24,
|
||||||
|
Skin11 = 25,
|
||||||
|
Skin12 = 26,
|
||||||
|
Skin13 = 27,
|
||||||
|
Skin14 = 28,
|
||||||
|
Skin15 = 29,
|
||||||
|
Skin16 = 30,
|
||||||
|
Skin17 = 31,
|
||||||
|
Skin18 = 32,
|
||||||
|
OrcFour = 33,
|
||||||
}
|
}
|
||||||
impl Skin {
|
impl Skin {
|
||||||
pub fn rgb(self) -> Rgb<u8> {
|
pub fn rgb(self) -> Rgb<u8> {
|
||||||
let color = match self {
|
let color = match self {
|
||||||
Self::Pale => (252, 211, 179),
|
Self::Skin1 => (255, 229, 200),
|
||||||
Self::White => (253, 195, 164),
|
Self::Skin2 => (255, 218, 190),
|
||||||
Self::Tanned => (222, 181, 151),
|
Self::Skin3 => (255, 206, 180),
|
||||||
Self::Brown => (123, 80, 45),
|
Self::Skin4 => (255, 195, 170),
|
||||||
Self::TannedBrown => (135, 70, 50),
|
Self::Skin5 => (240, 184, 160),
|
||||||
Self::TannedDarkBrown => (116, 61, 43),
|
Self::Skin6 => (225, 172, 150),
|
||||||
|
Self::Skin7 => (210, 161, 140),
|
||||||
|
Self::Skin8 => (195, 149, 130),
|
||||||
|
Self::Skin9 => (180, 138, 120),
|
||||||
|
Self::Skin10 => (165, 126, 110),
|
||||||
|
Self::Skin11 => (150, 114, 100),
|
||||||
|
Self::Skin12 => (135, 103, 90),
|
||||||
|
Self::Skin13 => (120, 92, 80),
|
||||||
|
Self::Skin14 => (105, 80, 70),
|
||||||
|
Self::Skin15 => (90, 69, 60),
|
||||||
|
Self::Skin16 => (75, 57, 50),
|
||||||
|
Self::Skin17 => (60, 46, 40),
|
||||||
|
Self::Skin18 => (45, 34, 30),
|
||||||
Self::Iron => (135, 113, 95),
|
Self::Iron => (135, 113, 95),
|
||||||
Self::Steel => (108, 94, 86),
|
Self::Steel => (108, 94, 86),
|
||||||
Self::DanariOne => (104, 168, 196),
|
Self::DanariOne => (104, 168, 196),
|
||||||
@ -623,6 +698,7 @@ impl Skin {
|
|||||||
Self::OrcOne => (61, 130, 42),
|
Self::OrcOne => (61, 130, 42),
|
||||||
Self::OrcTwo => (82, 117, 36),
|
Self::OrcTwo => (82, 117, 36),
|
||||||
Self::OrcThree => (71, 94, 42),
|
Self::OrcThree => (71, 94, 42),
|
||||||
|
Self::OrcFour => (97, 54, 29),
|
||||||
Self::UndeadOne => (240, 243, 239),
|
Self::UndeadOne => (240, 243, 239),
|
||||||
Self::UndeadTwo => (178, 178, 178),
|
Self::UndeadTwo => (178, 178, 178),
|
||||||
Self::UndeadThree => (145, 135, 121),
|
Self::UndeadThree => (145, 135, 121),
|
||||||
@ -632,12 +708,24 @@ impl Skin {
|
|||||||
|
|
||||||
pub fn light_rgb(self) -> Rgb<u8> {
|
pub fn light_rgb(self) -> Rgb<u8> {
|
||||||
let color = match self {
|
let color = match self {
|
||||||
Self::Pale => (255, 227, 193),
|
Self::Skin1 => (255, 229, 200),
|
||||||
Self::White => (255, 210, 180),
|
Self::Skin2 => (255, 218, 190),
|
||||||
Self::Tanned => (239, 197, 164),
|
Self::Skin3 => (255, 206, 180),
|
||||||
Self::Brown => (150, 104, 68),
|
Self::Skin4 => (255, 195, 170),
|
||||||
Self::TannedBrown => (148, 85, 64),
|
Self::Skin5 => (240, 184, 160),
|
||||||
Self::TannedDarkBrown => (132, 74, 56),
|
Self::Skin6 => (225, 172, 150),
|
||||||
|
Self::Skin7 => (210, 161, 140),
|
||||||
|
Self::Skin8 => (195, 149, 130),
|
||||||
|
Self::Skin9 => (180, 138, 120),
|
||||||
|
Self::Skin10 => (165, 126, 110),
|
||||||
|
Self::Skin11 => (150, 114, 100),
|
||||||
|
Self::Skin12 => (135, 103, 90),
|
||||||
|
Self::Skin13 => (120, 92, 80),
|
||||||
|
Self::Skin14 => (105, 80, 70),
|
||||||
|
Self::Skin15 => (90, 69, 60),
|
||||||
|
Self::Skin16 => (75, 57, 50),
|
||||||
|
Self::Skin17 => (60, 46, 40),
|
||||||
|
Self::Skin18 => (45, 34, 30),
|
||||||
Self::Iron => (144, 125, 106),
|
Self::Iron => (144, 125, 106),
|
||||||
Self::Steel => (120, 107, 99),
|
Self::Steel => (120, 107, 99),
|
||||||
Self::DanariOne => (116, 176, 208),
|
Self::DanariOne => (116, 176, 208),
|
||||||
@ -650,6 +738,7 @@ impl Skin {
|
|||||||
Self::OrcOne => (83, 165, 56),
|
Self::OrcOne => (83, 165, 56),
|
||||||
Self::OrcTwo => (92, 132, 46),
|
Self::OrcTwo => (92, 132, 46),
|
||||||
Self::OrcThree => (84, 110, 54),
|
Self::OrcThree => (84, 110, 54),
|
||||||
|
Self::OrcFour => (97, 54, 29),
|
||||||
Self::UndeadOne => (254, 252, 251),
|
Self::UndeadOne => (254, 252, 251),
|
||||||
Self::UndeadTwo => (190, 192, 191),
|
Self::UndeadTwo => (190, 192, 191),
|
||||||
Self::UndeadThree => (160, 151, 134),
|
Self::UndeadThree => (160, 151, 134),
|
||||||
@ -659,12 +748,24 @@ impl Skin {
|
|||||||
|
|
||||||
pub fn dark_rgb(self) -> Rgb<u8> {
|
pub fn dark_rgb(self) -> Rgb<u8> {
|
||||||
let color = match self {
|
let color = match self {
|
||||||
Self::Pale => (229, 192, 163),
|
Self::Skin1 => (242, 217, 189),
|
||||||
Self::White => (239, 179, 150),
|
Self::Skin2 => (242, 207, 189),
|
||||||
Self::Tanned => (208, 167, 135),
|
Self::Skin3 => (242, 197, 172),
|
||||||
Self::Brown => (106, 63, 30),
|
Self::Skin4 => (242, 186, 162),
|
||||||
Self::TannedBrown => (122, 58, 40),
|
Self::Skin5 => (212, 173, 150),
|
||||||
Self::TannedDarkBrown => (100, 47, 32),
|
Self::Skin6 => (212, 163, 142),
|
||||||
|
Self::Skin7 => (196, 151, 132),
|
||||||
|
Self::Skin8 => (181, 139, 121),
|
||||||
|
Self::Skin9 => (168, 129, 113),
|
||||||
|
Self::Skin10 => (153, 117, 103),
|
||||||
|
Self::Skin11 => (138, 105, 92),
|
||||||
|
Self::Skin12 => (122, 93, 82),
|
||||||
|
Self::Skin13 => (107, 82, 72),
|
||||||
|
Self::Skin14 => (92, 70, 62),
|
||||||
|
Self::Skin15 => (77, 59, 51),
|
||||||
|
Self::Skin16 => (61, 47, 41),
|
||||||
|
Self::Skin17 => (48, 37, 32),
|
||||||
|
Self::Skin18 => (33, 25, 22),
|
||||||
Self::Iron => (124, 99, 82),
|
Self::Iron => (124, 99, 82),
|
||||||
Self::Steel => (96, 81, 72),
|
Self::Steel => (96, 81, 72),
|
||||||
Self::DanariOne => (92, 155, 183),
|
Self::DanariOne => (92, 155, 183),
|
||||||
@ -677,6 +778,7 @@ impl Skin {
|
|||||||
Self::OrcOne => (55, 114, 36),
|
Self::OrcOne => (55, 114, 36),
|
||||||
Self::OrcTwo => (70, 104, 29),
|
Self::OrcTwo => (70, 104, 29),
|
||||||
Self::OrcThree => (60, 83, 32),
|
Self::OrcThree => (60, 83, 32),
|
||||||
|
Self::OrcFour => (84, 47, 25),
|
||||||
Self::UndeadOne => (229, 231, 230),
|
Self::UndeadOne => (229, 231, 230),
|
||||||
Self::UndeadTwo => (165, 166, 164),
|
Self::UndeadTwo => (165, 166, 164),
|
||||||
Self::UndeadThree => (130, 122, 106),
|
Self::UndeadThree => (130, 122, 106),
|
||||||
|
@ -90,7 +90,7 @@ pub enum Armor {
|
|||||||
Necklace,
|
Necklace,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub enum Consumable {
|
pub enum Consumable {
|
||||||
Apple,
|
Apple,
|
||||||
Cheese,
|
Cheese,
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
pub mod item;
|
pub mod item;
|
||||||
|
|
||||||
// Reexports
|
// Reexports
|
||||||
pub use item::{Debug, Item, ItemKind, Tool};
|
pub use item::{Consumable, Debug, Item, ItemKind, Tool};
|
||||||
|
|
||||||
use crate::assets;
|
use crate::assets;
|
||||||
use specs::{Component, HashMapStorage, NullStorage};
|
use specs::{Component, FlaggedStorage, HashMapStorage};
|
||||||
|
use specs_idvs::IDVStorage;
|
||||||
use std::ops::Not;
|
use std::ops::Not;
|
||||||
|
|
||||||
|
// The limit on distance between the entity and a collectible (squared)
|
||||||
|
pub const MAX_PICKUP_RANGE_SQR: f32 = 64.0;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub struct Inventory {
|
pub struct Inventory {
|
||||||
pub slots: Vec<Option<Item>>,
|
pub slots: Vec<Option<Item>>,
|
||||||
@ -136,12 +140,38 @@ impl Component for Inventory {
|
|||||||
type Storage = HashMapStorage<Self>;
|
type Storage = HashMapStorage<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForceUpdate
|
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum InventoryUpdateEvent {
|
||||||
|
Init,
|
||||||
|
Used,
|
||||||
|
Consumed(Consumable),
|
||||||
|
Gave,
|
||||||
|
Given,
|
||||||
|
Swapped,
|
||||||
|
Dropped,
|
||||||
|
Collected,
|
||||||
|
CollectFailed,
|
||||||
|
Possession,
|
||||||
|
Debug,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for InventoryUpdateEvent {
|
||||||
|
fn default() -> Self { Self::Init }
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
|
||||||
pub struct InventoryUpdate;
|
pub struct InventoryUpdate {
|
||||||
|
event: InventoryUpdateEvent,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InventoryUpdate {
|
||||||
|
pub fn new(event: InventoryUpdateEvent) -> Self { Self { event } }
|
||||||
|
|
||||||
|
pub fn event(&self) -> InventoryUpdateEvent { self.event }
|
||||||
|
}
|
||||||
|
|
||||||
impl Component for InventoryUpdate {
|
impl Component for InventoryUpdate {
|
||||||
type Storage = NullStorage<Self>;
|
type Storage = FlaggedStorage<Self, IDVStorage<Self>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)] mod test;
|
#[cfg(test)] mod test;
|
||||||
|
@ -28,7 +28,9 @@ pub use controller::{
|
|||||||
};
|
};
|
||||||
pub use energy::{Energy, EnergySource};
|
pub use energy::{Energy, EnergySource};
|
||||||
pub use inputs::CanBuild;
|
pub use inputs::CanBuild;
|
||||||
pub use inventory::{item, Inventory, InventoryUpdate, Item, ItemKind};
|
pub use inventory::{
|
||||||
|
item, Inventory, InventoryUpdate, InventoryUpdateEvent, Item, ItemKind, MAX_PICKUP_RANGE_SQR,
|
||||||
|
};
|
||||||
pub use last::Last;
|
pub use last::Last;
|
||||||
pub use location::{Waypoint, WaypointArea};
|
pub use location::{Waypoint, WaypointArea};
|
||||||
pub use phys::{ForceUpdate, Gravity, Mass, Ori, PhysicsState, Pos, Scale, Sticky, Vel};
|
pub use phys::{ForceUpdate, Gravity, Mass, Ori, PhysicsState, Pos, Scale, Sticky, Vel};
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use authc::Uuid;
|
||||||
use specs::{Component, FlaggedStorage, NullStorage};
|
use specs::{Component, FlaggedStorage, NullStorage};
|
||||||
use specs_idvs::IDVStorage;
|
use specs_idvs::IDVStorage;
|
||||||
|
|
||||||
@ -7,20 +8,26 @@ const MAX_ALIAS_LEN: usize = 32;
|
|||||||
pub struct Player {
|
pub struct Player {
|
||||||
pub alias: String,
|
pub alias: String,
|
||||||
pub view_distance: Option<u32>,
|
pub view_distance: Option<u32>,
|
||||||
|
uuid: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Player {
|
impl Player {
|
||||||
pub fn new(alias: String, view_distance: Option<u32>) -> Self {
|
pub fn new(alias: String, view_distance: Option<u32>, uuid: Uuid) -> Self {
|
||||||
Self {
|
Self {
|
||||||
alias,
|
alias,
|
||||||
view_distance,
|
view_distance,
|
||||||
|
uuid,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_valid(&self) -> bool {
|
pub fn is_valid(&self) -> bool { Self::alias_is_valid(&self.alias) }
|
||||||
self.alias.chars().all(|c| c.is_alphanumeric() || c == '_')
|
|
||||||
&& self.alias.len() <= MAX_ALIAS_LEN
|
pub fn alias_is_valid(alias: &str) -> bool {
|
||||||
|
alias.chars().all(|c| c.is_alphanumeric() || c == '_') && alias.len() <= MAX_ALIAS_LEN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Not to be confused with uid
|
||||||
|
pub fn uuid(&self) -> Uuid { self.uuid }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for Player {
|
impl Component for Player {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{comp, sync::Uid};
|
use crate::{comp, sync::Uid};
|
||||||
use comp::item::Tool;
|
use comp::{item::Tool, InventoryUpdateEvent};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use specs::Entity as EcsEntity;
|
use specs::Entity as EcsEntity;
|
||||||
@ -9,22 +9,26 @@ use vek::*;
|
|||||||
pub struct SfxEventItem {
|
pub struct SfxEventItem {
|
||||||
pub sfx: SfxEvent,
|
pub sfx: SfxEvent,
|
||||||
pub pos: Option<Vec3<f32>>,
|
pub pos: Option<Vec3<f32>>,
|
||||||
|
pub vol: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SfxEventItem {
|
impl SfxEventItem {
|
||||||
pub fn new(sfx: SfxEvent, pos: Option<Vec3<f32>>) -> Self { Self { sfx, pos } }
|
pub fn new(sfx: SfxEvent, pos: Option<Vec3<f32>>, vol: Option<f32>) -> Self {
|
||||||
|
Self { sfx, pos, vol }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn at_player_position(sfx: SfxEvent) -> Self { Self { sfx, pos: None } }
|
pub fn at_player_position(sfx: SfxEvent) -> Self {
|
||||||
|
Self {
|
||||||
|
sfx,
|
||||||
|
pos: None,
|
||||||
|
vol: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Hash, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Hash, Eq)]
|
||||||
pub enum SfxEvent {
|
pub enum SfxEvent {
|
||||||
Idle,
|
Idle,
|
||||||
PlaceBlock,
|
|
||||||
RemoveBlock,
|
|
||||||
OpenChest,
|
|
||||||
ChatTellReceived,
|
|
||||||
OpenBag,
|
|
||||||
Run,
|
Run,
|
||||||
Roll,
|
Roll,
|
||||||
Climb,
|
Climb,
|
||||||
@ -36,10 +40,9 @@ pub enum SfxEvent {
|
|||||||
Fall,
|
Fall,
|
||||||
ExperienceGained,
|
ExperienceGained,
|
||||||
LevelUp,
|
LevelUp,
|
||||||
LightLantern,
|
Wield(Tool),
|
||||||
ExtinguishLantern,
|
Unwield(Tool),
|
||||||
Attack(Tool),
|
Inventory(InventoryUpdateEvent),
|
||||||
AttackWolf,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum LocalEvent {
|
pub enum LocalEvent {
|
||||||
|
@ -4,8 +4,8 @@ use vek::*;
|
|||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub enum ClientMsg {
|
pub enum ClientMsg {
|
||||||
Register {
|
Register {
|
||||||
player: comp::Player,
|
view_distance: Option<u32>,
|
||||||
password: String,
|
token_or_username: String,
|
||||||
},
|
},
|
||||||
Character {
|
Character {
|
||||||
name: String,
|
name: String,
|
||||||
@ -35,4 +35,5 @@ pub enum ClientMsg {
|
|||||||
key: Vec2<i32>,
|
key: Vec2<i32>,
|
||||||
},
|
},
|
||||||
Disconnect,
|
Disconnect,
|
||||||
|
Terminate,
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ pub mod server;
|
|||||||
pub use self::{
|
pub use self::{
|
||||||
client::ClientMsg,
|
client::ClientMsg,
|
||||||
ecs_packet::EcsCompPacket,
|
ecs_packet::EcsCompPacket,
|
||||||
server::{PlayerListUpdate, RequestStateError, ServerError, ServerInfo, ServerMsg},
|
server::{PlayerListUpdate, RegisterError, RequestStateError, ServerInfo, ServerMsg},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||||
|
@ -4,23 +4,17 @@ use crate::{
|
|||||||
terrain::{Block, TerrainChunk},
|
terrain::{Block, TerrainChunk},
|
||||||
ChatType,
|
ChatType,
|
||||||
};
|
};
|
||||||
|
use authc::AuthClientError;
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
||||||
pub enum RequestStateError {
|
|
||||||
Denied,
|
|
||||||
Already,
|
|
||||||
Impossible,
|
|
||||||
WrongMessage,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct ServerInfo {
|
pub struct ServerInfo {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub git_hash: String,
|
pub git_hash: String,
|
||||||
pub git_date: String,
|
pub git_date: String,
|
||||||
|
pub auth_provider: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
@ -71,24 +65,37 @@ pub enum ServerMsg {
|
|||||||
entity: u64,
|
entity: u64,
|
||||||
character_state: comp::CharacterState,
|
character_state: comp::CharacterState,
|
||||||
},
|
},
|
||||||
InventoryUpdate(comp::Inventory),
|
InventoryUpdate(comp::Inventory, comp::InventoryUpdateEvent),
|
||||||
TerrainChunkUpdate {
|
TerrainChunkUpdate {
|
||||||
key: Vec2<i32>,
|
key: Vec2<i32>,
|
||||||
chunk: Result<Box<TerrainChunk>, ()>,
|
chunk: Result<Box<TerrainChunk>, ()>,
|
||||||
},
|
},
|
||||||
TerrainBlockUpdates(HashMap<Vec3<i32>, Block>),
|
TerrainBlockUpdates(HashMap<Vec3<i32>, Block>),
|
||||||
Error(ServerError),
|
|
||||||
Disconnect,
|
Disconnect,
|
||||||
Shutdown,
|
Shutdown,
|
||||||
|
TooManyPlayers,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
pub enum ServerError {
|
pub enum RequestStateError {
|
||||||
TooManyPlayers,
|
RegisterDenied(RegisterError),
|
||||||
InvalidAuth,
|
Denied,
|
||||||
|
Already,
|
||||||
|
Impossible,
|
||||||
|
WrongMessage,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub enum RegisterError {
|
||||||
|
AlreadyLoggedIn,
|
||||||
|
AuthError(String),
|
||||||
//TODO: InvalidAlias,
|
//TODO: InvalidAlias,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<AuthClientError> for RegisterError {
|
||||||
|
fn from(err: AuthClientError) -> Self { Self::AuthError(err.to_string()) }
|
||||||
|
}
|
||||||
|
|
||||||
impl ServerMsg {
|
impl ServerMsg {
|
||||||
pub fn chat(message: String) -> ServerMsg {
|
pub fn chat(message: String) -> ServerMsg {
|
||||||
ServerMsg::ChatMsg {
|
ServerMsg::ChatMsg {
|
||||||
|
@ -119,16 +119,16 @@ impl<S: PostMsg, R: PostMsg> PostBox<S, R> {
|
|||||||
|
|
||||||
pub fn send_message(&mut self, msg: S) { let _ = self.send_tx.send(msg); }
|
pub fn send_message(&mut self, msg: S) { let _ = self.send_tx.send(msg); }
|
||||||
|
|
||||||
pub fn next_message(&mut self) -> Option<R> {
|
pub fn next_message(&mut self) -> Result<R, Error> {
|
||||||
if self.error.is_some() {
|
if let Some(e) = self.error.clone() {
|
||||||
return None;
|
return Err(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.recv_rx.recv().ok()? {
|
match self.recv_rx.recv().map_err(|_| Error::ChannelFailure)? {
|
||||||
Ok(msg) => Some(msg),
|
Ok(msg) => Ok(msg),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.error = Some(e);
|
self.error = Some(e.clone());
|
||||||
None
|
Err(e)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -286,8 +286,35 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run RegionMap tick to update entity region occupancy
|
||||||
|
pub fn update_region_map(&self) {
|
||||||
|
self.ecs.write_resource::<RegionMap>().tick(
|
||||||
|
self.ecs.read_storage::<comp::Pos>(),
|
||||||
|
self.ecs.read_storage::<comp::Vel>(),
|
||||||
|
self.ecs.entities(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply terrain changes
|
||||||
|
pub fn apply_terrain_changes(&self) {
|
||||||
|
let mut terrain = self.ecs.write_resource::<TerrainGrid>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/// Execute a single tick, simulating the game state by the given duration.
|
/// Execute a single tick, simulating the game state by the given duration.
|
||||||
pub fn tick(&mut self, dt: Duration, add_foreign_systems: impl Fn(&mut DispatcherBuilder)) {
|
pub fn tick(
|
||||||
|
&mut self,
|
||||||
|
dt: Duration,
|
||||||
|
add_foreign_systems: impl Fn(&mut DispatcherBuilder),
|
||||||
|
update_terrain_and_regions: bool,
|
||||||
|
) {
|
||||||
// Change the time accordingly.
|
// Change the time accordingly.
|
||||||
self.ecs.write_resource::<TimeOfDay>().0 += dt.as_secs_f64() * DAY_CYCLE_FACTOR;
|
self.ecs.write_resource::<TimeOfDay>().0 += dt.as_secs_f64() * DAY_CYCLE_FACTOR;
|
||||||
self.ecs.write_resource::<Time>().0 += dt.as_secs_f64();
|
self.ecs.write_resource::<Time>().0 += dt.as_secs_f64();
|
||||||
@ -297,12 +324,9 @@ impl State {
|
|||||||
// important physics events.
|
// important physics events.
|
||||||
self.ecs.write_resource::<DeltaTime>().0 = dt.as_secs_f32().min(MAX_DELTA_TIME);
|
self.ecs.write_resource::<DeltaTime>().0 = dt.as_secs_f32().min(MAX_DELTA_TIME);
|
||||||
|
|
||||||
// Run RegionMap tick to update entity region occupancy
|
if update_terrain_and_regions {
|
||||||
self.ecs.write_resource::<RegionMap>().tick(
|
self.update_region_map();
|
||||||
self.ecs.read_storage::<comp::Pos>(),
|
}
|
||||||
self.ecs.read_storage::<comp::Vel>(),
|
|
||||||
self.ecs.entities(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Run systems to update the world.
|
// Run systems to update the world.
|
||||||
// Create and run a dispatcher for ecs systems.
|
// Create and run a dispatcher for ecs systems.
|
||||||
@ -315,16 +339,9 @@ impl State {
|
|||||||
|
|
||||||
self.ecs.maintain();
|
self.ecs.maintain();
|
||||||
|
|
||||||
// Apply terrain changes
|
if update_terrain_and_regions {
|
||||||
let mut terrain = self.ecs.write_resource::<TerrainGrid>();
|
self.apply_terrain_changes();
|
||||||
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
|
// Process local events
|
||||||
let events = self.ecs.read_resource::<EventBus<LocalEvent>>().recv_all();
|
let events = self.ecs.read_resource::<EventBus<LocalEvent>>().recv_all();
|
||||||
|
@ -153,7 +153,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
been_close,
|
been_close,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
if let (Some(tgt_pos), _tgt_stats, tgt_alignment) = (
|
if let (Some(tgt_pos), Some(tgt_stats), tgt_alignment) = (
|
||||||
positions.get(*target),
|
positions.get(*target),
|
||||||
stats.get(*target),
|
stats.get(*target),
|
||||||
alignments
|
alignments
|
||||||
@ -164,7 +164,8 @@ impl<'a> System<'a> for Sys {
|
|||||||
// Don't attack entities we are passive towards
|
// Don't attack entities we are passive towards
|
||||||
// TODO: This is here, it's a bit of a hack
|
// TODO: This is here, it's a bit of a hack
|
||||||
if let Some(alignment) = alignment {
|
if let Some(alignment) = alignment {
|
||||||
if (*alignment).passive_towards(tgt_alignment) {
|
if (*alignment).passive_towards(tgt_alignment) || tgt_stats.is_dead
|
||||||
|
{
|
||||||
do_idle = true;
|
do_idle = true;
|
||||||
break 'activity;
|
break 'activity;
|
||||||
}
|
}
|
||||||
|
8
server-cli/Dockerfile
Normal file
8
server-cli/Dockerfile
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
FROM debian:stable-slim
|
||||||
|
|
||||||
|
ARG PROJECTNAME=server-cli
|
||||||
|
|
||||||
|
COPY ./server-cli/docker-run.sh /opt/docker-run.sh
|
||||||
|
COPY ./veloren-server-cli /opt/veloren-server-cli
|
||||||
|
COPY ./assets/common /opt/assets/common
|
||||||
|
COPY ./assets/world /opt/assets/world
|
17
server-cli/docker-compose.yml
Normal file
17
server-cli/docker-compose.yml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
version: "3.7"
|
||||||
|
|
||||||
|
services:
|
||||||
|
game-server:
|
||||||
|
image: registry.gitlab.com/veloren/veloren:master-server
|
||||||
|
ports:
|
||||||
|
- "14004:14004"
|
||||||
|
- "14005:14005"
|
||||||
|
deploy:
|
||||||
|
replicas: 1
|
||||||
|
update_config:
|
||||||
|
parallelism: 2
|
||||||
|
delay: 10s
|
||||||
|
order: stop-first
|
||||||
|
failure_action: rollback
|
||||||
|
restart_policy:
|
||||||
|
condition: on-failure
|
3
server-cli/docker-run.sh
Executable file
3
server-cli/docker-run.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
cd /opt
|
||||||
|
RUST_LOG=info,common=debug,common::net=info RUST_BACKTRACE=1 /opt/veloren-server-cli
|
@ -31,3 +31,4 @@ prometheus = "0.7"
|
|||||||
prometheus-static-metric = "0.2"
|
prometheus-static-metric = "0.2"
|
||||||
rouille = "3.0.0"
|
rouille = "3.0.0"
|
||||||
portpicker = { git = "https://github.com/wusyong/portpicker-rs", branch = "fix_ipv6" }
|
portpicker = { git = "https://github.com/wusyong/portpicker-rs", branch = "fix_ipv6" }
|
||||||
|
authc = { git = "https://gitlab.com/veloren/auth.git", rev = "65571ade0d954a0e0bd995fdb314854ff146ab97" }
|
||||||
|
@ -1,32 +1,88 @@
|
|||||||
|
use authc::{AuthClient, AuthToken, Uuid};
|
||||||
|
use common::msg::RegisterError;
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use log::{info, warn};
|
use log::error;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
fn derive_uuid(username: &str) -> Uuid {
|
||||||
|
let mut state: [u8; 16] = [
|
||||||
|
52, 17, 19, 239, 52, 17, 19, 239, 52, 17, 19, 239, 52, 17, 19, 239,
|
||||||
|
];
|
||||||
|
for mix_byte_1 in username.as_bytes() {
|
||||||
|
for i in 0..16 {
|
||||||
|
let mix_byte_step: u8 = mix_byte_1
|
||||||
|
.wrapping_pow(239)
|
||||||
|
.wrapping_mul((i as u8).wrapping_pow(43));
|
||||||
|
let mix_byte_2 = state[(i + mix_byte_step as usize) % 16];
|
||||||
|
let rot_step: u8 = mix_byte_1
|
||||||
|
.wrapping_pow(29)
|
||||||
|
.wrapping_mul((i as u8).wrapping_pow(163));
|
||||||
|
state[i] = (state[i] ^ mix_byte_1)
|
||||||
|
.wrapping_mul(mix_byte_2)
|
||||||
|
.rotate_left(rot_step as u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Uuid::from_slice(&state).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
pub struct AuthProvider {
|
pub struct AuthProvider {
|
||||||
accounts: HashMap<String, String>,
|
accounts: HashMap<Uuid, String>,
|
||||||
|
auth_server: Option<AuthClient>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AuthProvider {
|
impl AuthProvider {
|
||||||
pub fn new() -> Self {
|
pub fn new(auth_addr: Option<String>) -> Self {
|
||||||
|
let auth_server = match auth_addr {
|
||||||
|
Some(addr) => Some(AuthClient::new(addr)),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
AuthProvider {
|
AuthProvider {
|
||||||
accounts: HashMap::new(),
|
accounts: HashMap::new(),
|
||||||
|
auth_server,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn query(&mut self, username: String, password: String) -> bool {
|
pub fn logout(&mut self, uuid: Uuid) {
|
||||||
let pwd = password.clone();
|
if self.accounts.remove(&uuid).is_none() {
|
||||||
if self.accounts.entry(username.clone()).or_insert_with(|| {
|
error!("Attempted to logout user that is not logged in.");
|
||||||
info!("Registered new user '{}'", &username);
|
};
|
||||||
pwd
|
}
|
||||||
}) == &password
|
|
||||||
{
|
pub fn query(&mut self, username_or_token: String) -> Result<(String, Uuid), RegisterError> {
|
||||||
info!("User '{}' successfully authenticated", username);
|
// Based on whether auth server is provided or not we expect an username or
|
||||||
true
|
// token
|
||||||
} else {
|
match &self.auth_server {
|
||||||
warn!(
|
// Token from auth server expected
|
||||||
"User '{}' attempted to log in with invalid password '{}'!",
|
Some(srv) => {
|
||||||
username, password
|
log::info!("Validating '{}' token.", &username_or_token);
|
||||||
);
|
// Parse token
|
||||||
false
|
let token = AuthToken::from_str(&username_or_token)
|
||||||
|
.map_err(|e| RegisterError::AuthError(e.to_string()))?;
|
||||||
|
// Validate token
|
||||||
|
let uuid = srv.validate(token)?;
|
||||||
|
// Check if already logged in
|
||||||
|
if self.accounts.contains_key(&uuid) {
|
||||||
|
return Err(RegisterError::AlreadyLoggedIn);
|
||||||
|
}
|
||||||
|
// Log in
|
||||||
|
let username = srv.uuid_to_username(uuid)?;
|
||||||
|
self.accounts.insert(uuid, username.clone());
|
||||||
|
Ok((username, uuid))
|
||||||
|
},
|
||||||
|
// Username is expected
|
||||||
|
None => {
|
||||||
|
// Assume username was provided
|
||||||
|
let username = username_or_token;
|
||||||
|
let uuid = derive_uuid(&username);
|
||||||
|
if !self.accounts.contains_key(&uuid) {
|
||||||
|
log::info!("New User '{}'", username);
|
||||||
|
self.accounts.insert(uuid, username.clone());
|
||||||
|
Ok((username, uuid))
|
||||||
|
} else {
|
||||||
|
Err(RegisterError::AlreadyLoggedIn)
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,11 +234,18 @@ lazy_static! {
|
|||||||
),
|
),
|
||||||
ChatCommand::new(
|
ChatCommand::new(
|
||||||
"give_exp",
|
"give_exp",
|
||||||
"{} {}",
|
"{d} {}",
|
||||||
"/give_exp <playername> <amount> : Give experience to specified player",
|
"/give_exp <amount> <playername?> : Give experience to yourself or specify a target player",
|
||||||
true,
|
true,
|
||||||
handle_exp,
|
handle_exp,
|
||||||
),
|
),
|
||||||
|
ChatCommand::new(
|
||||||
|
"set_level",
|
||||||
|
"{d} {}",
|
||||||
|
"/set_level <level> <playername?> : Set own Level or specify a target player",
|
||||||
|
true,
|
||||||
|
handle_level
|
||||||
|
),
|
||||||
ChatCommand::new(
|
ChatCommand::new(
|
||||||
"removelights",
|
"removelights",
|
||||||
"{}",
|
"{}",
|
||||||
@ -255,6 +262,7 @@ lazy_static! {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_give(server: &mut Server, entity: EcsEntity, args: String, _action: &ChatCommand) {
|
fn handle_give(server: &mut Server, entity: EcsEntity, args: String, _action: &ChatCommand) {
|
||||||
if let Ok(item) = assets::load_cloned(&args) {
|
if let Ok(item) = assets::load_cloned(&args) {
|
||||||
server
|
server
|
||||||
@ -267,7 +275,10 @@ fn handle_give(server: &mut Server, entity: EcsEntity, args: String, _action: &C
|
|||||||
.state
|
.state
|
||||||
.ecs()
|
.ecs()
|
||||||
.write_storage::<comp::InventoryUpdate>()
|
.write_storage::<comp::InventoryUpdate>()
|
||||||
.insert(entity, comp::InventoryUpdate);
|
.insert(
|
||||||
|
entity,
|
||||||
|
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Given),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
server.notify_client(entity, ServerMsg::private(String::from("Invalid item!")));
|
server.notify_client(entity, ServerMsg::private(String::from("Invalid item!")));
|
||||||
}
|
}
|
||||||
@ -632,7 +643,7 @@ fn handle_object(server: &mut Server, entity: EcsEntity, args: String, _action:
|
|||||||
.read_storage::<comp::Ori>()
|
.read_storage::<comp::Ori>()
|
||||||
.get(entity)
|
.get(entity)
|
||||||
.copied();
|
.copied();
|
||||||
/*let builder = server
|
/*let builder = server.state
|
||||||
.create_object(pos, ori, obj_type)
|
.create_object(pos, ori, obj_type)
|
||||||
.with(ori);*/
|
.with(ori);*/
|
||||||
if let (Some(pos), Some(ori)) = (pos, ori) {
|
if let (Some(pos), Some(ori)) = (pos, ori) {
|
||||||
@ -694,6 +705,7 @@ fn handle_object(server: &mut Server, entity: EcsEntity, args: String, _action:
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
server
|
server
|
||||||
|
.state
|
||||||
.create_object(pos, obj_type)
|
.create_object(pos, obj_type)
|
||||||
.with(comp::Ori(
|
.with(comp::Ori(
|
||||||
// converts player orientation into a 90° rotation for the object by using the axis
|
// converts player orientation into a 90° rotation for the object by using the axis
|
||||||
@ -1015,27 +1027,74 @@ spawn_rate {:?} "#,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_exp(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
|
fn find_target(
|
||||||
let (a_alias, a_exp) = scan_fmt_some!(&args, action.arg_fmt, String, i64);
|
ecs: &specs::World,
|
||||||
if let (Some(alias), Some(exp)) = (a_alias, a_exp) {
|
opt_alias: Option<String>,
|
||||||
let ecs = server.state.ecs_mut();
|
fallback: EcsEntity,
|
||||||
let opt_player = (&ecs.entities(), &ecs.read_storage::<comp::Player>())
|
) -> Result<EcsEntity, ServerMsg> {
|
||||||
|
if let Some(alias) = opt_alias {
|
||||||
|
(&ecs.entities(), &ecs.read_storage::<comp::Player>())
|
||||||
.join()
|
.join()
|
||||||
.find(|(_, player)| player.alias == alias)
|
.find(|(_, player)| player.alias == alias)
|
||||||
.map(|(entity, _)| entity);
|
.map(|(entity, _)| entity)
|
||||||
|
.ok_or(ServerMsg::private(format!("Player '{}' not found!", alias)))
|
||||||
|
} else {
|
||||||
|
Ok(fallback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_exp(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
|
||||||
|
let (a_exp, a_alias) = scan_fmt_some!(&args, action.arg_fmt, i64, String);
|
||||||
|
|
||||||
|
if let Some(exp) = a_exp {
|
||||||
|
let ecs = server.state.ecs_mut();
|
||||||
|
let target = find_target(&ecs, a_alias, entity);
|
||||||
|
|
||||||
let mut error_msg = None;
|
let mut error_msg = None;
|
||||||
|
|
||||||
match opt_player {
|
match target {
|
||||||
Some(_alias) => {
|
Ok(player) => {
|
||||||
if let Some(stats) = ecs.write_storage::<comp::Stats>().get_mut(entity) {
|
if let Some(stats) = ecs.write_storage::<comp::Stats>().get_mut(player) {
|
||||||
stats.exp.change_by(exp);
|
stats.exp.change_by(exp);
|
||||||
} else {
|
} else {
|
||||||
error_msg = Some(ServerMsg::private(String::from("Player has no stats!")));
|
error_msg = Some(ServerMsg::private(String::from("Player has no stats!")));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => {
|
Err(e) => {
|
||||||
error_msg = Some(ServerMsg::private(format!("Player '{}' not found!", alias)));
|
error_msg = Some(e);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(msg) = error_msg {
|
||||||
|
server.notify_client(entity, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_level(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
|
||||||
|
let (a_lvl, a_alias) = scan_fmt_some!(&args, action.arg_fmt, u32, String);
|
||||||
|
|
||||||
|
if let Some(lvl) = a_lvl {
|
||||||
|
let ecs = server.state.ecs_mut();
|
||||||
|
let target = find_target(&ecs, a_alias, entity);
|
||||||
|
|
||||||
|
let mut error_msg = None;
|
||||||
|
|
||||||
|
match target {
|
||||||
|
Ok(player) => {
|
||||||
|
if let Some(stats) = ecs.write_storage::<comp::Stats>().get_mut(player) {
|
||||||
|
stats.level.set_level(lvl);
|
||||||
|
|
||||||
|
stats.update_max_hp();
|
||||||
|
stats
|
||||||
|
.health
|
||||||
|
.set_to(stats.health.maximum(), comp::HealthSource::LevelUp);
|
||||||
|
} else {
|
||||||
|
error_msg = Some(ServerMsg::private(String::from("Player has no stats!")));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
error_msg = Some(e);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1059,7 +1118,10 @@ fn handle_debug(server: &mut Server, entity: EcsEntity, _args: String, _action:
|
|||||||
.state
|
.state
|
||||||
.ecs()
|
.ecs()
|
||||||
.write_storage::<comp::InventoryUpdate>()
|
.write_storage::<comp::InventoryUpdate>()
|
||||||
.insert(entity, comp::InventoryUpdate);
|
.insert(
|
||||||
|
entity,
|
||||||
|
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Debug),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
server.notify_client(
|
server.notify_client(
|
||||||
entity,
|
entity,
|
||||||
|
84
server/src/events/entity_creation.rs
Normal file
84
server/src/events/entity_creation.rs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
use crate::{sys, Server, StateExt};
|
||||||
|
use common::comp::{
|
||||||
|
self, Agent, Alignment, Body, Gravity, LightEmitter, Pos, Projectile, Scale, Stats, Vel,
|
||||||
|
WaypointArea,
|
||||||
|
};
|
||||||
|
use specs::{Builder, Entity as EcsEntity, WorldExt};
|
||||||
|
use vek::{Rgb, Vec3};
|
||||||
|
|
||||||
|
pub fn handle_create_character(
|
||||||
|
server: &mut Server,
|
||||||
|
entity: EcsEntity,
|
||||||
|
name: String,
|
||||||
|
body: Body,
|
||||||
|
main: Option<String>,
|
||||||
|
) {
|
||||||
|
let state = &mut server.state;
|
||||||
|
let server_settings = &server.server_settings;
|
||||||
|
|
||||||
|
state.create_player_character(entity, name, body, main, server_settings);
|
||||||
|
sys::subscription::initialize_region_subscription(state.ecs(), entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_create_npc(
|
||||||
|
server: &mut Server,
|
||||||
|
pos: Pos,
|
||||||
|
stats: Stats,
|
||||||
|
body: Body,
|
||||||
|
agent: Agent,
|
||||||
|
alignment: Alignment,
|
||||||
|
scale: Scale,
|
||||||
|
) {
|
||||||
|
server
|
||||||
|
.state
|
||||||
|
.create_npc(pos, stats, body)
|
||||||
|
.with(agent)
|
||||||
|
.with(scale)
|
||||||
|
.with(alignment)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_shoot(
|
||||||
|
server: &mut Server,
|
||||||
|
entity: EcsEntity,
|
||||||
|
dir: Vec3<f32>,
|
||||||
|
body: Body,
|
||||||
|
light: Option<LightEmitter>,
|
||||||
|
projectile: Projectile,
|
||||||
|
gravity: Option<Gravity>,
|
||||||
|
) {
|
||||||
|
let state = server.state_mut();
|
||||||
|
|
||||||
|
let mut pos = state
|
||||||
|
.ecs()
|
||||||
|
.read_storage::<Pos>()
|
||||||
|
.get(entity)
|
||||||
|
.expect("Failed to fetch entity")
|
||||||
|
.0;
|
||||||
|
|
||||||
|
// TODO: Player height
|
||||||
|
pos.z += 1.2;
|
||||||
|
|
||||||
|
let mut builder = state.create_projectile(Pos(pos), Vel(dir * 100.0), body, projectile);
|
||||||
|
if let Some(light) = light {
|
||||||
|
builder = builder.with(light)
|
||||||
|
}
|
||||||
|
if let Some(gravity) = gravity {
|
||||||
|
builder = builder.with(gravity)
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_create_waypoint(server: &mut Server, pos: Vec3<f32>) {
|
||||||
|
server
|
||||||
|
.state
|
||||||
|
.create_object(Pos(pos), comp::object::Body::CampfireLit)
|
||||||
|
.with(LightEmitter {
|
||||||
|
offset: Vec3::unit_z() * 0.5,
|
||||||
|
col: Rgb::new(1.0, 0.65, 0.2),
|
||||||
|
strength: 2.0,
|
||||||
|
})
|
||||||
|
.with(WaypointArea::default())
|
||||||
|
.build();
|
||||||
|
}
|
175
server/src/events/entity_manipulation.rs
Normal file
175
server/src/events/entity_manipulation.rs
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
use crate::{client::Client, Server, SpawnPoint, StateExt};
|
||||||
|
use common::{
|
||||||
|
comp::{self, HealthChange, HealthSource, Player, Stats},
|
||||||
|
msg::ServerMsg,
|
||||||
|
state::BlockChange,
|
||||||
|
sync::{Uid, WorldSyncExt},
|
||||||
|
terrain::{Block, TerrainGrid},
|
||||||
|
vol::{ReadVol, Vox},
|
||||||
|
};
|
||||||
|
use log::error;
|
||||||
|
use specs::{Entity as EcsEntity, WorldExt};
|
||||||
|
use vek::Vec3;
|
||||||
|
|
||||||
|
pub fn handle_damage(server: &Server, uid: Uid, change: HealthChange) {
|
||||||
|
let state = &server.state;
|
||||||
|
let ecs = state.ecs();
|
||||||
|
if let Some(entity) = ecs.entity_from_uid(uid.into()) {
|
||||||
|
if let Some(stats) = ecs.write_storage::<Stats>().get_mut(entity) {
|
||||||
|
stats.health.change_by(change);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSource) {
|
||||||
|
let state = server.state_mut();
|
||||||
|
|
||||||
|
// Chat message
|
||||||
|
if let Some(player) = state.ecs().read_storage::<Player>().get(entity) {
|
||||||
|
let msg = if let HealthSource::Attack { by } = cause {
|
||||||
|
state.ecs().entity_from_uid(by.into()).and_then(|attacker| {
|
||||||
|
state
|
||||||
|
.ecs()
|
||||||
|
.read_storage::<Player>()
|
||||||
|
.get(attacker)
|
||||||
|
.map(|attacker_alias| {
|
||||||
|
format!("{} was killed by {}", &player.alias, &attacker_alias.alias)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
.unwrap_or(format!("{} died", &player.alias));
|
||||||
|
|
||||||
|
state.notify_registered_clients(ServerMsg::kill(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Give EXP to the killer if entity had stats
|
||||||
|
let mut stats = state.ecs().write_storage::<Stats>();
|
||||||
|
if let Some(entity_stats) = stats.get(entity).cloned() {
|
||||||
|
if let HealthSource::Attack { by } = cause {
|
||||||
|
state.ecs().entity_from_uid(by.into()).map(|attacker| {
|
||||||
|
if let Some(attacker_stats) = stats.get_mut(attacker) {
|
||||||
|
// TODO: Discuss whether we should give EXP by Player
|
||||||
|
// Killing or not.
|
||||||
|
attacker_stats
|
||||||
|
.exp
|
||||||
|
.change_by((entity_stats.level.level() * 10) as i64);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if state
|
||||||
|
.ecs()
|
||||||
|
.write_storage::<Client>()
|
||||||
|
.get_mut(entity)
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
state
|
||||||
|
.ecs()
|
||||||
|
.write_storage()
|
||||||
|
.insert(entity, comp::Vel(Vec3::zero()))
|
||||||
|
.err()
|
||||||
|
.map(|err| error!("Failed to set zero vel on dead client: {:?}", err));
|
||||||
|
state
|
||||||
|
.ecs()
|
||||||
|
.write_storage()
|
||||||
|
.insert(entity, comp::ForceUpdate)
|
||||||
|
.err()
|
||||||
|
.map(|err| error!("Failed to insert ForceUpdate on dead client: {:?}", err));
|
||||||
|
state
|
||||||
|
.ecs()
|
||||||
|
.write_storage::<comp::Energy>()
|
||||||
|
.get_mut(entity)
|
||||||
|
.map(|energy| energy.set_to(energy.maximum(), comp::EnergySource::Revive));
|
||||||
|
let _ = state
|
||||||
|
.ecs()
|
||||||
|
.write_storage::<comp::CharacterState>()
|
||||||
|
.insert(entity, comp::CharacterState::default());
|
||||||
|
} else {
|
||||||
|
// If not a player delete the entity
|
||||||
|
if let Err(err) = state.delete_entity_recorded(entity) {
|
||||||
|
error!("Failed to delete destroyed entity: {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>) {
|
||||||
|
let state = &server.state;
|
||||||
|
if vel.z <= -37.0 {
|
||||||
|
if let Some(stats) = state.ecs().write_storage::<comp::Stats>().get_mut(entity) {
|
||||||
|
let falldmg = (vel.z / 2.5) as i32;
|
||||||
|
if falldmg < 0 {
|
||||||
|
stats.health.change_by(comp::HealthChange {
|
||||||
|
amount: falldmg,
|
||||||
|
cause: comp::HealthSource::World,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_respawn(server: &Server, entity: EcsEntity) {
|
||||||
|
let state = &server.state;
|
||||||
|
|
||||||
|
// Only clients can respawn
|
||||||
|
if state
|
||||||
|
.ecs()
|
||||||
|
.write_storage::<Client>()
|
||||||
|
.get_mut(entity)
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
let respawn_point = state
|
||||||
|
.read_component_cloned::<comp::Waypoint>(entity)
|
||||||
|
.map(|wp| wp.get_pos())
|
||||||
|
.unwrap_or(state.ecs().read_resource::<SpawnPoint>().0);
|
||||||
|
|
||||||
|
state
|
||||||
|
.ecs()
|
||||||
|
.write_storage::<comp::Stats>()
|
||||||
|
.get_mut(entity)
|
||||||
|
.map(|stats| stats.revive());
|
||||||
|
state
|
||||||
|
.ecs()
|
||||||
|
.write_storage::<comp::Pos>()
|
||||||
|
.get_mut(entity)
|
||||||
|
.map(|pos| pos.0 = respawn_point);
|
||||||
|
state
|
||||||
|
.ecs()
|
||||||
|
.write_storage()
|
||||||
|
.insert(entity, comp::ForceUpdate)
|
||||||
|
.err()
|
||||||
|
.map(|err| {
|
||||||
|
error!(
|
||||||
|
"Error inserting ForceUpdate component when respawning client: {:?}",
|
||||||
|
err
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_explosion(server: &Server, pos: Vec3<f32>, radius: f32) {
|
||||||
|
const RAYS: usize = 500;
|
||||||
|
|
||||||
|
for _ in 0..RAYS {
|
||||||
|
let dir = Vec3::new(
|
||||||
|
rand::random::<f32>() - 0.5,
|
||||||
|
rand::random::<f32>() - 0.5,
|
||||||
|
rand::random::<f32>() - 0.5,
|
||||||
|
)
|
||||||
|
.normalized();
|
||||||
|
|
||||||
|
let ecs = server.state.ecs();
|
||||||
|
let mut block_change = ecs.write_resource::<BlockChange>();
|
||||||
|
|
||||||
|
let _ = ecs
|
||||||
|
.read_resource::<TerrainGrid>()
|
||||||
|
.ray(pos, pos + dir * radius)
|
||||||
|
.until(|_| rand::random::<f32>() < 0.05)
|
||||||
|
.for_each(|pos| block_change.set(pos, Block::empty()))
|
||||||
|
.cast();
|
||||||
|
}
|
||||||
|
}
|
174
server/src/events/interaction.rs
Normal file
174
server/src/events/interaction.rs
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
use crate::{
|
||||||
|
client::{Client, RegionSubscription},
|
||||||
|
Server,
|
||||||
|
};
|
||||||
|
use common::{
|
||||||
|
assets, comp,
|
||||||
|
msg::ServerMsg,
|
||||||
|
sync::{Uid, WorldSyncExt},
|
||||||
|
};
|
||||||
|
use log::error;
|
||||||
|
use specs::{world::WorldExt, Entity as EcsEntity};
|
||||||
|
|
||||||
|
pub fn handle_mount(server: &mut Server, mounter: EcsEntity, mountee: EcsEntity) {
|
||||||
|
let state = server.state_mut();
|
||||||
|
|
||||||
|
if state
|
||||||
|
.ecs()
|
||||||
|
.read_storage::<comp::Mounting>()
|
||||||
|
.get(mounter)
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
let not_mounting_yet = if let Some(comp::MountState::Unmounted) = state
|
||||||
|
.ecs()
|
||||||
|
.read_storage::<comp::MountState>()
|
||||||
|
.get(mountee)
|
||||||
|
.cloned()
|
||||||
|
{
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
if not_mounting_yet {
|
||||||
|
if let (Some(mounter_uid), Some(mountee_uid)) = (
|
||||||
|
state.ecs().uid_from_entity(mounter),
|
||||||
|
state.ecs().uid_from_entity(mountee),
|
||||||
|
) {
|
||||||
|
state.write_component(mountee, comp::MountState::MountedBy(mounter_uid.into()));
|
||||||
|
state.write_component(mounter, comp::Mounting(mountee_uid.into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_unmount(server: &mut Server, mounter: EcsEntity) {
|
||||||
|
let state = server.state_mut();
|
||||||
|
let mountee_entity = state
|
||||||
|
.ecs()
|
||||||
|
.write_storage::<comp::Mounting>()
|
||||||
|
.get(mounter)
|
||||||
|
.and_then(|mountee| state.ecs().entity_from_uid(mountee.0.into()));
|
||||||
|
if let Some(mountee_entity) = mountee_entity {
|
||||||
|
state
|
||||||
|
.ecs()
|
||||||
|
.write_storage::<comp::MountState>()
|
||||||
|
.get_mut(mountee_entity)
|
||||||
|
.map(|ms| *ms = comp::MountState::Unmounted);
|
||||||
|
}
|
||||||
|
state.delete_component::<comp::Mounting>(mounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_possess(server: &Server, possessor_uid: Uid, possesse_uid: Uid) {
|
||||||
|
let state = &server.state;
|
||||||
|
let ecs = state.ecs();
|
||||||
|
if let (Some(possessor), Some(possesse)) = (
|
||||||
|
ecs.entity_from_uid(possessor_uid.into()),
|
||||||
|
ecs.entity_from_uid(possesse_uid.into()),
|
||||||
|
) {
|
||||||
|
// You can't possess other players
|
||||||
|
let mut clients = ecs.write_storage::<Client>();
|
||||||
|
if clients.get_mut(possesse).is_none() {
|
||||||
|
if let Some(mut client) = clients.remove(possessor) {
|
||||||
|
client.notify(ServerMsg::SetPlayerEntity(possesse_uid.into()));
|
||||||
|
clients.insert(possesse, client).err().map(|e| {
|
||||||
|
error!(
|
||||||
|
"Error inserting client component during possession: {:?}",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
});
|
||||||
|
// Create inventory if it doesn't exist
|
||||||
|
{
|
||||||
|
let mut inventories = ecs.write_storage::<comp::Inventory>();
|
||||||
|
if let Some(inventory) = inventories.get_mut(possesse) {
|
||||||
|
inventory.push(assets::load_expect_cloned("common.items.debug.possess"));
|
||||||
|
} else {
|
||||||
|
inventories
|
||||||
|
.insert(possesse, comp::Inventory {
|
||||||
|
slots: vec![
|
||||||
|
Some(assets::load_expect_cloned("common.items.debug.possess")),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.err()
|
||||||
|
.map(|e| {
|
||||||
|
error!(
|
||||||
|
"Error inserting inventory component during possession: {:?}",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ecs.write_storage::<comp::InventoryUpdate>()
|
||||||
|
.insert(
|
||||||
|
possesse,
|
||||||
|
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Possession),
|
||||||
|
)
|
||||||
|
.err()
|
||||||
|
.map(|e| {
|
||||||
|
error!(
|
||||||
|
"Error inserting inventory update component during possession: {:?}",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
});
|
||||||
|
// Move player component
|
||||||
|
{
|
||||||
|
let mut players = ecs.write_storage::<comp::Player>();
|
||||||
|
if let Some(player) = players.remove(possessor) {
|
||||||
|
players.insert(possesse, player).err().map(|e| {
|
||||||
|
error!(
|
||||||
|
"Error inserting player component during possession: {:?}",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Transfer region subscription
|
||||||
|
{
|
||||||
|
let mut subscriptions = ecs.write_storage::<RegionSubscription>();
|
||||||
|
if let Some(s) = subscriptions.remove(possessor) {
|
||||||
|
subscriptions.insert(possesse, s).err().map(|e| {
|
||||||
|
error!(
|
||||||
|
"Error inserting subscription component during possession: {:?}",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Remove will of the entity
|
||||||
|
ecs.write_storage::<comp::Agent>().remove(possesse);
|
||||||
|
// Reset controller of former shell
|
||||||
|
ecs.write_storage::<comp::Controller>()
|
||||||
|
.get_mut(possessor)
|
||||||
|
.map(|c| c.reset());
|
||||||
|
// Transfer admin powers
|
||||||
|
{
|
||||||
|
let mut admins = ecs.write_storage::<comp::Admin>();
|
||||||
|
if let Some(admin) = admins.remove(possessor) {
|
||||||
|
admins.insert(possesse, admin).err().map(|e| {
|
||||||
|
error!("Error inserting admin component during possession: {:?}", e)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Transfer waypoint
|
||||||
|
{
|
||||||
|
let mut waypoints = ecs.write_storage::<comp::Waypoint>();
|
||||||
|
if let Some(waypoint) = waypoints.remove(possessor) {
|
||||||
|
waypoints.insert(possesse, waypoint).err().map(|e| {
|
||||||
|
error!(
|
||||||
|
"Error inserting waypoint component during possession {:?}",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
280
server/src/events/inventory_manip.rs
Normal file
280
server/src/events/inventory_manip.rs
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
use crate::{Server, StateExt};
|
||||||
|
use common::{
|
||||||
|
comp::{self, Pos, MAX_PICKUP_RANGE_SQR},
|
||||||
|
sync::WorldSyncExt,
|
||||||
|
terrain::block::Block,
|
||||||
|
vol::{ReadVol, Vox},
|
||||||
|
};
|
||||||
|
use log::error;
|
||||||
|
use rand::Rng;
|
||||||
|
use specs::{join::Join, world::WorldExt, Builder, Entity as EcsEntity};
|
||||||
|
use vek::Vec3;
|
||||||
|
|
||||||
|
pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::InventoryManip) {
|
||||||
|
let state = server.state_mut();
|
||||||
|
let mut dropped_items = Vec::new();
|
||||||
|
|
||||||
|
match manip {
|
||||||
|
comp::InventoryManip::Pickup(uid) => {
|
||||||
|
let item_entity = if let (Some((item, item_entity)), Some(inv)) = (
|
||||||
|
state
|
||||||
|
.ecs()
|
||||||
|
.entity_from_uid(uid.into())
|
||||||
|
.and_then(|item_entity| {
|
||||||
|
state
|
||||||
|
.ecs()
|
||||||
|
.write_storage::<comp::Item>()
|
||||||
|
.get_mut(item_entity)
|
||||||
|
.map(|item| (item.clone(), item_entity))
|
||||||
|
}),
|
||||||
|
state
|
||||||
|
.ecs()
|
||||||
|
.write_storage::<comp::Inventory>()
|
||||||
|
.get_mut(entity),
|
||||||
|
) {
|
||||||
|
if within_pickup_range(
|
||||||
|
state.ecs().read_storage::<comp::Pos>().get(entity),
|
||||||
|
state.ecs().read_storage::<comp::Pos>().get(item_entity),
|
||||||
|
) && inv.push(item).is_none()
|
||||||
|
{
|
||||||
|
Some(item_entity)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(item_entity) = item_entity {
|
||||||
|
if let Err(err) = state.delete_entity_recorded(item_entity) {
|
||||||
|
error!("Failed to delete picked up item entity: {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.write_component(
|
||||||
|
entity,
|
||||||
|
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Collected),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
comp::InventoryManip::Collect(pos) => {
|
||||||
|
let block = state.terrain().get(pos).ok().copied();
|
||||||
|
|
||||||
|
if let Some(block) = block {
|
||||||
|
let has_inv_space = state
|
||||||
|
.ecs()
|
||||||
|
.read_storage::<comp::Inventory>()
|
||||||
|
.get(entity)
|
||||||
|
.map(|inv| !inv.is_full())
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
if !has_inv_space {
|
||||||
|
state.write_component(
|
||||||
|
entity,
|
||||||
|
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::CollectFailed),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if block.is_collectible() && state.try_set_block(pos, Block::empty()).is_some()
|
||||||
|
{
|
||||||
|
comp::Item::try_reclaim_from_block(block)
|
||||||
|
.map(|item| state.give_item(entity, item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
comp::InventoryManip::Use(slot) => {
|
||||||
|
let item_opt = state
|
||||||
|
.ecs()
|
||||||
|
.write_storage::<comp::Inventory>()
|
||||||
|
.get_mut(entity)
|
||||||
|
.and_then(|inv| inv.remove(slot));
|
||||||
|
|
||||||
|
let mut event = comp::InventoryUpdateEvent::Used;
|
||||||
|
|
||||||
|
if let Some(item) = item_opt {
|
||||||
|
match item.kind {
|
||||||
|
comp::ItemKind::Tool { .. } => {
|
||||||
|
if let Some(stats) =
|
||||||
|
state.ecs().write_storage::<comp::Stats>().get_mut(entity)
|
||||||
|
{
|
||||||
|
// Insert old item into inventory
|
||||||
|
if let Some(old_item) = stats.equipment.main.take() {
|
||||||
|
state
|
||||||
|
.ecs()
|
||||||
|
.write_storage::<comp::Inventory>()
|
||||||
|
.get_mut(entity)
|
||||||
|
.map(|inv| inv.insert(slot, old_item));
|
||||||
|
}
|
||||||
|
|
||||||
|
stats.equipment.main = Some(item);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
comp::ItemKind::Consumable { kind, effect } => {
|
||||||
|
event = comp::InventoryUpdateEvent::Consumed(kind);
|
||||||
|
state.apply_effect(entity, effect);
|
||||||
|
},
|
||||||
|
comp::ItemKind::Utility { kind } => match kind {
|
||||||
|
comp::item::Utility::Collar => {
|
||||||
|
let reinsert = if let Some(pos) =
|
||||||
|
state.read_storage::<comp::Pos>().get(entity)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
&state.read_storage::<comp::Alignment>(),
|
||||||
|
&state.read_storage::<comp::Agent>(),
|
||||||
|
)
|
||||||
|
.join()
|
||||||
|
.filter(|(alignment, _)| {
|
||||||
|
alignment == &&comp::Alignment::Owned(entity)
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
>= 3
|
||||||
|
{
|
||||||
|
true
|
||||||
|
} else if let Some(tameable_entity) = {
|
||||||
|
let nearest_tameable = (
|
||||||
|
&state.ecs().entities(),
|
||||||
|
&state.ecs().read_storage::<comp::Pos>(),
|
||||||
|
&state.ecs().read_storage::<comp::Alignment>(),
|
||||||
|
)
|
||||||
|
.join()
|
||||||
|
.filter(|(_, wild_pos, _)| {
|
||||||
|
wild_pos.0.distance_squared(pos.0) < 5.0f32.powf(2.0)
|
||||||
|
})
|
||||||
|
.filter(|(_, _, alignment)| {
|
||||||
|
alignment == &&comp::Alignment::Wild
|
||||||
|
})
|
||||||
|
.min_by_key(|(_, wild_pos, _)| {
|
||||||
|
(wild_pos.0.distance_squared(pos.0) * 100.0) as i32
|
||||||
|
})
|
||||||
|
.map(|(entity, _, _)| entity);
|
||||||
|
nearest_tameable
|
||||||
|
} {
|
||||||
|
let _ = state
|
||||||
|
.ecs()
|
||||||
|
.write_storage()
|
||||||
|
.insert(tameable_entity, comp::Alignment::Owned(entity));
|
||||||
|
let _ = state
|
||||||
|
.ecs()
|
||||||
|
.write_storage()
|
||||||
|
.insert(tameable_entity, comp::Agent::default());
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
};
|
||||||
|
|
||||||
|
if reinsert {
|
||||||
|
let _ = state
|
||||||
|
.ecs()
|
||||||
|
.write_storage::<comp::Inventory>()
|
||||||
|
.get_mut(entity)
|
||||||
|
.map(|inv| inv.insert(slot, item));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
let _ = state
|
||||||
|
.ecs()
|
||||||
|
.write_storage::<comp::Inventory>()
|
||||||
|
.get_mut(entity)
|
||||||
|
.map(|inv| inv.insert(slot, item));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.write_component(entity, comp::InventoryUpdate::new(event));
|
||||||
|
},
|
||||||
|
|
||||||
|
comp::InventoryManip::Swap(a, b) => {
|
||||||
|
state
|
||||||
|
.ecs()
|
||||||
|
.write_storage::<comp::Inventory>()
|
||||||
|
.get_mut(entity)
|
||||||
|
.map(|inv| inv.swap_slots(a, b));
|
||||||
|
state.write_component(
|
||||||
|
entity,
|
||||||
|
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Swapped),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
comp::InventoryManip::Drop(slot) => {
|
||||||
|
let item = state
|
||||||
|
.ecs()
|
||||||
|
.write_storage::<comp::Inventory>()
|
||||||
|
.get_mut(entity)
|
||||||
|
.and_then(|inv| inv.remove(slot));
|
||||||
|
|
||||||
|
if let (Some(item), Some(pos)) =
|
||||||
|
(item, state.ecs().read_storage::<comp::Pos>().get(entity))
|
||||||
|
{
|
||||||
|
dropped_items.push((
|
||||||
|
*pos,
|
||||||
|
state
|
||||||
|
.ecs()
|
||||||
|
.read_storage::<comp::Ori>()
|
||||||
|
.get(entity)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(comp::Ori(Vec3::unit_y())),
|
||||||
|
item,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
state.write_component(
|
||||||
|
entity,
|
||||||
|
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Dropped),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop items
|
||||||
|
for (pos, ori, item) in dropped_items {
|
||||||
|
let vel = ori.0.normalized() * 5.0
|
||||||
|
+ Vec3::unit_z() * 10.0
|
||||||
|
+ Vec3::<f32>::zero().map(|_| rand::thread_rng().gen::<f32>() - 0.5) * 4.0;
|
||||||
|
|
||||||
|
state
|
||||||
|
.create_object(Default::default(), comp::object::Body::Pouch)
|
||||||
|
.with(comp::Pos(pos.0 + Vec3::unit_z() * 0.25))
|
||||||
|
.with(item)
|
||||||
|
.with(comp::Vel(vel))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn within_pickup_range(player_position: Option<&Pos>, item_position: Option<&Pos>) -> bool {
|
||||||
|
match (player_position, item_position) {
|
||||||
|
(Some(ppos), Some(ipos)) => ppos.0.distance_squared(ipos.0) < MAX_PICKUP_RANGE_SQR,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use common::comp::Pos;
|
||||||
|
use vek::Vec3;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pickup_distance_within_range() {
|
||||||
|
let player_position = Pos(Vec3::zero());
|
||||||
|
let item_position = Pos(Vec3::one());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
within_pickup_range(Some(&player_position), Some(&item_position)),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pickup_distance_not_within_range() {
|
||||||
|
let player_position = Pos(Vec3::zero());
|
||||||
|
let item_position = Pos(Vec3::one() * 500.0);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
within_pickup_range(Some(&player_position), Some(&item_position)),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
109
server/src/events/mod.rs
Normal file
109
server/src/events/mod.rs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
use crate::Server;
|
||||||
|
use common::event::{EventBus, ServerEvent};
|
||||||
|
use entity_creation::{
|
||||||
|
handle_create_character, handle_create_npc, handle_create_waypoint, handle_shoot,
|
||||||
|
};
|
||||||
|
use entity_manipulation::{
|
||||||
|
handle_damage, handle_destroy, handle_explosion, handle_land_on_ground, handle_respawn,
|
||||||
|
};
|
||||||
|
use interaction::{handle_mount, handle_possess, handle_unmount};
|
||||||
|
use inventory_manip::handle_inventory;
|
||||||
|
use player::{handle_client_disconnect, handle_exit_ingame};
|
||||||
|
use specs::{Entity as EcsEntity, WorldExt};
|
||||||
|
|
||||||
|
mod entity_creation;
|
||||||
|
mod entity_manipulation;
|
||||||
|
mod interaction;
|
||||||
|
mod inventory_manip;
|
||||||
|
mod player;
|
||||||
|
|
||||||
|
pub enum Event {
|
||||||
|
ClientConnected {
|
||||||
|
entity: EcsEntity,
|
||||||
|
},
|
||||||
|
ClientDisconnected {
|
||||||
|
entity: EcsEntity,
|
||||||
|
},
|
||||||
|
Chat {
|
||||||
|
entity: Option<EcsEntity>,
|
||||||
|
msg: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Server {
|
||||||
|
pub fn handle_events(&mut self) -> Vec<Event> {
|
||||||
|
let mut frontend_events = Vec::new();
|
||||||
|
|
||||||
|
let mut requested_chunks = Vec::new();
|
||||||
|
let mut chat_commands = Vec::new();
|
||||||
|
|
||||||
|
let events = self
|
||||||
|
.state
|
||||||
|
.ecs()
|
||||||
|
.read_resource::<EventBus<ServerEvent>>()
|
||||||
|
.recv_all();
|
||||||
|
|
||||||
|
for event in events {
|
||||||
|
match event {
|
||||||
|
ServerEvent::Explosion { pos, radius } => handle_explosion(&self, pos, radius),
|
||||||
|
ServerEvent::Shoot {
|
||||||
|
entity,
|
||||||
|
dir,
|
||||||
|
body,
|
||||||
|
light,
|
||||||
|
projectile,
|
||||||
|
gravity,
|
||||||
|
} => handle_shoot(self, entity, dir, body, light, projectile, gravity),
|
||||||
|
ServerEvent::Damage { uid, change } => handle_damage(&self, uid, change),
|
||||||
|
ServerEvent::Destroy { entity, cause } => handle_destroy(self, entity, cause),
|
||||||
|
ServerEvent::InventoryManip(entity, manip) => handle_inventory(self, entity, manip),
|
||||||
|
ServerEvent::Respawn(entity) => handle_respawn(&self, entity),
|
||||||
|
ServerEvent::LandOnGround { entity, vel } => {
|
||||||
|
handle_land_on_ground(&self, entity, vel)
|
||||||
|
},
|
||||||
|
ServerEvent::Mount(mounter, mountee) => handle_mount(self, mounter, mountee),
|
||||||
|
ServerEvent::Unmount(mounter) => handle_unmount(self, mounter),
|
||||||
|
ServerEvent::Possess(possessor_uid, possesse_uid) => {
|
||||||
|
handle_possess(&self, possessor_uid, possesse_uid)
|
||||||
|
},
|
||||||
|
ServerEvent::CreateCharacter {
|
||||||
|
entity,
|
||||||
|
name,
|
||||||
|
body,
|
||||||
|
main,
|
||||||
|
} => handle_create_character(self, entity, name, body, main),
|
||||||
|
ServerEvent::ExitIngame { entity } => handle_exit_ingame(self, entity),
|
||||||
|
ServerEvent::CreateNpc {
|
||||||
|
pos,
|
||||||
|
stats,
|
||||||
|
body,
|
||||||
|
agent,
|
||||||
|
alignment,
|
||||||
|
scale,
|
||||||
|
} => handle_create_npc(self, pos, stats, body, agent, alignment, scale),
|
||||||
|
ServerEvent::CreateWaypoint(pos) => handle_create_waypoint(self, pos),
|
||||||
|
ServerEvent::ClientDisconnect(entity) => {
|
||||||
|
frontend_events.push(handle_client_disconnect(self, entity))
|
||||||
|
},
|
||||||
|
|
||||||
|
ServerEvent::ChunkRequest(entity, key) => {
|
||||||
|
requested_chunks.push((entity, key));
|
||||||
|
},
|
||||||
|
ServerEvent::ChatCmd(entity, cmd) => {
|
||||||
|
chat_commands.push((entity, cmd));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate requested chunks.
|
||||||
|
for (entity, key) in requested_chunks {
|
||||||
|
self.generate_chunk(entity, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (entity, cmd) in chat_commands {
|
||||||
|
self.process_chat_cmd(entity, cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
frontend_events
|
||||||
|
}
|
||||||
|
}
|
77
server/src/events/player.rs
Normal file
77
server/src/events/player.rs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
use super::Event;
|
||||||
|
use crate::{auth_provider::AuthProvider, client::Client, state_ext::StateExt, Server};
|
||||||
|
use common::{
|
||||||
|
comp,
|
||||||
|
comp::Player,
|
||||||
|
msg::{ClientState, PlayerListUpdate, ServerMsg},
|
||||||
|
sync::{Uid, UidAllocator},
|
||||||
|
};
|
||||||
|
use log::error;
|
||||||
|
use specs::{saveload::MarkerAllocator, Builder, Entity as EcsEntity, Join, WorldExt};
|
||||||
|
|
||||||
|
pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) {
|
||||||
|
let state = server.state_mut();
|
||||||
|
|
||||||
|
// Create new entity with just `Client`, `Uid`, and `Player` components
|
||||||
|
// Easier than checking and removing all other known components
|
||||||
|
// Note: If other `ServerEvent`s are referring to this entity they will be
|
||||||
|
// disrupted
|
||||||
|
let maybe_client = state.ecs().write_storage::<Client>().remove(entity);
|
||||||
|
let maybe_uid = state.read_component_cloned::<Uid>(entity);
|
||||||
|
let maybe_player = state.ecs().write_storage::<comp::Player>().remove(entity);
|
||||||
|
if let (Some(mut client), Some(uid), Some(player)) = (maybe_client, maybe_uid, maybe_player) {
|
||||||
|
// Tell client its request was successful
|
||||||
|
client.allow_state(ClientState::Registered);
|
||||||
|
// Tell client to clear out other entities and its own components
|
||||||
|
client.notify(ServerMsg::ExitIngameCleanup);
|
||||||
|
|
||||||
|
let entity_builder = state.ecs_mut().create_entity().with(client).with(player);
|
||||||
|
// Ensure UidAllocator maps this uid to the new entity
|
||||||
|
let uid = entity_builder
|
||||||
|
.world
|
||||||
|
.write_resource::<UidAllocator>()
|
||||||
|
.allocate(entity_builder.entity, Some(uid.into()));
|
||||||
|
entity_builder.with(uid).build();
|
||||||
|
}
|
||||||
|
// Delete old entity
|
||||||
|
if let Err(err) = state.delete_entity_recorded(entity) {
|
||||||
|
error!("Failed to delete entity when removing character: {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event {
|
||||||
|
let state = server.state_mut();
|
||||||
|
|
||||||
|
// Tell other clients to remove from player list
|
||||||
|
if let (Some(uid), Some(_)) = (
|
||||||
|
state.read_storage::<Uid>().get(entity),
|
||||||
|
state.read_storage::<comp::Player>().get(entity),
|
||||||
|
) {
|
||||||
|
state.notify_registered_clients(ServerMsg::PlayerListUpdate(PlayerListUpdate::Remove(
|
||||||
|
(*uid).into(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure to remove the player from the logged in list. (See AuthProvider)
|
||||||
|
// And send a disconnected message
|
||||||
|
{
|
||||||
|
let players = state.ecs().read_storage::<Player>();
|
||||||
|
let mut accounts = state.ecs().write_resource::<AuthProvider>();
|
||||||
|
let mut clients = state.ecs().write_storage::<Client>();
|
||||||
|
|
||||||
|
if let Some(player) = players.get(entity) {
|
||||||
|
accounts.logout(player.uuid());
|
||||||
|
|
||||||
|
let msg = ServerMsg::broadcast(format!("{} went offline.", &player.alias));
|
||||||
|
for client in (&mut clients).join().filter(|c| c.is_registered()) {
|
||||||
|
client.notify(msg.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Delete client entity
|
||||||
|
if let Err(err) = state.delete_entity_recorded(entity) {
|
||||||
|
error!("Failed to delete disconnected client: {:?}", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
Event::ClientDisconnected { entity }
|
||||||
|
}
|
1046
server/src/lib.rs
1046
server/src/lib.rs
File diff suppressed because it is too large
Load Diff
@ -10,12 +10,12 @@ const DEFAULT_WORLD_SEED: u32 = 5284;
|
|||||||
pub struct ServerSettings {
|
pub struct ServerSettings {
|
||||||
pub gameserver_address: SocketAddr,
|
pub gameserver_address: SocketAddr,
|
||||||
pub metrics_address: SocketAddr,
|
pub metrics_address: SocketAddr,
|
||||||
|
pub auth_server_address: Option<String>,
|
||||||
pub max_players: usize,
|
pub max_players: usize,
|
||||||
pub world_seed: u32,
|
pub world_seed: u32,
|
||||||
//pub pvp_enabled: bool,
|
//pub pvp_enabled: bool,
|
||||||
pub server_name: String,
|
pub server_name: String,
|
||||||
pub server_description: String,
|
pub server_description: String,
|
||||||
//pub login_server: whatever
|
|
||||||
pub start_time: f64,
|
pub start_time: f64,
|
||||||
pub admins: Vec<String>,
|
pub admins: Vec<String>,
|
||||||
/// When set to None, loads the default map file (if available); otherwise,
|
/// When set to None, loads the default map file (if available); otherwise,
|
||||||
@ -28,6 +28,7 @@ impl Default for ServerSettings {
|
|||||||
Self {
|
Self {
|
||||||
gameserver_address: SocketAddr::from(([0; 4], 14004)),
|
gameserver_address: SocketAddr::from(([0; 4], 14004)),
|
||||||
metrics_address: SocketAddr::from(([0; 4], 14005)),
|
metrics_address: SocketAddr::from(([0; 4], 14005)),
|
||||||
|
auth_server_address: Some("https://auth.veloren.net".into()),
|
||||||
world_seed: DEFAULT_WORLD_SEED,
|
world_seed: DEFAULT_WORLD_SEED,
|
||||||
server_name: "Veloren Alpha".to_owned(),
|
server_name: "Veloren Alpha".to_owned(),
|
||||||
server_description: "This is the best Veloren server.".to_owned(),
|
server_description: "This is the best Veloren server.".to_owned(),
|
||||||
@ -107,6 +108,7 @@ impl ServerSettings {
|
|||||||
[127, 0, 0, 1],
|
[127, 0, 0, 1],
|
||||||
pick_unused_port().expect("Failed to find unused port!"),
|
pick_unused_port().expect("Failed to find unused port!"),
|
||||||
)),
|
)),
|
||||||
|
auth_server_address: None,
|
||||||
// If loading the default map file, make sure the seed is also default.
|
// If loading the default map file, make sure the seed is also default.
|
||||||
world_seed: if load.map_file.is_some() {
|
world_seed: if load.map_file.is_some() {
|
||||||
load.world_seed
|
load.world_seed
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user