Merge remote-tracking branch 'origin/master' into sharp/map-colors

This commit is contained in:
Joshua Yanovski 2020-03-13 13:32:42 +01:00
commit f8926a5737
193 changed files with 7800 additions and 3319 deletions

View File

@ -8,7 +8,8 @@ variables:
stages:
- optional-builds
- check-compile
- post
- build-post
- publish
before_script:
- source $HOME/.cargo/env
@ -70,7 +71,7 @@ check:
tags:
- veloren-docker
script:
- RUSTFLAGS="-D warnings" cargo check
- RUSTFLAGS="-D warnings" cargo check --locked
code-quality:
stage: check-compile
@ -90,29 +91,30 @@ security:
# --
# -- post build
# -- build-post
unittests:
stage: post
stage: build-post
when: delayed
start_in: 5 seconds
tags:
- veloren-docker
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"
- 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:
stage: post
stage: build-post
when: delayed
start_in: 5 seconds
tags:
- veloren-docker
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:
stage: post
stage: build-post
when: delayed
start_in: 5 seconds
tags:
@ -121,14 +123,26 @@ benchmarks:
- unset DISABLE_GIT_LFS_CHECK
- 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:
stage: post
stage: build-post
when: delayed
start_in: 5 seconds
only:
refs:
- /^r[0-9]+\.[0-9]+\.[0-9]+/
- /^v[0-9]+\.[0-9]+\.[0-9]+/
- /^v[0-9]+\.[0-9]+/
- /^master$/
tags:
- veloren-docker
@ -147,13 +161,13 @@ linux:
expire_in: 1 week
windows:
stage: post
stage: build-post
when: delayed
start_in: 5 seconds
only:
refs:
- /^r[0-9]+\.[0-9]+\.[0-9]+/
- /^v[0-9]+\.[0-9]+\.[0-9]+/
- /^v[0-9]+\.[0-9]+/
- /^master$/
tags:
- veloren-docker
@ -169,4 +183,52 @@ windows:
- LICENSE
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
View 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

View File

@ -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 daily Mac builds
- 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
- Brighter / higher contrast main-map
- 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

1013
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,9 @@ Currently the communication of contributors happens mainly on our [official Disc
## 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.
[Future Plans](https://gitlab.com/veloren/veloren/milestones) - Go here for information about Veloren's development roadmap and what we're currently working on.

View File

@ -19,6 +19,7 @@
"Colborn",
"Dagfinn",
"Dagrod",
"Digbod",
"Dimian",
"Domnhar",
"Ebraheim",
@ -458,6 +459,10 @@
"peacock": {
"keyword": "peacock",
"generic": "Peacock"
},
"eagle": {
"keyword": "eagle",
"generic": "Eagle"
}
}
},

View File

@ -23,5 +23,77 @@
],
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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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)

Binary file not shown.

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

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

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)

Binary file not shown.

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

Binary file not shown.

BIN
assets/voxygen/element/misc_bg/textbox_top.png (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

View File

@ -17,6 +17,28 @@ VoxygenLocalization(
language_identifier: "en",
),
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: {
/// Start Common section
// Texts used in multiple locations with the same formatting
@ -43,6 +65,8 @@ VoxygenLocalization(
"common.disclaimer": "Disclaimer",
"common.cancel": "Cancel",
"common.none": "None",
"common.error": "Error",
"common.fatal_error": "Fatal Error",
// Message when connection to the server is 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
"main.login_process": r#"Information on the Login Process:
Put in any username. No Account needed yet.
Character names and appearances will be saved locally.
If you are having issues signing in:
Levels/Items are not saved yet."#,
Please note that you now need an account
to play on auth-enabled servers.
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
@ -186,6 +223,7 @@ Enjoy your stay in the World of Veloren."#,
"hud.settings.view_distance": "View Distance",
"hud.settings.maximum_fps": "Maximum FPS",
"hud.settings.fov": "Field of View (deg)",
"hud.settings.gamma": "Gamma",
"hud.settings.antialiasing_mode": "AntiAliasing Mode",
"hud.settings.cloud_rendering_mode": "Cloud Rendering Mode",
"hud.settings.fluid_rendering_mode": "Fluid Rendering Mode",
@ -335,4 +373,4 @@ Willpower
"esc_menu.quit_game": "Quit Game",
/// End Escape Menu Section
}
)
)

View File

@ -4,7 +4,29 @@ VoxygenLocalization(
language_name: "Français",
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: {
// Common texts used in multiple locations
"common.username": "pseudo",
@ -16,7 +38,7 @@ VoxygenLocalization(
"common.languages": "Langues",
"common.interface": "Interface",
"common.gameplay": "Gameplay",
"common.controls": "Controles",
"common.controls": "Contrôles",
"common.video": "Video",
"common.sound": "Audio",
"common.resume": "Reprendre",

View 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>

View 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

Binary file not shown.

View File

@ -12,4 +12,5 @@ uniform u_globals {
uvec4 light_shadow_count;
uvec4 medium;
ivec4 select_pos;
vec4 gamma;
};

View File

@ -45,7 +45,7 @@ void main() {
//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 = aa_color;
vec4 final_color = pow(aa_color, gamma);
if (medium.x == 1u) {
final_color *= vec4(0.2, 0.2, 0.8, 1.0);

View File

@ -111,4 +111,32 @@
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"),
)
),
})

View File

@ -143,4 +143,40 @@
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"),
)
),
})

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

BIN
assets/voxygen/voxel/figure/hair/orc/female-6.vox (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

View File

@ -27,6 +27,7 @@
Some(("figure.hair.human.male-20", (-3, -4, -7))),
],
beard: [
None,
Some(("figure.beard.human.human-0", (4, 6, -2))),
Some(("figure.beard.human.human-1", (5, 10, -2))),
Some(("figure.beard.human.human-2", (3, 7, -3))),
@ -92,17 +93,24 @@
],
),
(Orc, Female): (
offset: (-8.0, -3.0, -6.0),
head: ("figure.head.orc.female", (0, 2, 0)),
eyes: ("figure.eyes.orc.female-0", (3, 9, 2)),
offset: (-8.0, -2.5, -6.0),
head: ("figure.head.orc.female", (0, 1, 0)),
eyes: ("figure.eyes.orc.female-0", (3, 8, 2)),
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],
accessory: [
None,
Some(("figure.accessory.orc.earring-female-0", (2, 5, 1))),
Some(("figure.accessory.orc.warpaint-female-0", (3, 5, 1))),
Some(("figure.accessory.orc.earring-female-0", (2, 4, 1))),
Some(("figure.accessory.orc.warpaint-female-0", (-2, -4, -7))),
Some(("figure.accessory.orc.warpaint-female-1", (-2, -4, -7))),
],
),
(Elf, Male): (
@ -239,6 +247,7 @@
Some(("figure.hair.danari.male-1", (3, 1, 2))),
],
beard: [
None,
Some(("figure.beard.danari.danari-0", (4, 6, -1))),
],
accessory: [

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

View File

@ -386,7 +386,7 @@
central: ("npc.lion.male.ears"),
),
tail: (
offset: (-0.5, -1.0, -8.0),
offset: (-0.5, -1.0, -1.0),
central: ("npc.lion.male.tail"),
),
),
@ -417,7 +417,7 @@
),
tail: (
offset: (-0.5, -1.0, -1.0),
central: ("npc.lion.male.tail"),
central: ("npc.lion.female.tail"),
),
),
(Tarasque, Male): (

Binary file not shown.

View File

@ -51,7 +51,9 @@ fn main() {
println!("Players online: {:?}", client.get_players());
client
.register(comp::Player::new(username, None), password)
.register(username, password, |provider| {
provider == "https://auth.veloren.net"
})
.unwrap();
let (tx, rx) = mpsc::channel();

View File

@ -15,3 +15,4 @@ log = "0.4.8"
specs = "0.15.1"
vek = { version = "0.9.9", features = ["serde"] }
hashbrown = { version = "0.6.2", features = ["rayon", "serde", "nightly"] }
authc = { git = "https://gitlab.com/veloren/auth.git", rev = "65571ade0d954a0e0bd995fdb314854ff146ab97" }

View File

@ -1,3 +1,4 @@
use authc::AuthClientError;
use common::net::PostError;
#[derive(Debug)]
@ -7,11 +8,18 @@ pub enum Error {
ServerTimeout,
ServerShutdown,
TooManyPlayers,
InvalidAuth,
AlreadyLoggedIn,
AuthErr(String),
AuthClientError(AuthClientError),
AuthServerNotTrusted,
//TODO: InvalidAlias,
Other(String),
}
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) }
}

View File

@ -5,6 +5,7 @@ pub mod error;
// Reexports
pub use crate::error::Error;
pub use authc::AuthClientError;
pub use specs::{
join::Join,
saveload::{Marker, MarkerAllocator},
@ -13,10 +14,13 @@ pub use specs::{
use byteorder::{ByteOrder, LittleEndian};
use common::{
comp::{self, ControlEvent, Controller, ControllerInputs, InventoryManip},
comp::{
self, ControlEvent, Controller, ControllerInputs, InventoryManip, InventoryUpdateEvent,
},
event::{EventBus, SfxEvent, SfxEventItem},
msg::{
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,
state::State,
@ -39,11 +43,11 @@ use vek::*;
// The duration of network inactivity until the player is kicked
// @TODO: in the future, this should be configurable on the server
// 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
// their chat window
const SERVER_TIMEOUT_GRACE_PERIOD: Duration = Duration::from_secs(14);
const SERVER_TIMEOUT_GRACE_PERIOD: f64 = 14.0;
pub enum Event {
Chat {
@ -63,8 +67,8 @@ pub struct Client {
postbox: PostBox<ClientMsg, ServerMsg>,
last_server_ping: Instant,
last_server_pong: Instant,
last_server_ping: f64,
last_server_pong: f64,
last_ping_delta: f64,
tick: u64,
@ -85,13 +89,13 @@ impl Client {
let mut postbox = PostBox::to(addr)?;
// Wait for initial sync
let (state, entity, server_info, world_map) = match postbox.next_message() {
Some(ServerMsg::InitialSync {
let (state, entity, server_info, world_map) = match postbox.next_message()? {
ServerMsg::InitialSync {
entity_package,
server_info,
time_of_day,
world_map: (map_size, world_map),
}) => {
} => {
// TODO: Display that versions don't match in Voxygen
if server_info.git_hash != common::util::GIT_HASH.to_string() {
log::warn!(
@ -104,6 +108,8 @@ impl Client {
);
}
log::debug!("Auth Server: {:?}", server_info.auth_provider);
// Initialize `State`
let mut state = State::default();
let entity = state.ecs_mut().apply_entity_package(entity_package);
@ -128,9 +134,7 @@ impl Client {
(state, entity, server_info, (world_map, map_size))
},
Some(ServerMsg::Error(ServerError::TooManyPlayers)) => {
return Err(Error::TooManyPlayers);
},
ServerMsg::TooManyPlayers => return Err(Error::TooManyPlayers),
_ => return Err(Error::ServerWentMad),
};
@ -151,8 +155,8 @@ impl Client {
postbox,
last_server_ping: Instant::now(),
last_server_pong: Instant::now(),
last_server_ping: 0.0,
last_server_pong: 0.0,
last_ping_delta: 0.0,
tick: 0,
@ -171,16 +175,40 @@ impl Client {
}
/// Request a state transition to `ClientState::Registered`.
pub fn register(&mut self, player: comp::Player, password: String) -> Result<(), Error> {
self.postbox
.send_message(ClientMsg::Register { player, password });
pub fn register(
&mut self,
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;
loop {
match self.postbox.next_message() {
Some(ServerMsg::StateAnswer(Err((RequestStateError::Denied, _)))) => {
break Err(Error::InvalidAuth);
match self.postbox.next_message()? {
ServerMsg::StateAnswer(Err((RequestStateError::RegisterDenied(err), state))) => {
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
pub fn request_logout(&mut self) {
self.postbox.send_message(ClientMsg::Disconnect);
self.client_state = ClientState::Pending;
}
pub fn request_logout(&mut self) { self.postbox.send_message(ClientMsg::Disconnect); }
/// Request a state transition to `ClientState::Registered` from an ingame
/// state.
@ -377,13 +402,14 @@ impl Client {
}
}
}
// Handle new messages from the server.
frontend_events.append(&mut self.handle_new_messages()?);
// 3) Update client local data
// 4) Tick the client's LocalState
self.state.tick(dt, add_foreign_systems);
self.state.tick(dt, add_foreign_systems, true);
// 5) Terrain
let pos = self
@ -479,9 +505,9 @@ impl Client {
}
// 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.last_server_ping = Instant::now();
self.last_server_ping = self.state.get_time();
}
// 6) Update the server about the player's physics attributes.
@ -526,16 +552,14 @@ impl Client {
// Check that we have an valid connection.
// Use the last ping time as a 1s rate limiter, we only notify the user once per
// second
if Instant::now().duration_since(self.last_server_ping) > Duration::from_secs(1) {
let duration_since_last_pong = Instant::now().duration_since(self.last_server_pong);
if self.state.get_time() - self.last_server_ping > 1. {
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
if duration_since_last_pong.as_secs() >= SERVER_TIMEOUT_GRACE_PERIOD.as_secs() {
if let Some(seconds_until_kick) =
SERVER_TIMEOUT.checked_sub(duration_since_last_pong)
{
if duration_since_last_pong >= SERVER_TIMEOUT_GRACE_PERIOD {
if self.state.get_time() - duration_since_last_pong > 0. {
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 {
for msg in new_msgs {
match msg {
ServerMsg::Error(e) => match e {
ServerError::TooManyPlayers => return Err(Error::ServerWentMad),
ServerError::InvalidAuth => return Err(Error::InvalidAuth),
//TODO: ServerError::InvalidAlias => return Err(Error::InvalidAlias),
ServerMsg::TooManyPlayers => {
return Err(Error::ServerWentMad);
},
ServerMsg::Shutdown => return Err(Error::ServerShutdown),
ServerMsg::InitialSync { .. } => return Err(Error::ServerWentMad),
@ -589,11 +611,10 @@ impl Client {
ServerMsg::Ping => self.postbox.send_message(ClientMsg::Pong),
ServerMsg::Pong => {
self.last_server_pong = Instant::now();
self.last_server_pong = self.state.get_time();
self.last_ping_delta = Instant::now()
.duration_since(self.last_server_ping)
.as_secs_f64();
self.last_ping_delta =
(self.state.get_time() - self.last_server_ping).round();
},
ServerMsg::ChatMsg { message, chat_type } => {
frontend_events.push(Event::Chat { message, chat_type })
@ -669,8 +690,26 @@ impl Client {
self.state.write_component(entity, character_state);
}
},
ServerMsg::InventoryUpdate(inventory) => {
self.state.write_component(self.entity, inventory)
ServerMsg::InventoryUpdate(inventory, event) => {
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 } => {
if let Ok(chunk) = chunk {
@ -687,10 +726,6 @@ impl Client {
self.client_state = state;
},
ServerMsg::StateAnswer(Err((error, state))) => {
if error == RequestStateError::Denied {
warn!("Connection denied!");
return Err(Error::InvalidAuth);
}
warn!(
"StateAnswer: {:?}. Server thinks client is in state {:?}.",
error, state
@ -698,13 +733,14 @@ impl Client {
},
ServerMsg::Disconnect => {
frontend_events.push(Event::Disconnect);
self.postbox.send_message(ClientMsg::Terminate);
},
}
}
} else if let Some(err) = self.postbox.error() {
return Err(err.into());
// 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);
}
Ok(frontend_events)

View File

@ -30,9 +30,10 @@ hashbrown = { version = "0.6.2", features = ["rayon", "serde", "nightly"] }
find_folder = "0.3.0"
parking_lot = "0.9.0"
crossbeam = "=0.7.2"
notify = "5.0.0-pre.1"
notify = "5.0.0-pre.2"
indexmap = "1.3.0"
sum_type = "0.2.0"
authc = { git = "https://gitlab.com/veloren/auth.git", rev = "65571ade0d954a0e0bd995fdb314854ff146ab97" }
[dev-dependencies]
criterion = "0.3"

View File

@ -31,6 +31,7 @@ pub enum Species {
Chicken = 1,
Goose = 2,
Peacock = 3,
Eagle = 4,
}
/// Data representing per-species generic data.
@ -42,6 +43,7 @@ pub struct AllSpecies<SpeciesMeta> {
pub chicken: SpeciesMeta,
pub goose: SpeciesMeta,
pub peacock: SpeciesMeta,
pub eagle: 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::Goose => &self.goose,
Species::Peacock => &self.peacock,
Species::Eagle => &self.eagle,
}
}
}
pub const ALL_SPECIES: [Species; 4] = [
pub const ALL_SPECIES: [Species; 5] = [
Species::Duck,
Species::Chicken,
Species::Goose,
Species::Peacock,
Species::Eagle,
];
impl<'a, SpeciesMeta: 'a> IntoIterator for &'a AllSpecies<SpeciesMeta> {

View File

@ -131,10 +131,10 @@ pub const DANARI_HAIR_COLORS: [(u8, u8, u8); 11] = [
//(228, 208, 147), // Gold Blonde
//(228, 223, 141), // Platinum Blonde
(199, 131, 58), // Summer Blonde
(107, 76, 51), // Oak Brown
//(203, 154, 98), // Light Brown
(64, 32, 18), // Chocolate Brown
(86, 72, 71), // Ash Brown
(107, 76, 51), // Oak Skin4
//(203, 154, 98), // Light Skin4
(64, 32, 18), // Skin7 Skin4
(86, 72, 71), // Ash Skin4
(57, 56, 61), // Raven Black
(101, 83, 95), // Matte 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, 223, 141), // Platinum Blonde
(199, 131, 58), // Summer Blonde
(107, 76, 51), // Oak Brown
(203, 154, 98), // Light Brown
(64, 32, 18), // Chocolate Brown
(86, 72, 71), // Ash Brown
(107, 76, 51), // Oak Skin4
(203, 154, 98), // Light Skin4
(64, 32, 18), // Skin7 Skin4
(86, 72, 71), // Ash Skin4
(57, 56, 61), // Raven Black
(101, 83, 95), // Matte 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, 223, 141), // Platinum Blonde
(199, 131, 58), // Summer Blonde
(107, 76, 51), // Oak Brown
(203, 154, 98), // Light Brown
(64, 32, 18), // Chocolate Brown
(86, 72, 71), // Ash Brown
(107, 76, 51), // Oak Skin4
(203, 154, 98), // Light Skin4
(64, 32, 18), // Skin7 Skin4
(86, 72, 71), // Ash Skin4
(57, 56, 61), // Raven Black
(101, 83, 95), // Matte 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, 223, 141), // Platinum Blonde
(199, 131, 58), // Summer Blonde
(107, 76, 51), // Oak Brown
(203, 154, 98), // Light Brown
(64, 32, 18), // Chocolate Brown
(86, 72, 71), // Ash Brown
(107, 76, 51), // Oak Skin4
(203, 154, 98), // Light Skin4
(64, 32, 18), // Skin7 Skin4
(86, 72, 71), // Ash Skin4
(57, 56, 61), // Raven Black
(101, 83, 95), // Matte 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] = [
(66, 66, 59), // Wise Grey
//(107, 76, 51), // Oak Brown
//(203, 154, 98), // Light Brown
(64, 32, 18), // Chocolate Brown
(54, 30, 26), // Dark Chocolate
(86, 72, 71), // Ash Brown
//(107, 76, 51), // Oak Skin4
//(203, 154, 98), // Light Skin4
(64, 32, 18), // Skin7 Skin4
(54, 30, 26), // Dark Skin7
(86, 72, 71), // Ash Skin4
(57, 56, 61), // Raven Black
(101, 83, 95), // Matte 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, 223, 141), // Platinum Blonde
(199, 131, 58), // Summer Blonde
(107, 76, 51), // Oak Brown
(203, 154, 98), // Light Brown
(64, 32, 18), // Chocolate Brown
(86, 72, 71), // Ash Brown
(107, 76, 51), // Oak Skin4
(203, 154, 98), // Light Skin4
(64, 32, 18), // Skin7 Skin4
(86, 72, 71), // Ash Skin4
(57, 56, 61), // Raven Black
(101, 83, 95), // Matte Purple
(101, 57, 90), // Witch Purple
@ -261,30 +261,59 @@ pub const DANARI_SKIN_COLORS: [Skin; 4] = [
Skin::DanariThree,
Skin::DanariFour,
];
pub const DWARF_SKIN_COLORS: [Skin; 5] = [
Skin::Pale,
Skin::White,
Skin::Tanned,
pub const DWARF_SKIN_COLORS: [Skin; 14] = [
Skin::Skin1,
Skin::Skin2,
Skin::Skin3,
Skin::Skin4,
Skin::Skin5,
Skin::Skin6,
Skin::Skin7,
Skin::Skin8,
Skin::Skin9,
Skin::Skin10,
Skin::Skin11,
Skin::Skin12,
Skin::Iron,
Skin::Steel,
];
pub const ELF_SKIN_COLORS: [Skin; 7] = [
Skin::Pale,
pub const ELF_SKIN_COLORS: [Skin; 14] = [
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::ElfTwo,
Skin::ElfThree,
Skin::White,
Skin::Tanned,
Skin::TannedBrown,
];
pub const HUMAN_SKIN_COLORS: [Skin; 5] = [
Skin::Pale,
Skin::White,
Skin::Tanned,
Skin::TannedBrown,
Skin::TannedDarkBrown,
pub const HUMAN_SKIN_COLORS: [Skin; 18] = [
Skin::Skin1,
Skin::Skin2,
Skin::Skin3,
Skin::Skin4,
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];
// Eye colors
@ -293,22 +322,31 @@ pub const DANARI_EYE_COLORS: [EyeColor; 3] = [
EyeColor::LoyalBrown,
EyeColor::ViciousRed,
];
pub const DWARF_EYE_COLORS: [EyeColor; 3] = [
pub const DWARF_EYE_COLORS: [EyeColor; 4] = [
EyeColor::CuriousGreen,
EyeColor::LoyalBrown,
EyeColor::NobleBlue,
EyeColor::CornflowerBlue,
];
pub const ELF_EYE_COLORS: [EyeColor; 3] = [
pub const ELF_EYE_COLORS: [EyeColor; 4] = [
EyeColor::NobleBlue,
EyeColor::CornflowerBlue,
EyeColor::CuriousGreen,
EyeColor::LoyalBrown,
];
pub const HUMAN_EYE_COLORS: [EyeColor; 3] = [
pub const HUMAN_EYE_COLORS: [EyeColor; 4] = [
EyeColor::NobleBlue,
EyeColor::CornflowerBlue,
EyeColor::CuriousGreen,
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] = [
EyeColor::ViciousRed,
EyeColor::PumpkinOrange,
@ -365,7 +403,7 @@ impl Race {
self.skin_colors()
.get(val as usize)
.copied()
.unwrap_or(Skin::Tanned)
.unwrap_or(Skin::Skin3)
}
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::Human, BodyType::Female) => 19,
(Race::Human, BodyType::Male) => 17,
(Race::Orc, BodyType::Female) => 1,
(Race::Orc, BodyType::Female) => 7,
(Race::Orc, BodyType::Male) => 8,
(Race::Undead, BodyType::Female) => 4,
(Race::Undead, BodyType::Male) => 3,
@ -406,7 +444,7 @@ impl Race {
(Race::Elf, BodyType::Male) => 1,
(Race::Human, BodyType::Female) => 1,
(Race::Human, BodyType::Male) => 1,
(Race::Orc, BodyType::Female) => 3,
(Race::Orc, BodyType::Female) => 4,
(Race::Orc, BodyType::Male) => 5,
(Race::Undead, BodyType::Female) => 1,
(Race::Undead, BodyType::Male) => 1,
@ -534,6 +572,10 @@ pub enum EyeColor {
MagicPurple = 7,
ToxicGreen = 8,
ExoticPurple = 9,
SulfurYellow = 10,
AmberOrange = 11,
PineGreen = 12,
CornflowerBlue = 13,
}
impl EyeColor {
pub fn light_rgb(self) -> Rgb<u8> {
@ -548,6 +590,10 @@ impl EyeColor {
EyeColor::MagicPurple => Rgb::new(137, 4, 177),
EyeColor::ToxicGreen => Rgb::new(1, 223, 1),
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::ToxicGreen => Rgb::new(1, 185, 1),
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)]
#[repr(u32)]
pub enum Skin {
Pale = 0,
White = 1,
Tanned = 2,
Brown = 3,
TannedBrown = 4,
TannedDarkBrown = 5,
Skin1 = 0,
Skin2 = 1,
Skin3 = 2,
Skin4 = 3,
Skin5 = 4,
Skin6 = 5,
Iron = 6,
Steel = 7,
DanariOne = 8,
@ -601,16 +651,41 @@ pub enum Skin {
UndeadOne = 18,
UndeadTwo = 19,
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 {
pub fn rgb(self) -> Rgb<u8> {
let color = match self {
Self::Pale => (252, 211, 179),
Self::White => (253, 195, 164),
Self::Tanned => (222, 181, 151),
Self::Brown => (123, 80, 45),
Self::TannedBrown => (135, 70, 50),
Self::TannedDarkBrown => (116, 61, 43),
Self::Skin1 => (255, 229, 200),
Self::Skin2 => (255, 218, 190),
Self::Skin3 => (255, 206, 180),
Self::Skin4 => (255, 195, 170),
Self::Skin5 => (240, 184, 160),
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::Steel => (108, 94, 86),
Self::DanariOne => (104, 168, 196),
@ -623,6 +698,7 @@ impl Skin {
Self::OrcOne => (61, 130, 42),
Self::OrcTwo => (82, 117, 36),
Self::OrcThree => (71, 94, 42),
Self::OrcFour => (97, 54, 29),
Self::UndeadOne => (240, 243, 239),
Self::UndeadTwo => (178, 178, 178),
Self::UndeadThree => (145, 135, 121),
@ -632,12 +708,24 @@ impl Skin {
pub fn light_rgb(self) -> Rgb<u8> {
let color = match self {
Self::Pale => (255, 227, 193),
Self::White => (255, 210, 180),
Self::Tanned => (239, 197, 164),
Self::Brown => (150, 104, 68),
Self::TannedBrown => (148, 85, 64),
Self::TannedDarkBrown => (132, 74, 56),
Self::Skin1 => (255, 229, 200),
Self::Skin2 => (255, 218, 190),
Self::Skin3 => (255, 206, 180),
Self::Skin4 => (255, 195, 170),
Self::Skin5 => (240, 184, 160),
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::Steel => (120, 107, 99),
Self::DanariOne => (116, 176, 208),
@ -650,6 +738,7 @@ impl Skin {
Self::OrcOne => (83, 165, 56),
Self::OrcTwo => (92, 132, 46),
Self::OrcThree => (84, 110, 54),
Self::OrcFour => (97, 54, 29),
Self::UndeadOne => (254, 252, 251),
Self::UndeadTwo => (190, 192, 191),
Self::UndeadThree => (160, 151, 134),
@ -659,12 +748,24 @@ impl Skin {
pub fn dark_rgb(self) -> Rgb<u8> {
let color = match self {
Self::Pale => (229, 192, 163),
Self::White => (239, 179, 150),
Self::Tanned => (208, 167, 135),
Self::Brown => (106, 63, 30),
Self::TannedBrown => (122, 58, 40),
Self::TannedDarkBrown => (100, 47, 32),
Self::Skin1 => (242, 217, 189),
Self::Skin2 => (242, 207, 189),
Self::Skin3 => (242, 197, 172),
Self::Skin4 => (242, 186, 162),
Self::Skin5 => (212, 173, 150),
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::Steel => (96, 81, 72),
Self::DanariOne => (92, 155, 183),
@ -677,6 +778,7 @@ impl Skin {
Self::OrcOne => (55, 114, 36),
Self::OrcTwo => (70, 104, 29),
Self::OrcThree => (60, 83, 32),
Self::OrcFour => (84, 47, 25),
Self::UndeadOne => (229, 231, 230),
Self::UndeadTwo => (165, 166, 164),
Self::UndeadThree => (130, 122, 106),

View File

@ -90,7 +90,7 @@ pub enum Armor {
Necklace,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Consumable {
Apple,
Cheese,

View File

@ -1,12 +1,16 @@
pub mod item;
// Reexports
pub use item::{Debug, Item, ItemKind, Tool};
pub use item::{Consumable, Debug, Item, ItemKind, Tool};
use crate::assets;
use specs::{Component, HashMapStorage, NullStorage};
use specs::{Component, FlaggedStorage, HashMapStorage};
use specs_idvs::IDVStorage;
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)]
pub struct Inventory {
pub slots: Vec<Option<Item>>,
@ -136,12 +140,38 @@ impl Component for Inventory {
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)]
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 {
type Storage = NullStorage<Self>;
type Storage = FlaggedStorage<Self, IDVStorage<Self>>;
}
#[cfg(test)] mod test;

View File

@ -28,7 +28,9 @@ pub use controller::{
};
pub use energy::{Energy, EnergySource};
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 location::{Waypoint, WaypointArea};
pub use phys::{ForceUpdate, Gravity, Mass, Ori, PhysicsState, Pos, Scale, Sticky, Vel};

View File

@ -1,3 +1,4 @@
use authc::Uuid;
use specs::{Component, FlaggedStorage, NullStorage};
use specs_idvs::IDVStorage;
@ -7,20 +8,26 @@ const MAX_ALIAS_LEN: usize = 32;
pub struct Player {
pub alias: String,
pub view_distance: Option<u32>,
uuid: Uuid,
}
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 {
alias,
view_distance,
uuid,
}
}
pub fn is_valid(&self) -> bool {
self.alias.chars().all(|c| c.is_alphanumeric() || c == '_')
&& self.alias.len() <= MAX_ALIAS_LEN
pub fn is_valid(&self) -> bool { Self::alias_is_valid(&self.alias) }
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 {

View File

@ -1,5 +1,5 @@
use crate::{comp, sync::Uid};
use comp::item::Tool;
use comp::{item::Tool, InventoryUpdateEvent};
use parking_lot::Mutex;
use serde::Deserialize;
use specs::Entity as EcsEntity;
@ -9,22 +9,26 @@ use vek::*;
pub struct SfxEventItem {
pub sfx: SfxEvent,
pub pos: Option<Vec3<f32>>,
pub vol: Option<f32>,
}
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)]
pub enum SfxEvent {
Idle,
PlaceBlock,
RemoveBlock,
OpenChest,
ChatTellReceived,
OpenBag,
Run,
Roll,
Climb,
@ -36,10 +40,9 @@ pub enum SfxEvent {
Fall,
ExperienceGained,
LevelUp,
LightLantern,
ExtinguishLantern,
Attack(Tool),
AttackWolf,
Wield(Tool),
Unwield(Tool),
Inventory(InventoryUpdateEvent),
}
pub enum LocalEvent {

View File

@ -4,8 +4,8 @@ use vek::*;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ClientMsg {
Register {
player: comp::Player,
password: String,
view_distance: Option<u32>,
token_or_username: String,
},
Character {
name: String,
@ -35,4 +35,5 @@ pub enum ClientMsg {
key: Vec2<i32>,
},
Disconnect,
Terminate,
}

View File

@ -6,7 +6,7 @@ pub mod server;
pub use self::{
client::ClientMsg,
ecs_packet::EcsCompPacket,
server::{PlayerListUpdate, RequestStateError, ServerError, ServerInfo, ServerMsg},
server::{PlayerListUpdate, RegisterError, RequestStateError, ServerInfo, ServerMsg},
};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]

View File

@ -4,23 +4,17 @@ use crate::{
terrain::{Block, TerrainChunk},
ChatType,
};
use authc::AuthClientError;
use hashbrown::HashMap;
use vek::*;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum RequestStateError {
Denied,
Already,
Impossible,
WrongMessage,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerInfo {
pub name: String,
pub description: String,
pub git_hash: String,
pub git_date: String,
pub auth_provider: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -71,24 +65,37 @@ pub enum ServerMsg {
entity: u64,
character_state: comp::CharacterState,
},
InventoryUpdate(comp::Inventory),
InventoryUpdate(comp::Inventory, comp::InventoryUpdateEvent),
TerrainChunkUpdate {
key: Vec2<i32>,
chunk: Result<Box<TerrainChunk>, ()>,
},
TerrainBlockUpdates(HashMap<Vec3<i32>, Block>),
Error(ServerError),
Disconnect,
Shutdown,
TooManyPlayers,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ServerError {
TooManyPlayers,
InvalidAuth,
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum RequestStateError {
RegisterDenied(RegisterError),
Denied,
Already,
Impossible,
WrongMessage,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum RegisterError {
AlreadyLoggedIn,
AuthError(String),
//TODO: InvalidAlias,
}
impl From<AuthClientError> for RegisterError {
fn from(err: AuthClientError) -> Self { Self::AuthError(err.to_string()) }
}
impl ServerMsg {
pub fn chat(message: String) -> ServerMsg {
ServerMsg::ChatMsg {

View File

@ -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 next_message(&mut self) -> Option<R> {
if self.error.is_some() {
return None;
pub fn next_message(&mut self) -> Result<R, Error> {
if let Some(e) = self.error.clone() {
return Err(e);
}
match self.recv_rx.recv().ok()? {
Ok(msg) => Some(msg),
match self.recv_rx.recv().map_err(|_| Error::ChannelFailure)? {
Ok(msg) => Ok(msg),
Err(e) => {
self.error = Some(e);
None
self.error = Some(e.clone());
Err(e)
},
}
}

View File

@ -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.
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.
self.ecs.write_resource::<TimeOfDay>().0 += dt.as_secs_f64() * DAY_CYCLE_FACTOR;
self.ecs.write_resource::<Time>().0 += dt.as_secs_f64();
@ -297,12 +324,9 @@ impl State {
// important physics events.
self.ecs.write_resource::<DeltaTime>().0 = dt.as_secs_f32().min(MAX_DELTA_TIME);
// Run RegionMap tick to update entity region occupancy
self.ecs.write_resource::<RegionMap>().tick(
self.ecs.read_storage::<comp::Pos>(),
self.ecs.read_storage::<comp::Vel>(),
self.ecs.entities(),
);
if update_terrain_and_regions {
self.update_region_map();
}
// Run systems to update the world.
// Create and run a dispatcher for ecs systems.
@ -315,16 +339,9 @@ impl State {
self.ecs.maintain();
// Apply terrain changes
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;
if update_terrain_and_regions {
self.apply_terrain_changes();
}
// Process local events
let events = self.ecs.read_resource::<EventBus<LocalEvent>>().recv_all();

View File

@ -153,7 +153,7 @@ impl<'a> System<'a> for Sys {
been_close,
..
} => {
if let (Some(tgt_pos), _tgt_stats, tgt_alignment) = (
if let (Some(tgt_pos), Some(tgt_stats), tgt_alignment) = (
positions.get(*target),
stats.get(*target),
alignments
@ -164,7 +164,8 @@ impl<'a> System<'a> for Sys {
// Don't attack entities we are passive towards
// TODO: This is here, it's a bit of a hack
if let Some(alignment) = alignment {
if (*alignment).passive_towards(tgt_alignment) {
if (*alignment).passive_towards(tgt_alignment) || tgt_stats.is_dead
{
do_idle = true;
break 'activity;
}

8
server-cli/Dockerfile Normal file
View 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

View 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
View File

@ -0,0 +1,3 @@
#!/bin/sh
cd /opt
RUST_LOG=info,common=debug,common::net=info RUST_BACKTRACE=1 /opt/veloren-server-cli

View File

@ -31,3 +31,4 @@ prometheus = "0.7"
prometheus-static-metric = "0.2"
rouille = "3.0.0"
portpicker = { git = "https://github.com/wusyong/portpicker-rs", branch = "fix_ipv6" }
authc = { git = "https://gitlab.com/veloren/auth.git", rev = "65571ade0d954a0e0bd995fdb314854ff146ab97" }

View File

@ -1,32 +1,88 @@
use authc::{AuthClient, AuthToken, Uuid};
use common::msg::RegisterError;
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 {
accounts: HashMap<String, String>,
accounts: HashMap<Uuid, String>,
auth_server: Option<AuthClient>,
}
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 {
accounts: HashMap::new(),
auth_server,
}
}
pub fn query(&mut self, username: String, password: String) -> bool {
let pwd = password.clone();
if self.accounts.entry(username.clone()).or_insert_with(|| {
info!("Registered new user '{}'", &username);
pwd
}) == &password
{
info!("User '{}' successfully authenticated", username);
true
} else {
warn!(
"User '{}' attempted to log in with invalid password '{}'!",
username, password
);
false
pub fn logout(&mut self, uuid: Uuid) {
if self.accounts.remove(&uuid).is_none() {
error!("Attempted to logout user that is not logged in.");
};
}
pub fn query(&mut self, username_or_token: String) -> Result<(String, Uuid), RegisterError> {
// Based on whether auth server is provided or not we expect an username or
// token
match &self.auth_server {
// Token from auth server expected
Some(srv) => {
log::info!("Validating '{}' token.", &username_or_token);
// Parse token
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)
}
},
}
}
}

View File

@ -234,11 +234,18 @@ lazy_static! {
),
ChatCommand::new(
"give_exp",
"{} {}",
"/give_exp <playername> <amount> : Give experience to specified player",
"{d} {}",
"/give_exp <amount> <playername?> : Give experience to yourself or specify a target player",
true,
handle_exp,
),
ChatCommand::new(
"set_level",
"{d} {}",
"/set_level <level> <playername?> : Set own Level or specify a target player",
true,
handle_level
),
ChatCommand::new(
"removelights",
"{}",
@ -255,6 +262,7 @@ lazy_static! {
),
];
}
fn handle_give(server: &mut Server, entity: EcsEntity, args: String, _action: &ChatCommand) {
if let Ok(item) = assets::load_cloned(&args) {
server
@ -267,7 +275,10 @@ fn handle_give(server: &mut Server, entity: EcsEntity, args: String, _action: &C
.state
.ecs()
.write_storage::<comp::InventoryUpdate>()
.insert(entity, comp::InventoryUpdate);
.insert(
entity,
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Given),
);
} else {
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>()
.get(entity)
.copied();
/*let builder = server
/*let builder = server.state
.create_object(pos, ori, obj_type)
.with(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
.state
.create_object(pos, obj_type)
.with(comp::Ori(
// 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) {
let (a_alias, a_exp) = scan_fmt_some!(&args, action.arg_fmt, String, i64);
if let (Some(alias), Some(exp)) = (a_alias, a_exp) {
let ecs = server.state.ecs_mut();
let opt_player = (&ecs.entities(), &ecs.read_storage::<comp::Player>())
fn find_target(
ecs: &specs::World,
opt_alias: Option<String>,
fallback: EcsEntity,
) -> Result<EcsEntity, ServerMsg> {
if let Some(alias) = opt_alias {
(&ecs.entities(), &ecs.read_storage::<comp::Player>())
.join()
.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;
match opt_player {
Some(_alias) => {
if let Some(stats) = ecs.write_storage::<comp::Stats>().get_mut(entity) {
match target {
Ok(player) => {
if let Some(stats) = ecs.write_storage::<comp::Stats>().get_mut(player) {
stats.exp.change_by(exp);
} else {
error_msg = Some(ServerMsg::private(String::from("Player has no stats!")));
}
},
_ => {
error_msg = Some(ServerMsg::private(format!("Player '{}' not found!", alias)));
Err(e) => {
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
.ecs()
.write_storage::<comp::InventoryUpdate>()
.insert(entity, comp::InventoryUpdate);
.insert(
entity,
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Debug),
);
} else {
server.notify_client(
entity,

View 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();
}

View 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();
}
}

View 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
)
});
}
}
}
}
}
}

View 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
View 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
}
}

View 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 }
}

File diff suppressed because it is too large Load Diff

View File

@ -10,12 +10,12 @@ const DEFAULT_WORLD_SEED: u32 = 5284;
pub struct ServerSettings {
pub gameserver_address: SocketAddr,
pub metrics_address: SocketAddr,
pub auth_server_address: Option<String>,
pub max_players: usize,
pub world_seed: u32,
//pub pvp_enabled: bool,
pub server_name: String,
pub server_description: String,
//pub login_server: whatever
pub start_time: f64,
pub admins: Vec<String>,
/// When set to None, loads the default map file (if available); otherwise,
@ -28,6 +28,7 @@ impl Default for ServerSettings {
Self {
gameserver_address: SocketAddr::from(([0; 4], 14004)),
metrics_address: SocketAddr::from(([0; 4], 14005)),
auth_server_address: Some("https://auth.veloren.net".into()),
world_seed: DEFAULT_WORLD_SEED,
server_name: "Veloren Alpha".to_owned(),
server_description: "This is the best Veloren server.".to_owned(),
@ -107,6 +108,7 @@ impl ServerSettings {
[127, 0, 0, 1],
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.
world_seed: if load.map_file.is_some() {
load.world_seed

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