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/small-fixes
This commit is contained in:
commit
0ed801d540
@ -4,4 +4,7 @@ rustflags = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[alias]
|
[alias]
|
||||||
generate = "run --package tools --"
|
generate = "run --package tools --"
|
||||||
|
test-server = "-Zpackage-features run --bin veloren-server-cli --no-default-features"
|
||||||
|
server = "run --bin veloren-server-cli"
|
||||||
|
|
||||||
|
@ -49,6 +49,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Loading-Screen tips
|
- Loading-Screen tips
|
||||||
- Feeding animation for some animals
|
- Feeding animation for some animals
|
||||||
- Power stat to weapons which affects weapon damage
|
- Power stat to weapons which affects weapon damage
|
||||||
|
- Add detection of entities under the cursor
|
||||||
|
- Functional group-system with exp-sharing and disabled damage to group members
|
||||||
|
- Some Campfire, fireball & bomb; particle, light & sound effects.
|
||||||
|
- Added setting to change resolution
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
@ -80,6 +84,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Fixed window resizing on Mac OS X.
|
- Fixed window resizing on Mac OS X.
|
||||||
- Dehardcoded many item variants
|
- Dehardcoded many item variants
|
||||||
- Tooltips avoid the mouse better and disappear when hovered
|
- Tooltips avoid the mouse better and disappear when hovered
|
||||||
|
- Improved social window functions and visuals
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
5
Cargo.lock
generated
5
Cargo.lock
generated
@ -4465,7 +4465,8 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "treeculler"
|
name = "treeculler"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://gitlab.com/yusdacra/treeculler.git#efcf5283cf386117a7e654abdaa45ef664a08e42"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aa14b9f5cd7d513bab5accebe8f403df8b1ac22302cac549a6ac99c0a007c84a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"vek 0.11.2",
|
"vek 0.11.2",
|
||||||
@ -4703,6 +4704,7 @@ dependencies = [
|
|||||||
"roots",
|
"roots",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"slab",
|
||||||
"specs",
|
"specs",
|
||||||
"specs-idvs",
|
"specs-idvs",
|
||||||
"sum_type",
|
"sum_type",
|
||||||
@ -4783,6 +4785,7 @@ dependencies = [
|
|||||||
"guillotiere",
|
"guillotiere",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
"image",
|
"image",
|
||||||
|
"itertools",
|
||||||
"msgbox",
|
"msgbox",
|
||||||
"num 0.2.1",
|
"num 0.2.1",
|
||||||
"old_school_gfx_glutin_ext",
|
"old_school_gfx_glutin_ext",
|
||||||
|
18
README.md
18
README.md
@ -40,22 +40,6 @@ Due to rapid developement stable versions become outdated fast and might be **in
|
|||||||
|
|
||||||
If you want to compile Veloren yourself, follow the instructions in our [Book](https://book.veloren.net/contributors/introduction.html).
|
If you want to compile Veloren yourself, follow the instructions in our [Book](https://book.veloren.net/contributors/introduction.html).
|
||||||
|
|
||||||
### Packaging status
|
|
||||||
|
|
||||||
#### Fedora
|
|
||||||
|
|
||||||
[COPR repo](https://copr.fedorainfracloud.org/coprs/atim/veloren/): `sudo dnf copr enable atim/veloren -y && sudo dnf install veloren -y`
|
|
||||||
|
|
||||||
#### Arch
|
|
||||||
|
|
||||||
[AUR Airshipper](https://aur.archlinux.org/packages/airshipper-git): `yay -Syu airshipper-git`
|
|
||||||
|
|
||||||
[AUR latest binary release](https://aur.archlinux.org/packages/veloren-bin/): `yay -Syu veloren-bin`
|
|
||||||
|
|
||||||
[AUR latest release](https://aur.archlinux.org/packages/veloren/): `yay -Syu veloren`
|
|
||||||
|
|
||||||
[AUR latest master](https://aur.archlinux.org/packages/veloren-git): `yay -Syu veloren-git`
|
|
||||||
|
|
||||||
## F.A.Q.
|
## F.A.Q.
|
||||||
|
|
||||||
### **Q:** How is this game licensed?
|
### **Q:** How is this game licensed?
|
||||||
@ -68,7 +52,7 @@ If you want to compile Veloren yourself, follow the instructions in our [Book](h
|
|||||||
|
|
||||||
### **Q:** Do you accept donations?
|
### **Q:** Do you accept donations?
|
||||||
|
|
||||||
**A:** To keep Veloren a passion project free from financial incentives we will **only accept donations to cover server hosting expenses.** There is no way to donate yet.
|
**A:** You can support the project on our [OpenCollective Page](https://opencollective.com/veloren).
|
||||||
|
|
||||||
## Credit
|
## Credit
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ Item(
|
|||||||
(
|
(
|
||||||
kind: Back("Short0"),
|
kind: Back("Short0"),
|
||||||
stats: (
|
stats: (
|
||||||
protection: Normal(0.0),
|
protection: Normal(0.2),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
12
assets/common/items/armor/back/short_1.ron
Normal file
12
assets/common/items/armor/back/short_1.ron
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
Item(
|
||||||
|
name: "Green Blanket",
|
||||||
|
description: "Keeps your shoulders warm.",
|
||||||
|
kind: Armor(
|
||||||
|
(
|
||||||
|
kind: Back("Short1"),
|
||||||
|
stats: (
|
||||||
|
protection: Normal(0.1),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
12
assets/common/items/armor/neck/neck_1.ron
Normal file
12
assets/common/items/armor/neck/neck_1.ron
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
Item(
|
||||||
|
name: "Gem of lesser Protection",
|
||||||
|
description: "Surrounded by a discrete magical glow.",
|
||||||
|
kind: Armor(
|
||||||
|
(
|
||||||
|
kind: Neck("Neck1"),
|
||||||
|
stats: (
|
||||||
|
protection: Normal(0.5),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
@ -1,6 +1,6 @@
|
|||||||
Item(
|
Item(
|
||||||
name: "Uneven Bow",
|
name: "Uneven Bow",
|
||||||
description: "Someone carved his initials into it.",
|
description: "Someone carved their initials into it.",
|
||||||
kind: Tool(
|
kind: Tool(
|
||||||
(
|
(
|
||||||
kind: Bow("ShortBow0"),
|
kind: Bow("ShortBow0"),
|
||||||
|
@ -88,7 +88,7 @@
|
|||||||
(0.50, "common.items.weapons.staff.starter_staff"),
|
(0.50, "common.items.weapons.staff.starter_staff"),
|
||||||
(0.35, "common.items.weapons.staff.bone_staff"),
|
(0.35, "common.items.weapons.staff.bone_staff"),
|
||||||
(0.15, "common.items.weapons.staff.amethyst_staff"),
|
(0.15, "common.items.weapons.staff.amethyst_staff"),
|
||||||
(0.01, "common.items.weapons.staff.cultist_staff"),
|
//(0.01, "common.items.weapons.staff.cultist_staff"),
|
||||||
// hammers
|
// hammers
|
||||||
(0.05, "common.items.weapons.hammer.starter_hammer"),
|
(0.05, "common.items.weapons.hammer.starter_hammer"),
|
||||||
(0.05, "common.items.weapons.hammer.wood_hammer-0"),
|
(0.05, "common.items.weapons.hammer.wood_hammer-0"),
|
||||||
@ -230,6 +230,8 @@
|
|||||||
(0.6, "common.items.armor.ring.ring_0"),
|
(0.6, "common.items.armor.ring.ring_0"),
|
||||||
// capes
|
// capes
|
||||||
(0.6, "common.items.armor.back.short_0"),
|
(0.6, "common.items.armor.back.short_0"),
|
||||||
|
(0.7, "common.items.armor.back.short_1"),
|
||||||
// necks
|
// necks
|
||||||
(0.6, "common.items.armor.neck.neck_0"),
|
(0.6, "common.items.armor.neck.neck_0"),
|
||||||
|
(0.4, "common.items.armor.neck.neck_1"),
|
||||||
]
|
]
|
||||||
|
@ -106,6 +106,18 @@
|
|||||||
"voxygen.audio.sfx.inventory.consumable.food",
|
"voxygen.audio.sfx.inventory.consumable.food",
|
||||||
],
|
],
|
||||||
threshold: 0.3,
|
threshold: 0.3,
|
||||||
)
|
),
|
||||||
|
Explosion: (
|
||||||
|
files: [
|
||||||
|
"voxygen.audio.sfx.explosion",
|
||||||
|
],
|
||||||
|
threshold: 0.2,
|
||||||
|
),
|
||||||
|
ProjectileShot: (
|
||||||
|
files: [
|
||||||
|
"voxygen.audio.sfx.glider_open",
|
||||||
|
],
|
||||||
|
threshold: 0.5,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
BIN
assets/voxygen/audio/sfx/explosion.wav
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/audio/sfx/explosion.wav
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/group.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/group.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/group_hover.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/group_hover.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/group_press.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/group_press.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/social_tab_active.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/social_tab_active.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/social_tab_inactive.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/social_tab_inactive.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/frames/enemybar_1.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/frames/enemybar_1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/frames/enemybar_bg_1.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/frames/enemybar_bg_1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/frames/group_member_bg.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/frames/group_member_bg.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/frames/group_member_frame.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/frames/group_member_frame.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/neck-0.png
(Stored with Git LFS)
BIN
assets/voxygen/element/icons/neck-0.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/icons/neck-1.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/icons/neck-1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/misc_bg/social_bg.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/misc_bg/social_bg.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/misc_bg/social_frame.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/misc_bg/social_frame.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/misc_bg/social_tab_active.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/misc_bg/social_tab_active.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/misc_bg/social_tab_inactive.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/misc_bg/social_tab_inactive.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/misc_bg/social_tab_online.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/misc_bg/social_tab_online.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -388,5 +388,9 @@ Siła woli
|
|||||||
"esc_menu.quit_game": "Opuść gre",
|
"esc_menu.quit_game": "Opuść gre",
|
||||||
/// End Escape Menu Section
|
/// End Escape Menu Section
|
||||||
/// Koniec sekcji Menu pauzy
|
/// Koniec sekcji Menu pauzy
|
||||||
}
|
},
|
||||||
|
|
||||||
|
vector_map: {
|
||||||
|
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
@ -68,6 +68,9 @@ VoxygenLocalization(
|
|||||||
"common.none": "Kein",
|
"common.none": "Kein",
|
||||||
"common.error": "Fehler",
|
"common.error": "Fehler",
|
||||||
"common.fatal_error": "Fataler Fehler",
|
"common.fatal_error": "Fataler Fehler",
|
||||||
|
"common.decline": "Ablehnen",
|
||||||
|
"common.you": "Ihr",
|
||||||
|
"common.automatic": "Auto",
|
||||||
/// End Common section
|
/// End Common section
|
||||||
|
|
||||||
// Message when connection to the server is lost
|
// Message when connection to the server is lost
|
||||||
@ -295,6 +298,10 @@ magischen Gegenstände ergattern?"#,
|
|||||||
"hud.settings.fluid_rendering_mode.cheap": "Niedrig",
|
"hud.settings.fluid_rendering_mode.cheap": "Niedrig",
|
||||||
"hud.settings.fluid_rendering_mode.shiny": "Hoch",
|
"hud.settings.fluid_rendering_mode.shiny": "Hoch",
|
||||||
"hud.settings.cloud_rendering_mode.regular": "Realistisch",
|
"hud.settings.cloud_rendering_mode.regular": "Realistisch",
|
||||||
|
"hud.settings.particles": "Partikel",
|
||||||
|
"hud.settings.resolution": "Auflösung",
|
||||||
|
"hud.settings.bit_depth": "Bittiefe",
|
||||||
|
"hud.settings.refresh_rate": "Bildwiederholrate",
|
||||||
"hud.settings.fullscreen": "Vollbild",
|
"hud.settings.fullscreen": "Vollbild",
|
||||||
"hud.settings.lighting_rendering_mode": "Beleuchtung",
|
"hud.settings.lighting_rendering_mode": "Beleuchtung",
|
||||||
"hud.settings.lighting_rendering_mode.ashikhmin": "Typ A",
|
"hud.settings.lighting_rendering_mode.ashikhmin": "Typ A",
|
||||||
@ -315,7 +322,7 @@ magischen Gegenstände ergattern?"#,
|
|||||||
"hud.settings.unbound": "-",
|
"hud.settings.unbound": "-",
|
||||||
"hud.settings.reset_keybinds": "Auf Standard zurücksetzen",
|
"hud.settings.reset_keybinds": "Auf Standard zurücksetzen",
|
||||||
|
|
||||||
"hud.social": "Sozial",
|
"hud.social": "Andere Spieler",
|
||||||
"hud.social.online": "Online",
|
"hud.social.online": "Online",
|
||||||
"hud.social.friends": "Freunde",
|
"hud.social.friends": "Freunde",
|
||||||
"hud.social.not_yet_available": "Noch nicht verfügbar",
|
"hud.social.not_yet_available": "Noch nicht verfügbar",
|
||||||
@ -323,6 +330,23 @@ magischen Gegenstände ergattern?"#,
|
|||||||
"hud.social.play_online_fmt": "{nb_player} Spieler online",
|
"hud.social.play_online_fmt": "{nb_player} Spieler online",
|
||||||
|
|
||||||
"hud.spell": "Zauber",
|
"hud.spell": "Zauber",
|
||||||
|
|
||||||
|
"hud.social.name" : "Name",
|
||||||
|
"hud.social.level" : "Lvl",
|
||||||
|
"hud.social.zone" : "Gebiet",
|
||||||
|
|
||||||
|
"hud.group": "Gruppe",
|
||||||
|
"hud.group.invite_to_join": "{name} lädt euch in seine Gruppe ein!",
|
||||||
|
"hud.group.invite": "Einladen",
|
||||||
|
"hud.group.kick": "Kicken",
|
||||||
|
"hud.group.assign_leader": "Anführer",
|
||||||
|
"hud.group.leave": "Gruppe Verlassen",
|
||||||
|
"hud.group.dead" : "Tot",
|
||||||
|
"hud.group.out_of_range": "Außer Reichweite",
|
||||||
|
"hud.group.add_friend": "Freund hinzufügen",
|
||||||
|
"hud.group.link_group": "Gruppen verbinden",
|
||||||
|
"hud.group.in_menu": "In Menü",
|
||||||
|
"hud.group.members": "Gruppen Mitglieder",
|
||||||
|
|
||||||
"hud.crafting": "Herstellen",
|
"hud.crafting": "Herstellen",
|
||||||
"hud.crafting.recipes": "Rezepte",
|
"hud.crafting.recipes": "Rezepte",
|
||||||
@ -385,6 +409,9 @@ magischen Gegenstände ergattern?"#,
|
|||||||
"gameinput.freelook": "Freie Sicht",
|
"gameinput.freelook": "Freie Sicht",
|
||||||
"gameinput.autowalk": "Automatisch Laufen",
|
"gameinput.autowalk": "Automatisch Laufen",
|
||||||
"gameinput.dance": "Tanzen",
|
"gameinput.dance": "Tanzen",
|
||||||
|
"gameinput.declinegroupinvite": "Ablehnen",
|
||||||
|
"gameinput.acceptgroupinvite": "Annehmen",
|
||||||
|
"gameinput.select": "Auswählen",
|
||||||
|
|
||||||
/// End GameInput section
|
/// End GameInput section
|
||||||
|
|
||||||
|
@ -65,11 +65,14 @@ VoxygenLocalization(
|
|||||||
"common.create": "Create",
|
"common.create": "Create",
|
||||||
"common.okay": "Okay",
|
"common.okay": "Okay",
|
||||||
"common.accept": "Accept",
|
"common.accept": "Accept",
|
||||||
|
"common.decline": "Decline",
|
||||||
"common.disclaimer": "Disclaimer",
|
"common.disclaimer": "Disclaimer",
|
||||||
"common.cancel": "Cancel",
|
"common.cancel": "Cancel",
|
||||||
"common.none": "None",
|
"common.none": "None",
|
||||||
"common.error": "Error",
|
"common.error": "Error",
|
||||||
"common.fatal_error": "Fatal Error",
|
"common.fatal_error": "Fatal Error",
|
||||||
|
"common.you": "You",
|
||||||
|
"common.automatic": "Auto",
|
||||||
|
|
||||||
// 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!
|
||||||
@ -295,6 +298,10 @@ magically infused items?"#,
|
|||||||
"hud.settings.fluid_rendering_mode.cheap": "Cheap",
|
"hud.settings.fluid_rendering_mode.cheap": "Cheap",
|
||||||
"hud.settings.fluid_rendering_mode.shiny": "Shiny",
|
"hud.settings.fluid_rendering_mode.shiny": "Shiny",
|
||||||
"hud.settings.cloud_rendering_mode.regular": "Regular",
|
"hud.settings.cloud_rendering_mode.regular": "Regular",
|
||||||
|
"hud.settings.particles": "Particles",
|
||||||
|
"hud.settings.resolution": "Resolution",
|
||||||
|
"hud.settings.bit_depth": "Bit Depth",
|
||||||
|
"hud.settings.refresh_rate": "Refresh Rate",
|
||||||
"hud.settings.fullscreen": "Fullscreen",
|
"hud.settings.fullscreen": "Fullscreen",
|
||||||
"hud.settings.save_window_size": "Save window size",
|
"hud.settings.save_window_size": "Save window size",
|
||||||
"hud.settings.lighting_rendering_mode": "Lighting Rendering Mode",
|
"hud.settings.lighting_rendering_mode": "Lighting Rendering Mode",
|
||||||
@ -318,12 +325,16 @@ magically infused items?"#,
|
|||||||
"hud.settings.unbound": "None",
|
"hud.settings.unbound": "None",
|
||||||
"hud.settings.reset_keybinds": "Reset to Defaults",
|
"hud.settings.reset_keybinds": "Reset to Defaults",
|
||||||
|
|
||||||
"hud.social": "Social",
|
"hud.social": "Other Players",
|
||||||
"hud.social.online": "Online",
|
"hud.social.online": "Online:",
|
||||||
"hud.social.friends": "Friends",
|
"hud.social.friends": "Friends",
|
||||||
"hud.social.not_yet_available": "Not yet available",
|
"hud.social.not_yet_available": "Not yet available",
|
||||||
"hud.social.faction": "Faction",
|
"hud.social.faction": "Faction",
|
||||||
"hud.social.play_online_fmt": "{nb_player} player(s) online",
|
"hud.social.play_online_fmt": "{nb_player} player(s) online",
|
||||||
|
"hud.social.name": "Name",
|
||||||
|
"hud.social.level": "Level",
|
||||||
|
"hud.social.zone": "Zone",
|
||||||
|
|
||||||
|
|
||||||
"hud.crafting": "Crafting",
|
"hud.crafting": "Crafting",
|
||||||
"hud.crafting.recipes": "Recipes",
|
"hud.crafting.recipes": "Recipes",
|
||||||
@ -331,7 +342,20 @@ magically infused items?"#,
|
|||||||
"hud.crafting.craft": "Craft",
|
"hud.crafting.craft": "Craft",
|
||||||
"hud.crafting.tool_cata": "Requires:",
|
"hud.crafting.tool_cata": "Requires:",
|
||||||
|
|
||||||
"hud.spell": "Spells",
|
"hud.group": "Group",
|
||||||
|
"hud.group.invite_to_join": "{name} invited you to their group!",
|
||||||
|
"hud.group.invite": "Invite",
|
||||||
|
"hud.group.kick": "Kick",
|
||||||
|
"hud.group.assign_leader": "Assign Leader",
|
||||||
|
"hud.group.leave": "Leave Group",
|
||||||
|
"hud.group.dead" : "Dead",
|
||||||
|
"hud.group.out_of_range": "Out of range",
|
||||||
|
"hud.group.add_friend": "Add to Friends",
|
||||||
|
"hud.group.link_group": "Link Groups",
|
||||||
|
"hud.group.in_menu": "In Menu",
|
||||||
|
"hud.group.members": "Group Members",
|
||||||
|
|
||||||
|
"hud.spell": "Spells",
|
||||||
|
|
||||||
"hud.free_look_indicator": "Free look active",
|
"hud.free_look_indicator": "Free look active",
|
||||||
"hud.auto_walk_indicator": "Auto walk active",
|
"hud.auto_walk_indicator": "Auto walk active",
|
||||||
@ -343,7 +367,7 @@ magically infused items?"#,
|
|||||||
|
|
||||||
"gameinput.primary": "Basic Attack",
|
"gameinput.primary": "Basic Attack",
|
||||||
"gameinput.secondary": "Secondary Attack/Block/Aim",
|
"gameinput.secondary": "Secondary Attack/Block/Aim",
|
||||||
"gameinput.slot1": "Hotbar Slot 1",
|
"gameinput.slot1": "Hotbar Slot 1",
|
||||||
"gameinput.slot2": "Hotbar Slot 2",
|
"gameinput.slot2": "Hotbar Slot 2",
|
||||||
"gameinput.slot3": "Hotbar Slot 3",
|
"gameinput.slot3": "Hotbar Slot 3",
|
||||||
"gameinput.slot4": "Hotbar Slot 4",
|
"gameinput.slot4": "Hotbar Slot 4",
|
||||||
@ -389,7 +413,10 @@ magically infused items?"#,
|
|||||||
"gameinput.freelook": "Free Look",
|
"gameinput.freelook": "Free Look",
|
||||||
"gameinput.autowalk": "Auto Walk",
|
"gameinput.autowalk": "Auto Walk",
|
||||||
"gameinput.dance": "Dance",
|
"gameinput.dance": "Dance",
|
||||||
|
"gameinput.select": "Select Entity",
|
||||||
|
"gameinput.acceptgroupinvite": "Accept Group Invite",
|
||||||
|
"gameinput.declinegroupinvite": "Decline Group Invite",
|
||||||
|
|
||||||
/// End GameInput section
|
/// End GameInput section
|
||||||
|
|
||||||
|
|
||||||
@ -448,7 +475,8 @@ Protection
|
|||||||
"Press 'F1' to see all default keybindings.",
|
"Press 'F1' to see all default keybindings.",
|
||||||
"You can type /say or /s to only chat with players directly around you.",
|
"You can type /say or /s to only chat with players directly around you.",
|
||||||
"You can type /region or /r to only chat with players a couple of hundred blocks around you.",
|
"You can type /region or /r to only chat with players a couple of hundred blocks around you.",
|
||||||
"To send private message type /tell followed by a player name and your message.",
|
"You can type /group or /g to only chat with players in your current group.",
|
||||||
|
"To send private messages type /tell followed by a player name and your message.",
|
||||||
"NPCs with the same level can have a different difficulty.",
|
"NPCs with the same level can have a different difficulty.",
|
||||||
"Look at the ground for food, chests and other loot!",
|
"Look at the ground for food, chests and other loot!",
|
||||||
"Inventory filled with food? Try crafting better food from it!",
|
"Inventory filled with food? Try crafting better food from it!",
|
||||||
@ -459,7 +487,9 @@ Protection
|
|||||||
"Press 'J' to dance. Party!",
|
"Press 'J' to dance. Party!",
|
||||||
"Press 'L-Shift' to open your Glider and conquer the skies.",
|
"Press 'L-Shift' to open your Glider and conquer the skies.",
|
||||||
"Veloren is still in Pre-Alpha. We do our best to improve it every day!",
|
"Veloren is still in Pre-Alpha. We do our best to improve it every day!",
|
||||||
"If you want to join the Dev-Team or just have a chat with us join our Discord-Server.",
|
"If you want to join the Dev-Team or just have a chat with us join our Discord-Server.",
|
||||||
|
"You can toggle showing your amount of health on the healthbar in the settings.",
|
||||||
|
"In order to see your stats click the 'Stats' button in your inventory.",
|
||||||
],
|
],
|
||||||
"npc.speech.villager_under_attack": [
|
"npc.speech.villager_under_attack": [
|
||||||
"Help, I'm under attack!",
|
"Help, I'm under attack!",
|
||||||
|
@ -44,10 +44,10 @@ VoxygenLocalization(
|
|||||||
"common.resume": "Reprendre",
|
"common.resume": "Reprendre",
|
||||||
"common.characters": "Personages",
|
"common.characters": "Personages",
|
||||||
"common.close": "Fermer",
|
"common.close": "Fermer",
|
||||||
"common.create": "Créer",
|
|
||||||
"common.back": "Retour",
|
|
||||||
"common.yes": "Oui",
|
"common.yes": "Oui",
|
||||||
"common.no": "Non",
|
"common.no": "Non",
|
||||||
|
"common.back": "Retour",
|
||||||
|
"common.create": "Créer",
|
||||||
"common.okay": "Compris",
|
"common.okay": "Compris",
|
||||||
"common.accept": "Accepter",
|
"common.accept": "Accepter",
|
||||||
"common.disclaimer": "Avertissement",
|
"common.disclaimer": "Avertissement",
|
||||||
@ -56,6 +56,12 @@ VoxygenLocalization(
|
|||||||
"common.error": "Erreur",
|
"common.error": "Erreur",
|
||||||
"common.fatal_error": "Erreur Fatale",
|
"common.fatal_error": "Erreur Fatale",
|
||||||
|
|
||||||
|
// Message when connection to the server is lost
|
||||||
|
"common.connection_lost": r#"Connexion perdue !
|
||||||
|
Le serveur a-il redémarré?
|
||||||
|
Le client est-il à jour?"#,
|
||||||
|
|
||||||
|
|
||||||
"common.species.orc": "Orc",
|
"common.species.orc": "Orc",
|
||||||
"common.species.human": "Humain",
|
"common.species.human": "Humain",
|
||||||
"common.species.dwarf": "Nain",
|
"common.species.dwarf": "Nain",
|
||||||
@ -69,27 +75,28 @@ VoxygenLocalization(
|
|||||||
"common.weapons.bow": "Arc",
|
"common.weapons.bow": "Arc",
|
||||||
"common.weapons.hammer": "Marteau",
|
"common.weapons.hammer": "Marteau",
|
||||||
|
|
||||||
// Message when connection to the server is lost
|
|
||||||
"common.connection_lost": r#"Connexion perdue!
|
|
||||||
Le serveur a-il redémarré?
|
|
||||||
Le client est-il à jour?"#,
|
|
||||||
|
|
||||||
|
|
||||||
// Main screen texts
|
// Main screen texts
|
||||||
"main.connecting": "Connexion",
|
"main.connecting": "Connexion",
|
||||||
"main.creating_world": "Création du monde",
|
"main.creating_world": "Création du monde",
|
||||||
|
"main.tip": "Astuce:",
|
||||||
|
|
||||||
|
// Annonce de bienvenui qui apparaît la première fois que Veloren est lancé
|
||||||
"main.notice": r#"Bienvenue dans la version alpha de Veloren!
|
"main.notice": r#"Bienvenue dans la version alpha de Veloren!
|
||||||
|
|
||||||
Avant de commencer à vous amuser, merci de garder les choses suivantes en tête:
|
Avant de commencer à vous amuser, merci de garder les choses suivantes en tête:
|
||||||
|
|
||||||
- Il s'agit d'une version alpha très jeune. Attendez-vous à des bugs, un gameplay non terminé, des mécaniques non peaufinées et des fonctionalités manquantes.
|
- Il s'agit d'une version alpha très jeune. Attendez-vous à des bugs, un gameplay non terminé, des mécaniques non peaufinées et des fonctionalités manquantes.
|
||||||
- Si vous avez des retours constructifs ou avez detecté un bug, vous pouvez nous contacter via Reddit, GitLab ou notre communauté Discord.
|
|
||||||
- Veloren est un logiciel open-source sour licence GPL3. Cela signifit que vous êtes libre de jouer, modfier et redistribuer le jeu comme il vous semble (licence contaminante sous GPL 3 pour toute modification)
|
|
||||||
- Veloren est un projet communautaire à but non-lucratif développé par des bénévolles.
|
|
||||||
Si vous apprecier ce jeu, vous êtes les bienvenues pour rejoindre les équipes de développement ou d'artistes!
|
|
||||||
- Le genre 'Voxel RPG' est un genre à part entiere. Les FPS étaient appelé Doom-like. De la même façon, nous essayons de construire un genre à part entière. Ce jeu n'est pas un clone et ses mechaniques changeront au cours du developpement.
|
|
||||||
|
|
||||||
Merci d'avoir pris le temps de lire cette notice, nous esperons que vous apprecierez le jeu!
|
- Si vous avez des retours constructifs ou avez detecté un bug, vous pouvez nous contacter via Reddit, GitLab ou le serveur de notre communauté Discord.
|
||||||
|
|
||||||
|
- Veloren est un logiciel open-source sour licence GPL3. Cela signifit que vous êtes libre de jouer, modfier et redistribuer le jeu comme il vous semble (licence contaminante sous GPL 3 pour toute modification)
|
||||||
|
|
||||||
|
- Veloren est un projet communautaire à but non-lucratif développé par des bénévoles.
|
||||||
|
Si vous apprecier ce jeu, vous êtes les bienvenues pour rejoindre les équipes de développement ou d'artistes!
|
||||||
|
|
||||||
|
Merci d'avoir pris le temps de lire cette annonce, nous espérons que vous apprecierez le jeu!
|
||||||
|
|
||||||
~ L'équipe de Veloren"#,
|
~ L'équipe de Veloren"#,
|
||||||
|
|
||||||
@ -107,7 +114,7 @@ https://account.veloren.net."#,
|
|||||||
"main.login.server_full": "Serveur plein",
|
"main.login.server_full": "Serveur plein",
|
||||||
"main.login.untrusted_auth_server": "Le serveur d'authentification n'est pas de confiance",
|
"main.login.untrusted_auth_server": "Le serveur d'authentification n'est pas de confiance",
|
||||||
"main.login.outdated_client_or_server": "ServeurPasContent: Les versions sont probablement incompatibles, verifiez les mises à jour.",
|
"main.login.outdated_client_or_server": "ServeurPasContent: Les versions sont probablement incompatibles, verifiez les mises à jour.",
|
||||||
"main.login.timeout": "DélaiEcoulé: Le serveur n'a pas repondu à temps. (Surchage ou Problèmes réseau).",
|
"main.login.timeout": "DélaiEcoulé: Le serveur n'a pas repondu à temps. (Surchage ou problèmes réseau).",
|
||||||
"main.login.server_shut_down": "Extinction du Serveur",
|
"main.login.server_shut_down": "Extinction du Serveur",
|
||||||
"main.login.already_logged_in": "Vous êtes déjà connecté à ce serveur.",
|
"main.login.already_logged_in": "Vous êtes déjà connecté à ce serveur.",
|
||||||
"main.login.network_error": "Problème Réseau",
|
"main.login.network_error": "Problème Réseau",
|
||||||
@ -115,10 +122,10 @@ https://account.veloren.net."#,
|
|||||||
"main.login.invalid_character": "Le personnage sélectionné n'est pas valide",
|
"main.login.invalid_character": "Le personnage sélectionné n'est pas valide",
|
||||||
"main.login.client_crashed": "Le client a planté",
|
"main.login.client_crashed": "Le client a planté",
|
||||||
"main.login.not_on_whitelist": "Vous devez être ajouté à la liste blanche par un Admin pour pouvoir entrer",
|
"main.login.not_on_whitelist": "Vous devez être ajouté à la liste blanche par un Admin pour pouvoir entrer",
|
||||||
"main.tip": "Astuce:",
|
|
||||||
|
|
||||||
/// End Main screen section
|
/// End Main screen section
|
||||||
|
|
||||||
|
|
||||||
///Début section Hud
|
///Début section Hud
|
||||||
"hud.do_not_show_on_startup": "Ne pas afficher au démarage",
|
"hud.do_not_show_on_startup": "Ne pas afficher au démarage",
|
||||||
"hud.show_tips": "Voir les astuces",
|
"hud.show_tips": "Voir les astuces",
|
||||||
@ -126,11 +133,10 @@ https://account.veloren.net."#,
|
|||||||
"hud.you_died": "Vous êtes mort",
|
"hud.you_died": "Vous êtes mort",
|
||||||
"hud.waypoint_saved": "Point de Repère Sauvegardé",
|
"hud.waypoint_saved": "Point de Repère Sauvegardé",
|
||||||
|
|
||||||
|
|
||||||
"hud.press_key_to_show_keybindings_fmt": "Appuyer sur {key} pour afficher les contrôles",
|
"hud.press_key_to_show_keybindings_fmt": "Appuyer sur {key} pour afficher les contrôles",
|
||||||
"hud.press_key_to_show_debug_info_fmt": "Appuyer sur {key} pour afficher les informations de debogage",
|
"hud.press_key_to_show_debug_info_fmt": "Appuyer sur {key} pour afficher les informations de debogage",
|
||||||
"hud.press_key_to_toggle_debug_info_fmt": "Appuyer sur {key} pour activer les informations de debogage",
|
|
||||||
"hud.press_key_to_toggle_keybindings_fmt": "Appuyer sur {key} pour afficher les contrôles",
|
"hud.press_key_to_toggle_keybindings_fmt": "Appuyer sur {key} pour afficher les contrôles",
|
||||||
|
"hud.press_key_to_toggle_debug_info_fmt": "Appuyer sur {key} pour activer les informations de debogage",
|
||||||
|
|
||||||
// Sorties Tchat
|
// Sorties Tchat
|
||||||
"hud.chat.online_msg": "[{name}] est maintenant en ligne.",
|
"hud.chat.online_msg": "[{name}] est maintenant en ligne.",
|
||||||
@ -147,7 +153,6 @@ https://account.veloren.net."#,
|
|||||||
// Respawn message
|
// Respawn message
|
||||||
"hud.press_key_to_respawn": r#"Appuyez sur {key} pour réapparaitre au dernier feu de camp visité"#,
|
"hud.press_key_to_respawn": r#"Appuyez sur {key} pour réapparaitre au dernier feu de camp visité"#,
|
||||||
|
|
||||||
|
|
||||||
/// Welcome message
|
/// Welcome message
|
||||||
"hud.welcome": r#"Bienvenue dans la version Alpha de Veloren!
|
"hud.welcome": r#"Bienvenue dans la version Alpha de Veloren!
|
||||||
|
|
||||||
@ -155,16 +160,12 @@ https://account.veloren.net."#,
|
|||||||
Quelques astuces avant de démarrer:
|
Quelques astuces avant de démarrer:
|
||||||
|
|
||||||
|
|
||||||
POINT LE PLUS IMPORTANT: Pour configurer votre point de resurection tapez
|
|
||||||
/waypoint dans le chat. (y-compris si vous être déjà mort!)
|
|
||||||
|
|
||||||
|
|
||||||
Appuyer sur F1 pour voir les commandes disponibles.
|
Appuyer sur F1 pour voir les commandes disponibles.
|
||||||
|
|
||||||
Tapez /help dans le chat pour voir les commandes du chat.
|
Tapez /help dans le chat pour voir les commandes du chat.
|
||||||
|
|
||||||
|
|
||||||
Des coffres et autres objets sont disposés aléatoirement dans le monde!
|
Des coffres et autres objets sont disposés aléatoirement dans le monde !
|
||||||
|
|
||||||
Utilisez le click droit pour le collecter.
|
Utilisez le click droit pour le collecter.
|
||||||
|
|
||||||
@ -174,15 +175,18 @@ Double cliquez sur les éléments de votre sac pour les utiliser ou les équiper
|
|||||||
|
|
||||||
Jettez-les en cliquant sur un element puis en cliquant en dehors du sac.
|
Jettez-les en cliquant sur un element puis en cliquant en dehors du sac.
|
||||||
|
|
||||||
|
|
||||||
Les nuits peuvent être très sombre à Veloren.
|
Les nuits peuvent être très sombre à Veloren.
|
||||||
|
|
||||||
Allumez votre lanterne en tapant /lantern dans le chat.
|
Allumez votre lanterne en appuyant sur 'G'.
|
||||||
|
|
||||||
|
|
||||||
|
Vous souhaitez libérer votre souris pour fermer cette fenêtre? Appuyez sur TAB!
|
||||||
|
|
||||||
Vous souhaitez libérer votre souris pour fermer cette fenêtre? Tapez sur TAB!
|
|
||||||
|
|
||||||
Profitez de votre séjour dans le monde de Veloren."#,
|
Profitez de votre séjour dans le monde de Veloren."#,
|
||||||
|
|
||||||
"hud.temp_quest_headline": r#"S'il vous plaît, aidez nous voyageur!"#,
|
"hud.temp_quest_headline": r#"S'il vous plaît, aidez-nous voyageur!"#,
|
||||||
"hud.temp_quest_text": r#"Des donjons remplis de cultistes malfaisants
|
"hud.temp_quest_text": r#"Des donjons remplis de cultistes malfaisants
|
||||||
sont apparus tout autour de nos paisibles villages!
|
sont apparus tout autour de nos paisibles villages!
|
||||||
|
|
||||||
@ -204,7 +208,7 @@ objets magiques ?"#,
|
|||||||
"hud.bag.stats": "Attributs",
|
"hud.bag.stats": "Attributs",
|
||||||
"hud.bag.head": "Tête",
|
"hud.bag.head": "Tête",
|
||||||
"hud.bag.neck": "Cou",
|
"hud.bag.neck": "Cou",
|
||||||
"hud.bag.tabard": "Tabar",
|
"hud.bag.tabard": "Tabard",
|
||||||
"hud.bag.shoulders": "Epaules",
|
"hud.bag.shoulders": "Epaules",
|
||||||
"hud.bag.chest": "Torse",
|
"hud.bag.chest": "Torse",
|
||||||
"hud.bag.hands": "Mains",
|
"hud.bag.hands": "Mains",
|
||||||
@ -215,17 +219,17 @@ objets magiques ?"#,
|
|||||||
"hud.bag.legs": "Jambes",
|
"hud.bag.legs": "Jambes",
|
||||||
"hud.bag.feet": "Pieds",
|
"hud.bag.feet": "Pieds",
|
||||||
"hud.bag.mainhand": "Main Dominante",
|
"hud.bag.mainhand": "Main Dominante",
|
||||||
"hud.bag.offhand": "Main Dominée",
|
"hud.bag.offhand": "Main Secondaire",
|
||||||
|
|
||||||
|
|
||||||
// Carte et journal de quetes
|
// Carte et journal de quetes
|
||||||
"hud.map.map_title": "Carte",
|
"hud.map.map_title": "Carte",
|
||||||
"hud.map.qlog_title": "Quêtes",
|
"hud.map.qlog_title": "Quêtes",
|
||||||
|
|
||||||
|
|
||||||
//Paramètres
|
//Paramètres
|
||||||
"hud.settings.general": "Général",
|
"hud.settings.general": "Général",
|
||||||
"hud.settings.none": "Aucun",
|
"hud.settings.none": "Aucun",
|
||||||
"hud.settings.press_behavior.toggle": "Activer/Desactiver",
|
"hud.settings.press_behavior.toggle": "Activer/Désactiver",
|
||||||
"hud.settings.press_behavior.hold": "Maintenir",
|
"hud.settings.press_behavior.hold": "Maintenir",
|
||||||
"hud.settings.help_window": "Fenêtre d'aide",
|
"hud.settings.help_window": "Fenêtre d'aide",
|
||||||
"hud.settings.debug_info": "Information de débogage",
|
"hud.settings.debug_info": "Information de débogage",
|
||||||
@ -237,12 +241,12 @@ objets magiques ?"#,
|
|||||||
"hud.settings.transparency": "Transparence",
|
"hud.settings.transparency": "Transparence",
|
||||||
"hud.settings.hotbar": "Barre d'action",
|
"hud.settings.hotbar": "Barre d'action",
|
||||||
"hud.settings.toggle_shortcuts": "Activer les raccourcis",
|
"hud.settings.toggle_shortcuts": "Activer les raccourcis",
|
||||||
"hud.settings.toggle_bar_experience": "Activer la barre d'experience",
|
"hud.settings.toggle_bar_experience": "Activer la barre d'expérience",
|
||||||
"hud.settings.scrolling_combat_text": "Dégats de combat",
|
"hud.settings.scrolling_combat_text": "Dégats de combat",
|
||||||
"hud.settings.single_damage_number": "Dégat adversaire (par dégat)",
|
"hud.settings.single_damage_number": "Dégats infligés",
|
||||||
"hud.settings.cumulated_damage": "Dégat adversaire (cumulé)",
|
"hud.settings.cumulated_damage": "Dégat infligés cumulés",
|
||||||
"hud.settings.incoming_damage": "Dégat personnage (par dégat)",
|
"hud.settings.incoming_damage": "Dégats reçus",
|
||||||
"hud.settings.cumulated_incoming_damage": "Dégat personnage (cumulé)",
|
"hud.settings.cumulated_incoming_damage": "Dégats reçus cumulés",
|
||||||
"hud.settings.speech_bubble": "Bulle de dialogue",
|
"hud.settings.speech_bubble": "Bulle de dialogue",
|
||||||
"hud.settings.speech_bubble_dark_mode": "Bulle de dialogue Mode Sombre",
|
"hud.settings.speech_bubble_dark_mode": "Bulle de dialogue Mode Sombre",
|
||||||
"hud.settings.speech_bubble_icon": "Icône Bulle de dialogue",
|
"hud.settings.speech_bubble_icon": "Icône Bulle de dialogue",
|
||||||
@ -251,8 +255,8 @@ objets magiques ?"#,
|
|||||||
"hud.settings.percentages": "Pourcentages",
|
"hud.settings.percentages": "Pourcentages",
|
||||||
"hud.settings.chat": "Tchat",
|
"hud.settings.chat": "Tchat",
|
||||||
"hud.settings.background_transparency": "Transparence du fond",
|
"hud.settings.background_transparency": "Transparence du fond",
|
||||||
"hud.settings.chat_character_name": "Nom des personnages dans le tchat",
|
"hud.settings.chat_character_name": "Noms des personnages dans le tchat",
|
||||||
"hud.settings.loading_tips": "Astuces de chargement",
|
"hud.settings.loading_tips": "Astuces sur l'écran de chargement",
|
||||||
|
|
||||||
"hud.settings.pan_sensitivity": "Sensibilité de la souris",
|
"hud.settings.pan_sensitivity": "Sensibilité de la souris",
|
||||||
"hud.settings.zoom_sensitivity": "Sensibilité du zoom",
|
"hud.settings.zoom_sensitivity": "Sensibilité du zoom",
|
||||||
@ -287,11 +291,11 @@ objets magiques ?"#,
|
|||||||
"hud.settings.reset_keybinds": "Réinitialiser touches par défaut",
|
"hud.settings.reset_keybinds": "Réinitialiser touches par défaut",
|
||||||
|
|
||||||
"hud.social": "Social",
|
"hud.social": "Social",
|
||||||
|
"hud.social.online": "En ligne",
|
||||||
"hud.social.friends": "Amis",
|
"hud.social.friends": "Amis",
|
||||||
"hud.social.faction": "Faction",
|
|
||||||
"hud.social.online": "Jeu en ligne",
|
|
||||||
"hud.social.not_yet_available": "Pas encore disponible",
|
"hud.social.not_yet_available": "Pas encore disponible",
|
||||||
"hud.social.play_online_fmt": "{nb_player} joueurs en ligne",
|
"hud.social.faction": "Faction",
|
||||||
|
"hud.social.play_online_fmt": "{nb_player} joueur(s) en ligne",
|
||||||
|
|
||||||
"hud.crafting": "Fabrication",
|
"hud.crafting": "Fabrication",
|
||||||
"hud.crafting.recipes": "Recettes",
|
"hud.crafting.recipes": "Recettes",
|
||||||
@ -304,9 +308,11 @@ objets magiques ?"#,
|
|||||||
"hud.free_look_indicator": "Vue libre active",
|
"hud.free_look_indicator": "Vue libre active",
|
||||||
"hud.auto_walk_indicator": "Marche automatique active",
|
"hud.auto_walk_indicator": "Marche automatique active",
|
||||||
|
|
||||||
//Fin Section Hud
|
/// Fin Section Hud
|
||||||
|
|
||||||
|
|
||||||
/// Debut de section GameInput
|
/// Debut de section GameInput
|
||||||
|
|
||||||
"gameinput.primary": "Attaque Basique",
|
"gameinput.primary": "Attaque Basique",
|
||||||
"gameinput.secondary": "Attaque Secondaire/Bloquer/Viser",
|
"gameinput.secondary": "Attaque Secondaire/Bloquer/Viser",
|
||||||
"gameinput.slot1": "Emplacement rapide 1",
|
"gameinput.slot1": "Emplacement rapide 1",
|
||||||
@ -323,7 +329,7 @@ objets magiques ?"#,
|
|||||||
"gameinput.togglecursor": "Activer/Desactiver Curseur",
|
"gameinput.togglecursor": "Activer/Desactiver Curseur",
|
||||||
"gameinput.help": "Activer/Desactiver Fenêtre d'aide",
|
"gameinput.help": "Activer/Desactiver Fenêtre d'aide",
|
||||||
"gameinput.toggleinterface": "Activer/Desactiver Interface",
|
"gameinput.toggleinterface": "Activer/Desactiver Interface",
|
||||||
"gameinput.toggledebug": "Activer/Desactiver IPS et Infos Debogage",
|
"gameinput.toggledebug": "Activer/Desactiver FPS et Infos Debogage",
|
||||||
"gameinput.screenshot": "Prendre une capture d'écran",
|
"gameinput.screenshot": "Prendre une capture d'écran",
|
||||||
"gameinput.toggleingameui": "Activer/Desactiver Noms de joueurs",
|
"gameinput.toggleingameui": "Activer/Desactiver Noms de joueurs",
|
||||||
"gameinput.fullscreen": "Activer/Desactiver Plein Ecran",
|
"gameinput.fullscreen": "Activer/Desactiver Plein Ecran",
|
||||||
@ -341,7 +347,7 @@ objets magiques ?"#,
|
|||||||
"gameinput.mount": "Monture",
|
"gameinput.mount": "Monture",
|
||||||
"gameinput.enter": "Entrer",
|
"gameinput.enter": "Entrer",
|
||||||
"gameinput.command": "Commande",
|
"gameinput.command": "Commande",
|
||||||
"gameinput.escape": "Fuir",
|
"gameinput.escape": "Menu principal/Fermer menu",
|
||||||
"gameinput.map": "Carte",
|
"gameinput.map": "Carte",
|
||||||
"gameinput.bag": "Sac",
|
"gameinput.bag": "Sac",
|
||||||
"gameinput.social": "Social",
|
"gameinput.social": "Social",
|
||||||
@ -358,43 +364,41 @@ objets magiques ?"#,
|
|||||||
|
|
||||||
/// End GameInput section
|
/// End GameInput section
|
||||||
|
|
||||||
/// Debut Section Menu Start Quitter
|
|
||||||
"esc_menu.quit_game": "Quitter le jeu",
|
|
||||||
"esc_menu.logout": "Se déconnecter",
|
|
||||||
|
|
||||||
/// Fin Section Menu Start Quitter
|
|
||||||
|
|
||||||
/// Debut de la section Création du personnage
|
/// Debut de la section Création du personnage
|
||||||
"char_selection.accessories": "Accessoires",
|
"char_selection.loading_characters": "Chargement des personnages...",
|
||||||
"char_selection.beard": "Barbe",
|
"char_selection.delete_permanently": "Supprimer définitivement ce personnage ?",
|
||||||
|
"char_selection.deleting_character": "Suppression du personnage...",
|
||||||
|
"char_selection.change_server": "Changer de serveur",
|
||||||
|
"char_selection.enter_world": "Entrer dans le monde",
|
||||||
|
"char_selection.logout": "Se déconnecter",
|
||||||
"char_selection.create_new_charater": "Créer un nouveau personnage",
|
"char_selection.create_new_charater": "Créer un nouveau personnage",
|
||||||
"char_selection.creating_character": "Création du personnage...",
|
"char_selection.creating_character": "Création du personnage...",
|
||||||
"char_selection.character_creation": "Création de personnages",
|
"char_selection.character_creation": "Création de personnage",
|
||||||
"char_selection.create_info_name": "Votre personnage doit avoir un prénom !",
|
|
||||||
"char_selection.deleting_character": "Suppression du personnage...",
|
|
||||||
"char_selection.enter_world": "Entrer dans le monde",
|
|
||||||
"char_selection.eyebrows": "Sourcils",
|
|
||||||
"char_selection.eye_color": "Couleur des yeux",
|
|
||||||
"char_selection.eyeshape": "Forme des yeux",
|
|
||||||
"char_selection.hair_style": "Coupe de cheveux",
|
|
||||||
"char_selection.hair_color": "Couleur des cheveux",
|
|
||||||
"char_selection.human_default": "Humain par défault",
|
"char_selection.human_default": "Humain par défault",
|
||||||
"char_selection.level_fmt": "Niveau {level_nb}",
|
"char_selection.level_fmt": "Niveau {level_nb}",
|
||||||
"char_selection.logout": "Se déconnecter",
|
"char_selection.uncanny_valley": "Région sauvage",
|
||||||
"char_selection.loading_characters": "Chargement des personnages...",
|
"char_selection.plains_of_uncertainty": "Plaines de l'Incertitude",
|
||||||
"char_selection.plains_of_uncertainty": "Plaines de l'incertitude",
|
"char_selection.beard": "Barbe",
|
||||||
"char_selection.skin": "Couleur de peau",
|
"char_selection.hair_style": "Coupe de cheveux",
|
||||||
"char_selection.uncanny_valley": "Vallée dérangeante",
|
"char_selection.hair_color": "Couleur des cheveux",
|
||||||
"char_selection.change_server": "Changer de serveur",
|
"char_selection.eye_color": "Couleur des yeux",
|
||||||
"char_selection.delete_permanently": "Supprimer définitivement ce personnage ?",
|
"char_selection.skin": "Couleur de la peau",
|
||||||
|
"char_selection.eyeshape": "Forme des yeux",
|
||||||
|
"char_selection.accessories": "Accessoires",
|
||||||
|
"char_selection.create_info_name": "Votre personnage doit avoir un prénom !",
|
||||||
|
|
||||||
/// Fin de la section Création du personnage
|
/// Fin de la section Création du personnage
|
||||||
|
|
||||||
|
/// Start character window section
|
||||||
"character_window.character_name": "Personnage",
|
"character_window.character_name": "Personnage",
|
||||||
|
// Character stats
|
||||||
"character_window.character_stats": r#"Endurance
|
"character_window.character_stats": r#"Endurance
|
||||||
|
|
||||||
Force
|
Force
|
||||||
|
|
||||||
Dexterité
|
Volonté
|
||||||
|
|
||||||
Protection
|
Protection
|
||||||
"#,
|
"#,
|
||||||
@ -408,105 +412,106 @@ Protection
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
vector_map: {
|
vector_map: {
|
||||||
"loading.tips": [
|
"loading.tips": [
|
||||||
"Appuie sur 'G' pour allumer ta lanterne.",
|
"Appuiez sur 'G' pour allumer ta lanterne.",
|
||||||
"Appuie sur 'F1' pour voir les raccourcis clavier par défaut.",
|
"Appuiez sur 'F1' pour voir les raccourcis clavier par défaut.",
|
||||||
"Tu peux écrire /say ou /s pour discuter aux joueurs directement à côté toi.",
|
"Vous pouvez taper /say ou /s pour discuter aux joueurs directement à côté toi.",
|
||||||
"Tu peux écrire /region ou /r pour discuter avec les joueurs situés à quelques centaines de blocs de toi.",
|
"Vous pouvez taper /region ou /r pour discuter avec les joueurs situés à quelques centaines de blocs de toi.",
|
||||||
"Pour envoyer un message privé, écrit /tell suivi par un nom de joueur puis ton message.",
|
"Pour envoyer un message privé, tapez /tell suivi par un nom de joueur puis votre message.",
|
||||||
"Des PNJs avec le même niveau peuvent varier en difficulté.",
|
"Des PNJs avec le même niveau peuvent varier en difficulté.",
|
||||||
"Regarde le sol pour trouver de la nourriture, des coffres et d'autres butins!",
|
"Regardez le sol pour trouver de la nourriture, des coffres et d'autres butins!",
|
||||||
"Ton inventaire est rempli de nourriture? Essaie de créer un meilleur repas avec!",
|
"Votre inventaire est rempli de nourriture? Essayez de créer un meilleur repas avec!",
|
||||||
"Tu cherches une activité? Les donjons sont marqués avec des points marron sur la carte!",
|
"Vous cherchez une activité? Les donjons sont marqués avec des points marron sur la carte !",
|
||||||
"N'oublie pas d'ajuster les graphiques pour ton système. Appuie sur 'N' pour ouvrir les paramètres.",
|
"N'oubliez pas d'ajuster les graphiques pour votre système. Appuyez sur 'N' pour ouvrir les paramètres.",
|
||||||
"Jouer à plusieurs est amusant! Appuie sur 'O' pour voir qui est en ligne.",
|
"Jouer à plusieurs est amusant! Appuyez sur 'O' pour voir qui est en ligne.",
|
||||||
"Un PNJ avec une tête-de-mort sous sa barre de vie est plus puissant que toi.",
|
"Un PNJ avec une tête de mort sous sa barre de vie est plus puissant que vous.",
|
||||||
"Appuie sur 'J' pour danser. C'est la fête!",
|
"Appuyez sur 'J' pour danser. C'est la fête !",
|
||||||
"Appuie sur 'L-Shift'pour ouvrir ton deltaplane et conquérir les cieux.",
|
"Appuyez sur 'L-Shift'pour ouvrir votre deltaplane et conquérir les cieux.",
|
||||||
"Veloren est encore Pre-Alpha. Nous faisons de notre mieux pour l'améliorer chaque jour!",
|
"Veloren est encore Pre-Alpha. Nous faisons de notre mieux pour l'améliorer chaque jour!",
|
||||||
"Si tu veux te joindre à l'équipe de développement ou juste discuter avec nous, rejoins notre serveur Discord.",
|
"Si vous voulez vous joindre à l'équipe de développement ou juste discuter avec nous, rejoignez notre serveur Discord.",
|
||||||
],
|
],
|
||||||
"npc.speech.villager_under_attack": [
|
"npc.speech.villager_under_attack": [
|
||||||
"À l'aide, on m'attaque!",
|
"À l'aide, on m'attaque !",
|
||||||
"À l'aide! On m'attaque!",
|
"À l'aide ! On m'attaque !",
|
||||||
"Aïe! On m'attaque!",
|
"Aïe ! On m'attaque !",
|
||||||
"Aïe! On m'attaque! À l'aide!",
|
"Aïe ! On m'attaque ! À l'aide !",
|
||||||
"Aidez-moi! On m'attaque!",
|
"Aidez-moi! On m'attaque !",
|
||||||
"On m'attaque! À l'aide!",
|
"On m'attaque ! À l'aide !",
|
||||||
"On m'attaque! Aidez-moi!",
|
"On m'attaque ! Aidez-moi !",
|
||||||
"À l'aide!",
|
"À l'aide !",
|
||||||
"À l'aide! À l'aide!",
|
"À l'aide ! À l'aide !",
|
||||||
"À l'aide! À l'aide! À l'aide!",
|
"À l'aide ! À l'aide ! À l'aide !",
|
||||||
"On m'attaque!",
|
"On m'attaque !",
|
||||||
"AAAHHH! On m'attaque!",
|
"AAAHHH ! On m'attaque !",
|
||||||
"AAAHHH! On m'attaque! À l'aide!",
|
"AAAHHH ! On m'attaque ! À l'aide !",
|
||||||
"À l'aide! Nous sommes attaqués!",
|
"À l'aide ! Nous sommes attaqués !",
|
||||||
"À l'aide! Assassin!",
|
"À l'aide ! Assassin !",
|
||||||
"À l'aide! Il y a un assassin en liberté!",
|
"À l'aide ! Il y a un assassin en liberté !",
|
||||||
"À l'aide! On essaie de me tuer!",
|
"À l'aide ! On essaie de me tuer !",
|
||||||
"Gardes, on m'attaque!",
|
"Gardes, on m'attaque !",
|
||||||
"Gardes! On m'attaque!",
|
"Gardes ! On m'attaque !",
|
||||||
"On m'attaque! Gardes!",
|
"On m'attaque ! Gardes !",
|
||||||
"À l'aide! Gardes! On m'attaque!",
|
"À l'aide ! Gardes ! On m'attaque !",
|
||||||
"Gardes! Venez vite!",
|
"Gardes ! Venez vite !",
|
||||||
"Gardes! Gardes!",
|
"Gardes ! Gardes !",
|
||||||
"Gardes! Un scélérat m'attaque!",
|
"Gardes ! Un scélérat m'attaque !",
|
||||||
"Gardes, abattez ce scélérat!",
|
"Gardes, abattez ce scélérat !",
|
||||||
"Gardes! Il y a un meurtrier!",
|
"Gardes ! Il y a un meurtrier !",
|
||||||
"Gardes! Aidez-moi!",
|
"Gardes ! Aidez-moi!",
|
||||||
"Vous ne vous en tirerez pas comme ça! Gardes!",
|
"Vous ne vous en tirerez pas comme ça! Gardes !",
|
||||||
"Monstre!",
|
"Monstre !",
|
||||||
"Aidez-moi!",
|
"Aidez-moi!",
|
||||||
"À l'aide! S'il vous plait!",
|
"À l'aide ! S'il vous plait !",
|
||||||
"Aïe! Gardes! À l'aide!",
|
"Aïe ! Gardes ! À l'aide !",
|
||||||
"Ils viennent pour moi !",
|
"Ils viennent pour moi !",
|
||||||
"À l'aide! À l'aide! Je me fais réprimer!",
|
"À l'aide ! À l'aide ! Je me fais réprimer !",
|
||||||
"Ah, nous voyons maintenant la violence inhérente au système.",
|
"Ah, nous voyons maintenant la violence inhérente au système.",
|
||||||
"C'est seulement une égratignure.",
|
"C'est seulement une égratignure.",
|
||||||
"Arrêtez ça!",
|
"Arrêtez ça !",
|
||||||
"Qu'est ce que je t'ai fait?!",
|
"Qu'est ce que je vous ai fait ?!",
|
||||||
"S'il te plaît arrête de m'attaquer!",
|
"S'il vous plaît arrêtez de m'attaquer !",
|
||||||
"Hé! Regardez où vous pointez cette chose!",
|
"Hé! Regardez où vous pointez cette chose !",
|
||||||
"Misérable, allez-vous-en!",
|
"Misérable, allez-vous-en !",
|
||||||
"Arrêtez! Partez! Arrêtez!",
|
"Arrêtez ! Partez ! Arrêtez !",
|
||||||
"Vous m'avez ennervé!",
|
"Vous m'avez ennervé !",
|
||||||
"Oi! Qui croyez-vous être?!",
|
"Oi ! Qui croyez-vous être ?!",
|
||||||
"J'aurais votre tête pour ça!",
|
"J'aurais votre tête pour ça !",
|
||||||
"Stoppez s'il vous plaît! Je ne transporte rien de valeur!",
|
"Arrêtez, s'il vous plaît ! Je ne transporte rien de valeur !",
|
||||||
"Je vais appeler mon frère, il est plus grand que moi!",
|
"Je vais appeler mon frère, il est plus grand que moi !",
|
||||||
"Nooon, Je vais le dire à ma mère!",
|
"Nooon, Je vais le dire à ma mère !",
|
||||||
"Soyez maudit!",
|
"Soyez maudit !",
|
||||||
"Ne faites pas ça.",
|
"Ne faites pas ça.",
|
||||||
"Ce n'était pas très gentil!",
|
"Ce n'était pas très gentil !",
|
||||||
"Ton arme fonctionne, tu peux la ranger maintenant!",
|
"Ton arme fonctionne, tu peux la ranger maintenant !",
|
||||||
"Épargnez-moi!",
|
"Épargnez-moi !",
|
||||||
"Pitié, J'ai une famille!",
|
"Pitié, J'ai une famille !",
|
||||||
"Je suis trop jeune pour mourrir!",
|
"Je suis trop jeune pour mourrir !",
|
||||||
"On peut en parler?",
|
"On peut en parler ?",
|
||||||
"La violence n'est jamais la solution!",
|
"La violence n'est jamais la solution !",
|
||||||
"Aujourd'hui est une très mauvaise journée...",
|
"Aujourd'hui est une très mauvaise journée...",
|
||||||
"Hé, ça fait mal!",
|
"Hé, ça fait mal !",
|
||||||
"Aïe!",
|
"Aïe !",
|
||||||
"Quelle impolitesse!",
|
"Quelle impolitesse !",
|
||||||
"Stop, je vous en prie!",
|
"Stop, je vous en prie !",
|
||||||
"Que la peste vous emporte!",
|
"Que la peste vous emporte !",
|
||||||
"Ce n'est pas amusant.",
|
"Ce n'est pas amusant.",
|
||||||
"Comment osez-vous?!",
|
"Comment osez-vous ?!",
|
||||||
"Vous allez payer!",
|
"Vous allez payer !",
|
||||||
"Continue et tu vas le regretter!",
|
"Continue et tu vas le regretter !",
|
||||||
"Ne m'obligez pas à vous faire du mal!",
|
"Ne m'obligez pas à vous faire du mal !",
|
||||||
"Il doit y avoir erreur!",
|
"Il doit y avoir erreur !",
|
||||||
"Vous n'avez pas besoin de faire ça!",
|
"Vous n'avez pas besoin de faire ça !",
|
||||||
"Fuyez, monstre!",
|
"Fuyez, monstre !",
|
||||||
"Ça fait vraiment mal!",
|
"Ça fait vraiment mal !",
|
||||||
"Pourquoi faites-vous cela?",
|
"Pourquoi faites-vous cela ?",
|
||||||
"Par les esprits, cessez!",
|
"Par les esprits, cessez !",
|
||||||
"Vous devez m'avoir confondu avec quelqu'un d'autre!",
|
"Vous devez m'avoir confondu avec quelqu'un d'autre !",
|
||||||
"Je ne mérite pas cela!",
|
"Je ne mérite pas cela !",
|
||||||
"Ne faites plus cela.",
|
"Ne faites plus cela.",
|
||||||
"Gardes, jetez ce monstre dans le lac!",
|
"Gardes, jetez ce monstre dans le lac !",
|
||||||
"Je vais t'envoyer ma tarrasque!",
|
"Je vais t'envoyer ma tarrasque !",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -17,6 +17,13 @@ VoxygenLocalization(
|
|||||||
language_identifier: "sv",
|
language_identifier: "sv",
|
||||||
),
|
),
|
||||||
convert_utf8_to_ascii: false,
|
convert_utf8_to_ascii: false,
|
||||||
|
// Make sure that fonts contain all swedisch characters
|
||||||
|
fonts: {
|
||||||
|
"opensans": Font (
|
||||||
|
asset_key: "voxygen.font.OpenSans-Regular",
|
||||||
|
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
|
||||||
@ -332,5 +339,9 @@ Willpower
|
|||||||
"esc_menu.logout": "Logout",
|
"esc_menu.logout": "Logout",
|
||||||
"esc_menu.quit_game": "Quit Game",
|
"esc_menu.quit_game": "Quit Game",
|
||||||
/// End Escape Menu Section
|
/// End Escape Menu Section
|
||||||
}
|
},
|
||||||
|
|
||||||
|
vector_map: {
|
||||||
|
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
@ -377,5 +377,9 @@ Veloren 半夜會特別暗。
|
|||||||
"esc_menu.logout": "登出",
|
"esc_menu.logout": "登出",
|
||||||
"esc_menu.quit_game": "退出遊戲",
|
"esc_menu.quit_game": "退出遊戲",
|
||||||
/// End Escape Menu Section
|
/// End Escape Menu Section
|
||||||
}
|
},
|
||||||
|
|
||||||
|
vector_map: {
|
||||||
|
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
@ -1016,6 +1016,10 @@
|
|||||||
Armor(Back("Short0")): VoxTrans(
|
Armor(Back("Short0")): VoxTrans(
|
||||||
"voxel.armor.back.short-0",
|
"voxel.armor.back.short-0",
|
||||||
(0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.0,
|
(0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.0,
|
||||||
|
),
|
||||||
|
Armor(Back("Short1")): VoxTrans(
|
||||||
|
"voxel.armor.back.short-1",
|
||||||
|
(0.0, -2.0, 0.0), (-90.0, 180.0, 0.0), 1.0,
|
||||||
),
|
),
|
||||||
Armor(Back("Admin")): VoxTrans(
|
Armor(Back("Admin")): VoxTrans(
|
||||||
"voxel.armor.back.admin",
|
"voxel.armor.back.admin",
|
||||||
@ -1033,6 +1037,9 @@
|
|||||||
Armor(Neck("Neck0")): Png(
|
Armor(Neck("Neck0")): Png(
|
||||||
"element.icons.neck-0",
|
"element.icons.neck-0",
|
||||||
),
|
),
|
||||||
|
Armor(Neck("Neck1")): Png(
|
||||||
|
"element.icons.neck-1",
|
||||||
|
),
|
||||||
// Tabards
|
// Tabards
|
||||||
Armor(Tabard("Admin")): Png(
|
Armor(Tabard("Admin")): Png(
|
||||||
"element.icons.tabard_admin",
|
"element.icons.tabard_admin",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
uniform sampler2D t_noise;
|
uniform sampler2D t_noise;
|
||||||
|
|
||||||
float hash(vec4 p) {
|
float hash(vec4 p) {
|
||||||
p = fract(p * 0.3183099 + 0.1);
|
p = fract(p * 0.3183099 + 0.1) - fract(p + 23.22121);
|
||||||
p *= 17.0;
|
p *= 17.0;
|
||||||
return (fract(p.x * p.y * p.z * p.w * (p.x + p.y + p.z + p.w)) - 0.5) * 2.0;
|
return (fract(p.x * p.y * p.z * p.w * (p.x + p.y + p.z + p.w)) - 0.5) * 2.0;
|
||||||
}
|
}
|
||||||
|
@ -343,10 +343,10 @@ float is_star_at(vec3 dir) {
|
|||||||
vec3 pos = (floor(dir * star_scale) - 0.5) / star_scale;
|
vec3 pos = (floor(dir * star_scale) - 0.5) / star_scale;
|
||||||
|
|
||||||
// Noisy offsets
|
// Noisy offsets
|
||||||
pos += (3.0 / star_scale) * /*rand_perm_3*/hash(vec4(pos, 1.0));
|
pos += (3.0 / star_scale) * (1.0 + hash(pos.yxzz) * 0.85);
|
||||||
|
|
||||||
// Find distance to fragment
|
// Find distance to fragment
|
||||||
float dist = length(normalize(pos) - dir);
|
float dist = length(pos - dir);
|
||||||
|
|
||||||
// Star threshold
|
// Star threshold
|
||||||
if (dist < 0.0015) {
|
if (dist < 0.0015) {
|
||||||
|
86
assets/voxygen/shaders/particle-frag.glsl
Normal file
86
assets/voxygen/shaders/particle-frag.glsl
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
#version 330 core
|
||||||
|
|
||||||
|
#include <constants.glsl>
|
||||||
|
|
||||||
|
#define LIGHTING_TYPE LIGHTING_TYPE_REFLECTION
|
||||||
|
|
||||||
|
#define LIGHTING_REFLECTION_KIND LIGHTING_REFLECTION_KIND_GLOSSY
|
||||||
|
|
||||||
|
#define LIGHTING_TRANSPORT_MODE LIGHTING_TRANSPORT_MODE_IMPORTANCE
|
||||||
|
|
||||||
|
#define LIGHTING_DISTRIBUTION_SCHEME LIGHTING_DISTRIBUTION_SCHEME_MICROFACET
|
||||||
|
|
||||||
|
#define LIGHTING_DISTRIBUTION LIGHTING_DISTRIBUTION_BECKMANN
|
||||||
|
|
||||||
|
#define HAS_SHADOW_MAPS
|
||||||
|
|
||||||
|
#include <globals.glsl>
|
||||||
|
|
||||||
|
in vec3 f_pos;
|
||||||
|
flat in vec3 f_norm;
|
||||||
|
in vec3 f_col;
|
||||||
|
|
||||||
|
out vec4 tgt_color;
|
||||||
|
|
||||||
|
#include <sky.glsl>
|
||||||
|
#include <light.glsl>
|
||||||
|
#include <lod.glsl>
|
||||||
|
|
||||||
|
const float FADE_DIST = 32.0;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec3 cam_to_frag = normalize(f_pos - cam_pos.xyz);
|
||||||
|
vec3 view_dir = -cam_to_frag;
|
||||||
|
|
||||||
|
#if (SHADOW_MODE == SHADOW_MODE_CHEAP || SHADOW_MODE == SHADOW_MODE_MAP || FLUID_MODE == FLUID_MODE_SHINY)
|
||||||
|
float f_alt = alt_at(f_pos.xy);
|
||||||
|
#elif (SHADOW_MODE == SHADOW_MODE_NONE || FLUID_MODE == FLUID_MODE_CHEAP)
|
||||||
|
float f_alt = f_pos.z;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if (SHADOW_MODE == SHADOW_MODE_CHEAP || SHADOW_MODE == SHADOW_MODE_MAP)
|
||||||
|
vec4 f_shadow = textureBicubic(t_horizon, pos_to_tex(f_pos.xy));
|
||||||
|
float sun_shade_frac = horizon_at2(f_shadow, f_alt, f_pos, sun_dir);
|
||||||
|
#elif (SHADOW_MODE == SHADOW_MODE_NONE)
|
||||||
|
float sun_shade_frac = 1.0;
|
||||||
|
#endif
|
||||||
|
float moon_shade_frac = 1.0;
|
||||||
|
|
||||||
|
float point_shadow = shadow_at(f_pos, f_norm);
|
||||||
|
DirectionalLight sun_info = get_sun_info(sun_dir, point_shadow * sun_shade_frac, f_pos);
|
||||||
|
DirectionalLight moon_info = get_moon_info(moon_dir, point_shadow * moon_shade_frac);
|
||||||
|
|
||||||
|
vec3 surf_color = f_col;
|
||||||
|
float alpha = 1.0;
|
||||||
|
const float n2 = 1.5;
|
||||||
|
const float R_s2s0 = pow((1.0 - n2) / (1.0 + n2), 2);
|
||||||
|
const float R_s1s0 = pow((1.3325 - n2) / (1.3325 + n2), 2);
|
||||||
|
const float R_s2s1 = pow((1.0 - 1.3325) / (1.0 + 1.3325), 2);
|
||||||
|
const float R_s1s2 = pow((1.3325 - 1.0) / (1.3325 + 1.0), 2);
|
||||||
|
float R_s = (f_pos.z < f_alt) ? mix(R_s2s1 * R_s1s0, R_s1s0, medium.x) : mix(R_s2s0, R_s1s2 * R_s2s0, medium.x);
|
||||||
|
|
||||||
|
vec3 k_a = vec3(1.0);
|
||||||
|
vec3 k_d = vec3(1.0);
|
||||||
|
vec3 k_s = vec3(R_s);
|
||||||
|
|
||||||
|
vec3 emitted_light, reflected_light;
|
||||||
|
|
||||||
|
// To account for prior saturation.
|
||||||
|
float max_light = 0.0;
|
||||||
|
max_light += get_sun_diffuse2(sun_info, moon_info, f_norm, view_dir, k_a, k_d, k_s, alpha, emitted_light, reflected_light);
|
||||||
|
|
||||||
|
max_light += lights_at(f_pos, f_norm, view_dir, k_a, k_d, k_s, alpha, emitted_light, reflected_light);
|
||||||
|
|
||||||
|
surf_color = illuminate(max_light, view_dir, surf_color * emitted_light, surf_color * reflected_light);
|
||||||
|
|
||||||
|
#if (CLOUD_MODE == CLOUD_MODE_REGULAR)
|
||||||
|
float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x);
|
||||||
|
vec4 clouds;
|
||||||
|
vec3 fog_color = get_sky_color(cam_to_frag, time_of_day.x, cam_pos.xyz, f_pos, 0.5, false, clouds);
|
||||||
|
vec3 color = mix(mix(surf_color, fog_color, fog_level), clouds.rgb, clouds.a);
|
||||||
|
#elif (CLOUD_MODE == CLOUD_MODE_NONE)
|
||||||
|
vec3 color = surf_color;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
tgt_color = vec4(color, 1.0 - clamp((distance(focus_pos.xy, f_pos.xy) - (1000.0 - FADE_DIST)) / FADE_DIST, 0, 1));
|
||||||
|
}
|
140
assets/voxygen/shaders/particle-vert.glsl
Normal file
140
assets/voxygen/shaders/particle-vert.glsl
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
#version 330 core
|
||||||
|
|
||||||
|
#include <constants.glsl>
|
||||||
|
|
||||||
|
#define LIGHTING_TYPE LIGHTING_TYPE_REFLECTION
|
||||||
|
|
||||||
|
#define LIGHTING_REFLECTION_KIND LIGHTING_REFLECTION_KIND_GLOSSY
|
||||||
|
|
||||||
|
#define LIGHTING_TRANSPORT_MODE LIGHTING_TRANSPORT_MODE_IMPORTANCE
|
||||||
|
|
||||||
|
#define LIGHTING_DISTRIBUTION_SCHEME LIGHTING_DISTRIBUTION_SCHEME_MICROFACET
|
||||||
|
|
||||||
|
#define LIGHTING_DISTRIBUTION LIGHTING_DISTRIBUTION_BECKMANN
|
||||||
|
|
||||||
|
#include <globals.glsl>
|
||||||
|
#include <srgb.glsl>
|
||||||
|
#include <random.glsl>
|
||||||
|
|
||||||
|
in vec3 v_pos;
|
||||||
|
// in uint v_col;
|
||||||
|
in uint v_norm_ao;
|
||||||
|
in vec3 inst_pos;
|
||||||
|
in float inst_time;
|
||||||
|
in float inst_entropy;
|
||||||
|
in int inst_mode;
|
||||||
|
|
||||||
|
out vec3 f_pos;
|
||||||
|
flat out vec3 f_norm;
|
||||||
|
out vec3 f_col;
|
||||||
|
out float f_ao;
|
||||||
|
out float f_light;
|
||||||
|
|
||||||
|
const float SCALE = 1.0 / 11.0;
|
||||||
|
|
||||||
|
// Modes
|
||||||
|
const int SMOKE = 0;
|
||||||
|
const int FIRE = 1;
|
||||||
|
const int GUN_POWDER_SPARK = 2;
|
||||||
|
const int SHRAPNEL = 3;
|
||||||
|
|
||||||
|
// meters per second squared (acceleration)
|
||||||
|
const float earth_gravity = 9.807;
|
||||||
|
|
||||||
|
struct Attr {
|
||||||
|
vec3 offs;
|
||||||
|
float scale;
|
||||||
|
vec3 col;
|
||||||
|
};
|
||||||
|
|
||||||
|
float lifetime = tick.x - inst_time;
|
||||||
|
|
||||||
|
vec3 linear_motion(vec3 init_offs, vec3 vel) {
|
||||||
|
return init_offs + vel * lifetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 grav_vel(float grav) {
|
||||||
|
return vec3(0, 0, -grav * lifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
float exp_scale(float factor) {
|
||||||
|
return 1 / (1 - lifetime * factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
float rand0 = hash(vec4(inst_entropy + 0));
|
||||||
|
float rand1 = hash(vec4(inst_entropy + 1));
|
||||||
|
float rand2 = hash(vec4(inst_entropy + 2));
|
||||||
|
float rand3 = hash(vec4(inst_entropy + 3));
|
||||||
|
float rand4 = hash(vec4(inst_entropy + 4));
|
||||||
|
float rand5 = hash(vec4(inst_entropy + 5));
|
||||||
|
float rand6 = hash(vec4(inst_entropy + 6));
|
||||||
|
float rand7 = hash(vec4(inst_entropy + 7));
|
||||||
|
|
||||||
|
Attr attr;
|
||||||
|
|
||||||
|
if (inst_mode == SMOKE) {
|
||||||
|
attr = Attr(
|
||||||
|
linear_motion(
|
||||||
|
vec3(rand0 * 0.25, rand1 * 0.25, 1.7 + rand5),
|
||||||
|
vec3(rand2 * 0.2, rand3 * 0.2, 1.0 + rand4 * 0.5)// + vec3(sin(lifetime), sin(lifetime + 1.5), sin(lifetime * 4) * 0.25)
|
||||||
|
),
|
||||||
|
exp_scale(-0.2),
|
||||||
|
vec3(1)
|
||||||
|
);
|
||||||
|
} else if (inst_mode == FIRE) {
|
||||||
|
attr = Attr(
|
||||||
|
linear_motion(
|
||||||
|
vec3(rand0 * 0.25, rand1 * 0.25, 0.3),
|
||||||
|
vec3(rand2 * 0.1, rand3 * 0.1, 2.0 + rand4 * 1.0)
|
||||||
|
),
|
||||||
|
1.0,
|
||||||
|
vec3(2, rand5 + 2, 0)
|
||||||
|
);
|
||||||
|
} else if (inst_mode == GUN_POWDER_SPARK) {
|
||||||
|
attr = Attr(
|
||||||
|
linear_motion(
|
||||||
|
vec3(rand0, rand1, rand3) * 0.3,
|
||||||
|
vec3(rand4, rand5, rand6) * 2.0 + grav_vel(earth_gravity)
|
||||||
|
),
|
||||||
|
1.0,
|
||||||
|
vec3(3.5, 3 + rand7, 0)
|
||||||
|
);
|
||||||
|
} else if (inst_mode == SHRAPNEL) {
|
||||||
|
attr = Attr(
|
||||||
|
linear_motion(
|
||||||
|
vec3(0),
|
||||||
|
vec3(rand4, rand5, rand6) * 40.0 + grav_vel(earth_gravity)
|
||||||
|
),
|
||||||
|
3.0 + rand0,
|
||||||
|
vec3(0.6 + rand7 * 0.4)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
attr = Attr(
|
||||||
|
linear_motion(
|
||||||
|
vec3(rand0 * 0.25, rand1 * 0.25, 1.7 + rand5),
|
||||||
|
vec3(rand2 * 0.1, rand3 * 0.1, 1.0 + rand4 * 0.5)
|
||||||
|
),
|
||||||
|
exp_scale(-0.2),
|
||||||
|
vec3(1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
f_pos = (inst_pos - focus_off.xyz) + (v_pos * attr.scale * SCALE + attr.offs);
|
||||||
|
|
||||||
|
// First 3 normals are negative, next 3 are positive
|
||||||
|
vec3 normals[6] = vec3[](vec3(-1,0,0), vec3(1,0,0), vec3(0,-1,0), vec3(0,1,0), vec3(0,0,-1), vec3(0,0,1));
|
||||||
|
f_norm =
|
||||||
|
// inst_pos *
|
||||||
|
normals[(v_norm_ao >> 0) & 0x7u];
|
||||||
|
|
||||||
|
//vec3 col = vec3((uvec3(v_col) >> uvec3(0, 8, 16)) & uvec3(0xFFu)) / 255.0;
|
||||||
|
f_col =
|
||||||
|
//srgb_to_linear(col) *
|
||||||
|
srgb_to_linear(attr.col);
|
||||||
|
|
||||||
|
gl_Position =
|
||||||
|
all_mat *
|
||||||
|
vec4(f_pos, 1);
|
||||||
|
gl_Position.z = -1000.0 / (gl_Position.z + 10000.0);
|
||||||
|
}
|
BIN
assets/voxygen/voxel/armor/back/short-1.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/armor/back/short-1.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -12,6 +12,18 @@
|
|||||||
offset: (-5.0, -4.5, -9.0),
|
offset: (-5.0, -4.5, -9.0),
|
||||||
center: ("npc.ogre.male.torso_lower"),
|
center: ("npc.ogre.male.torso_lower"),
|
||||||
),
|
),
|
||||||
|
jaw: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
|
tail: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
|
second: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
main: (
|
main: (
|
||||||
offset: (-8.0, -4.5, -5.0),
|
offset: (-8.0, -4.5, -5.0),
|
||||||
center: ("armor.empty"),
|
center: ("armor.empty"),
|
||||||
@ -30,6 +42,18 @@
|
|||||||
offset: (-5.0, -4.5, -9.0),
|
offset: (-5.0, -4.5, -9.0),
|
||||||
center: ("npc.ogre.male.torso_lower"),
|
center: ("npc.ogre.male.torso_lower"),
|
||||||
),
|
),
|
||||||
|
jaw: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
|
tail: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
|
second: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
main: (
|
main: (
|
||||||
offset: (-8.0, -4.5, -5.0),
|
offset: (-8.0, -4.5, -5.0),
|
||||||
center: ("armor.empty"),
|
center: ("armor.empty"),
|
||||||
@ -48,6 +72,18 @@
|
|||||||
offset: (-6.0, -5.5, -12.0),
|
offset: (-6.0, -5.5, -12.0),
|
||||||
center: ("npc.cyclops.male.torso_lower"),
|
center: ("npc.cyclops.male.torso_lower"),
|
||||||
),
|
),
|
||||||
|
jaw: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
|
tail: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
|
second: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
main: (
|
main: (
|
||||||
offset: (-5.0, -6.5, -4.0),
|
offset: (-5.0, -6.5, -4.0),
|
||||||
center: ("npc.cyclops.male.hammer"),
|
center: ("npc.cyclops.male.hammer"),
|
||||||
@ -66,6 +102,18 @@
|
|||||||
offset: (-6.0, -5.5, -12.0),
|
offset: (-6.0, -5.5, -12.0),
|
||||||
center: ("npc.cyclops.male.torso_lower"),
|
center: ("npc.cyclops.male.torso_lower"),
|
||||||
),
|
),
|
||||||
|
jaw: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
|
tail: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
|
second: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
main: (
|
main: (
|
||||||
offset: (-5.0, -6.5, -4.0),
|
offset: (-5.0, -6.5, -4.0),
|
||||||
center: ("npc.cyclops.male.hammer"),
|
center: ("npc.cyclops.male.hammer"),
|
||||||
@ -84,6 +132,18 @@
|
|||||||
offset: (-4.0, -2.0, -4.0),
|
offset: (-4.0, -2.0, -4.0),
|
||||||
center: ("npc.wendigo.male.torso_lower"),
|
center: ("npc.wendigo.male.torso_lower"),
|
||||||
),
|
),
|
||||||
|
jaw: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
|
tail: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
|
second: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
main: (
|
main: (
|
||||||
offset: (-8.0, -4.5, -5.0),
|
offset: (-8.0, -4.5, -5.0),
|
||||||
center: ("armor.empty"),
|
center: ("armor.empty"),
|
||||||
@ -102,6 +162,18 @@
|
|||||||
offset: (-4.0, -2.0, -4.0),
|
offset: (-4.0, -2.0, -4.0),
|
||||||
center: ("npc.wendigo.male.torso_lower"),
|
center: ("npc.wendigo.male.torso_lower"),
|
||||||
),
|
),
|
||||||
|
jaw: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
|
tail: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
|
second: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
main: (
|
main: (
|
||||||
offset: (-8.0, -4.5, -5.0),
|
offset: (-8.0, -4.5, -5.0),
|
||||||
center: ("armor.empty"),
|
center: ("armor.empty"),
|
||||||
@ -120,6 +192,18 @@
|
|||||||
offset: (-6.0, -3.5, -5.0),
|
offset: (-6.0, -3.5, -5.0),
|
||||||
center: ("npc.troll.male.torso_lower"),
|
center: ("npc.troll.male.torso_lower"),
|
||||||
),
|
),
|
||||||
|
jaw: (
|
||||||
|
offset: (-4.0, 0.0, -4.5),
|
||||||
|
center: ("npc.troll.male.jaw"),
|
||||||
|
),
|
||||||
|
tail: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
|
second: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
main: (
|
main: (
|
||||||
offset: (-8.0, -4.5, -5.0),
|
offset: (-8.0, -4.5, -5.0),
|
||||||
center: ("armor.empty"),
|
center: ("armor.empty"),
|
||||||
@ -138,6 +222,18 @@
|
|||||||
offset: (-6.0, -3.5, -5.0),
|
offset: (-6.0, -3.5, -5.0),
|
||||||
center: ("npc.troll.male.torso_lower"),
|
center: ("npc.troll.male.torso_lower"),
|
||||||
),
|
),
|
||||||
|
jaw: (
|
||||||
|
offset: (-4.0, 0.0, -4.5),
|
||||||
|
center: ("npc.troll.male.jaw"),
|
||||||
|
),
|
||||||
|
tail: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
|
second: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
main: (
|
main: (
|
||||||
offset: (-8.0, -4.5, -5.0),
|
offset: (-8.0, -4.5, -5.0),
|
||||||
center: ("armor.empty"),
|
center: ("armor.empty"),
|
||||||
@ -158,6 +254,18 @@
|
|||||||
offset: (-8.0, -6.0, -9.0),
|
offset: (-8.0, -6.0, -9.0),
|
||||||
center: ("npc.dullahan.male.torso_lower"),
|
center: ("npc.dullahan.male.torso_lower"),
|
||||||
),
|
),
|
||||||
|
jaw: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
|
tail: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
|
second: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
main: (
|
main: (
|
||||||
offset: (-1.5, -9.0, -10.0),
|
offset: (-1.5, -9.0, -10.0),
|
||||||
center: ("npc.dullahan.male.sword"),
|
center: ("npc.dullahan.male.sword"),
|
||||||
@ -177,6 +285,18 @@
|
|||||||
offset: (-8.0, -6.0, -9.0),
|
offset: (-8.0, -6.0, -9.0),
|
||||||
center: ("npc.dullahan.male.torso_lower"),
|
center: ("npc.dullahan.male.torso_lower"),
|
||||||
),
|
),
|
||||||
|
jaw: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
|
tail: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
|
second: (
|
||||||
|
offset: (0.0, 0.0, 0.0),
|
||||||
|
center: ("armor.empty"),
|
||||||
|
),
|
||||||
main: (
|
main: (
|
||||||
offset: (-1.5, -9.0, -10.0),
|
offset: (-1.5, -9.0, -10.0),
|
||||||
center: ("npc.dullahan.male.sword"),
|
center: ("npc.dullahan.male.sword"),
|
||||||
|
@ -15,6 +15,10 @@
|
|||||||
"DungPurp0": (
|
"DungPurp0": (
|
||||||
vox_spec: ("armor.back.dung_purp-0", (-5.0, -1.0, -14.0)),
|
vox_spec: ("armor.back.dung_purp-0", (-5.0, -1.0, -14.0)),
|
||||||
color: None
|
color: None
|
||||||
),
|
),
|
||||||
|
"Short1": (
|
||||||
|
vox_spec: ("armor.back.short-1", (-5.0, -1.0, -11.0)),
|
||||||
|
color: None
|
||||||
|
),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
BIN
assets/voxygen/voxel/particle.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/particle.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/weapon/staff/firestaff_cultist.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/weapon/staff/firestaff_cultist.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/world/structure/dungeon/misc_entrance/tower-ruin.vox
(Stored with Git LFS)
BIN
assets/world/structure/dungeon/misc_entrance/tower-ruin.vox
(Stored with Git LFS)
Binary file not shown.
@ -17,14 +17,15 @@ use byteorder::{ByteOrder, LittleEndian};
|
|||||||
use common::{
|
use common::{
|
||||||
character::CharacterItem,
|
character::CharacterItem,
|
||||||
comp::{
|
comp::{
|
||||||
self, ControlAction, ControlEvent, Controller, ControllerInputs, InventoryManip,
|
self, group, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip,
|
||||||
InventoryUpdateEvent,
|
InventoryManip, InventoryUpdateEvent,
|
||||||
},
|
},
|
||||||
msg::{
|
msg::{
|
||||||
validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, Notification,
|
validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, InviteAnswer,
|
||||||
PlayerInfo, PlayerListUpdate, RegisterError, RequestStateError, ServerInfo, ServerMsg,
|
Notification, PlayerInfo, PlayerListUpdate, RegisterError, RequestStateError, ServerInfo,
|
||||||
MAX_BYTES_CHAT_MSG,
|
ServerMsg, MAX_BYTES_CHAT_MSG,
|
||||||
},
|
},
|
||||||
|
outcome::Outcome,
|
||||||
recipe::RecipeBook,
|
recipe::RecipeBook,
|
||||||
state::State,
|
state::State,
|
||||||
sync::{Uid, UidAllocator, WorldSyncExt},
|
sync::{Uid, UidAllocator, WorldSyncExt},
|
||||||
@ -68,6 +69,7 @@ pub enum Event {
|
|||||||
InventoryUpdated(InventoryUpdateEvent),
|
InventoryUpdated(InventoryUpdateEvent),
|
||||||
Notification(Notification),
|
Notification(Notification),
|
||||||
SetViewDistance(u32),
|
SetViewDistance(u32),
|
||||||
|
Outcome(Outcome),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
@ -102,6 +104,15 @@ pub struct Client {
|
|||||||
recipe_book: RecipeBook,
|
recipe_book: RecipeBook,
|
||||||
available_recipes: HashSet<String>,
|
available_recipes: HashSet<String>,
|
||||||
|
|
||||||
|
max_group_size: u32,
|
||||||
|
// Client has received an invite (inviter uid, time out instant)
|
||||||
|
group_invite: Option<(Uid, std::time::Instant, std::time::Duration)>,
|
||||||
|
group_leader: Option<Uid>,
|
||||||
|
// Note: potentially representable as a client only component
|
||||||
|
group_members: HashMap<Uid, group::Role>,
|
||||||
|
// Pending invites that this client has sent out
|
||||||
|
pending_invites: HashSet<Uid>,
|
||||||
|
|
||||||
_network: Network,
|
_network: Network,
|
||||||
participant: Option<Participant>,
|
participant: Option<Participant>,
|
||||||
singleton_stream: Stream,
|
singleton_stream: Stream,
|
||||||
@ -149,166 +160,170 @@ impl Client {
|
|||||||
let mut stream = block_on(participant.open(10, PROMISES_ORDERED | PROMISES_CONSISTENCY))?;
|
let mut stream = block_on(participant.open(10, PROMISES_ORDERED | PROMISES_CONSISTENCY))?;
|
||||||
|
|
||||||
// Wait for initial sync
|
// Wait for initial sync
|
||||||
let (state, entity, server_info, lod_base, lod_alt, lod_horizon, world_map, recipe_book) =
|
let (
|
||||||
block_on(async {
|
state,
|
||||||
loop {
|
entity,
|
||||||
match stream.recv().await? {
|
server_info,
|
||||||
ServerMsg::InitialSync {
|
lod_base,
|
||||||
entity_package,
|
lod_alt,
|
||||||
server_info,
|
lod_horizon,
|
||||||
time_of_day,
|
world_map,
|
||||||
world_map,
|
recipe_book,
|
||||||
recipe_book,
|
max_group_size,
|
||||||
} => {
|
) = block_on(async {
|
||||||
// TODO: Display that versions don't match in Voxygen
|
loop {
|
||||||
if &server_info.git_hash != *common::util::GIT_HASH {
|
match stream.recv().await? {
|
||||||
warn!(
|
ServerMsg::InitialSync {
|
||||||
"Server is running {}[{}], you are running {}[{}], versions \
|
entity_package,
|
||||||
might be incompatible!",
|
server_info,
|
||||||
server_info.git_hash,
|
time_of_day,
|
||||||
server_info.git_date,
|
max_group_size,
|
||||||
common::util::GIT_HASH.to_string(),
|
world_map,
|
||||||
common::util::GIT_DATE.to_string(),
|
recipe_book,
|
||||||
);
|
} => {
|
||||||
}
|
// TODO: Display that versions don't match in Voxygen
|
||||||
|
if &server_info.git_hash != *common::util::GIT_HASH {
|
||||||
|
warn!(
|
||||||
|
"Server is running {}[{}], you are running {}[{}], versions might \
|
||||||
|
be incompatible!",
|
||||||
|
server_info.git_hash,
|
||||||
|
server_info.git_date,
|
||||||
|
common::util::GIT_HASH.to_string(),
|
||||||
|
common::util::GIT_DATE.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
debug!("Auth Server: {:?}", server_info.auth_provider);
|
debug!("Auth Server: {:?}", server_info.auth_provider);
|
||||||
|
|
||||||
// Initialize `State`
|
// Initialize `State`
|
||||||
let mut state = State::default();
|
let mut state = State::default();
|
||||||
// Client-only components
|
// Client-only components
|
||||||
state
|
state
|
||||||
.ecs_mut()
|
.ecs_mut()
|
||||||
.register::<comp::Last<comp::CharacterState>>();
|
.register::<comp::Last<comp::CharacterState>>();
|
||||||
|
|
||||||
let entity = state.ecs_mut().apply_entity_package(entity_package);
|
let entity = state.ecs_mut().apply_entity_package(entity_package);
|
||||||
*state.ecs_mut().write_resource() = time_of_day;
|
*state.ecs_mut().write_resource() = time_of_day;
|
||||||
|
|
||||||
let map_size_lg = common::terrain::MapSizeLg::new(
|
let map_size_lg = common::terrain::MapSizeLg::new(world_map.dimensions_lg)
|
||||||
world_map.dimensions_lg,
|
|
||||||
)
|
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
Error::Other(format!(
|
Error::Other(format!(
|
||||||
"Server sent bad world map dimensions: {:?}",
|
"Server sent bad world map dimensions: {:?}",
|
||||||
world_map.dimensions_lg,
|
world_map.dimensions_lg,
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
let map_size = map_size_lg.chunks();
|
let map_size = map_size_lg.chunks();
|
||||||
let max_height = world_map.max_height;
|
let max_height = world_map.max_height;
|
||||||
let sea_level = world_map.sea_level;
|
let sea_level = world_map.sea_level;
|
||||||
let rgba = world_map.rgba;
|
let rgba = world_map.rgba;
|
||||||
let alt = world_map.alt;
|
let alt = world_map.alt;
|
||||||
let expected_size =
|
let expected_size =
|
||||||
(u32::from(map_size.x) * u32::from(map_size.y)) as usize;
|
(u32::from(map_size.x) * u32::from(map_size.y)) as usize;
|
||||||
if rgba.len() != expected_size {
|
if rgba.len() != expected_size {
|
||||||
return Err(Error::Other(
|
return Err(Error::Other("Server sent a bad world map image".into()));
|
||||||
"Server sent a bad world map image".into(),
|
}
|
||||||
));
|
if alt.len() != expected_size {
|
||||||
}
|
return Err(Error::Other("Server sent a bad altitude map.".into()));
|
||||||
if alt.len() != expected_size {
|
}
|
||||||
return Err(Error::Other("Server sent a bad altitude map.".into()));
|
let [west, east] = world_map.horizons;
|
||||||
}
|
let scale_angle =
|
||||||
let [west, east] = world_map.horizons;
|
|a: u8| (a as f32 / 255.0 * <f32 as FloatConst>::FRAC_PI_2()).tan();
|
||||||
let scale_angle =
|
let scale_height = |h: u8| h as f32 / 255.0 * max_height;
|
||||||
|a: u8| (a as f32 / 255.0 * <f32 as FloatConst>::FRAC_PI_2()).tan();
|
let scale_height_big = |h: u32| (h >> 3) as f32 / 8191.0 * max_height;
|
||||||
let scale_height = |h: u8| h as f32 / 255.0 * max_height;
|
|
||||||
let scale_height_big = |h: u32| (h >> 3) as f32 / 8191.0 * max_height;
|
|
||||||
|
|
||||||
debug!("Preparing image...");
|
debug!("Preparing image...");
|
||||||
let unzip_horizons = |(angles, heights): &(Vec<_>, Vec<_>)| {
|
let unzip_horizons = |(angles, heights): &(Vec<_>, Vec<_>)| {
|
||||||
(
|
(
|
||||||
angles.iter().copied().map(scale_angle).collect::<Vec<_>>(),
|
angles.iter().copied().map(scale_angle).collect::<Vec<_>>(),
|
||||||
heights
|
heights
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.map(scale_height)
|
.map(scale_height)
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let horizons = [unzip_horizons(&west), unzip_horizons(&east)];
|
let horizons = [unzip_horizons(&west), unzip_horizons(&east)];
|
||||||
|
|
||||||
// Redraw map (with shadows this time).
|
// Redraw map (with shadows this time).
|
||||||
let mut world_map = vec![0u32; rgba.len()];
|
let mut world_map = vec![0u32; rgba.len()];
|
||||||
let mut map_config = common::terrain::map::MapConfig::orthographic(
|
let mut map_config = common::terrain::map::MapConfig::orthographic(
|
||||||
map_size_lg,
|
map_size_lg,
|
||||||
core::ops::RangeInclusive::new(0.0, max_height),
|
core::ops::RangeInclusive::new(0.0, max_height),
|
||||||
);
|
);
|
||||||
map_config.horizons = Some(&horizons);
|
map_config.horizons = Some(&horizons);
|
||||||
let rescale_height = |h: f32| h / max_height;
|
let rescale_height = |h: f32| h / max_height;
|
||||||
let bounds_check = |pos: Vec2<i32>| {
|
let bounds_check = |pos: Vec2<i32>| {
|
||||||
pos.reduce_partial_min() >= 0
|
pos.reduce_partial_min() >= 0
|
||||||
&& pos.x < map_size.x as i32
|
&& pos.x < map_size.x as i32
|
||||||
&& pos.y < map_size.y as i32
|
&& pos.y < map_size.y as i32
|
||||||
};
|
};
|
||||||
map_config.generate(
|
map_config.generate(
|
||||||
|pos| {
|
|pos| {
|
||||||
let (rgba, alt, downhill_wpos) = if bounds_check(pos) {
|
let (rgba, alt, downhill_wpos) = if bounds_check(pos) {
|
||||||
let posi =
|
let posi =
|
||||||
pos.y as usize * map_size.x as usize + pos.x as usize;
|
pos.y as usize * map_size.x as usize + pos.x as usize;
|
||||||
let [r, g, b, a] = rgba[posi].to_le_bytes();
|
let [r, g, b, a] = rgba[posi].to_le_bytes();
|
||||||
let alti = alt[posi];
|
let alti = alt[posi];
|
||||||
// Compute downhill.
|
// Compute downhill.
|
||||||
let downhill = {
|
let downhill = {
|
||||||
let mut best = -1;
|
let mut best = -1;
|
||||||
let mut besth = alti;
|
let mut besth = alti;
|
||||||
for nposi in neighbors(map_size_lg, posi) {
|
for nposi in neighbors(map_size_lg, posi) {
|
||||||
let nbh = alt[nposi];
|
let nbh = alt[nposi];
|
||||||
if nbh < besth {
|
if nbh < besth {
|
||||||
besth = nbh;
|
besth = nbh;
|
||||||
best = nposi as isize;
|
best = nposi as isize;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
best
|
}
|
||||||
};
|
best
|
||||||
let downhill_wpos = if downhill < 0 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(
|
|
||||||
Vec2::new(
|
|
||||||
(downhill as usize % map_size.x as usize)
|
|
||||||
as i32,
|
|
||||||
(downhill as usize / map_size.x as usize)
|
|
||||||
as i32,
|
|
||||||
) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
(Rgba::new(r, g, b, a), alti, downhill_wpos)
|
|
||||||
} else {
|
|
||||||
(Rgba::zero(), 0, None)
|
|
||||||
};
|
};
|
||||||
let wpos = pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32);
|
let downhill_wpos = if downhill < 0 {
|
||||||
let downhill_wpos = downhill_wpos.unwrap_or(
|
None
|
||||||
wpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
|
|
||||||
);
|
|
||||||
let alt = rescale_height(scale_height_big(alt));
|
|
||||||
common::terrain::map::MapSample {
|
|
||||||
rgb: Rgb::from(rgba),
|
|
||||||
alt: f64::from(alt),
|
|
||||||
downhill_wpos,
|
|
||||||
connections: None,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|wpos| {
|
|
||||||
let pos =
|
|
||||||
wpos.map2(TerrainChunkSize::RECT_SIZE, |e, f| e / f as i32);
|
|
||||||
rescale_height(if bounds_check(pos) {
|
|
||||||
let posi =
|
|
||||||
pos.y as usize * map_size.x as usize + pos.x as usize;
|
|
||||||
scale_height_big(alt[posi])
|
|
||||||
} else {
|
} else {
|
||||||
0.0
|
Some(
|
||||||
})
|
Vec2::new(
|
||||||
},
|
(downhill as usize % map_size.x as usize) as i32,
|
||||||
|pos, (r, g, b, a)| {
|
(downhill as usize / map_size.x as usize) as i32,
|
||||||
world_map[pos.y * map_size.x as usize + pos.x] =
|
) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
|
||||||
u32::from_le_bytes([r, g, b, a]);
|
)
|
||||||
},
|
};
|
||||||
);
|
(Rgba::new(r, g, b, a), alti, downhill_wpos)
|
||||||
let make_raw = |rgba| -> Result<_, Error> {
|
} else {
|
||||||
let mut raw = vec![0u8; 4 * world_map.len()];
|
(Rgba::zero(), 0, None)
|
||||||
LittleEndian::write_u32_into(rgba, &mut raw);
|
};
|
||||||
Ok(Arc::new(
|
let wpos = pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32);
|
||||||
image::DynamicImage::ImageRgba8({
|
let downhill_wpos = downhill_wpos.unwrap_or(
|
||||||
|
wpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
|
||||||
|
);
|
||||||
|
let alt = rescale_height(scale_height_big(alt));
|
||||||
|
common::terrain::map::MapSample {
|
||||||
|
rgb: Rgb::from(rgba),
|
||||||
|
alt: f64::from(alt),
|
||||||
|
downhill_wpos,
|
||||||
|
connections: None,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|wpos| {
|
||||||
|
let pos =
|
||||||
|
wpos.map2(TerrainChunkSize::RECT_SIZE, |e, f| e / f as i32);
|
||||||
|
rescale_height(if bounds_check(pos) {
|
||||||
|
let posi =
|
||||||
|
pos.y as usize * map_size.x as usize + pos.x as usize;
|
||||||
|
scale_height_big(alt[posi])
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|pos, (r, g, b, a)| {
|
||||||
|
world_map[pos.y * map_size.x as usize + pos.x] =
|
||||||
|
u32::from_le_bytes([r, g, b, a]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let make_raw = |rgba| -> Result<_, Error> {
|
||||||
|
let mut raw = vec![0u8; 4 * world_map.len()];
|
||||||
|
LittleEndian::write_u32_into(rgba, &mut raw);
|
||||||
|
Ok(Arc::new(
|
||||||
|
image::DynamicImage::ImageRgba8({
|
||||||
// Should not fail if the dimensions are correct.
|
// Should not fail if the dimensions are correct.
|
||||||
let map =
|
let map =
|
||||||
image::ImageBuffer::from_raw(u32::from(map_size.x), u32::from(map_size.y), raw);
|
image::ImageBuffer::from_raw(u32::from(map_size.x), u32::from(map_size.y), raw);
|
||||||
@ -317,37 +332,38 @@ impl Client {
|
|||||||
// Flip the image, since Voxygen uses an orientation where rotation from
|
// Flip the image, since Voxygen uses an orientation where rotation from
|
||||||
// positive x axis to positive y axis is counterclockwise around the z axis.
|
// positive x axis to positive y axis is counterclockwise around the z axis.
|
||||||
.flipv(),
|
.flipv(),
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
let lod_base = rgba;
|
let lod_base = rgba;
|
||||||
let lod_alt = alt;
|
let lod_alt = alt;
|
||||||
let world_map = make_raw(&world_map)?;
|
let world_map = make_raw(&world_map)?;
|
||||||
let horizons = (west.0, west.1, east.0, east.1)
|
let horizons = (west.0, west.1, east.0, east.1)
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.map(|(wa, wh, ea, eh)| u32::from_le_bytes([wa, wh, ea, eh]))
|
.map(|(wa, wh, ea, eh)| u32::from_le_bytes([wa, wh, ea, eh]))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let lod_horizon = horizons;
|
let lod_horizon = horizons;
|
||||||
let map_bounds = Vec2::new(sea_level, max_height);
|
let map_bounds = Vec2::new(sea_level, max_height);
|
||||||
debug!("Done preparing image...");
|
debug!("Done preparing image...");
|
||||||
|
|
||||||
break Ok((
|
break Ok((
|
||||||
state,
|
state,
|
||||||
entity,
|
entity,
|
||||||
server_info,
|
server_info,
|
||||||
lod_base,
|
lod_base,
|
||||||
lod_alt,
|
lod_alt,
|
||||||
lod_horizon,
|
lod_horizon,
|
||||||
(world_map, map_size, map_bounds),
|
(world_map, map_size, map_bounds),
|
||||||
recipe_book,
|
recipe_book,
|
||||||
));
|
max_group_size,
|
||||||
},
|
));
|
||||||
ServerMsg::TooManyPlayers => break Err(Error::TooManyPlayers),
|
},
|
||||||
err => {
|
ServerMsg::TooManyPlayers => break Err(Error::TooManyPlayers),
|
||||||
warn!("whoops, server mad {:?}, ignoring", err);
|
err => {
|
||||||
},
|
warn!("whoops, server mad {:?}, ignoring", err);
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
})?;
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
stream.send(ClientMsg::Ping)?;
|
stream.send(ClientMsg::Ping)?;
|
||||||
|
|
||||||
@ -371,6 +387,12 @@ impl Client {
|
|||||||
recipe_book,
|
recipe_book,
|
||||||
available_recipes: HashSet::default(),
|
available_recipes: HashSet::default(),
|
||||||
|
|
||||||
|
max_group_size,
|
||||||
|
group_invite: None,
|
||||||
|
group_leader: None,
|
||||||
|
group_members: HashMap::new(),
|
||||||
|
pending_invites: HashSet::new(),
|
||||||
|
|
||||||
_network: network,
|
_network: network,
|
||||||
participant: Some(participant),
|
participant: Some(participant),
|
||||||
singleton_stream: stream,
|
singleton_stream: stream,
|
||||||
@ -533,7 +555,7 @@ impl Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn pick_up(&mut self, entity: EcsEntity) {
|
pub fn pick_up(&mut self, entity: EcsEntity) {
|
||||||
if let Some(uid) = self.state.ecs().read_storage::<Uid>().get(entity).copied() {
|
if let Some(uid) = self.state.read_component_copied(entity) {
|
||||||
self.singleton_stream
|
self.singleton_stream
|
||||||
.send(ClientMsg::ControlEvent(ControlEvent::InventoryManip(
|
.send(ClientMsg::ControlEvent(ControlEvent::InventoryManip(
|
||||||
InventoryManip::Pickup(uid),
|
InventoryManip::Pickup(uid),
|
||||||
@ -582,6 +604,72 @@ impl Client {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn max_group_size(&self) -> u32 { self.max_group_size }
|
||||||
|
|
||||||
|
pub fn group_invite(&self) -> Option<(Uid, std::time::Instant, std::time::Duration)> {
|
||||||
|
self.group_invite
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn group_info(&self) -> Option<(String, Uid)> {
|
||||||
|
self.group_leader.map(|l| ("Group".into(), l)) // TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn group_members(&self) -> &HashMap<Uid, group::Role> { &self.group_members }
|
||||||
|
|
||||||
|
pub fn pending_invites(&self) -> &HashSet<Uid> { &self.pending_invites }
|
||||||
|
|
||||||
|
pub fn send_group_invite(&mut self, invitee: Uid) {
|
||||||
|
self.singleton_stream
|
||||||
|
.send(ClientMsg::ControlEvent(ControlEvent::GroupManip(
|
||||||
|
GroupManip::Invite(invitee),
|
||||||
|
)))
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn accept_group_invite(&mut self) {
|
||||||
|
// Clear invite
|
||||||
|
self.group_invite.take();
|
||||||
|
self.singleton_stream
|
||||||
|
.send(ClientMsg::ControlEvent(ControlEvent::GroupManip(
|
||||||
|
GroupManip::Accept,
|
||||||
|
)))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decline_group_invite(&mut self) {
|
||||||
|
// Clear invite
|
||||||
|
self.group_invite.take();
|
||||||
|
self.singleton_stream
|
||||||
|
.send(ClientMsg::ControlEvent(ControlEvent::GroupManip(
|
||||||
|
GroupManip::Decline,
|
||||||
|
)))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn leave_group(&mut self) {
|
||||||
|
self.singleton_stream
|
||||||
|
.send(ClientMsg::ControlEvent(ControlEvent::GroupManip(
|
||||||
|
GroupManip::Leave,
|
||||||
|
)))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kick_from_group(&mut self, uid: Uid) {
|
||||||
|
self.singleton_stream
|
||||||
|
.send(ClientMsg::ControlEvent(ControlEvent::GroupManip(
|
||||||
|
GroupManip::Kick(uid),
|
||||||
|
)))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn assign_group_leader(&mut self, uid: Uid) {
|
||||||
|
self.singleton_stream
|
||||||
|
.send(ClientMsg::ControlEvent(ControlEvent::GroupManip(
|
||||||
|
GroupManip::AssignLeader(uid),
|
||||||
|
)))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_mounted(&self) -> bool {
|
pub fn is_mounted(&self) -> bool {
|
||||||
self.state
|
self.state
|
||||||
.ecs()
|
.ecs()
|
||||||
@ -591,7 +679,7 @@ impl Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn mount(&mut self, entity: EcsEntity) {
|
pub fn mount(&mut self, entity: EcsEntity) {
|
||||||
if let Some(uid) = self.state.ecs().read_storage::<Uid>().get(entity).copied() {
|
if let Some(uid) = self.state.read_component_copied(entity) {
|
||||||
self.singleton_stream
|
self.singleton_stream
|
||||||
.send(ClientMsg::ControlEvent(ControlEvent::Mount(uid)))
|
.send(ClientMsg::ControlEvent(ControlEvent::Mount(uid)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -667,6 +755,21 @@ impl Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn toggle_sneak(&mut self) {
|
||||||
|
let is_sneaking = self
|
||||||
|
.state
|
||||||
|
.ecs()
|
||||||
|
.read_storage::<comp::CharacterState>()
|
||||||
|
.get(self.entity)
|
||||||
|
.map(|cs| matches!(cs, comp::CharacterState::Sneak));
|
||||||
|
|
||||||
|
match is_sneaking {
|
||||||
|
Some(true) => self.control_action(ControlAction::Stand),
|
||||||
|
Some(false) => self.control_action(ControlAction::Sneak),
|
||||||
|
None => warn!("Can't toggle sneak, client entity doesn't have a `CharacterState`"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn toggle_glide(&mut self) {
|
pub fn toggle_glide(&mut self) {
|
||||||
let is_gliding = self
|
let is_gliding = self
|
||||||
.state
|
.state
|
||||||
@ -848,6 +951,13 @@ impl Client {
|
|||||||
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
|
||||||
|
// Check if the group invite has timed out and remove if so
|
||||||
|
if self
|
||||||
|
.group_invite
|
||||||
|
.map_or(false, |(_, timeout, dur)| timeout.elapsed() > dur)
|
||||||
|
{
|
||||||
|
self.group_invite = None;
|
||||||
|
}
|
||||||
|
|
||||||
// 4) Tick the client's LocalState
|
// 4) Tick the client's LocalState
|
||||||
self.state.tick(dt, add_foreign_systems, true);
|
self.state.tick(dt, add_foreign_systems, true);
|
||||||
@ -1093,7 +1203,102 @@ impl Client {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
ServerMsg::GroupUpdate(change_notification) => {
|
||||||
|
use comp::group::ChangeNotification::*;
|
||||||
|
// Note: we use a hashmap since this would not work with entities outside
|
||||||
|
// the view distance
|
||||||
|
match change_notification {
|
||||||
|
Added(uid, role) => {
|
||||||
|
// Check if this is a newly formed group by looking for absence of
|
||||||
|
// other non pet group members
|
||||||
|
if !matches!(role, group::Role::Pet)
|
||||||
|
&& !self
|
||||||
|
.group_members
|
||||||
|
.values()
|
||||||
|
.any(|r| !matches!(r, group::Role::Pet))
|
||||||
|
{
|
||||||
|
frontend_events.push(Event::Chat(comp::ChatType::Meta.chat_msg(
|
||||||
|
"Type /g or /group to chat with your group members",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if let Some(player_info) = self.player_list.get(&uid) {
|
||||||
|
frontend_events.push(Event::Chat(
|
||||||
|
comp::ChatType::GroupMeta("Group".into()).chat_msg(format!(
|
||||||
|
"[{}] joined group",
|
||||||
|
player_info.player_alias
|
||||||
|
)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if self.group_members.insert(uid, role) == Some(role) {
|
||||||
|
warn!(
|
||||||
|
"Received msg to add uid {} to the group members but they \
|
||||||
|
were already there",
|
||||||
|
uid
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Removed(uid) => {
|
||||||
|
if let Some(player_info) = self.player_list.get(&uid) {
|
||||||
|
frontend_events.push(Event::Chat(
|
||||||
|
comp::ChatType::GroupMeta("Group".into()).chat_msg(format!(
|
||||||
|
"[{}] left group",
|
||||||
|
player_info.player_alias
|
||||||
|
)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if self.group_members.remove(&uid).is_none() {
|
||||||
|
warn!(
|
||||||
|
"Received msg to remove uid {} from group members but by they \
|
||||||
|
weren't in there!",
|
||||||
|
uid
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NewLeader(leader) => {
|
||||||
|
self.group_leader = Some(leader);
|
||||||
|
},
|
||||||
|
NewGroup { leader, members } => {
|
||||||
|
self.group_leader = Some(leader);
|
||||||
|
self.group_members = members.into_iter().collect();
|
||||||
|
// Currently add/remove messages treat client as an implicit member
|
||||||
|
// of the group whereas this message explicitly includes them so to
|
||||||
|
// be consistent for now we will remove the client from the
|
||||||
|
// received hashset
|
||||||
|
if let Some(uid) = self.uid() {
|
||||||
|
self.group_members.remove(&uid);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NoGroup => {
|
||||||
|
self.group_leader = None;
|
||||||
|
self.group_members = HashMap::new();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ServerMsg::GroupInvite { inviter, timeout } => {
|
||||||
|
self.group_invite = Some((inviter, std::time::Instant::now(), timeout));
|
||||||
|
},
|
||||||
|
ServerMsg::InvitePending(uid) => {
|
||||||
|
if !self.pending_invites.insert(uid) {
|
||||||
|
warn!("Received message about pending invite that was already pending");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ServerMsg::InviteComplete { target, answer } => {
|
||||||
|
if !self.pending_invites.remove(&target) {
|
||||||
|
warn!(
|
||||||
|
"Received completed invite message for invite that was not in the \
|
||||||
|
list of pending invites"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// TODO: expose this as a new event variant instead of going
|
||||||
|
// through the chat
|
||||||
|
let msg = match answer {
|
||||||
|
// TODO: say who accepted/declined/timed out the invite
|
||||||
|
InviteAnswer::Accepted => "Invite accepted",
|
||||||
|
InviteAnswer::Declined => "Invite declined",
|
||||||
|
InviteAnswer::TimedOut => "Invite timed out",
|
||||||
|
};
|
||||||
|
frontend_events.push(Event::Chat(comp::ChatType::Meta.chat_msg(msg)));
|
||||||
|
},
|
||||||
ServerMsg::Ping => {
|
ServerMsg::Ping => {
|
||||||
self.singleton_stream.send(ClientMsg::Pong)?;
|
self.singleton_stream.send(ClientMsg::Pong)?;
|
||||||
},
|
},
|
||||||
@ -1134,7 +1339,7 @@ impl Client {
|
|||||||
self.state.ecs_mut().apply_entity_package(entity_package);
|
self.state.ecs_mut().apply_entity_package(entity_package);
|
||||||
},
|
},
|
||||||
ServerMsg::DeleteEntity(entity) => {
|
ServerMsg::DeleteEntity(entity) => {
|
||||||
if self.state.read_component_cloned::<Uid>(self.entity) != Some(entity) {
|
if self.uid() != Some(entity) {
|
||||||
self.state
|
self.state
|
||||||
.ecs_mut()
|
.ecs_mut()
|
||||||
.delete_entity_and_clear_from_uid_allocator(entity.0);
|
.delete_entity_and_clear_from_uid_allocator(entity.0);
|
||||||
@ -1200,6 +1405,9 @@ impl Client {
|
|||||||
self.view_distance = Some(vd);
|
self.view_distance = Some(vd);
|
||||||
frontend_events.push(Event::SetViewDistance(vd));
|
frontend_events.push(Event::SetViewDistance(vd));
|
||||||
},
|
},
|
||||||
|
ServerMsg::Outcomes(outcomes) => {
|
||||||
|
frontend_events.extend(outcomes.into_iter().map(Event::Outcome))
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1244,6 +1452,9 @@ impl Client {
|
|||||||
/// Get the player's entity.
|
/// Get the player's entity.
|
||||||
pub fn entity(&self) -> EcsEntity { self.entity }
|
pub fn entity(&self) -> EcsEntity { self.entity }
|
||||||
|
|
||||||
|
/// Get the player's Uid.
|
||||||
|
pub fn uid(&self) -> Option<Uid> { self.state.read_component_copied(self.entity) }
|
||||||
|
|
||||||
/// Get the client state
|
/// Get the client state
|
||||||
pub fn get_client_state(&self) -> ClientState { self.client_state }
|
pub fn get_client_state(&self) -> ClientState { self.client_state }
|
||||||
|
|
||||||
@ -1295,7 +1506,7 @@ impl Client {
|
|||||||
pub fn is_admin(&self) -> bool {
|
pub fn is_admin(&self) -> bool {
|
||||||
let client_uid = self
|
let client_uid = self
|
||||||
.state
|
.state
|
||||||
.read_component_cloned::<Uid>(self.entity)
|
.read_component_copied::<Uid>(self.entity)
|
||||||
.expect("Client doesn't have a Uid!!!");
|
.expect("Client doesn't have a Uid!!!");
|
||||||
|
|
||||||
self.player_list
|
self.player_list
|
||||||
@ -1306,8 +1517,7 @@ impl Client {
|
|||||||
/// Clean client ECS state
|
/// Clean client ECS state
|
||||||
fn clean_state(&mut self) {
|
fn clean_state(&mut self) {
|
||||||
let client_uid = self
|
let client_uid = self
|
||||||
.state
|
.uid()
|
||||||
.read_component_cloned::<Uid>(self.entity)
|
|
||||||
.map(|u| u.into())
|
.map(|u| u.into())
|
||||||
.expect("Client doesn't have a Uid!!!");
|
.expect("Client doesn't have a Uid!!!");
|
||||||
|
|
||||||
@ -1378,7 +1588,7 @@ impl Client {
|
|||||||
comp::ChatType::Tell(from, to) => {
|
comp::ChatType::Tell(from, to) => {
|
||||||
let from_alias = alias_of_uid(from);
|
let from_alias = alias_of_uid(from);
|
||||||
let to_alias = alias_of_uid(to);
|
let to_alias = alias_of_uid(to);
|
||||||
if Some(from) == self.state.ecs().read_storage::<Uid>().get(self.entity) {
|
if Some(*from) == self.uid() {
|
||||||
format!("To [{}]: {}", to_alias, message)
|
format!("To [{}]: {}", to_alias, message)
|
||||||
} else {
|
} else {
|
||||||
format!("From [{}]: {}", from_alias, message)
|
format!("From [{}]: {}", from_alias, message)
|
||||||
|
@ -31,6 +31,7 @@ notify = "5.0.0-pre.3"
|
|||||||
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 = "b943c85e4a38f5ec60cd18c34c73097640162bfe" }
|
authc = { git = "https://gitlab.com/veloren/auth.git", rev = "b943c85e4a38f5ec60cd18c34c73097640162bfe" }
|
||||||
|
slab = "0.4.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.3"
|
criterion = "0.3"
|
||||||
|
@ -38,6 +38,7 @@ pub enum ChatCommand {
|
|||||||
Adminify,
|
Adminify,
|
||||||
Alias,
|
Alias,
|
||||||
Build,
|
Build,
|
||||||
|
Campfire,
|
||||||
Debug,
|
Debug,
|
||||||
DebugColumn,
|
DebugColumn,
|
||||||
Dummy,
|
Dummy,
|
||||||
@ -50,7 +51,6 @@ pub enum ChatCommand {
|
|||||||
Health,
|
Health,
|
||||||
Help,
|
Help,
|
||||||
JoinFaction,
|
JoinFaction,
|
||||||
JoinGroup,
|
|
||||||
Jump,
|
Jump,
|
||||||
Kill,
|
Kill,
|
||||||
KillNpcs,
|
KillNpcs,
|
||||||
@ -80,6 +80,7 @@ pub static CHAT_COMMANDS: &[ChatCommand] = &[
|
|||||||
ChatCommand::Adminify,
|
ChatCommand::Adminify,
|
||||||
ChatCommand::Alias,
|
ChatCommand::Alias,
|
||||||
ChatCommand::Build,
|
ChatCommand::Build,
|
||||||
|
ChatCommand::Campfire,
|
||||||
ChatCommand::Debug,
|
ChatCommand::Debug,
|
||||||
ChatCommand::DebugColumn,
|
ChatCommand::DebugColumn,
|
||||||
ChatCommand::Dummy,
|
ChatCommand::Dummy,
|
||||||
@ -92,7 +93,6 @@ pub static CHAT_COMMANDS: &[ChatCommand] = &[
|
|||||||
ChatCommand::Health,
|
ChatCommand::Health,
|
||||||
ChatCommand::Help,
|
ChatCommand::Help,
|
||||||
ChatCommand::JoinFaction,
|
ChatCommand::JoinFaction,
|
||||||
ChatCommand::JoinGroup,
|
|
||||||
ChatCommand::Jump,
|
ChatCommand::Jump,
|
||||||
ChatCommand::Kill,
|
ChatCommand::Kill,
|
||||||
ChatCommand::KillNpcs,
|
ChatCommand::KillNpcs,
|
||||||
@ -187,6 +187,7 @@ impl ChatCommand {
|
|||||||
),
|
),
|
||||||
ChatCommand::Alias => cmd(vec![Any("name", Required)], "Change your alias", NoAdmin),
|
ChatCommand::Alias => cmd(vec![Any("name", Required)], "Change your alias", NoAdmin),
|
||||||
ChatCommand::Build => cmd(vec![], "Toggles build mode on and off", Admin),
|
ChatCommand::Build => cmd(vec![], "Toggles build mode on and off", Admin),
|
||||||
|
ChatCommand::Campfire => cmd(vec![], "Spawns a campfire", Admin),
|
||||||
ChatCommand::Debug => cmd(vec![], "Place all debug items into your pack.", Admin),
|
ChatCommand::Debug => cmd(vec![], "Place all debug items into your pack.", Admin),
|
||||||
ChatCommand::DebugColumn => cmd(
|
ChatCommand::DebugColumn => cmd(
|
||||||
vec![Integer("x", 15000, Required), Integer("y", 15000, Required)],
|
vec![Integer("x", 15000, Required), Integer("y", 15000, Required)],
|
||||||
@ -246,11 +247,6 @@ impl ChatCommand {
|
|||||||
"Join/leave the specified faction",
|
"Join/leave the specified faction",
|
||||||
NoAdmin,
|
NoAdmin,
|
||||||
),
|
),
|
||||||
ChatCommand::JoinGroup => ChatCommandData::new(
|
|
||||||
vec![Any("group", Optional)],
|
|
||||||
"Join/leave the specified group",
|
|
||||||
NoAdmin,
|
|
||||||
),
|
|
||||||
ChatCommand::Jump => cmd(
|
ChatCommand::Jump => cmd(
|
||||||
vec![
|
vec![
|
||||||
Float("x", 0.0, Required),
|
Float("x", 0.0, Required),
|
||||||
@ -372,6 +368,7 @@ impl ChatCommand {
|
|||||||
ChatCommand::Adminify => "adminify",
|
ChatCommand::Adminify => "adminify",
|
||||||
ChatCommand::Alias => "alias",
|
ChatCommand::Alias => "alias",
|
||||||
ChatCommand::Build => "build",
|
ChatCommand::Build => "build",
|
||||||
|
ChatCommand::Campfire => "campfire",
|
||||||
ChatCommand::Debug => "debug",
|
ChatCommand::Debug => "debug",
|
||||||
ChatCommand::DebugColumn => "debug_column",
|
ChatCommand::DebugColumn => "debug_column",
|
||||||
ChatCommand::Dummy => "dummy",
|
ChatCommand::Dummy => "dummy",
|
||||||
@ -383,7 +380,6 @@ impl ChatCommand {
|
|||||||
ChatCommand::Group => "group",
|
ChatCommand::Group => "group",
|
||||||
ChatCommand::Health => "health",
|
ChatCommand::Health => "health",
|
||||||
ChatCommand::JoinFaction => "join_faction",
|
ChatCommand::JoinFaction => "join_faction",
|
||||||
ChatCommand::JoinGroup => "join_group",
|
|
||||||
ChatCommand::Help => "help",
|
ChatCommand::Help => "help",
|
||||||
ChatCommand::Jump => "jump",
|
ChatCommand::Jump => "jump",
|
||||||
ChatCommand::Kill => "kill",
|
ChatCommand::Kill => "kill",
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
use crate::{path::Chaser, sync::Uid};
|
use crate::{path::Chaser, sync::Uid};
|
||||||
use serde::{Deserialize, Serialize};
|
use specs::{Component, Entity as EcsEntity};
|
||||||
use specs::{Component, Entity as EcsEntity, FlaggedStorage};
|
|
||||||
use specs_idvs::IdvStorage;
|
use specs_idvs::IdvStorage;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub enum Alignment {
|
pub enum Alignment {
|
||||||
/// Wild animals and gentle giants
|
/// Wild animals and gentle giants
|
||||||
Wild,
|
Wild,
|
||||||
@ -52,7 +51,7 @@ impl Alignment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Component for Alignment {
|
impl Component for Alignment {
|
||||||
type Storage = FlaggedStorage<Self, IdvStorage<Self>>;
|
type Storage = IdvStorage<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
|
@ -41,6 +41,7 @@ pub enum CharacterState {
|
|||||||
Climb,
|
Climb,
|
||||||
Sit,
|
Sit,
|
||||||
Dance,
|
Dance,
|
||||||
|
Sneak,
|
||||||
Glide,
|
Glide,
|
||||||
GlideWield,
|
GlideWield,
|
||||||
/// A basic blocking state
|
/// A basic blocking state
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::{msg::ServerMsg, sync::Uid};
|
use crate::{comp::group::Group, msg::ServerMsg, sync::Uid};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use specs::Component;
|
use specs::Component;
|
||||||
use specs_idvs::IdvStorage;
|
use specs_idvs::IdvStorage;
|
||||||
@ -15,7 +15,7 @@ pub enum ChatMode {
|
|||||||
/// Talk to players in your region of the world
|
/// Talk to players in your region of the world
|
||||||
Region,
|
Region,
|
||||||
/// Talk to your current group of players
|
/// Talk to your current group of players
|
||||||
Group(String),
|
Group(Group),
|
||||||
/// Talk to your faction
|
/// Talk to your faction
|
||||||
Faction(String),
|
Faction(String),
|
||||||
/// Talk to every player on the server
|
/// Talk to every player on the server
|
||||||
@ -28,16 +28,16 @@ impl Component for ChatMode {
|
|||||||
|
|
||||||
impl ChatMode {
|
impl ChatMode {
|
||||||
/// Create a message from your current chat mode and uuid.
|
/// Create a message from your current chat mode and uuid.
|
||||||
pub fn new_message(&self, from: Uid, message: String) -> ChatMsg {
|
pub fn new_message(&self, from: Uid, message: String) -> UnresolvedChatMsg {
|
||||||
let chat_type = match self {
|
let chat_type = match self {
|
||||||
ChatMode::Tell(to) => ChatType::Tell(from, *to),
|
ChatMode::Tell(to) => ChatType::Tell(from, *to),
|
||||||
ChatMode::Say => ChatType::Say(from),
|
ChatMode::Say => ChatType::Say(from),
|
||||||
ChatMode::Region => ChatType::Region(from),
|
ChatMode::Region => ChatType::Region(from),
|
||||||
ChatMode::Group(name) => ChatType::Group(from, name.to_string()),
|
ChatMode::Group(group) => ChatType::Group(from, *group),
|
||||||
ChatMode::Faction(name) => ChatType::Faction(from, name.to_string()),
|
ChatMode::Faction(faction) => ChatType::Faction(from, faction.clone()),
|
||||||
ChatMode::World => ChatType::World(from),
|
ChatMode::World => ChatType::World(from),
|
||||||
};
|
};
|
||||||
ChatMsg { chat_type, message }
|
UnresolvedChatMsg { chat_type, message }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ impl Default for ChatMode {
|
|||||||
///
|
///
|
||||||
/// This is a superset of `SpeechBubbleType`, which is a superset of `ChatMode`
|
/// This is a superset of `SpeechBubbleType`, which is a superset of `ChatMode`
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub enum ChatType {
|
pub enum ChatType<G> {
|
||||||
/// A player came online
|
/// A player came online
|
||||||
Online,
|
Online,
|
||||||
/// A player went offline
|
/// A player went offline
|
||||||
@ -61,7 +61,7 @@ pub enum ChatType {
|
|||||||
/// Inform players that someone died
|
/// Inform players that someone died
|
||||||
Kill,
|
Kill,
|
||||||
/// Server notifications to a group, such as player join/leave
|
/// Server notifications to a group, such as player join/leave
|
||||||
GroupMeta(String),
|
GroupMeta(G),
|
||||||
/// Server notifications to a faction, such as player join/leave
|
/// Server notifications to a faction, such as player join/leave
|
||||||
FactionMeta(String),
|
FactionMeta(String),
|
||||||
/// One-on-one chat (from, to)
|
/// One-on-one chat (from, to)
|
||||||
@ -69,7 +69,7 @@ pub enum ChatType {
|
|||||||
/// Chat with nearby players
|
/// Chat with nearby players
|
||||||
Say(Uid),
|
Say(Uid),
|
||||||
/// Group chat
|
/// Group chat
|
||||||
Group(Uid, String),
|
Group(Uid, G),
|
||||||
/// Factional chat
|
/// Factional chat
|
||||||
Faction(Uid, String),
|
Faction(Uid, String),
|
||||||
/// Regional chat
|
/// Regional chat
|
||||||
@ -86,17 +86,18 @@ pub enum ChatType {
|
|||||||
Loot,
|
Loot,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChatType {
|
impl<G> ChatType<G> {
|
||||||
pub fn chat_msg<S>(self, msg: S) -> ChatMsg
|
pub fn chat_msg<S>(self, msg: S) -> GenericChatMsg<G>
|
||||||
where
|
where
|
||||||
S: Into<String>,
|
S: Into<String>,
|
||||||
{
|
{
|
||||||
ChatMsg {
|
GenericChatMsg {
|
||||||
chat_type: self,
|
chat_type: self,
|
||||||
message: msg.into(),
|
message: msg.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
impl ChatType<String> {
|
||||||
pub fn server_msg<S>(self, msg: S) -> ServerMsg
|
pub fn server_msg<S>(self, msg: S) -> ServerMsg
|
||||||
where
|
where
|
||||||
S: Into<String>,
|
S: Into<String>,
|
||||||
@ -106,12 +107,15 @@ impl ChatType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct ChatMsg {
|
pub struct GenericChatMsg<G> {
|
||||||
pub chat_type: ChatType,
|
pub chat_type: ChatType<G>,
|
||||||
pub message: String,
|
pub message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChatMsg {
|
pub type ChatMsg = GenericChatMsg<String>;
|
||||||
|
pub type UnresolvedChatMsg = GenericChatMsg<Group>;
|
||||||
|
|
||||||
|
impl<G> GenericChatMsg<G> {
|
||||||
pub const NPC_DISTANCE: f32 = 100.0;
|
pub const NPC_DISTANCE: f32 = 100.0;
|
||||||
pub const REGION_DISTANCE: f32 = 1000.0;
|
pub const REGION_DISTANCE: f32 = 1000.0;
|
||||||
pub const SAY_DISTANCE: f32 = 100.0;
|
pub const SAY_DISTANCE: f32 = 100.0;
|
||||||
@ -121,6 +125,32 @@ impl ChatMsg {
|
|||||||
Self { chat_type, message }
|
Self { chat_type, message }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn map_group<T>(self, mut f: impl FnMut(G) -> T) -> GenericChatMsg<T> {
|
||||||
|
let chat_type = match self.chat_type {
|
||||||
|
ChatType::Online => ChatType::Online,
|
||||||
|
ChatType::Offline => ChatType::Offline,
|
||||||
|
ChatType::CommandInfo => ChatType::CommandInfo,
|
||||||
|
ChatType::CommandError => ChatType::CommandError,
|
||||||
|
ChatType::Loot => ChatType::Loot,
|
||||||
|
ChatType::FactionMeta(a) => ChatType::FactionMeta(a),
|
||||||
|
ChatType::GroupMeta(g) => ChatType::GroupMeta(f(g)),
|
||||||
|
ChatType::Kill => ChatType::Kill,
|
||||||
|
ChatType::Tell(a, b) => ChatType::Tell(a, b),
|
||||||
|
ChatType::Say(a) => ChatType::Say(a),
|
||||||
|
ChatType::Group(a, g) => ChatType::Group(a, f(g)),
|
||||||
|
ChatType::Faction(a, b) => ChatType::Faction(a, b),
|
||||||
|
ChatType::Region(a) => ChatType::Region(a),
|
||||||
|
ChatType::World(a) => ChatType::World(a),
|
||||||
|
ChatType::Npc(a, b) => ChatType::Npc(a, b),
|
||||||
|
ChatType::Meta => ChatType::Meta,
|
||||||
|
};
|
||||||
|
|
||||||
|
GenericChatMsg {
|
||||||
|
chat_type,
|
||||||
|
message: self.message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_bubble(&self) -> Option<(SpeechBubble, Uid)> {
|
pub fn to_bubble(&self) -> Option<(SpeechBubble, Uid)> {
|
||||||
let icon = self.icon();
|
let icon = self.icon();
|
||||||
if let ChatType::Npc(from, r) = self.chat_type {
|
if let ChatType::Npc(from, r) = self.chat_type {
|
||||||
@ -174,19 +204,6 @@ impl ChatMsg {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Player groups are useful when forming raiding parties and coordinating
|
|
||||||
/// gameplay.
|
|
||||||
///
|
|
||||||
/// Groups are currently just an associated String (the group's name)
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Group(pub String);
|
|
||||||
impl Component for Group {
|
|
||||||
type Storage = IdvStorage<Self>;
|
|
||||||
}
|
|
||||||
impl From<String> for Group {
|
|
||||||
fn from(s: String) -> Self { Group(s) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Player factions are used to coordinate pvp vs hostile factions or segment
|
/// Player factions are used to coordinate pvp vs hostile factions or segment
|
||||||
/// chat from the world
|
/// chat from the world
|
||||||
///
|
///
|
||||||
|
@ -18,12 +18,23 @@ pub enum InventoryManip {
|
|||||||
CraftRecipe(String),
|
CraftRecipe(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum GroupManip {
|
||||||
|
Invite(Uid),
|
||||||
|
Accept,
|
||||||
|
Decline,
|
||||||
|
Leave,
|
||||||
|
Kick(Uid),
|
||||||
|
AssignLeader(Uid),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum ControlEvent {
|
pub enum ControlEvent {
|
||||||
ToggleLantern,
|
ToggleLantern,
|
||||||
Mount(Uid),
|
Mount(Uid),
|
||||||
Unmount,
|
Unmount,
|
||||||
InventoryManip(InventoryManip),
|
InventoryManip(InventoryManip),
|
||||||
|
GroupManip(GroupManip),
|
||||||
Respawn,
|
Respawn,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,6 +46,7 @@ pub enum ControlAction {
|
|||||||
Unwield,
|
Unwield,
|
||||||
Sit,
|
Sit,
|
||||||
Dance,
|
Dance,
|
||||||
|
Sneak,
|
||||||
Stand,
|
Stand,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,7 +171,8 @@ pub struct ControllerInputs {
|
|||||||
pub wall_leap: Input,
|
pub wall_leap: Input,
|
||||||
pub charge: Input,
|
pub charge: Input,
|
||||||
pub climb: Option<Climb>,
|
pub climb: Option<Climb>,
|
||||||
pub swim: Input,
|
pub swimup: Input,
|
||||||
|
pub swimdown: Input,
|
||||||
pub move_dir: Vec2<f32>,
|
pub move_dir: Vec2<f32>,
|
||||||
pub look_dir: Dir,
|
pub look_dir: Dir,
|
||||||
}
|
}
|
||||||
@ -183,7 +196,8 @@ impl ControllerInputs {
|
|||||||
self.glide.tick(dt);
|
self.glide.tick(dt);
|
||||||
self.wall_leap.tick(dt);
|
self.wall_leap.tick(dt);
|
||||||
self.charge.tick(dt);
|
self.charge.tick(dt);
|
||||||
self.swim.tick(dt);
|
self.swimup.tick(dt);
|
||||||
|
self.swimdown.tick(dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick_freshness(&mut self) {
|
pub fn tick_freshness(&mut self) {
|
||||||
@ -195,7 +209,8 @@ impl ControllerInputs {
|
|||||||
self.glide.tick_freshness();
|
self.glide.tick_freshness();
|
||||||
self.wall_leap.tick_freshness();
|
self.wall_leap.tick_freshness();
|
||||||
self.charge.tick_freshness();
|
self.charge.tick_freshness();
|
||||||
self.swim.tick_freshness();
|
self.swimup.tick_freshness();
|
||||||
|
self.swimdown.tick_freshness();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates Controller inputs with new version received from the client
|
/// Updates Controller inputs with new version received from the client
|
||||||
@ -209,7 +224,8 @@ impl ControllerInputs {
|
|||||||
self.wall_leap.update_with_new(new.wall_leap);
|
self.wall_leap.update_with_new(new.wall_leap);
|
||||||
self.charge.update_with_new(new.charge);
|
self.charge.update_with_new(new.charge);
|
||||||
self.climb = new.climb;
|
self.climb = new.climb;
|
||||||
self.swim.update_with_new(new.swim);
|
self.swimup.update_with_new(new.swimup);
|
||||||
|
self.swimdown.update_with_new(new.swimdown);
|
||||||
self.move_dir = new.move_dir;
|
self.move_dir = new.move_dir;
|
||||||
self.look_dir = new.look_dir;
|
self.look_dir = new.look_dir;
|
||||||
}
|
}
|
||||||
|
528
common/src/comp/group.rs
Normal file
528
common/src/comp/group.rs
Normal file
@ -0,0 +1,528 @@
|
|||||||
|
use crate::{comp::Alignment, sync::Uid};
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use slab::Slab;
|
||||||
|
use specs::{Component, FlaggedStorage, Join};
|
||||||
|
use specs_idvs::IdvStorage;
|
||||||
|
use tracing::{error, warn};
|
||||||
|
|
||||||
|
// Primitive group system
|
||||||
|
// Shortcomings include:
|
||||||
|
// - no support for more complex group structures
|
||||||
|
// - lack of npc group integration
|
||||||
|
// - relies on careful management of groups to maintain a valid state
|
||||||
|
// - the possesion rod could probably wreck this
|
||||||
|
// - clients don't know which pets are theirs (could be easy to solve by
|
||||||
|
// putting owner uid in Role::Pet)
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct Group(u32);
|
||||||
|
|
||||||
|
// TODO: Hack
|
||||||
|
// Corresponds to Alignment::Enemy
|
||||||
|
pub const ENEMY: Group = Group(u32::MAX);
|
||||||
|
// Corresponds to Alignment::Npc | Alignment::Tame
|
||||||
|
pub const NPC: Group = Group(u32::MAX - 1);
|
||||||
|
|
||||||
|
impl Component for Group {
|
||||||
|
type Storage = FlaggedStorage<Self, IdvStorage<Self>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Invite(pub specs::Entity);
|
||||||
|
impl Component for Invite {
|
||||||
|
type Storage = IdvStorage<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pending invites that an entity currently has sent out
|
||||||
|
// (invited entity, instant when invite times out)
|
||||||
|
pub struct PendingInvites(pub Vec<(specs::Entity, std::time::Instant)>);
|
||||||
|
impl Component for PendingInvites {
|
||||||
|
type Storage = IdvStorage<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct GroupInfo {
|
||||||
|
// TODO: what about enemy groups, either the leader will constantly change because they have to
|
||||||
|
// be loaded or we create a dummy entity or this needs to be optional
|
||||||
|
pub leader: specs::Entity,
|
||||||
|
// Number of group members (excluding pets)
|
||||||
|
pub num_members: u32,
|
||||||
|
// Name of the group
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum Role {
|
||||||
|
Member,
|
||||||
|
Pet,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum ChangeNotification<E> {
|
||||||
|
// :D
|
||||||
|
Added(E, Role),
|
||||||
|
// :(
|
||||||
|
Removed(E),
|
||||||
|
NewLeader(E),
|
||||||
|
// Use to put in a group overwriting existing group
|
||||||
|
NewGroup { leader: E, members: Vec<(E, Role)> },
|
||||||
|
// No longer in a group
|
||||||
|
NoGroup,
|
||||||
|
}
|
||||||
|
// Note: now that we are dipping into uids here consider just using
|
||||||
|
// ChangeNotification<Uid> everywhere
|
||||||
|
// Also note when the same notification is sent to multiple destinations the
|
||||||
|
// maping might be duplicated effort
|
||||||
|
impl<E> ChangeNotification<E> {
|
||||||
|
pub fn try_map<T>(self, f: impl Fn(E) -> Option<T>) -> Option<ChangeNotification<T>> {
|
||||||
|
match self {
|
||||||
|
Self::Added(e, r) => f(e).map(|t| ChangeNotification::Added(t, r)),
|
||||||
|
Self::Removed(e) => f(e).map(ChangeNotification::Removed),
|
||||||
|
Self::NewLeader(e) => f(e).map(ChangeNotification::NewLeader),
|
||||||
|
// Note just discards members that fail map
|
||||||
|
Self::NewGroup { leader, members } => {
|
||||||
|
f(leader).map(|leader| ChangeNotification::NewGroup {
|
||||||
|
leader,
|
||||||
|
members: members
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(e, r)| f(e).map(|t| (t, r)))
|
||||||
|
.collect(),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Self::NoGroup => Some(ChangeNotification::NoGroup),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type GroupsMut<'a> = specs::WriteStorage<'a, Group>;
|
||||||
|
type Groups<'a> = specs::ReadStorage<'a, Group>;
|
||||||
|
type Alignments<'a> = specs::ReadStorage<'a, Alignment>;
|
||||||
|
type Uids<'a> = specs::ReadStorage<'a, Uid>;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct GroupManager {
|
||||||
|
groups: Slab<GroupInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gather list of pets of the group member
|
||||||
|
// Note: iterating through all entities here could become slow at higher entity
|
||||||
|
// counts
|
||||||
|
fn pets(
|
||||||
|
entity: specs::Entity,
|
||||||
|
uid: Uid,
|
||||||
|
alignments: &Alignments,
|
||||||
|
entities: &specs::Entities,
|
||||||
|
) -> Vec<specs::Entity> {
|
||||||
|
(entities, alignments)
|
||||||
|
.join()
|
||||||
|
.filter_map(|(e, a)| {
|
||||||
|
matches!(a, Alignment::Owned(owner) if *owner == uid && e != entity).then_some(e)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns list of current members of a group
|
||||||
|
pub fn members<'a>(
|
||||||
|
group: Group,
|
||||||
|
groups: impl Join<Type = &'a Group> + 'a,
|
||||||
|
entities: &'a specs::Entities,
|
||||||
|
alignments: &'a Alignments,
|
||||||
|
uids: &'a Uids,
|
||||||
|
) -> impl Iterator<Item = (specs::Entity, Role)> + 'a {
|
||||||
|
(entities, groups, alignments, uids)
|
||||||
|
.join()
|
||||||
|
.filter_map(move |(e, g, a, u)| {
|
||||||
|
(*g == group).then(|| {
|
||||||
|
(
|
||||||
|
e,
|
||||||
|
if matches!(a, Alignment::Owned(owner) if owner != u) {
|
||||||
|
Role::Pet
|
||||||
|
} else {
|
||||||
|
Role::Member
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: optimize add/remove for massive NPC groups
|
||||||
|
impl GroupManager {
|
||||||
|
pub fn group_info(&self, group: Group) -> Option<&GroupInfo> {
|
||||||
|
self.groups.get(group.0 as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn group_info_mut(&mut self, group: Group) -> Option<&mut GroupInfo> {
|
||||||
|
self.groups.get_mut(group.0 as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_group(&mut self, leader: specs::Entity, num_members: u32) -> Group {
|
||||||
|
Group(self.groups.insert(GroupInfo {
|
||||||
|
leader,
|
||||||
|
num_members,
|
||||||
|
name: "Group".into(),
|
||||||
|
}) as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_group(&mut self, group: Group) { self.groups.remove(group.0 as usize); }
|
||||||
|
|
||||||
|
// Add someone to a group
|
||||||
|
// Also used to create new groups
|
||||||
|
#[allow(clippy::too_many_arguments)] // TODO: Pending review in #587
|
||||||
|
pub fn add_group_member(
|
||||||
|
&mut self,
|
||||||
|
leader: specs::Entity,
|
||||||
|
new_member: specs::Entity,
|
||||||
|
entities: &specs::Entities,
|
||||||
|
groups: &mut GroupsMut,
|
||||||
|
alignments: &Alignments,
|
||||||
|
uids: &Uids,
|
||||||
|
mut notifier: impl FnMut(specs::Entity, ChangeNotification<specs::Entity>),
|
||||||
|
) {
|
||||||
|
// Ensure leader is not inviting themselves
|
||||||
|
if leader == new_member {
|
||||||
|
warn!("Attempt to form group with leader as the only member (this is disallowed)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get uid
|
||||||
|
let new_member_uid = if let Some(uid) = uids.get(new_member) {
|
||||||
|
*uid
|
||||||
|
} else {
|
||||||
|
error!("Failed to retrieve uid for the new group member");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// If new member is a member of a different group remove that
|
||||||
|
if groups
|
||||||
|
.get(new_member)
|
||||||
|
.and_then(|g| self.group_info(*g))
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
self.leave_group(
|
||||||
|
new_member,
|
||||||
|
groups,
|
||||||
|
alignments,
|
||||||
|
uids,
|
||||||
|
entities,
|
||||||
|
&mut notifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let group = match groups.get(leader).copied() {
|
||||||
|
Some(id)
|
||||||
|
if self
|
||||||
|
.group_info(id)
|
||||||
|
.map(|info| info.leader == leader)
|
||||||
|
.unwrap_or(false) =>
|
||||||
|
{
|
||||||
|
Some(id)
|
||||||
|
},
|
||||||
|
// Member of an existing group can't be a leader
|
||||||
|
// If the lead is a member of another group leave that group first
|
||||||
|
Some(_) => {
|
||||||
|
self.leave_group(leader, groups, alignments, uids, entities, &mut notifier);
|
||||||
|
None
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let group = if let Some(group) = group {
|
||||||
|
// Increment group size
|
||||||
|
// Note: unwrap won't fail since we just retrieved the group successfully above
|
||||||
|
self.group_info_mut(group).unwrap().num_members += 1;
|
||||||
|
group
|
||||||
|
} else {
|
||||||
|
let new_group = self.create_group(leader, 2);
|
||||||
|
// Unwrap should not fail since we just found these entities and they should
|
||||||
|
// still exist Note: if there is an issue replace with a warn
|
||||||
|
groups.insert(leader, new_group).unwrap();
|
||||||
|
// Inform
|
||||||
|
notifier(leader, ChangeNotification::NewLeader(leader));
|
||||||
|
new_group
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_pets = pets(new_member, new_member_uid, alignments, entities);
|
||||||
|
|
||||||
|
// Inform
|
||||||
|
members(group, &*groups, entities, alignments, uids).for_each(|(e, role)| match role {
|
||||||
|
Role::Member => {
|
||||||
|
notifier(e, ChangeNotification::Added(new_member, Role::Member));
|
||||||
|
notifier(new_member, ChangeNotification::Added(e, Role::Member));
|
||||||
|
|
||||||
|
new_pets.iter().for_each(|p| {
|
||||||
|
notifier(e, ChangeNotification::Added(*p, Role::Pet));
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Role::Pet => {
|
||||||
|
notifier(new_member, ChangeNotification::Added(e, Role::Pet));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
notifier(new_member, ChangeNotification::NewLeader(leader));
|
||||||
|
|
||||||
|
// Add group id for new member and pets
|
||||||
|
// Unwrap should not fail since we just found these entities and they should
|
||||||
|
// still exist
|
||||||
|
// Note: if there is an issue replace with a warn
|
||||||
|
let _ = groups.insert(new_member, group).unwrap();
|
||||||
|
new_pets.iter().for_each(|e| {
|
||||||
|
let _ = groups.insert(*e, group).unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)] // TODO: Pending review in #587
|
||||||
|
pub fn new_pet(
|
||||||
|
&mut self,
|
||||||
|
pet: specs::Entity,
|
||||||
|
owner: specs::Entity,
|
||||||
|
groups: &mut GroupsMut,
|
||||||
|
entities: &specs::Entities,
|
||||||
|
alignments: &Alignments,
|
||||||
|
uids: &Uids,
|
||||||
|
notifier: &mut impl FnMut(specs::Entity, ChangeNotification<specs::Entity>),
|
||||||
|
) {
|
||||||
|
let group = match groups.get(owner).copied() {
|
||||||
|
Some(group) => group,
|
||||||
|
None => {
|
||||||
|
let new_group = self.create_group(owner, 1);
|
||||||
|
groups.insert(owner, new_group).unwrap();
|
||||||
|
// Inform
|
||||||
|
notifier(owner, ChangeNotification::NewLeader(owner));
|
||||||
|
new_group
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Inform
|
||||||
|
members(group, &*groups, entities, alignments, uids).for_each(|(e, role)| match role {
|
||||||
|
Role::Member => {
|
||||||
|
notifier(e, ChangeNotification::Added(pet, Role::Pet));
|
||||||
|
},
|
||||||
|
Role::Pet => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add
|
||||||
|
groups.insert(pet, group).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn leave_group(
|
||||||
|
&mut self,
|
||||||
|
member: specs::Entity,
|
||||||
|
groups: &mut GroupsMut,
|
||||||
|
alignments: &Alignments,
|
||||||
|
uids: &Uids,
|
||||||
|
entities: &specs::Entities,
|
||||||
|
notifier: &mut impl FnMut(specs::Entity, ChangeNotification<specs::Entity>),
|
||||||
|
) {
|
||||||
|
// Pets can't leave
|
||||||
|
if matches!(alignments.get(member), Some(Alignment::Owned(uid)) if uids.get(member).map_or(true, |u| u != uid))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.remove_from_group(member, groups, alignments, uids, entities, notifier, false);
|
||||||
|
|
||||||
|
// Set NPC back to their group
|
||||||
|
if let Some(alignment) = alignments.get(member) {
|
||||||
|
match alignment {
|
||||||
|
Alignment::Npc => {
|
||||||
|
let _ = groups.insert(member, NPC);
|
||||||
|
},
|
||||||
|
Alignment::Enemy => {
|
||||||
|
let _ = groups.insert(member, ENEMY);
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn entity_deleted(
|
||||||
|
&mut self,
|
||||||
|
member: specs::Entity,
|
||||||
|
groups: &mut GroupsMut,
|
||||||
|
alignments: &Alignments,
|
||||||
|
uids: &Uids,
|
||||||
|
entities: &specs::Entities,
|
||||||
|
notifier: &mut impl FnMut(specs::Entity, ChangeNotification<specs::Entity>),
|
||||||
|
) {
|
||||||
|
self.remove_from_group(member, groups, alignments, uids, entities, notifier, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove someone from a group if they are in one
|
||||||
|
// Don't need to check if they are in a group before calling this
|
||||||
|
// Also removes pets (ie call this if the pet no longer exists)
|
||||||
|
#[allow(clippy::too_many_arguments)] // TODO: Pending review in #587
|
||||||
|
fn remove_from_group(
|
||||||
|
&mut self,
|
||||||
|
member: specs::Entity,
|
||||||
|
groups: &mut GroupsMut,
|
||||||
|
alignments: &Alignments,
|
||||||
|
uids: &Uids,
|
||||||
|
entities: &specs::Entities,
|
||||||
|
notifier: &mut impl FnMut(specs::Entity, ChangeNotification<specs::Entity>),
|
||||||
|
to_be_deleted: bool,
|
||||||
|
) {
|
||||||
|
let group = match groups.get(member) {
|
||||||
|
Some(group) => *group,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
// If leaving entity was the leader disband the group
|
||||||
|
if self
|
||||||
|
.group_info(group)
|
||||||
|
.map(|info| info.leader == member)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
// Remove group
|
||||||
|
self.remove_group(group);
|
||||||
|
|
||||||
|
(entities, uids, &*groups, alignments.maybe())
|
||||||
|
.join()
|
||||||
|
.filter(|(e, _, g, _)| **g == group && !(to_be_deleted && *e == member))
|
||||||
|
.fold(
|
||||||
|
HashMap::<Uid, (Option<specs::Entity>, Vec<specs::Entity>)>::new(),
|
||||||
|
|mut acc, (e, uid, _, alignment)| {
|
||||||
|
if let Some(owner) = alignment.and_then(|a| match a {
|
||||||
|
Alignment::Owned(owner) if uid != owner => Some(owner),
|
||||||
|
_ => None,
|
||||||
|
}) {
|
||||||
|
// A pet
|
||||||
|
// Assumes owner will be in the group
|
||||||
|
acc.entry(*owner).or_default().1.push(e);
|
||||||
|
} else {
|
||||||
|
// Not a pet
|
||||||
|
acc.entry(*uid).or_default().0 = Some(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
acc
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_, v)| v)
|
||||||
|
.for_each(|(owner, pets)| {
|
||||||
|
if let Some(owner) = owner {
|
||||||
|
if !pets.is_empty() {
|
||||||
|
let mut members =
|
||||||
|
pets.iter().map(|e| (*e, Role::Pet)).collect::<Vec<_>>();
|
||||||
|
members.push((owner, Role::Member));
|
||||||
|
|
||||||
|
// New group
|
||||||
|
let new_group = self.create_group(owner, 1);
|
||||||
|
for (member, _) in &members {
|
||||||
|
groups.insert(*member, new_group).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
notifier(owner, ChangeNotification::NewGroup {
|
||||||
|
leader: owner,
|
||||||
|
members,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// If no pets just remove group
|
||||||
|
groups.remove(owner);
|
||||||
|
notifier(owner, ChangeNotification::NoGroup)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Owner not found, potentially the were removed from the world
|
||||||
|
pets.into_iter().for_each(|pet| {
|
||||||
|
groups.remove(pet);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Not leader
|
||||||
|
let leaving_member_uid = if let Some(uid) = uids.get(member) {
|
||||||
|
*uid
|
||||||
|
} else {
|
||||||
|
error!("Failed to retrieve uid for the leaving member");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let leaving_pets = pets(member, leaving_member_uid, alignments, entities);
|
||||||
|
|
||||||
|
// If pets and not about to be deleted form new group
|
||||||
|
if !leaving_pets.is_empty() && !to_be_deleted {
|
||||||
|
let new_group = self.create_group(member, 1);
|
||||||
|
|
||||||
|
notifier(member, ChangeNotification::NewGroup {
|
||||||
|
leader: member,
|
||||||
|
members: leaving_pets
|
||||||
|
.iter()
|
||||||
|
.map(|p| (*p, Role::Pet))
|
||||||
|
.chain(std::iter::once((member, Role::Member)))
|
||||||
|
.collect(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let _ = groups.insert(member, new_group).unwrap();
|
||||||
|
leaving_pets.iter().for_each(|&e| {
|
||||||
|
let _ = groups.insert(e, new_group).unwrap();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let _ = groups.remove(member);
|
||||||
|
notifier(member, ChangeNotification::NoGroup);
|
||||||
|
leaving_pets.iter().for_each(|&e| {
|
||||||
|
let _ = groups.remove(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(info) = self.group_info_mut(group) {
|
||||||
|
// If not pet, decrement number of members
|
||||||
|
if !matches!(alignments.get(member), Some(Alignment::Owned(owner)) if uids.get(member).map_or(true, |uid| uid != owner))
|
||||||
|
{
|
||||||
|
if info.num_members > 0 {
|
||||||
|
info.num_members -= 1;
|
||||||
|
} else {
|
||||||
|
error!("Group with invalid number of members")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut remaining_count = 0; // includes pets
|
||||||
|
// Inform remaining members
|
||||||
|
members(group, &*groups, entities, alignments, uids).for_each(|(e, role)| {
|
||||||
|
remaining_count += 1;
|
||||||
|
match role {
|
||||||
|
Role::Member => {
|
||||||
|
notifier(e, ChangeNotification::Removed(member));
|
||||||
|
leaving_pets.iter().for_each(|p| {
|
||||||
|
notifier(e, ChangeNotification::Removed(*p));
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Role::Pet => {},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// If leader is the last one left then disband the group
|
||||||
|
// Assumes last member is the leader
|
||||||
|
if remaining_count == 1 {
|
||||||
|
let leader = info.leader;
|
||||||
|
self.remove_group(group);
|
||||||
|
groups.remove(leader);
|
||||||
|
notifier(leader, ChangeNotification::NoGroup);
|
||||||
|
} else if remaining_count == 0 {
|
||||||
|
error!("Somehow group has no members")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign new group leader
|
||||||
|
// Does nothing if new leader is not part of a group
|
||||||
|
pub fn assign_leader(
|
||||||
|
&mut self,
|
||||||
|
new_leader: specs::Entity,
|
||||||
|
groups: &Groups,
|
||||||
|
entities: &specs::Entities,
|
||||||
|
alignments: &Alignments,
|
||||||
|
uids: &Uids,
|
||||||
|
mut notifier: impl FnMut(specs::Entity, ChangeNotification<specs::Entity>),
|
||||||
|
) {
|
||||||
|
let group = match groups.get(new_leader) {
|
||||||
|
Some(group) => *group,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set new leader
|
||||||
|
self.groups[group.0 as usize].leader = new_leader;
|
||||||
|
|
||||||
|
// Point to new leader
|
||||||
|
members(group, &*groups, entities, alignments, uids).for_each(|(e, role)| match role {
|
||||||
|
Role::Member => notifier(e, ChangeNotification::NewLeader(new_leader)),
|
||||||
|
Role::Pet => {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -261,6 +261,7 @@ impl Tool {
|
|||||||
col: (0.85, 0.5, 0.11).into(),
|
col: (0.85, 0.5, 0.11).into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
|
|
||||||
projectile_gravity: None,
|
projectile_gravity: None,
|
||||||
},
|
},
|
||||||
BasicRanged {
|
BasicRanged {
|
||||||
|
@ -7,6 +7,7 @@ mod chat;
|
|||||||
mod controller;
|
mod controller;
|
||||||
mod damage;
|
mod damage;
|
||||||
mod energy;
|
mod energy;
|
||||||
|
pub mod group;
|
||||||
mod inputs;
|
mod inputs;
|
||||||
mod inventory;
|
mod inventory;
|
||||||
mod last;
|
mod last;
|
||||||
@ -17,7 +18,7 @@ mod player;
|
|||||||
pub mod projectile;
|
pub mod projectile;
|
||||||
pub mod skills;
|
pub mod skills;
|
||||||
mod stats;
|
mod stats;
|
||||||
mod visual;
|
pub mod visual;
|
||||||
|
|
||||||
// Reexports
|
// Reexports
|
||||||
pub use ability::{CharacterAbility, CharacterAbilityType, ItemConfig, Loadout};
|
pub use ability::{CharacterAbility, CharacterAbilityType, ItemConfig, Loadout};
|
||||||
@ -28,13 +29,16 @@ pub use body::{
|
|||||||
humanoid, object, quadruped_low, quadruped_medium, quadruped_small, AllBodies, Body, BodyData,
|
humanoid, object, quadruped_low, quadruped_medium, quadruped_small, AllBodies, Body, BodyData,
|
||||||
};
|
};
|
||||||
pub use character_state::{Attacking, CharacterState, StateUpdate};
|
pub use character_state::{Attacking, CharacterState, StateUpdate};
|
||||||
pub use chat::{ChatMode, ChatMsg, ChatType, Faction, Group, SpeechBubble, SpeechBubbleType};
|
pub use chat::{
|
||||||
|
ChatMode, ChatMsg, ChatType, Faction, SpeechBubble, SpeechBubbleType, UnresolvedChatMsg,
|
||||||
|
};
|
||||||
pub use controller::{
|
pub use controller::{
|
||||||
Climb, ControlAction, ControlEvent, Controller, ControllerInputs, Input, InventoryManip,
|
Climb, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, Input,
|
||||||
MountState, Mounting,
|
InventoryManip, MountState, Mounting,
|
||||||
};
|
};
|
||||||
pub use damage::{Damage, DamageSource};
|
pub use damage::{Damage, DamageSource};
|
||||||
pub use energy::{Energy, EnergySource};
|
pub use energy::{Energy, EnergySource};
|
||||||
|
pub use group::Group;
|
||||||
pub use inputs::CanBuild;
|
pub use inputs::CanBuild;
|
||||||
pub use inventory::{
|
pub use inventory::{
|
||||||
item,
|
item,
|
||||||
|
@ -24,6 +24,10 @@ impl Component for Vel {
|
|||||||
#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Ori(pub Dir);
|
pub struct Ori(pub Dir);
|
||||||
|
|
||||||
|
impl Ori {
|
||||||
|
pub fn vec(&self) -> &Vec3<f32> { &*self.0 }
|
||||||
|
}
|
||||||
|
|
||||||
impl Component for Ori {
|
impl Component for Ori {
|
||||||
type Storage = IdvStorage<Self>;
|
type Storage = IdvStorage<Self>;
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ pub enum ServerEvent {
|
|||||||
pos: Vec3<f32>,
|
pos: Vec3<f32>,
|
||||||
power: f32,
|
power: f32,
|
||||||
owner: Option<Uid>,
|
owner: Option<Uid>,
|
||||||
|
friendly_damage: bool,
|
||||||
},
|
},
|
||||||
Damage {
|
Damage {
|
||||||
uid: Uid,
|
uid: Uid,
|
||||||
@ -35,6 +36,7 @@ pub enum ServerEvent {
|
|||||||
cause: comp::HealthSource,
|
cause: comp::HealthSource,
|
||||||
},
|
},
|
||||||
InventoryManip(EcsEntity, comp::InventoryManip),
|
InventoryManip(EcsEntity, comp::InventoryManip),
|
||||||
|
GroupManip(EcsEntity, comp::GroupManip),
|
||||||
Respawn(EcsEntity),
|
Respawn(EcsEntity),
|
||||||
Shoot {
|
Shoot {
|
||||||
entity: EcsEntity,
|
entity: EcsEntity,
|
||||||
@ -80,7 +82,7 @@ pub enum ServerEvent {
|
|||||||
ChunkRequest(EcsEntity, Vec2<i32>),
|
ChunkRequest(EcsEntity, Vec2<i32>),
|
||||||
ChatCmd(EcsEntity, String),
|
ChatCmd(EcsEntity, String),
|
||||||
/// Send a chat message to the player from an npc or other player
|
/// Send a chat message to the player from an npc or other player
|
||||||
Chat(comp::ChatMsg),
|
Chat(comp::UnresolvedChatMsg),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EventBus<E> {
|
pub struct EventBus<E> {
|
||||||
|
@ -26,6 +26,7 @@ pub mod generation;
|
|||||||
pub mod loadout_builder;
|
pub mod loadout_builder;
|
||||||
pub mod msg;
|
pub mod msg;
|
||||||
pub mod npc;
|
pub mod npc;
|
||||||
|
pub mod outcome;
|
||||||
pub mod path;
|
pub mod path;
|
||||||
pub mod ray;
|
pub mod ray;
|
||||||
pub mod recipe;
|
pub mod recipe;
|
||||||
|
@ -17,7 +17,7 @@ sum_type! {
|
|||||||
LightEmitter(comp::LightEmitter),
|
LightEmitter(comp::LightEmitter),
|
||||||
Item(comp::Item),
|
Item(comp::Item),
|
||||||
Scale(comp::Scale),
|
Scale(comp::Scale),
|
||||||
Alignment(comp::Alignment),
|
Group(comp::Group),
|
||||||
MountState(comp::MountState),
|
MountState(comp::MountState),
|
||||||
Mounting(comp::Mounting),
|
Mounting(comp::Mounting),
|
||||||
Mass(comp::Mass),
|
Mass(comp::Mass),
|
||||||
@ -44,7 +44,7 @@ sum_type! {
|
|||||||
LightEmitter(PhantomData<comp::LightEmitter>),
|
LightEmitter(PhantomData<comp::LightEmitter>),
|
||||||
Item(PhantomData<comp::Item>),
|
Item(PhantomData<comp::Item>),
|
||||||
Scale(PhantomData<comp::Scale>),
|
Scale(PhantomData<comp::Scale>),
|
||||||
Alignment(PhantomData<comp::Alignment>),
|
Group(PhantomData<comp::Group>),
|
||||||
MountState(PhantomData<comp::MountState>),
|
MountState(PhantomData<comp::MountState>),
|
||||||
Mounting(PhantomData<comp::Mounting>),
|
Mounting(PhantomData<comp::Mounting>),
|
||||||
Mass(PhantomData<comp::Mass>),
|
Mass(PhantomData<comp::Mass>),
|
||||||
@ -71,7 +71,7 @@ impl sync::CompPacket for EcsCompPacket {
|
|||||||
EcsCompPacket::LightEmitter(comp) => sync::handle_insert(comp, entity, world),
|
EcsCompPacket::LightEmitter(comp) => sync::handle_insert(comp, entity, world),
|
||||||
EcsCompPacket::Item(comp) => sync::handle_insert(comp, entity, world),
|
EcsCompPacket::Item(comp) => sync::handle_insert(comp, entity, world),
|
||||||
EcsCompPacket::Scale(comp) => sync::handle_insert(comp, entity, world),
|
EcsCompPacket::Scale(comp) => sync::handle_insert(comp, entity, world),
|
||||||
EcsCompPacket::Alignment(comp) => sync::handle_insert(comp, entity, world),
|
EcsCompPacket::Group(comp) => sync::handle_insert(comp, entity, world),
|
||||||
EcsCompPacket::MountState(comp) => sync::handle_insert(comp, entity, world),
|
EcsCompPacket::MountState(comp) => sync::handle_insert(comp, entity, world),
|
||||||
EcsCompPacket::Mounting(comp) => sync::handle_insert(comp, entity, world),
|
EcsCompPacket::Mounting(comp) => sync::handle_insert(comp, entity, world),
|
||||||
EcsCompPacket::Mass(comp) => sync::handle_insert(comp, entity, world),
|
EcsCompPacket::Mass(comp) => sync::handle_insert(comp, entity, world),
|
||||||
@ -96,7 +96,7 @@ impl sync::CompPacket for EcsCompPacket {
|
|||||||
EcsCompPacket::LightEmitter(comp) => sync::handle_modify(comp, entity, world),
|
EcsCompPacket::LightEmitter(comp) => sync::handle_modify(comp, entity, world),
|
||||||
EcsCompPacket::Item(comp) => sync::handle_modify(comp, entity, world),
|
EcsCompPacket::Item(comp) => sync::handle_modify(comp, entity, world),
|
||||||
EcsCompPacket::Scale(comp) => sync::handle_modify(comp, entity, world),
|
EcsCompPacket::Scale(comp) => sync::handle_modify(comp, entity, world),
|
||||||
EcsCompPacket::Alignment(comp) => sync::handle_modify(comp, entity, world),
|
EcsCompPacket::Group(comp) => sync::handle_modify(comp, entity, world),
|
||||||
EcsCompPacket::MountState(comp) => sync::handle_modify(comp, entity, world),
|
EcsCompPacket::MountState(comp) => sync::handle_modify(comp, entity, world),
|
||||||
EcsCompPacket::Mounting(comp) => sync::handle_modify(comp, entity, world),
|
EcsCompPacket::Mounting(comp) => sync::handle_modify(comp, entity, world),
|
||||||
EcsCompPacket::Mass(comp) => sync::handle_modify(comp, entity, world),
|
EcsCompPacket::Mass(comp) => sync::handle_modify(comp, entity, world),
|
||||||
@ -123,7 +123,7 @@ impl sync::CompPacket for EcsCompPacket {
|
|||||||
},
|
},
|
||||||
EcsCompPhantom::Item(_) => sync::handle_remove::<comp::Item>(entity, world),
|
EcsCompPhantom::Item(_) => sync::handle_remove::<comp::Item>(entity, world),
|
||||||
EcsCompPhantom::Scale(_) => sync::handle_remove::<comp::Scale>(entity, world),
|
EcsCompPhantom::Scale(_) => sync::handle_remove::<comp::Scale>(entity, world),
|
||||||
EcsCompPhantom::Alignment(_) => sync::handle_remove::<comp::Alignment>(entity, world),
|
EcsCompPhantom::Group(_) => sync::handle_remove::<comp::Group>(entity, world),
|
||||||
EcsCompPhantom::MountState(_) => sync::handle_remove::<comp::MountState>(entity, world),
|
EcsCompPhantom::MountState(_) => sync::handle_remove::<comp::MountState>(entity, world),
|
||||||
EcsCompPhantom::Mounting(_) => sync::handle_remove::<comp::Mounting>(entity, world),
|
EcsCompPhantom::Mounting(_) => sync::handle_remove::<comp::Mounting>(entity, world),
|
||||||
EcsCompPhantom::Mass(_) => sync::handle_remove::<comp::Mass>(entity, world),
|
EcsCompPhantom::Mass(_) => sync::handle_remove::<comp::Mass>(entity, world),
|
||||||
|
@ -7,7 +7,7 @@ pub use self::{
|
|||||||
client::ClientMsg,
|
client::ClientMsg,
|
||||||
ecs_packet::EcsCompPacket,
|
ecs_packet::EcsCompPacket,
|
||||||
server::{
|
server::{
|
||||||
CharacterInfo, Notification, PlayerInfo, PlayerListUpdate, RegisterError,
|
CharacterInfo, InviteAnswer, Notification, PlayerInfo, PlayerListUpdate, RegisterError,
|
||||||
RequestStateError, ServerInfo, ServerMsg,
|
RequestStateError, ServerInfo, ServerMsg,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,7 @@ use super::{ClientState, EcsCompPacket};
|
|||||||
use crate::{
|
use crate::{
|
||||||
character::CharacterItem,
|
character::CharacterItem,
|
||||||
comp,
|
comp,
|
||||||
|
outcome::Outcome,
|
||||||
recipe::RecipeBook,
|
recipe::RecipeBook,
|
||||||
state, sync,
|
state, sync,
|
||||||
sync::Uid,
|
sync::Uid,
|
||||||
@ -168,6 +169,13 @@ pub struct WorldMapMsg {
|
|||||||
pub horizons: [(Vec<u8>, Vec<u8>); 2],
|
pub horizons: [(Vec<u8>, Vec<u8>); 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum InviteAnswer {
|
||||||
|
Accepted,
|
||||||
|
Declined,
|
||||||
|
TimedOut,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub enum Notification {
|
pub enum Notification {
|
||||||
WaypointSaved,
|
WaypointSaved,
|
||||||
@ -180,6 +188,7 @@ pub enum ServerMsg {
|
|||||||
entity_package: sync::EntityPackage<EcsCompPacket>,
|
entity_package: sync::EntityPackage<EcsCompPacket>,
|
||||||
server_info: ServerInfo,
|
server_info: ServerInfo,
|
||||||
time_of_day: state::TimeOfDay,
|
time_of_day: state::TimeOfDay,
|
||||||
|
max_group_size: u32,
|
||||||
world_map: WorldMapMsg,
|
world_map: WorldMapMsg,
|
||||||
recipe_book: RecipeBook,
|
recipe_book: RecipeBook,
|
||||||
},
|
},
|
||||||
@ -190,6 +199,22 @@ pub enum ServerMsg {
|
|||||||
/// An error occured while creating or deleting a character
|
/// An error occured while creating or deleting a character
|
||||||
CharacterActionError(String),
|
CharacterActionError(String),
|
||||||
PlayerListUpdate(PlayerListUpdate),
|
PlayerListUpdate(PlayerListUpdate),
|
||||||
|
GroupUpdate(comp::group::ChangeNotification<sync::Uid>),
|
||||||
|
// Indicate to the client that they are invited to join a group
|
||||||
|
GroupInvite {
|
||||||
|
inviter: sync::Uid,
|
||||||
|
timeout: std::time::Duration,
|
||||||
|
},
|
||||||
|
// Indicate to the client that their sent invite was not invalid and is currently pending
|
||||||
|
InvitePending(sync::Uid),
|
||||||
|
// Note: this could potentially include all the failure cases such as inviting yourself in
|
||||||
|
// which case the `InvitePending` message could be removed and the client could consider their
|
||||||
|
// invite pending until they receive this message
|
||||||
|
// Indicate to the client the result of their invite
|
||||||
|
InviteComplete {
|
||||||
|
target: sync::Uid,
|
||||||
|
answer: InviteAnswer,
|
||||||
|
},
|
||||||
StateAnswer(Result<ClientState, (RequestStateError, ClientState)>),
|
StateAnswer(Result<ClientState, (RequestStateError, ClientState)>),
|
||||||
/// Trigger cleanup for when the client goes back to the `Registered` state
|
/// Trigger cleanup for when the client goes back to the `Registered` state
|
||||||
/// from an ingame state
|
/// from an ingame state
|
||||||
@ -217,6 +242,7 @@ pub enum ServerMsg {
|
|||||||
/// Send a popup notification such as "Waypoint Saved"
|
/// Send a popup notification such as "Waypoint Saved"
|
||||||
Notification(Notification),
|
Notification(Notification),
|
||||||
SetViewDistance(u32),
|
SetViewDistance(u32),
|
||||||
|
Outcomes(Vec<Outcome>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
30
common/src/outcome.rs
Normal file
30
common/src/outcome.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use crate::comp;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
|
/// An outcome represents the final result of an instantaneous event. It implies
|
||||||
|
/// that said event has already occurred. It is not a request for that event to
|
||||||
|
/// occur, nor is it something that may be cancelled or otherwise altered. Its
|
||||||
|
/// primary purpose is to act as something for frontends (both server and
|
||||||
|
/// client) to listen to in order to receive feedback about events in the world.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub enum Outcome {
|
||||||
|
Explosion {
|
||||||
|
pos: Vec3<f32>,
|
||||||
|
power: f32,
|
||||||
|
},
|
||||||
|
ProjectileShot {
|
||||||
|
pos: Vec3<f32>,
|
||||||
|
body: comp::Body,
|
||||||
|
vel: Vec3<f32>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Outcome {
|
||||||
|
pub fn get_pos(&self) -> Option<Vec3<f32>> {
|
||||||
|
match self {
|
||||||
|
Outcome::Explosion { pos, .. } => Some(*pos),
|
||||||
|
Outcome::ProjectileShot { pos, .. } => Some(*pos),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -123,7 +123,7 @@ impl State {
|
|||||||
ecs.register::<comp::Gravity>();
|
ecs.register::<comp::Gravity>();
|
||||||
ecs.register::<comp::CharacterState>();
|
ecs.register::<comp::CharacterState>();
|
||||||
ecs.register::<comp::Object>();
|
ecs.register::<comp::Object>();
|
||||||
ecs.register::<comp::Alignment>();
|
ecs.register::<comp::Group>();
|
||||||
|
|
||||||
// Register components send from clients -> server
|
// Register components send from clients -> server
|
||||||
ecs.register::<comp::Controller>();
|
ecs.register::<comp::Controller>();
|
||||||
@ -146,6 +146,7 @@ impl State {
|
|||||||
ecs.register::<comp::Last<comp::Pos>>();
|
ecs.register::<comp::Last<comp::Pos>>();
|
||||||
ecs.register::<comp::Last<comp::Vel>>();
|
ecs.register::<comp::Last<comp::Vel>>();
|
||||||
ecs.register::<comp::Last<comp::Ori>>();
|
ecs.register::<comp::Last<comp::Ori>>();
|
||||||
|
ecs.register::<comp::Alignment>();
|
||||||
ecs.register::<comp::Agent>();
|
ecs.register::<comp::Agent>();
|
||||||
ecs.register::<comp::WaypointArea>();
|
ecs.register::<comp::WaypointArea>();
|
||||||
ecs.register::<comp::ForceUpdate>();
|
ecs.register::<comp::ForceUpdate>();
|
||||||
@ -156,8 +157,9 @@ impl State {
|
|||||||
ecs.register::<comp::Attacking>();
|
ecs.register::<comp::Attacking>();
|
||||||
ecs.register::<comp::ItemDrop>();
|
ecs.register::<comp::ItemDrop>();
|
||||||
ecs.register::<comp::ChatMode>();
|
ecs.register::<comp::ChatMode>();
|
||||||
ecs.register::<comp::Group>();
|
|
||||||
ecs.register::<comp::Faction>();
|
ecs.register::<comp::Faction>();
|
||||||
|
ecs.register::<comp::group::Invite>();
|
||||||
|
ecs.register::<comp::group::PendingInvites>();
|
||||||
|
|
||||||
// Register synced resources used by the ECS.
|
// Register synced resources used by the ECS.
|
||||||
ecs.insert(TimeOfDay(0.0));
|
ecs.insert(TimeOfDay(0.0));
|
||||||
@ -168,9 +170,10 @@ impl State {
|
|||||||
ecs.insert(TerrainGrid::new().unwrap());
|
ecs.insert(TerrainGrid::new().unwrap());
|
||||||
ecs.insert(BlockChange::default());
|
ecs.insert(BlockChange::default());
|
||||||
ecs.insert(TerrainChanges::default());
|
ecs.insert(TerrainChanges::default());
|
||||||
|
ecs.insert(EventBus::<LocalEvent>::default());
|
||||||
// TODO: only register on the server
|
// TODO: only register on the server
|
||||||
ecs.insert(EventBus::<ServerEvent>::default());
|
ecs.insert(EventBus::<ServerEvent>::default());
|
||||||
ecs.insert(EventBus::<LocalEvent>::default());
|
ecs.insert(comp::group::GroupManager::default());
|
||||||
ecs.insert(RegionMap::new());
|
ecs.insert(RegionMap::new());
|
||||||
|
|
||||||
ecs
|
ecs
|
||||||
@ -196,8 +199,8 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Read a component attributed to a particular entity.
|
/// Read a component attributed to a particular entity.
|
||||||
pub fn read_component_cloned<C: Component + Clone>(&self, entity: EcsEntity) -> Option<C> {
|
pub fn read_component_copied<C: Component + Copy>(&self, entity: EcsEntity) -> Option<C> {
|
||||||
self.ecs.read_storage().get(entity).cloned()
|
self.ecs.read_storage().get(entity).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a read-only reference to the storage of a particular component type.
|
/// Get a read-only reference to the storage of a particular component type.
|
||||||
|
@ -52,7 +52,8 @@ impl CharacterBehavior for Data {
|
|||||||
|
|
||||||
// Expend energy if climbing
|
// Expend energy if climbing
|
||||||
let energy_use = match climb {
|
let energy_use = match climb {
|
||||||
Climb::Up | Climb::Down => 8,
|
Climb::Up => 5,
|
||||||
|
Climb::Down => 1,
|
||||||
Climb::Hold => 1,
|
Climb::Hold => 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -79,14 +80,15 @@ impl CharacterBehavior for Data {
|
|||||||
match climb {
|
match climb {
|
||||||
Climb::Down => {
|
Climb::Down => {
|
||||||
update.vel.0 -=
|
update.vel.0 -=
|
||||||
data.dt.0 * update.vel.0.map(|e| e.abs().powf(1.5) * e.signum() * 6.0);
|
data.dt.0 * update.vel.0.map(|e| e.abs().powf(1.5) * e.signum() * 1.0);
|
||||||
},
|
},
|
||||||
Climb::Up => {
|
Climb::Up => {
|
||||||
update.vel.0.z = (update.vel.0.z + data.dt.0 * GRAVITY * 1.25).min(CLIMB_SPEED);
|
update.vel.0.z = (update.vel.0.z + data.dt.0 * GRAVITY * 1.25).min(CLIMB_SPEED);
|
||||||
},
|
},
|
||||||
Climb::Hold => {
|
Climb::Hold => {
|
||||||
// Antigrav
|
// Antigrav
|
||||||
update.vel.0.z = (update.vel.0.z + data.dt.0 * GRAVITY * 1.5).min(CLIMB_SPEED);
|
update.vel.0.z =
|
||||||
|
(update.vel.0.z + data.dt.0 * GRAVITY * 1.075).min(CLIMB_SPEED);
|
||||||
update.vel.0 = Lerp::lerp(
|
update.vel.0 = Lerp::lerp(
|
||||||
update.vel.0,
|
update.vel.0,
|
||||||
Vec3::zero(),
|
Vec3::zero(),
|
||||||
|
@ -24,7 +24,9 @@ impl CharacterBehavior for Data {
|
|||||||
update.character = CharacterState::GlideWield;
|
update.character = CharacterState::GlideWield;
|
||||||
return update;
|
return update;
|
||||||
}
|
}
|
||||||
|
if data.physics.in_fluid {
|
||||||
|
update.character = CharacterState::Idle;
|
||||||
|
}
|
||||||
// If there is a wall in front of character and they are trying to climb go to
|
// If there is a wall in front of character and they are trying to climb go to
|
||||||
// climb
|
// climb
|
||||||
handle_climb(&data, &mut update);
|
handle_climb(&data, &mut update);
|
||||||
|
@ -16,9 +16,12 @@ impl CharacterBehavior for Data {
|
|||||||
handle_wield(data, &mut update);
|
handle_wield(data, &mut update);
|
||||||
|
|
||||||
// If not on the ground while wielding glider enter gliding state
|
// If not on the ground while wielding glider enter gliding state
|
||||||
if !data.physics.on_ground && !data.physics.in_fluid {
|
if !data.physics.on_ground {
|
||||||
update.character = CharacterState::Glide;
|
update.character = CharacterState::Glide;
|
||||||
}
|
}
|
||||||
|
if data.physics.in_fluid {
|
||||||
|
update.character = CharacterState::Idle;
|
||||||
|
}
|
||||||
|
|
||||||
update
|
update
|
||||||
}
|
}
|
||||||
@ -35,6 +38,12 @@ impl CharacterBehavior for Data {
|
|||||||
update
|
update
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sneak(&self, data: &JoinData) -> StateUpdate {
|
||||||
|
let mut update = StateUpdate::from(data);
|
||||||
|
attempt_sneak(data, &mut update);
|
||||||
|
update
|
||||||
|
}
|
||||||
|
|
||||||
fn unwield(&self, data: &JoinData) -> StateUpdate {
|
fn unwield(&self, data: &JoinData) -> StateUpdate {
|
||||||
let mut update = StateUpdate::from(data);
|
let mut update = StateUpdate::from(data);
|
||||||
update.character = CharacterState::Idle;
|
update.character = CharacterState::Idle;
|
||||||
|
@ -37,6 +37,12 @@ impl CharacterBehavior for Data {
|
|||||||
update
|
update
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sneak(&self, data: &JoinData) -> StateUpdate {
|
||||||
|
let mut update = StateUpdate::from(data);
|
||||||
|
attempt_sneak(data, &mut update);
|
||||||
|
update
|
||||||
|
}
|
||||||
|
|
||||||
fn glide_wield(&self, data: &JoinData) -> StateUpdate {
|
fn glide_wield(&self, data: &JoinData) -> StateUpdate {
|
||||||
let mut update = StateUpdate::from(data);
|
let mut update = StateUpdate::from(data);
|
||||||
attempt_glide_wield(data, &mut update);
|
attempt_glide_wield(data, &mut update);
|
||||||
|
@ -13,6 +13,7 @@ pub mod idle;
|
|||||||
pub mod leap_melee;
|
pub mod leap_melee;
|
||||||
pub mod roll;
|
pub mod roll;
|
||||||
pub mod sit;
|
pub mod sit;
|
||||||
|
pub mod sneak;
|
||||||
pub mod spin_melee;
|
pub mod spin_melee;
|
||||||
pub mod triple_strike;
|
pub mod triple_strike;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
56
common/src/states/sneak.rs
Normal file
56
common/src/states/sneak.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
use super::utils::*;
|
||||||
|
use crate::{
|
||||||
|
comp::{CharacterState, StateUpdate},
|
||||||
|
sys::character_behavior::{CharacterBehavior, JoinData},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Data;
|
||||||
|
|
||||||
|
impl CharacterBehavior for Data {
|
||||||
|
fn behavior(&self, data: &JoinData) -> StateUpdate {
|
||||||
|
let mut update = StateUpdate::from(data);
|
||||||
|
|
||||||
|
handle_move(data, &mut update, 0.4);
|
||||||
|
handle_jump(data, &mut update);
|
||||||
|
handle_wield(data, &mut update);
|
||||||
|
handle_climb(data, &mut update);
|
||||||
|
handle_dodge_input(data, &mut update);
|
||||||
|
|
||||||
|
// Try to Fall/Stand up/Move
|
||||||
|
if !data.physics.on_ground {
|
||||||
|
update.character = CharacterState::Idle;
|
||||||
|
}
|
||||||
|
|
||||||
|
update
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wield(&self, data: &JoinData) -> StateUpdate {
|
||||||
|
let mut update = StateUpdate::from(data);
|
||||||
|
attempt_wield(data, &mut update);
|
||||||
|
update
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sit(&self, data: &JoinData) -> StateUpdate {
|
||||||
|
let mut update = StateUpdate::from(data);
|
||||||
|
attempt_sit(data, &mut update);
|
||||||
|
update
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dance(&self, data: &JoinData) -> StateUpdate {
|
||||||
|
let mut update = StateUpdate::from(data);
|
||||||
|
attempt_dance(data, &mut update);
|
||||||
|
update
|
||||||
|
}
|
||||||
|
|
||||||
|
fn glide_wield(&self, data: &JoinData) -> StateUpdate {
|
||||||
|
let mut update = StateUpdate::from(data);
|
||||||
|
attempt_glide_wield(data, &mut update);
|
||||||
|
update
|
||||||
|
}
|
||||||
|
|
||||||
|
fn swap_loadout(&self, data: &JoinData) -> StateUpdate {
|
||||||
|
let mut update = StateUpdate::from(data);
|
||||||
|
attempt_swap_loadout(data, &mut update);
|
||||||
|
update
|
||||||
|
}
|
||||||
|
}
|
@ -118,9 +118,14 @@ fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
|
|||||||
handle_orientation(data, update, if data.physics.on_ground { 9.0 } else { 2.0 });
|
handle_orientation(data, update, if data.physics.on_ground { 9.0 } else { 2.0 });
|
||||||
|
|
||||||
// Swim
|
// Swim
|
||||||
if data.inputs.swim.is_pressed() {
|
if data.inputs.swimup.is_pressed() {
|
||||||
update.vel.0.z =
|
update.vel.0.z =
|
||||||
(update.vel.0.z + data.dt.0 * GRAVITY * 2.25).min(BASE_HUMANOID_WATER_SPEED);
|
(update.vel.0.z + data.dt.0 * GRAVITY * 4.0).min(BASE_HUMANOID_WATER_SPEED);
|
||||||
|
}
|
||||||
|
// Swim
|
||||||
|
if data.inputs.swimdown.is_pressed() {
|
||||||
|
update.vel.0.z =
|
||||||
|
(update.vel.0.z + data.dt.0 * GRAVITY * -3.5).min(BASE_HUMANOID_WATER_SPEED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,6 +164,12 @@ pub fn attempt_dance(data: &JoinData, update: &mut StateUpdate) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn attempt_sneak(data: &JoinData, update: &mut StateUpdate) {
|
||||||
|
if data.physics.on_ground && data.body.is_humanoid() {
|
||||||
|
update.character = CharacterState::Sneak;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks that player can `Climb` and updates `CharacterState` if so
|
/// Checks that player can `Climb` and updates `CharacterState` if so
|
||||||
pub fn handle_climb(data: &JoinData, update: &mut StateUpdate) {
|
pub fn handle_climb(data: &JoinData, update: &mut StateUpdate) {
|
||||||
if data.inputs.climb.is_some()
|
if data.inputs.climb.is_some()
|
||||||
|
@ -33,6 +33,12 @@ impl CharacterBehavior for Data {
|
|||||||
update
|
update
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sneak(&self, data: &JoinData) -> StateUpdate {
|
||||||
|
let mut update = StateUpdate::from(data);
|
||||||
|
attempt_sneak(data, &mut update);
|
||||||
|
update
|
||||||
|
}
|
||||||
|
|
||||||
fn unwield(&self, data: &JoinData) -> StateUpdate {
|
fn unwield(&self, data: &JoinData) -> StateUpdate {
|
||||||
let mut update = StateUpdate::from(data);
|
let mut update = StateUpdate::from(data);
|
||||||
update.character = CharacterState::Idle;
|
update.character = CharacterState::Idle;
|
||||||
|
@ -2,9 +2,12 @@ use crate::{
|
|||||||
comp::{
|
comp::{
|
||||||
self,
|
self,
|
||||||
agent::Activity,
|
agent::Activity,
|
||||||
|
group,
|
||||||
|
group::Invite,
|
||||||
item::{tool::ToolKind, ItemKind},
|
item::{tool::ToolKind, ItemKind},
|
||||||
Agent, Alignment, Body, CharacterState, ChatMsg, ControlAction, Controller, Loadout,
|
Agent, Alignment, Body, CharacterState, ControlAction, ControlEvent, Controller,
|
||||||
MountState, Ori, PhysicsState, Pos, Scale, Stats, Vel,
|
GroupManip, Loadout, MountState, Ori, PhysicsState, Pos, Scale, Stats, UnresolvedChatMsg,
|
||||||
|
Vel,
|
||||||
},
|
},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
path::{Chaser, TraversalConfig},
|
path::{Chaser, TraversalConfig},
|
||||||
@ -29,6 +32,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
Read<'a, UidAllocator>,
|
Read<'a, UidAllocator>,
|
||||||
Read<'a, Time>,
|
Read<'a, Time>,
|
||||||
Read<'a, DeltaTime>,
|
Read<'a, DeltaTime>,
|
||||||
|
Read<'a, group::GroupManager>,
|
||||||
Write<'a, EventBus<ServerEvent>>,
|
Write<'a, EventBus<ServerEvent>>,
|
||||||
Entities<'a>,
|
Entities<'a>,
|
||||||
ReadStorage<'a, Pos>,
|
ReadStorage<'a, Pos>,
|
||||||
@ -40,12 +44,14 @@ impl<'a> System<'a> for Sys {
|
|||||||
ReadStorage<'a, CharacterState>,
|
ReadStorage<'a, CharacterState>,
|
||||||
ReadStorage<'a, PhysicsState>,
|
ReadStorage<'a, PhysicsState>,
|
||||||
ReadStorage<'a, Uid>,
|
ReadStorage<'a, Uid>,
|
||||||
|
ReadStorage<'a, group::Group>,
|
||||||
ReadExpect<'a, TerrainGrid>,
|
ReadExpect<'a, TerrainGrid>,
|
||||||
ReadStorage<'a, Alignment>,
|
ReadStorage<'a, Alignment>,
|
||||||
ReadStorage<'a, Body>,
|
ReadStorage<'a, Body>,
|
||||||
WriteStorage<'a, Agent>,
|
WriteStorage<'a, Agent>,
|
||||||
WriteStorage<'a, Controller>,
|
WriteStorage<'a, Controller>,
|
||||||
ReadStorage<'a, MountState>,
|
ReadStorage<'a, MountState>,
|
||||||
|
ReadStorage<'a, Invite>,
|
||||||
);
|
);
|
||||||
|
|
||||||
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
|
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
|
||||||
@ -55,6 +61,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
uid_allocator,
|
uid_allocator,
|
||||||
time,
|
time,
|
||||||
dt,
|
dt,
|
||||||
|
group_manager,
|
||||||
event_bus,
|
event_bus,
|
||||||
entities,
|
entities,
|
||||||
positions,
|
positions,
|
||||||
@ -66,12 +73,14 @@ impl<'a> System<'a> for Sys {
|
|||||||
character_states,
|
character_states,
|
||||||
physics_states,
|
physics_states,
|
||||||
uids,
|
uids,
|
||||||
|
groups,
|
||||||
terrain,
|
terrain,
|
||||||
alignments,
|
alignments,
|
||||||
bodies,
|
bodies,
|
||||||
mut agents,
|
mut agents,
|
||||||
mut controllers,
|
mut controllers,
|
||||||
mount_states,
|
mount_states,
|
||||||
|
invites,
|
||||||
): Self::SystemData,
|
): Self::SystemData,
|
||||||
) {
|
) {
|
||||||
for (
|
for (
|
||||||
@ -88,6 +97,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
agent,
|
agent,
|
||||||
controller,
|
controller,
|
||||||
mount_state,
|
mount_state,
|
||||||
|
group,
|
||||||
) in (
|
) in (
|
||||||
&entities,
|
&entities,
|
||||||
&positions,
|
&positions,
|
||||||
@ -102,9 +112,23 @@ impl<'a> System<'a> for Sys {
|
|||||||
&mut agents,
|
&mut agents,
|
||||||
&mut controllers,
|
&mut controllers,
|
||||||
mount_states.maybe(),
|
mount_states.maybe(),
|
||||||
|
groups.maybe(),
|
||||||
)
|
)
|
||||||
.join()
|
.join()
|
||||||
{
|
{
|
||||||
|
// Hack, replace with better system when groups are more sophisticated
|
||||||
|
// Override alignment if in a group unless entity is owned already
|
||||||
|
let alignment = if !matches!(alignment, Some(Alignment::Owned(_))) {
|
||||||
|
group
|
||||||
|
.and_then(|g| group_manager.group_info(*g))
|
||||||
|
.and_then(|info| uids.get(info.leader))
|
||||||
|
.copied()
|
||||||
|
.map(Alignment::Owned)
|
||||||
|
.or(alignment.copied())
|
||||||
|
} else {
|
||||||
|
alignment.copied()
|
||||||
|
};
|
||||||
|
|
||||||
// Skip mounted entities
|
// Skip mounted entities
|
||||||
if mount_state
|
if mount_state
|
||||||
.map(|ms| *ms != MountState::Unmounted)
|
.map(|ms| *ms != MountState::Unmounted)
|
||||||
@ -117,7 +141,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
|
|
||||||
let mut inputs = &mut controller.inputs;
|
let mut inputs = &mut controller.inputs;
|
||||||
|
|
||||||
// Default to looking in orientation direction
|
// Default to looking in orientation direction (can be overriden below)
|
||||||
inputs.look_dir = ori.0;
|
inputs.look_dir = ori.0;
|
||||||
|
|
||||||
const AVG_FOLLOW_DIST: f32 = 6.0;
|
const AVG_FOLLOW_DIST: f32 = 6.0;
|
||||||
@ -148,11 +172,9 @@ impl<'a> System<'a> for Sys {
|
|||||||
thread_rng().gen::<f32>() - 0.5,
|
thread_rng().gen::<f32>() - 0.5,
|
||||||
) * 0.1
|
) * 0.1
|
||||||
- *bearing * 0.003
|
- *bearing * 0.003
|
||||||
- if let Some(patrol_origin) = agent.patrol_origin {
|
- agent.patrol_origin.map_or(Vec2::zero(), |patrol_origin| {
|
||||||
Vec2::<f32>::from(pos.0 - patrol_origin) * 0.0002
|
(pos.0 - patrol_origin).xy() * 0.0002
|
||||||
} else {
|
});
|
||||||
Vec2::zero()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Stop if we're too close to a wall
|
// Stop if we're too close to a wall
|
||||||
*bearing *= 0.1
|
*bearing *= 0.1
|
||||||
@ -169,8 +191,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
.until(|block| block.is_solid())
|
.until(|block| block.is_solid())
|
||||||
.cast()
|
.cast()
|
||||||
.1
|
.1
|
||||||
.map(|b| b.is_none())
|
.map_or(true, |b| b.is_none())
|
||||||
.unwrap_or(true)
|
|
||||||
{
|
{
|
||||||
0.9
|
0.9
|
||||||
} else {
|
} else {
|
||||||
@ -269,8 +290,7 @@ 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) || tgt_stats.is_dead
|
if alignment.passive_towards(tgt_alignment) || tgt_stats.is_dead {
|
||||||
{
|
|
||||||
do_idle = true;
|
do_idle = true;
|
||||||
break 'activity;
|
break 'activity;
|
||||||
}
|
}
|
||||||
@ -418,8 +438,9 @@ impl<'a> System<'a> for Sys {
|
|||||||
if stats.get(attacker).map_or(false, |a| !a.is_dead) {
|
if stats.get(attacker).map_or(false, |a| !a.is_dead) {
|
||||||
if agent.can_speak {
|
if agent.can_speak {
|
||||||
let msg = "npc.speech.villager_under_attack".to_string();
|
let msg = "npc.speech.villager_under_attack".to_string();
|
||||||
event_bus
|
event_bus.emit_now(ServerEvent::Chat(
|
||||||
.emit_now(ServerEvent::Chat(ChatMsg::npc(*uid, msg)));
|
UnresolvedChatMsg::npc(*uid, msg),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
agent.activity = Activity::Attack {
|
agent.activity = Activity::Attack {
|
||||||
@ -437,7 +458,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Follow owner if we're too far, or if they're under attack
|
// Follow owner if we're too far, or if they're under attack
|
||||||
if let Some(Alignment::Owned(owner)) = alignment.copied() {
|
if let Some(Alignment::Owned(owner)) = alignment {
|
||||||
(|| {
|
(|| {
|
||||||
let owner = uid_allocator.retrieve_entity_internal(owner.id())?;
|
let owner = uid_allocator.retrieve_entity_internal(owner.id())?;
|
||||||
|
|
||||||
@ -477,5 +498,23 @@ impl<'a> System<'a> for Sys {
|
|||||||
debug_assert!(inputs.move_dir.map(|e| !e.is_nan()).reduce_and());
|
debug_assert!(inputs.move_dir.map(|e| !e.is_nan()).reduce_and());
|
||||||
debug_assert!(inputs.look_dir.map(|e| !e.is_nan()).reduce_and());
|
debug_assert!(inputs.look_dir.map(|e| !e.is_nan()).reduce_and());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Proccess group invites
|
||||||
|
for (_invite, alignment, agent, controller) in
|
||||||
|
(&invites, &alignments, &mut agents, &mut controllers).join()
|
||||||
|
{
|
||||||
|
let accept = matches!(alignment, Alignment::Npc);
|
||||||
|
if accept {
|
||||||
|
// Clear agent comp
|
||||||
|
*agent = Agent::default();
|
||||||
|
controller
|
||||||
|
.events
|
||||||
|
.push(ControlEvent::GroupManip(GroupManip::Accept));
|
||||||
|
} else {
|
||||||
|
controller
|
||||||
|
.events
|
||||||
|
.push(ControlEvent::GroupManip(GroupManip::Decline));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ pub trait CharacterBehavior {
|
|||||||
fn unwield(&self, data: &JoinData) -> StateUpdate { StateUpdate::from(data) }
|
fn unwield(&self, data: &JoinData) -> StateUpdate { StateUpdate::from(data) }
|
||||||
fn sit(&self, data: &JoinData) -> StateUpdate { StateUpdate::from(data) }
|
fn sit(&self, data: &JoinData) -> StateUpdate { StateUpdate::from(data) }
|
||||||
fn dance(&self, data: &JoinData) -> StateUpdate { StateUpdate::from(data) }
|
fn dance(&self, data: &JoinData) -> StateUpdate { StateUpdate::from(data) }
|
||||||
|
fn sneak(&self, data: &JoinData) -> StateUpdate { StateUpdate::from(data) }
|
||||||
fn stand(&self, data: &JoinData) -> StateUpdate { StateUpdate::from(data) }
|
fn stand(&self, data: &JoinData) -> StateUpdate { StateUpdate::from(data) }
|
||||||
fn handle_event(&self, data: &JoinData, event: ControlAction) -> StateUpdate {
|
fn handle_event(&self, data: &JoinData, event: ControlAction) -> StateUpdate {
|
||||||
match event {
|
match event {
|
||||||
@ -36,6 +37,7 @@ pub trait CharacterBehavior {
|
|||||||
ControlAction::Unwield => self.unwield(data),
|
ControlAction::Unwield => self.unwield(data),
|
||||||
ControlAction::Sit => self.sit(data),
|
ControlAction::Sit => self.sit(data),
|
||||||
ControlAction::Dance => self.dance(data),
|
ControlAction::Dance => self.dance(data),
|
||||||
|
ControlAction::Sneak => self.sneak(data),
|
||||||
ControlAction::Stand => self.stand(data),
|
ControlAction::Stand => self.stand(data),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -232,6 +234,9 @@ impl<'a> System<'a> for Sys {
|
|||||||
CharacterState::Dance => {
|
CharacterState::Dance => {
|
||||||
states::dance::Data::handle_event(&states::dance::Data, &j, action)
|
states::dance::Data::handle_event(&states::dance::Data, &j, action)
|
||||||
},
|
},
|
||||||
|
CharacterState::Sneak => {
|
||||||
|
states::sneak::Data::handle_event(&states::sneak::Data, &j, action)
|
||||||
|
},
|
||||||
CharacterState::BasicBlock => {
|
CharacterState::BasicBlock => {
|
||||||
states::basic_block::Data.handle_event(&j, action)
|
states::basic_block::Data.handle_event(&j, action)
|
||||||
},
|
},
|
||||||
@ -261,6 +266,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
CharacterState::GlideWield => states::glide_wield::Data.behavior(&j),
|
CharacterState::GlideWield => states::glide_wield::Data.behavior(&j),
|
||||||
CharacterState::Sit => states::sit::Data::behavior(&states::sit::Data, &j),
|
CharacterState::Sit => states::sit::Data::behavior(&states::sit::Data, &j),
|
||||||
CharacterState::Dance => states::dance::Data::behavior(&states::dance::Data, &j),
|
CharacterState::Dance => states::dance::Data::behavior(&states::dance::Data, &j),
|
||||||
|
CharacterState::Sneak => states::sneak::Data::behavior(&states::sneak::Data, &j),
|
||||||
CharacterState::BasicBlock => states::basic_block::Data.behavior(&j),
|
CharacterState::BasicBlock => states::basic_block::Data.behavior(&j),
|
||||||
CharacterState::Roll(data) => data.behavior(&j),
|
CharacterState::Roll(data) => data.behavior(&j),
|
||||||
CharacterState::Wielding => states::wielding::Data.behavior(&j),
|
CharacterState::Wielding => states::wielding::Data.behavior(&j),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
comp::{
|
comp::{
|
||||||
Alignment, Attacking, Body, CharacterState, Damage, DamageSource, HealthChange,
|
group, Attacking, Body, CharacterState, Damage, DamageSource, HealthChange, HealthSource,
|
||||||
HealthSource, Loadout, Ori, Pos, Scale, Stats,
|
Loadout, Ori, Pos, Scale, Stats,
|
||||||
},
|
},
|
||||||
event::{EventBus, LocalEvent, ServerEvent},
|
event::{EventBus, LocalEvent, ServerEvent},
|
||||||
sync::Uid,
|
sync::Uid,
|
||||||
@ -26,10 +26,10 @@ impl<'a> System<'a> for Sys {
|
|||||||
ReadStorage<'a, Pos>,
|
ReadStorage<'a, Pos>,
|
||||||
ReadStorage<'a, Ori>,
|
ReadStorage<'a, Ori>,
|
||||||
ReadStorage<'a, Scale>,
|
ReadStorage<'a, Scale>,
|
||||||
ReadStorage<'a, Alignment>,
|
|
||||||
ReadStorage<'a, Body>,
|
ReadStorage<'a, Body>,
|
||||||
ReadStorage<'a, Stats>,
|
ReadStorage<'a, Stats>,
|
||||||
ReadStorage<'a, Loadout>,
|
ReadStorage<'a, Loadout>,
|
||||||
|
ReadStorage<'a, group::Group>,
|
||||||
WriteStorage<'a, Attacking>,
|
WriteStorage<'a, Attacking>,
|
||||||
WriteStorage<'a, CharacterState>,
|
WriteStorage<'a, CharacterState>,
|
||||||
);
|
);
|
||||||
@ -44,10 +44,10 @@ impl<'a> System<'a> for Sys {
|
|||||||
positions,
|
positions,
|
||||||
orientations,
|
orientations,
|
||||||
scales,
|
scales,
|
||||||
alignments,
|
|
||||||
bodies,
|
bodies,
|
||||||
stats,
|
stats,
|
||||||
loadouts,
|
loadouts,
|
||||||
|
groups,
|
||||||
mut attacking_storage,
|
mut attacking_storage,
|
||||||
character_states,
|
character_states,
|
||||||
): Self::SystemData,
|
): Self::SystemData,
|
||||||
@ -71,23 +71,12 @@ impl<'a> System<'a> for Sys {
|
|||||||
attack.applied = true;
|
attack.applied = true;
|
||||||
|
|
||||||
// Go through all other entities
|
// Go through all other entities
|
||||||
for (
|
for (b, uid_b, pos_b, ori_b, scale_b_maybe, character_b, stats_b, body_b) in (
|
||||||
b,
|
|
||||||
uid_b,
|
|
||||||
pos_b,
|
|
||||||
ori_b,
|
|
||||||
scale_b_maybe,
|
|
||||||
alignment_b_maybe,
|
|
||||||
character_b,
|
|
||||||
stats_b,
|
|
||||||
body_b,
|
|
||||||
) in (
|
|
||||||
&entities,
|
&entities,
|
||||||
&uids,
|
&uids,
|
||||||
&positions,
|
&positions,
|
||||||
&orientations,
|
&orientations,
|
||||||
scales.maybe(),
|
scales.maybe(),
|
||||||
alignments.maybe(),
|
|
||||||
character_states.maybe(),
|
character_states.maybe(),
|
||||||
&stats,
|
&stats,
|
||||||
&bodies,
|
&bodies,
|
||||||
@ -111,6 +100,17 @@ impl<'a> System<'a> for Sys {
|
|||||||
&& pos.0.distance_squared(pos_b.0) < (rad_b + scale * attack.range).powi(2)
|
&& pos.0.distance_squared(pos_b.0) < (rad_b + scale * attack.range).powi(2)
|
||||||
&& ori2.angle_between(pos_b2 - pos2) < attack.max_angle + (rad_b / pos2.distance(pos_b2)).atan()
|
&& ori2.angle_between(pos_b2 - pos2) < attack.max_angle + (rad_b / pos2.distance(pos_b2)).atan()
|
||||||
{
|
{
|
||||||
|
// See if entities are in the same group
|
||||||
|
let same_group = groups
|
||||||
|
.get(entity)
|
||||||
|
.map(|group_a| Some(group_a) == groups.get(b))
|
||||||
|
.unwrap_or(false);
|
||||||
|
// Don't heal if outside group
|
||||||
|
// Don't damage in the same group
|
||||||
|
if same_group != (attack.base_healthchange > 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Weapon gives base damage
|
// Weapon gives base damage
|
||||||
let source = if attack.base_healthchange > 0 {
|
let source = if attack.base_healthchange > 0 {
|
||||||
DamageSource::Healing
|
DamageSource::Healing
|
||||||
@ -121,28 +121,6 @@ impl<'a> System<'a> for Sys {
|
|||||||
healthchange: attack.base_healthchange as f32,
|
healthchange: attack.base_healthchange as f32,
|
||||||
source,
|
source,
|
||||||
};
|
};
|
||||||
let mut knockback = attack.knockback;
|
|
||||||
|
|
||||||
// TODO: remove this, either it will remain unused or be used as a temporary
|
|
||||||
// gameplay balance
|
|
||||||
//// NPCs do less damage
|
|
||||||
//if agent_maybe.is_some() {
|
|
||||||
// healthchange = (healthchange / 1.5).min(-1.0);
|
|
||||||
//}
|
|
||||||
|
|
||||||
// TODO: remove this when there is a better way to deal with alignment
|
|
||||||
// Don't heal NPCs
|
|
||||||
if (damage.healthchange > 0.0 && alignment_b_maybe
|
|
||||||
.map(|a| !a.is_friendly_to_players())
|
|
||||||
.unwrap_or(true))
|
|
||||||
// Don't hurt pets
|
|
||||||
|| (damage.healthchange < 0.0 && alignment_b_maybe
|
|
||||||
.map(|b| Alignment::Owned(*uid).passive_towards(*b))
|
|
||||||
.unwrap_or(false))
|
|
||||||
{
|
|
||||||
damage.healthchange = 0.0;
|
|
||||||
knockback = 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false)
|
let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false)
|
||||||
&& ori_b.0.angle_between(pos.0 - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0;
|
&& ori_b.0.angle_between(pos.0 - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0;
|
||||||
@ -160,10 +138,10 @@ impl<'a> System<'a> for Sys {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if knockback != 0.0 {
|
if attack.knockback != 0.0 {
|
||||||
local_emitter.emit(LocalEvent::ApplyForce {
|
local_emitter.emit(LocalEvent::ApplyForce {
|
||||||
entity: b,
|
entity: b,
|
||||||
force: knockback
|
force: attack.knockback
|
||||||
* *Dir::slerp(ori.0, Dir::new(Vec3::new(0.0, 0.0, 1.0)), 0.5),
|
* *Dir::slerp(ori.0, Dir::new(Vec3::new(0.0, 0.0, 1.0)), 0.5),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -96,6 +96,9 @@ impl<'a> System<'a> for Sys {
|
|||||||
}
|
}
|
||||||
server_emitter.emit(ServerEvent::InventoryManip(entity, manip))
|
server_emitter.emit(ServerEvent::InventoryManip(entity, manip))
|
||||||
},
|
},
|
||||||
|
ControlEvent::GroupManip(manip) => {
|
||||||
|
server_emitter.emit(ServerEvent::GroupManip(entity, manip))
|
||||||
|
},
|
||||||
ControlEvent::Respawn => server_emitter.emit(ServerEvent::Respawn(entity)),
|
ControlEvent::Respawn => server_emitter.emit(ServerEvent::Respawn(entity)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
comp::{Collider, Gravity, Mass, Mounting, Ori, PhysicsState, Pos, Scale, Sticky, Vel},
|
comp::{
|
||||||
|
Collider, Gravity, Group, Mass, Mounting, Ori, PhysicsState, Pos, Projectile, Scale,
|
||||||
|
Sticky, Vel,
|
||||||
|
},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
state::DeltaTime,
|
state::DeltaTime,
|
||||||
sync::Uid,
|
sync::{Uid, UidAllocator},
|
||||||
terrain::{Block, BlockKind, TerrainGrid},
|
terrain::{Block, BlockKind, TerrainGrid},
|
||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
};
|
};
|
||||||
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage};
|
use specs::{
|
||||||
|
saveload::MarkerAllocator, Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage,
|
||||||
|
};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
pub const GRAVITY: f32 = 9.81 * 5.0;
|
pub const GRAVITY: f32 = 9.81 * 5.0;
|
||||||
const BOUYANCY: f32 = 0.0;
|
const BOUYANCY: f32 = 1.0;
|
||||||
// Friction values used for linear damping. They are unitless quantities. The
|
// Friction values used for linear damping. They are unitless quantities. The
|
||||||
// value of these quantities must be between zero and one. They represent the
|
// value of these quantities must be between zero and one. They represent the
|
||||||
// amount an object will slow down within 1/60th of a second. Eg. if the frction
|
// amount an object will slow down within 1/60th of a second. Eg. if the frction
|
||||||
@ -44,6 +49,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
ReadStorage<'a, Uid>,
|
ReadStorage<'a, Uid>,
|
||||||
ReadExpect<'a, TerrainGrid>,
|
ReadExpect<'a, TerrainGrid>,
|
||||||
Read<'a, DeltaTime>,
|
Read<'a, DeltaTime>,
|
||||||
|
Read<'a, UidAllocator>,
|
||||||
Read<'a, EventBus<ServerEvent>>,
|
Read<'a, EventBus<ServerEvent>>,
|
||||||
ReadStorage<'a, Scale>,
|
ReadStorage<'a, Scale>,
|
||||||
ReadStorage<'a, Sticky>,
|
ReadStorage<'a, Sticky>,
|
||||||
@ -55,6 +61,8 @@ impl<'a> System<'a> for Sys {
|
|||||||
WriteStorage<'a, Vel>,
|
WriteStorage<'a, Vel>,
|
||||||
WriteStorage<'a, Ori>,
|
WriteStorage<'a, Ori>,
|
||||||
ReadStorage<'a, Mounting>,
|
ReadStorage<'a, Mounting>,
|
||||||
|
ReadStorage<'a, Group>,
|
||||||
|
ReadStorage<'a, Projectile>,
|
||||||
);
|
);
|
||||||
|
|
||||||
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
|
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
|
||||||
@ -66,6 +74,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
uids,
|
uids,
|
||||||
terrain,
|
terrain,
|
||||||
dt,
|
dt,
|
||||||
|
uid_allocator,
|
||||||
event_bus,
|
event_bus,
|
||||||
scales,
|
scales,
|
||||||
stickies,
|
stickies,
|
||||||
@ -77,6 +86,8 @@ impl<'a> System<'a> for Sys {
|
|||||||
mut velocities,
|
mut velocities,
|
||||||
mut orientations,
|
mut orientations,
|
||||||
mountings,
|
mountings,
|
||||||
|
groups,
|
||||||
|
projectiles,
|
||||||
): Self::SystemData,
|
): Self::SystemData,
|
||||||
) {
|
) {
|
||||||
let mut event_emitter = event_bus.emitter();
|
let mut event_emitter = event_bus.emitter();
|
||||||
@ -432,7 +443,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply pushback
|
// Apply pushback
|
||||||
for (pos, scale, mass, vel, _, _, _, physics) in (
|
for (pos, scale, mass, vel, _, _, _, physics, projectile) in (
|
||||||
&positions,
|
&positions,
|
||||||
scales.maybe(),
|
scales.maybe(),
|
||||||
masses.maybe(),
|
masses.maybe(),
|
||||||
@ -441,9 +452,12 @@ impl<'a> System<'a> for Sys {
|
|||||||
!&mountings,
|
!&mountings,
|
||||||
stickies.maybe(),
|
stickies.maybe(),
|
||||||
&mut physics_states,
|
&mut physics_states,
|
||||||
|
// TODO: if we need to avoid collisions for other things consider moving whether it
|
||||||
|
// should interact into the collider component or into a separate component
|
||||||
|
projectiles.maybe(),
|
||||||
)
|
)
|
||||||
.join()
|
.join()
|
||||||
.filter(|(_, _, _, _, _, _, sticky, physics)| {
|
.filter(|(_, _, _, _, _, _, sticky, physics, _)| {
|
||||||
sticky.is_none() || (physics.on_wall.is_none() && !physics.on_ground)
|
sticky.is_none() || (physics.on_wall.is_none() && !physics.on_ground)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
@ -452,16 +466,27 @@ impl<'a> System<'a> for Sys {
|
|||||||
let scale = scale.map(|s| s.0).unwrap_or(1.0);
|
let scale = scale.map(|s| s.0).unwrap_or(1.0);
|
||||||
let mass = mass.map(|m| m.0).unwrap_or(scale);
|
let mass = mass.map(|m| m.0).unwrap_or(scale);
|
||||||
|
|
||||||
for (other, pos_other, scale_other, mass_other, _, _) in (
|
// Group to ignore collisions with
|
||||||
|
let ignore_group = projectile
|
||||||
|
.and_then(|p| p.owner)
|
||||||
|
.and_then(|uid| uid_allocator.retrieve_entity_internal(uid.into()))
|
||||||
|
.and_then(|e| groups.get(e));
|
||||||
|
|
||||||
|
for (other, pos_other, scale_other, mass_other, _, _, group) in (
|
||||||
&uids,
|
&uids,
|
||||||
&positions,
|
&positions,
|
||||||
scales.maybe(),
|
scales.maybe(),
|
||||||
masses.maybe(),
|
masses.maybe(),
|
||||||
&colliders,
|
&colliders,
|
||||||
!&mountings,
|
!&mountings,
|
||||||
|
groups.maybe(),
|
||||||
)
|
)
|
||||||
.join()
|
.join()
|
||||||
{
|
{
|
||||||
|
if ignore_group.is_some() && ignore_group == group {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let scale_other = scale_other.map(|s| s.0).unwrap_or(1.0);
|
let scale_other = scale_other.map(|s| s.0).unwrap_or(1.0);
|
||||||
|
|
||||||
let mass_other = mass_other.map(|m| m.0).unwrap_or(scale_other);
|
let mass_other = mass_other.map(|m| m.0).unwrap_or(scale_other);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
comp::{
|
comp::{
|
||||||
projectile, Alignment, Damage, DamageSource, Energy, EnergySource, HealthChange,
|
projectile, Damage, DamageSource, Energy, EnergySource, HealthChange, HealthSource,
|
||||||
HealthSource, Loadout, Ori, PhysicsState, Pos, Projectile, Vel,
|
Loadout, Ori, PhysicsState, Pos, Projectile, Vel,
|
||||||
},
|
},
|
||||||
event::{EventBus, LocalEvent, ServerEvent},
|
event::{EventBus, LocalEvent, ServerEvent},
|
||||||
state::DeltaTime,
|
state::DeltaTime,
|
||||||
@ -28,7 +28,6 @@ impl<'a> System<'a> for Sys {
|
|||||||
WriteStorage<'a, Ori>,
|
WriteStorage<'a, Ori>,
|
||||||
WriteStorage<'a, Projectile>,
|
WriteStorage<'a, Projectile>,
|
||||||
WriteStorage<'a, Energy>,
|
WriteStorage<'a, Energy>,
|
||||||
ReadStorage<'a, Alignment>,
|
|
||||||
ReadStorage<'a, Loadout>,
|
ReadStorage<'a, Loadout>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -46,7 +45,6 @@ impl<'a> System<'a> for Sys {
|
|||||||
mut orientations,
|
mut orientations,
|
||||||
mut projectiles,
|
mut projectiles,
|
||||||
mut energies,
|
mut energies,
|
||||||
alignments,
|
|
||||||
loadouts,
|
loadouts,
|
||||||
): Self::SystemData,
|
): Self::SystemData,
|
||||||
) {
|
) {
|
||||||
@ -72,6 +70,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
pos: pos.0,
|
pos: pos.0,
|
||||||
power,
|
power,
|
||||||
owner: projectile.owner,
|
owner: projectile.owner,
|
||||||
|
friendly_damage: false,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
projectile::Effect::Vanish => server_emitter.emit(ServerEvent::Destroy {
|
projectile::Effect::Vanish => server_emitter.emit(ServerEvent::Destroy {
|
||||||
@ -92,23 +91,13 @@ impl<'a> System<'a> for Sys {
|
|||||||
healthchange: healthchange as f32,
|
healthchange: healthchange as f32,
|
||||||
source: DamageSource::Projectile,
|
source: DamageSource::Projectile,
|
||||||
};
|
};
|
||||||
if let Some(entity) =
|
|
||||||
uid_allocator.retrieve_entity_internal(other.into())
|
let other_entity = uid_allocator.retrieve_entity_internal(other.into());
|
||||||
{
|
if let Some(loadout) = other_entity.and_then(|e| loadouts.get(e)) {
|
||||||
if let Some(loadout) = loadouts.get(entity) {
|
damage.modify_damage(false, loadout);
|
||||||
damage.modify_damage(false, loadout);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Hacky: remove this when groups get implemented
|
|
||||||
let passive = uid_allocator
|
if other != owner_uid {
|
||||||
.retrieve_entity_internal(other.into())
|
|
||||||
.and_then(|other| {
|
|
||||||
alignments
|
|
||||||
.get(other)
|
|
||||||
.map(|a| Alignment::Owned(owner_uid).passive_towards(*a))
|
|
||||||
})
|
|
||||||
.unwrap_or(false);
|
|
||||||
if other != projectile.owner.unwrap() && !passive {
|
|
||||||
server_emitter.emit(ServerEvent::Damage {
|
server_emitter.emit(ServerEvent::Damage {
|
||||||
uid: other,
|
uid: other,
|
||||||
change: HealthChange {
|
change: HealthChange {
|
||||||
@ -143,6 +132,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
pos: pos.0,
|
pos: pos.0,
|
||||||
power,
|
power,
|
||||||
owner: projectile.owner,
|
owner: projectile.owner,
|
||||||
|
friendly_damage: false,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
projectile::Effect::Vanish => server_emitter.emit(ServerEvent::Destroy {
|
projectile::Effect::Vanish => server_emitter.emit(ServerEvent::Destroy {
|
||||||
|
@ -77,6 +77,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
CharacterState::Idle { .. }
|
CharacterState::Idle { .. }
|
||||||
| CharacterState::Sit { .. }
|
| CharacterState::Sit { .. }
|
||||||
| CharacterState::Dance { .. }
|
| CharacterState::Dance { .. }
|
||||||
|
| CharacterState::Sneak { .. }
|
||||||
| CharacterState::Glide { .. }
|
| CharacterState::Glide { .. }
|
||||||
| CharacterState::GlideWield { .. }
|
| CharacterState::GlideWield { .. }
|
||||||
| CharacterState::Wielding { .. }
|
| CharacterState::Wielding { .. }
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
//! To implement a new command, add an instance of `ChatCommand` to
|
//! To implement a new command, add an instance of `ChatCommand` to
|
||||||
//! `CHAT_COMMANDS` and provide a handler function.
|
//! `CHAT_COMMANDS` and provide a handler function.
|
||||||
|
|
||||||
use crate::{Server, StateExt};
|
use crate::{client::Client, Server, StateExt};
|
||||||
use chrono::{NaiveTime, Timelike};
|
use chrono::{NaiveTime, Timelike};
|
||||||
use common::{
|
use common::{
|
||||||
assets,
|
assets,
|
||||||
cmd::{ChatCommand, CHAT_COMMANDS, CHAT_SHORTCUTS},
|
cmd::{ChatCommand, CHAT_COMMANDS, CHAT_SHORTCUTS},
|
||||||
comp::{self, ChatType, Item},
|
comp::{self, ChatType, Item, LightEmitter, WaypointArea},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
msg::{Notification, PlayerListUpdate, ServerMsg},
|
msg::{Notification, PlayerListUpdate, ServerMsg},
|
||||||
npc::{self, get_npc_name},
|
npc::{self, get_npc_name},
|
||||||
@ -65,6 +65,7 @@ fn get_handler(cmd: &ChatCommand) -> CommandHandler {
|
|||||||
ChatCommand::Adminify => handle_adminify,
|
ChatCommand::Adminify => handle_adminify,
|
||||||
ChatCommand::Alias => handle_alias,
|
ChatCommand::Alias => handle_alias,
|
||||||
ChatCommand::Build => handle_build,
|
ChatCommand::Build => handle_build,
|
||||||
|
ChatCommand::Campfire => handle_spawn_campfire,
|
||||||
ChatCommand::Debug => handle_debug,
|
ChatCommand::Debug => handle_debug,
|
||||||
ChatCommand::DebugColumn => handle_debug_column,
|
ChatCommand::DebugColumn => handle_debug_column,
|
||||||
ChatCommand::Dummy => handle_spawn_training_dummy,
|
ChatCommand::Dummy => handle_spawn_training_dummy,
|
||||||
@ -77,7 +78,6 @@ fn get_handler(cmd: &ChatCommand) -> CommandHandler {
|
|||||||
ChatCommand::Health => handle_health,
|
ChatCommand::Health => handle_health,
|
||||||
ChatCommand::Help => handle_help,
|
ChatCommand::Help => handle_help,
|
||||||
ChatCommand::JoinFaction => handle_join_faction,
|
ChatCommand::JoinFaction => handle_join_faction,
|
||||||
ChatCommand::JoinGroup => handle_join_group,
|
|
||||||
ChatCommand::Jump => handle_jump,
|
ChatCommand::Jump => handle_jump,
|
||||||
ChatCommand::Kill => handle_kill,
|
ChatCommand::Kill => handle_kill,
|
||||||
ChatCommand::KillNpcs => handle_kill_npcs,
|
ChatCommand::KillNpcs => handle_kill_npcs,
|
||||||
@ -227,7 +227,7 @@ fn handle_jump(
|
|||||||
action: &ChatCommand,
|
action: &ChatCommand,
|
||||||
) {
|
) {
|
||||||
if let Ok((x, y, z)) = scan_fmt!(&args, &action.arg_fmt(), f32, f32, f32) {
|
if let Ok((x, y, z)) = scan_fmt!(&args, &action.arg_fmt(), f32, f32, f32) {
|
||||||
match server.state.read_component_cloned::<comp::Pos>(target) {
|
match server.state.read_component_copied::<comp::Pos>(target) {
|
||||||
Some(current_pos) => {
|
Some(current_pos) => {
|
||||||
server
|
server
|
||||||
.state
|
.state
|
||||||
@ -252,7 +252,7 @@ fn handle_goto(
|
|||||||
if let Ok((x, y, z)) = scan_fmt!(&args, &action.arg_fmt(), f32, f32, f32) {
|
if let Ok((x, y, z)) = scan_fmt!(&args, &action.arg_fmt(), f32, f32, f32) {
|
||||||
if server
|
if server
|
||||||
.state
|
.state
|
||||||
.read_component_cloned::<comp::Pos>(target)
|
.read_component_copied::<comp::Pos>(target)
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
server
|
server
|
||||||
@ -463,9 +463,9 @@ fn handle_tp(
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if let Some(_pos) = server.state.read_component_cloned::<comp::Pos>(target) {
|
if let Some(_pos) = server.state.read_component_copied::<comp::Pos>(target) {
|
||||||
if let Some(player) = opt_player {
|
if let Some(player) = opt_player {
|
||||||
if let Some(pos) = server.state.read_component_cloned::<comp::Pos>(player) {
|
if let Some(pos) = server.state.read_component_copied::<comp::Pos>(player) {
|
||||||
server.state.write_component(target, pos);
|
server.state.write_component(target, pos);
|
||||||
server.state.write_component(target, comp::ForceUpdate);
|
server.state.write_component(target, comp::ForceUpdate);
|
||||||
} else {
|
} else {
|
||||||
@ -510,7 +510,7 @@ fn handle_spawn(
|
|||||||
(Some(opt_align), Some(npc::NpcBody(id, mut body)), opt_amount, opt_ai) => {
|
(Some(opt_align), Some(npc::NpcBody(id, mut body)), opt_amount, opt_ai) => {
|
||||||
let uid = server
|
let uid = server
|
||||||
.state
|
.state
|
||||||
.read_component_cloned(target)
|
.read_component_copied(target)
|
||||||
.expect("Expected player to have a UID");
|
.expect("Expected player to have a UID");
|
||||||
if let Some(alignment) = parse_alignment(uid, &opt_align) {
|
if let Some(alignment) = parse_alignment(uid, &opt_align) {
|
||||||
let amount = opt_amount
|
let amount = opt_amount
|
||||||
@ -521,7 +521,7 @@ fn handle_spawn(
|
|||||||
|
|
||||||
let ai = opt_ai.unwrap_or_else(|| "true".to_string());
|
let ai = opt_ai.unwrap_or_else(|| "true".to_string());
|
||||||
|
|
||||||
match server.state.read_component_cloned::<comp::Pos>(target) {
|
match server.state.read_component_copied::<comp::Pos>(target) {
|
||||||
Some(pos) => {
|
Some(pos) => {
|
||||||
let agent =
|
let agent =
|
||||||
if let comp::Alignment::Owned(_) | comp::Alignment::Npc = alignment {
|
if let comp::Alignment::Owned(_) | comp::Alignment::Npc = alignment {
|
||||||
@ -557,6 +557,43 @@ fn handle_spawn(
|
|||||||
|
|
||||||
let new_entity = entity_base.build();
|
let new_entity = entity_base.build();
|
||||||
|
|
||||||
|
// Add to group system if a pet
|
||||||
|
if matches!(alignment, comp::Alignment::Owned { .. }) {
|
||||||
|
let state = server.state();
|
||||||
|
let mut clients = state.ecs().write_storage::<Client>();
|
||||||
|
let uids = state.ecs().read_storage::<Uid>();
|
||||||
|
let mut group_manager =
|
||||||
|
state.ecs().write_resource::<comp::group::GroupManager>();
|
||||||
|
group_manager.new_pet(
|
||||||
|
new_entity,
|
||||||
|
target,
|
||||||
|
&mut state.ecs().write_storage(),
|
||||||
|
&state.ecs().entities(),
|
||||||
|
&state.ecs().read_storage(),
|
||||||
|
&uids,
|
||||||
|
&mut |entity, group_change| {
|
||||||
|
clients
|
||||||
|
.get_mut(entity)
|
||||||
|
.and_then(|c| {
|
||||||
|
group_change
|
||||||
|
.try_map(|e| uids.get(e).copied())
|
||||||
|
.map(|g| (g, c))
|
||||||
|
})
|
||||||
|
.map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g)));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if let Some(group) = match alignment {
|
||||||
|
comp::Alignment::Wild => None,
|
||||||
|
comp::Alignment::Enemy => Some(comp::group::ENEMY),
|
||||||
|
comp::Alignment::Npc | comp::Alignment::Tame => {
|
||||||
|
Some(comp::group::NPC)
|
||||||
|
},
|
||||||
|
comp::Alignment::Owned(_) => unreachable!(),
|
||||||
|
} {
|
||||||
|
let _ =
|
||||||
|
server.state.ecs().write_storage().insert(new_entity, group);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(uid) = server.state.ecs().uid_from_entity(new_entity) {
|
if let Some(uid) = server.state.ecs().uid_from_entity(new_entity) {
|
||||||
server.notify_client(
|
server.notify_client(
|
||||||
client,
|
client,
|
||||||
@ -594,7 +631,7 @@ fn handle_spawn_training_dummy(
|
|||||||
_args: String,
|
_args: String,
|
||||||
_action: &ChatCommand,
|
_action: &ChatCommand,
|
||||||
) {
|
) {
|
||||||
match server.state.read_component_cloned::<comp::Pos>(target) {
|
match server.state.read_component_copied::<comp::Pos>(target) {
|
||||||
Some(pos) => {
|
Some(pos) => {
|
||||||
let vel = Vec3::new(
|
let vel = Vec3::new(
|
||||||
rand::thread_rng().gen_range(-2.0, 3.0),
|
rand::thread_rng().gen_range(-2.0, 3.0),
|
||||||
@ -628,6 +665,39 @@ fn handle_spawn_training_dummy(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_spawn_campfire(
|
||||||
|
server: &mut Server,
|
||||||
|
client: EcsEntity,
|
||||||
|
target: EcsEntity,
|
||||||
|
_args: String,
|
||||||
|
_action: &ChatCommand,
|
||||||
|
) {
|
||||||
|
match server.state.read_component_copied::<comp::Pos>(target) {
|
||||||
|
Some(pos) => {
|
||||||
|
server
|
||||||
|
.state
|
||||||
|
.create_object(pos, comp::object::Body::CampfireLit)
|
||||||
|
.with(LightEmitter {
|
||||||
|
col: Rgb::new(1.0, 0.65, 0.2),
|
||||||
|
strength: 2.0,
|
||||||
|
flicker: 1.0,
|
||||||
|
animated: true,
|
||||||
|
})
|
||||||
|
.with(WaypointArea::default())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
server.notify_client(
|
||||||
|
client,
|
||||||
|
ChatType::CommandInfo.server_msg("Spawned a campfire"),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
None => server.notify_client(
|
||||||
|
client,
|
||||||
|
ChatType::CommandError.server_msg("You have no position!"),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_players(
|
fn handle_players(
|
||||||
server: &mut Server,
|
server: &mut Server,
|
||||||
client: EcsEntity,
|
client: EcsEntity,
|
||||||
@ -961,13 +1031,14 @@ fn handle_explosion(
|
|||||||
|
|
||||||
let ecs = server.state.ecs();
|
let ecs = server.state.ecs();
|
||||||
|
|
||||||
match server.state.read_component_cloned::<comp::Pos>(target) {
|
match server.state.read_component_copied::<comp::Pos>(target) {
|
||||||
Some(pos) => {
|
Some(pos) => {
|
||||||
ecs.read_resource::<EventBus<ServerEvent>>()
|
ecs.read_resource::<EventBus<ServerEvent>>()
|
||||||
.emit_now(ServerEvent::Explosion {
|
.emit_now(ServerEvent::Explosion {
|
||||||
pos: pos.0,
|
pos: pos.0,
|
||||||
power,
|
power,
|
||||||
owner: ecs.read_storage::<Uid>().get(target).copied(),
|
owner: ecs.read_storage::<Uid>().get(target).copied(),
|
||||||
|
friendly_damage: true,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
None => server.notify_client(
|
None => server.notify_client(
|
||||||
@ -984,7 +1055,7 @@ fn handle_waypoint(
|
|||||||
_args: String,
|
_args: String,
|
||||||
_action: &ChatCommand,
|
_action: &ChatCommand,
|
||||||
) {
|
) {
|
||||||
match server.state.read_component_cloned::<comp::Pos>(target) {
|
match server.state.read_component_copied::<comp::Pos>(target) {
|
||||||
Some(pos) => {
|
Some(pos) => {
|
||||||
let time = server.state.ecs().read_resource();
|
let time = server.state.ecs().read_resource();
|
||||||
let _ = server
|
let _ = server
|
||||||
@ -1020,7 +1091,7 @@ fn handle_adminify(
|
|||||||
Some(player) => {
|
Some(player) => {
|
||||||
let is_admin = if server
|
let is_admin = if server
|
||||||
.state
|
.state
|
||||||
.read_component_cloned::<comp::Admin>(player)
|
.read_component_copied::<comp::Admin>(player)
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
ecs.write_storage::<comp::Admin>().remove(player);
|
ecs.write_storage::<comp::Admin>().remove(player);
|
||||||
@ -1161,8 +1232,8 @@ fn handle_group(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let ecs = server.state.ecs();
|
let ecs = server.state.ecs();
|
||||||
if let Some(comp::Group(group)) = ecs.read_storage().get(client) {
|
if let Some(group) = ecs.read_storage::<comp::Group>().get(client) {
|
||||||
let mode = comp::ChatMode::Group(group.to_string());
|
let mode = comp::ChatMode::Group(*group);
|
||||||
let _ = ecs.write_storage().insert(client, mode.clone());
|
let _ = ecs.write_storage().insert(client, mode.clone());
|
||||||
if !msg.is_empty() {
|
if !msg.is_empty() {
|
||||||
if let Some(uid) = ecs.read_storage().get(client) {
|
if let Some(uid) = ecs.read_storage().get(client) {
|
||||||
@ -1172,7 +1243,7 @@ fn handle_group(
|
|||||||
} else {
|
} else {
|
||||||
server.notify_client(
|
server.notify_client(
|
||||||
client,
|
client,
|
||||||
ChatType::CommandError.server_msg("Please join a group with /join_group"),
|
ChatType::CommandError.server_msg("Please create a group first"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1323,68 +1394,6 @@ fn handle_join_faction(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_join_group(
|
|
||||||
server: &mut Server,
|
|
||||||
client: EcsEntity,
|
|
||||||
target: EcsEntity,
|
|
||||||
args: String,
|
|
||||||
action: &ChatCommand,
|
|
||||||
) {
|
|
||||||
if client != target {
|
|
||||||
// This happens when [ab]using /sudo
|
|
||||||
server.notify_client(
|
|
||||||
client,
|
|
||||||
ChatType::CommandError.server_msg("It's rude to impersonate people"),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if let Some(alias) = server
|
|
||||||
.state
|
|
||||||
.ecs()
|
|
||||||
.read_storage::<comp::Player>()
|
|
||||||
.get(target)
|
|
||||||
.map(|player| player.alias.clone())
|
|
||||||
{
|
|
||||||
let group_leave = if let Ok(group) = scan_fmt!(&args, &action.arg_fmt(), String) {
|
|
||||||
let mode = comp::ChatMode::Group(group.clone());
|
|
||||||
let _ = server.state.ecs().write_storage().insert(client, mode);
|
|
||||||
let group_leave = server
|
|
||||||
.state
|
|
||||||
.ecs()
|
|
||||||
.write_storage()
|
|
||||||
.insert(client, comp::Group(group.clone()))
|
|
||||||
.ok()
|
|
||||||
.flatten()
|
|
||||||
.map(|f| f.0);
|
|
||||||
server.state.send_chat(
|
|
||||||
ChatType::GroupMeta(group.clone())
|
|
||||||
.chat_msg(format!("[{}] joined group ({})", alias, group)),
|
|
||||||
);
|
|
||||||
group_leave
|
|
||||||
} else {
|
|
||||||
let mode = comp::ChatMode::default();
|
|
||||||
let _ = server.state.ecs().write_storage().insert(client, mode);
|
|
||||||
server
|
|
||||||
.state
|
|
||||||
.ecs()
|
|
||||||
.write_storage()
|
|
||||||
.remove(client)
|
|
||||||
.map(|comp::Group(f)| f)
|
|
||||||
};
|
|
||||||
if let Some(group) = group_leave {
|
|
||||||
server.state.send_chat(
|
|
||||||
ChatType::GroupMeta(group.clone())
|
|
||||||
.chat_msg(format!("[{}] left group ({})", alias, group)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
server.notify_client(
|
|
||||||
client,
|
|
||||||
ChatType::CommandError.server_msg("Could not find your player alias"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "worldgen"))]
|
#[cfg(not(feature = "worldgen"))]
|
||||||
fn handle_debug_column(
|
fn handle_debug_column(
|
||||||
server: &mut Server,
|
server: &mut Server,
|
||||||
@ -1626,7 +1635,7 @@ fn handle_remove_lights(
|
|||||||
action: &ChatCommand,
|
action: &ChatCommand,
|
||||||
) {
|
) {
|
||||||
let opt_radius = scan_fmt_some!(&args, &action.arg_fmt(), f32);
|
let opt_radius = scan_fmt_some!(&args, &action.arg_fmt(), f32);
|
||||||
let opt_player_pos = server.state.read_component_cloned::<comp::Pos>(target);
|
let opt_player_pos = server.state.read_component_copied::<comp::Pos>(target);
|
||||||
let mut to_delete = vec![];
|
let mut to_delete = vec![];
|
||||||
|
|
||||||
match opt_player_pos {
|
match opt_player_pos {
|
||||||
|
@ -4,8 +4,10 @@ use common::{
|
|||||||
self, Agent, Alignment, Body, Gravity, Item, ItemDrop, LightEmitter, Loadout, Pos,
|
self, Agent, Alignment, Body, Gravity, Item, ItemDrop, LightEmitter, Loadout, Pos,
|
||||||
Projectile, Scale, Stats, Vel, WaypointArea,
|
Projectile, Scale, Stats, Vel, WaypointArea,
|
||||||
},
|
},
|
||||||
|
outcome::Outcome,
|
||||||
util::Dir,
|
util::Dir,
|
||||||
};
|
};
|
||||||
|
use comp::group;
|
||||||
use specs::{Builder, Entity as EcsEntity, WorldExt};
|
use specs::{Builder, Entity as EcsEntity, WorldExt};
|
||||||
use vek::{Rgb, Vec3};
|
use vek::{Rgb, Vec3};
|
||||||
|
|
||||||
@ -36,12 +38,26 @@ pub fn handle_create_npc(
|
|||||||
scale: Scale,
|
scale: Scale,
|
||||||
drop_item: Option<Item>,
|
drop_item: Option<Item>,
|
||||||
) {
|
) {
|
||||||
|
let group = match alignment {
|
||||||
|
Alignment::Wild => None,
|
||||||
|
Alignment::Enemy => Some(group::ENEMY),
|
||||||
|
Alignment::Npc | Alignment::Tame => Some(group::NPC),
|
||||||
|
// TODO: handle
|
||||||
|
Alignment::Owned(_) => None,
|
||||||
|
};
|
||||||
|
|
||||||
let entity = server
|
let entity = server
|
||||||
.state
|
.state
|
||||||
.create_npc(pos, stats, loadout, body)
|
.create_npc(pos, stats, loadout, body)
|
||||||
.with(scale)
|
.with(scale)
|
||||||
.with(alignment);
|
.with(alignment);
|
||||||
|
|
||||||
|
let entity = if let Some(group) = group {
|
||||||
|
entity.with(group)
|
||||||
|
} else {
|
||||||
|
entity
|
||||||
|
};
|
||||||
|
|
||||||
let entity = if let Some(agent) = agent.into() {
|
let entity = if let Some(agent) = agent.into() {
|
||||||
entity.with(agent)
|
entity.with(agent)
|
||||||
} else {
|
} else {
|
||||||
@ -75,10 +91,18 @@ pub fn handle_shoot(
|
|||||||
.expect("Failed to fetch entity")
|
.expect("Failed to fetch entity")
|
||||||
.0;
|
.0;
|
||||||
|
|
||||||
|
let vel = *dir * 100.0;
|
||||||
|
|
||||||
|
// Add an outcome
|
||||||
|
state
|
||||||
|
.ecs()
|
||||||
|
.write_resource::<Vec<Outcome>>()
|
||||||
|
.push(Outcome::ProjectileShot { pos, body, vel });
|
||||||
|
|
||||||
// TODO: Player height
|
// TODO: Player height
|
||||||
pos.z += 1.2;
|
pos.z += 1.2;
|
||||||
|
|
||||||
let mut builder = state.create_projectile(Pos(pos), Vel(*dir * 100.0), body, projectile);
|
let mut builder = state.create_projectile(Pos(pos), Vel(vel), body, projectile);
|
||||||
if let Some(light) = light {
|
if let Some(light) = light {
|
||||||
builder = builder.with(light)
|
builder = builder.with(light)
|
||||||
}
|
}
|
||||||
|
@ -2,17 +2,18 @@ use crate::{client::Client, Server, SpawnPoint, StateExt};
|
|||||||
use common::{
|
use common::{
|
||||||
assets,
|
assets,
|
||||||
comp::{
|
comp::{
|
||||||
self, item::lottery::Lottery, object, Body, Damage, DamageSource, HealthChange,
|
self, item::lottery::Lottery, object, Alignment, Body, Damage, DamageSource, Group,
|
||||||
HealthSource, Player, Stats,
|
HealthChange, HealthSource, Player, Pos, Stats,
|
||||||
},
|
},
|
||||||
msg::{PlayerListUpdate, ServerMsg},
|
msg::{PlayerListUpdate, ServerMsg},
|
||||||
|
outcome::Outcome,
|
||||||
state::BlockChange,
|
state::BlockChange,
|
||||||
sync::{Uid, WorldSyncExt},
|
sync::{Uid, UidAllocator, WorldSyncExt},
|
||||||
sys::combat::BLOCK_ANGLE,
|
sys::combat::BLOCK_ANGLE,
|
||||||
terrain::{Block, TerrainGrid},
|
terrain::{Block, TerrainGrid},
|
||||||
vol::{ReadVol, Vox},
|
vol::{ReadVol, Vox},
|
||||||
};
|
};
|
||||||
use specs::{join::Join, Entity as EcsEntity, WorldExt};
|
use specs::{join::Join, saveload::MarkerAllocator, Entity as EcsEntity, WorldExt};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
use vek::Vec3;
|
use vek::Vec3;
|
||||||
|
|
||||||
@ -55,28 +56,88 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
|
|||||||
state.notify_registered_clients(comp::ChatType::Kill.server_msg(msg));
|
state.notify_registered_clients(comp::ChatType::Kill.server_msg(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
// Give EXP to the killer if entity had stats
|
||||||
// Give EXP to the killer if entity had stats
|
(|| {
|
||||||
let mut stats = state.ecs().write_storage::<Stats>();
|
let mut stats = state.ecs().write_storage::<Stats>();
|
||||||
if let Some(entity_stats) = stats.get(entity).cloned() {
|
let by = if let HealthSource::Attack { by } | HealthSource::Projectile { owner: Some(by) } =
|
||||||
if let HealthSource::Attack { by } | HealthSource::Projectile { owner: Some(by) } =
|
cause
|
||||||
cause
|
{
|
||||||
{
|
by
|
||||||
state.ecs().entity_from_uid(by.into()).map(|attacker| {
|
} else {
|
||||||
if let Some(attacker_stats) = stats.get_mut(attacker) {
|
return;
|
||||||
// TODO: Discuss whether we should give EXP by Player
|
};
|
||||||
// Killing or not.
|
let attacker = if let Some(attacker) = state.ecs().entity_from_uid(by.into()) {
|
||||||
attacker_stats.exp.change_by(
|
attacker
|
||||||
(entity_stats.body_type.base_exp()
|
} else {
|
||||||
+ entity_stats.level.level()
|
return;
|
||||||
* entity_stats.body_type.base_exp_increase())
|
};
|
||||||
as i64,
|
let entity_stats = if let Some(entity_stats) = stats.get(entity) {
|
||||||
);
|
entity_stats
|
||||||
}
|
} else {
|
||||||
});
|
return;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
let groups = state.ecs().read_storage::<Group>();
|
||||||
|
let attacker_group = groups.get(attacker);
|
||||||
|
let destroyed_group = groups.get(entity);
|
||||||
|
// Don't give exp if attacker destroyed themselves or one of their group members
|
||||||
|
if (attacker_group.is_some() && attacker_group == destroyed_group) || attacker == entity {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Maximum distance for other group members to receive exp
|
||||||
|
const MAX_EXP_DIST: f32 = 150.0;
|
||||||
|
// Attacker gets same as exp of everyone else
|
||||||
|
const ATTACKER_EXP_WEIGHT: f32 = 1.0;
|
||||||
|
let mut exp_reward = (entity_stats.body_type.base_exp()
|
||||||
|
+ entity_stats.level.level() * entity_stats.body_type.base_exp_increase())
|
||||||
|
as f32;
|
||||||
|
|
||||||
|
// Distribute EXP to group
|
||||||
|
let positions = state.ecs().read_storage::<Pos>();
|
||||||
|
let alignments = state.ecs().read_storage::<Alignment>();
|
||||||
|
let uids = state.ecs().read_storage::<Uid>();
|
||||||
|
if let (Some(attacker_group), Some(pos)) = (attacker_group, positions.get(entity)) {
|
||||||
|
// TODO: rework if change to groups makes it easier to iterate entities in a
|
||||||
|
// group
|
||||||
|
let mut num_not_pets_in_range = 0;
|
||||||
|
let members_in_range = (
|
||||||
|
&state.ecs().entities(),
|
||||||
|
&groups,
|
||||||
|
&positions,
|
||||||
|
alignments.maybe(),
|
||||||
|
&uids,
|
||||||
|
)
|
||||||
|
.join()
|
||||||
|
.filter(|(entity, group, member_pos, _, _)| {
|
||||||
|
// Check if: in group, not main attacker, and in range
|
||||||
|
*group == attacker_group
|
||||||
|
&& *entity != attacker
|
||||||
|
&& pos.0.distance_squared(member_pos.0) < MAX_EXP_DIST.powi(2)
|
||||||
|
})
|
||||||
|
.map(|(entity, _, _, alignment, uid)| {
|
||||||
|
if !matches!(alignment, Some(Alignment::Owned(owner)) if owner != uid) {
|
||||||
|
num_not_pets_in_range += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let exp = exp_reward / (num_not_pets_in_range as f32 + ATTACKER_EXP_WEIGHT);
|
||||||
|
exp_reward = exp * ATTACKER_EXP_WEIGHT;
|
||||||
|
members_in_range.into_iter().for_each(|e| {
|
||||||
|
if let Some(stats) = stats.get_mut(e) {
|
||||||
|
stats.exp.change_by(exp.ceil() as i64);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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(exp_reward.ceil() as i64);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
if state
|
if state
|
||||||
.ecs()
|
.ecs()
|
||||||
@ -189,7 +250,7 @@ pub fn handle_respawn(server: &Server, entity: EcsEntity) {
|
|||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
let respawn_point = state
|
let respawn_point = state
|
||||||
.read_component_cloned::<comp::Waypoint>(entity)
|
.read_component_copied::<comp::Waypoint>(entity)
|
||||||
.map(|wp| wp.get_pos())
|
.map(|wp| wp.get_pos())
|
||||||
.unwrap_or(state.ecs().read_resource::<SpawnPoint>().0);
|
.unwrap_or(state.ecs().read_resource::<SpawnPoint>().0);
|
||||||
|
|
||||||
@ -217,11 +278,29 @@ pub fn handle_respawn(server: &Server, entity: EcsEntity) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_explosion(server: &Server, pos: Vec3<f32>, power: f32, owner: Option<Uid>) {
|
pub fn handle_explosion(
|
||||||
|
server: &Server,
|
||||||
|
pos: Vec3<f32>,
|
||||||
|
power: f32,
|
||||||
|
owner: Option<Uid>,
|
||||||
|
friendly_damage: bool,
|
||||||
|
) {
|
||||||
// Go through all other entities
|
// Go through all other entities
|
||||||
let hit_range = 3.0 * power;
|
let hit_range = 3.0 * power;
|
||||||
let ecs = &server.state.ecs();
|
let ecs = &server.state.ecs();
|
||||||
for (pos_b, ori_b, character_b, stats_b, loadout_b) in (
|
|
||||||
|
// Add an outcome
|
||||||
|
ecs.write_resource::<Vec<Outcome>>()
|
||||||
|
.push(Outcome::Explosion { pos, power });
|
||||||
|
|
||||||
|
let owner_entity = owner.and_then(|uid| {
|
||||||
|
ecs.read_resource::<UidAllocator>()
|
||||||
|
.retrieve_entity_internal(uid.into())
|
||||||
|
});
|
||||||
|
let groups = ecs.read_storage::<comp::Group>();
|
||||||
|
|
||||||
|
for (entity_b, pos_b, ori_b, character_b, stats_b, loadout_b) in (
|
||||||
|
&ecs.entities(),
|
||||||
&ecs.read_storage::<comp::Pos>(),
|
&ecs.read_storage::<comp::Pos>(),
|
||||||
&ecs.read_storage::<comp::Ori>(),
|
&ecs.read_storage::<comp::Ori>(),
|
||||||
ecs.read_storage::<comp::CharacterState>().maybe(),
|
ecs.read_storage::<comp::CharacterState>().maybe(),
|
||||||
@ -233,9 +312,13 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, power: f32, owner: Opti
|
|||||||
let distance_squared = pos.distance_squared(pos_b.0);
|
let distance_squared = pos.distance_squared(pos_b.0);
|
||||||
// Check if it is a hit
|
// Check if it is a hit
|
||||||
if !stats_b.is_dead
|
if !stats_b.is_dead
|
||||||
// Spherical wedge shaped attack field
|
|
||||||
// RADIUS
|
// RADIUS
|
||||||
&& distance_squared < hit_range.powi(2)
|
&& distance_squared < hit_range.powi(2)
|
||||||
|
// Skip if they are in the same group and friendly_damage is turned off for the
|
||||||
|
// explosion
|
||||||
|
&& (friendly_damage || !owner_entity
|
||||||
|
.and_then(|e| groups.get(e))
|
||||||
|
.map_or(false, |group_a| Some(group_a) == groups.get(entity_b)))
|
||||||
{
|
{
|
||||||
// Weapon gives base damage
|
// Weapon gives base damage
|
||||||
let dmg = (1.0 - distance_squared / hit_range.powi(2)) * power * 130.0;
|
let dmg = (1.0 - distance_squared / hit_range.powi(2)) * power * 130.0;
|
||||||
|
452
server/src/events/group_manip.rs
Normal file
452
server/src/events/group_manip.rs
Normal file
@ -0,0 +1,452 @@
|
|||||||
|
use crate::{client::Client, Server};
|
||||||
|
use common::{
|
||||||
|
comp::{
|
||||||
|
self,
|
||||||
|
group::{self, Group, GroupManager, Invite, PendingInvites},
|
||||||
|
ChatType, GroupManip,
|
||||||
|
},
|
||||||
|
msg::{InviteAnswer, ServerMsg},
|
||||||
|
sync,
|
||||||
|
sync::WorldSyncExt,
|
||||||
|
};
|
||||||
|
use specs::world::WorldExt;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
use tracing::{error, warn};
|
||||||
|
|
||||||
|
/// Time before invite times out
|
||||||
|
const INVITE_TIMEOUT_DUR: Duration = Duration::from_secs(31);
|
||||||
|
/// Reduced duration shown to the client to help alleviate latency issues
|
||||||
|
const PRESENTED_INVITE_TIMEOUT_DUR: Duration = Duration::from_secs(30);
|
||||||
|
|
||||||
|
// TODO: turn chat messages into enums
|
||||||
|
pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupManip) {
|
||||||
|
let max_group_size = server.settings().max_player_group_size;
|
||||||
|
let state = server.state_mut();
|
||||||
|
|
||||||
|
match manip {
|
||||||
|
GroupManip::Invite(uid) => {
|
||||||
|
let mut clients = state.ecs().write_storage::<Client>();
|
||||||
|
let invitee = match state.ecs().entity_from_uid(uid.into()) {
|
||||||
|
Some(t) => t,
|
||||||
|
None => {
|
||||||
|
// Inform of failure
|
||||||
|
if let Some(client) = clients.get_mut(entity) {
|
||||||
|
client.notify(
|
||||||
|
ChatType::Meta
|
||||||
|
.server_msg("Invite failed, target does not exist.".to_owned()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let uids = state.ecs().read_storage::<sync::Uid>();
|
||||||
|
|
||||||
|
// Check if entity is trying to invite themselves to a group
|
||||||
|
if uids
|
||||||
|
.get(entity)
|
||||||
|
.map_or(false, |inviter_uid| *inviter_uid == uid)
|
||||||
|
{
|
||||||
|
warn!("Entity tried to invite themselves into a group");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disallow inviting entity that is already in your group
|
||||||
|
let groups = state.ecs().read_storage::<Group>();
|
||||||
|
let group_manager = state.ecs().read_resource::<GroupManager>();
|
||||||
|
let already_in_same_group = groups.get(entity).map_or(false, |group| {
|
||||||
|
group_manager
|
||||||
|
.group_info(*group)
|
||||||
|
.map_or(false, |g| g.leader == entity)
|
||||||
|
&& groups.get(invitee) == Some(group)
|
||||||
|
});
|
||||||
|
if already_in_same_group {
|
||||||
|
// Inform of failure
|
||||||
|
if let Some(client) = clients.get_mut(entity) {
|
||||||
|
client.notify(ChatType::Meta.server_msg(
|
||||||
|
"Invite failed, can't invite someone already in your group".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pending_invites = state.ecs().write_storage::<PendingInvites>();
|
||||||
|
|
||||||
|
// Check if group max size is already reached
|
||||||
|
// Adding the current number of pending invites
|
||||||
|
let group_size_limit_reached = state
|
||||||
|
.ecs()
|
||||||
|
.read_storage()
|
||||||
|
.get(entity)
|
||||||
|
.copied()
|
||||||
|
.and_then(|group| {
|
||||||
|
// If entity is currently the leader of a full group then they can't invite
|
||||||
|
// anyone else
|
||||||
|
group_manager
|
||||||
|
.group_info(group)
|
||||||
|
.filter(|i| i.leader == entity)
|
||||||
|
.map(|i| i.num_members)
|
||||||
|
})
|
||||||
|
.unwrap_or(1) as usize
|
||||||
|
+ pending_invites.get(entity).map_or(0, |p| p.0.len())
|
||||||
|
>= max_group_size as usize;
|
||||||
|
if group_size_limit_reached {
|
||||||
|
// Inform inviter that they have reached the group size limit
|
||||||
|
if let Some(client) = clients.get_mut(entity) {
|
||||||
|
client.notify(
|
||||||
|
ChatType::Meta.server_msg(
|
||||||
|
"Invite failed, pending invites plus current group size have reached \
|
||||||
|
the group size limit"
|
||||||
|
.to_owned(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let agents = state.ecs().read_storage::<comp::Agent>();
|
||||||
|
let mut invites = state.ecs().write_storage::<Invite>();
|
||||||
|
|
||||||
|
if invites.contains(invitee) {
|
||||||
|
// Inform inviter that there is already an invite
|
||||||
|
if let Some(client) = clients.get_mut(entity) {
|
||||||
|
client.notify(
|
||||||
|
ChatType::Meta
|
||||||
|
.server_msg("This player already has a pending invite.".to_owned()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut invite_sent = false;
|
||||||
|
// Returns true if insertion was succesful
|
||||||
|
let mut send_invite = || {
|
||||||
|
match invites.insert(invitee, group::Invite(entity)) {
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to insert Invite component: {:?}", err);
|
||||||
|
false
|
||||||
|
},
|
||||||
|
Ok(_) => {
|
||||||
|
match pending_invites.entry(entity) {
|
||||||
|
Ok(entry) => {
|
||||||
|
entry
|
||||||
|
.or_insert_with(|| PendingInvites(Vec::new()))
|
||||||
|
.0
|
||||||
|
.push((invitee, Instant::now() + INVITE_TIMEOUT_DUR));
|
||||||
|
invite_sent = true;
|
||||||
|
true
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
error!(
|
||||||
|
"Failed to get entry for pending invites component: {:?}",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
// Cleanup
|
||||||
|
invites.remove(invitee);
|
||||||
|
false
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// If client comp
|
||||||
|
if let (Some(client), Some(inviter)) =
|
||||||
|
(clients.get_mut(invitee), uids.get(entity).copied())
|
||||||
|
{
|
||||||
|
if send_invite() {
|
||||||
|
client.notify(ServerMsg::GroupInvite {
|
||||||
|
inviter,
|
||||||
|
timeout: PRESENTED_INVITE_TIMEOUT_DUR,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if agents.contains(invitee) {
|
||||||
|
send_invite();
|
||||||
|
} else if let Some(client) = clients.get_mut(entity) {
|
||||||
|
client.notify(
|
||||||
|
ChatType::Meta.server_msg("Can't invite, not a player or npc".to_owned()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify inviter that the invite is pending
|
||||||
|
if invite_sent {
|
||||||
|
if let Some(client) = clients.get_mut(entity) {
|
||||||
|
client.notify(ServerMsg::InvitePending(uid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
GroupManip::Accept => {
|
||||||
|
let mut clients = state.ecs().write_storage::<Client>();
|
||||||
|
let uids = state.ecs().read_storage::<sync::Uid>();
|
||||||
|
let mut invites = state.ecs().write_storage::<Invite>();
|
||||||
|
if let Some(inviter) = invites.remove(entity).and_then(|invite| {
|
||||||
|
let inviter = invite.0;
|
||||||
|
let mut pending_invites = state.ecs().write_storage::<PendingInvites>();
|
||||||
|
let pending = &mut pending_invites.get_mut(inviter)?.0;
|
||||||
|
// Check that inviter has a pending invite and remove it from the list
|
||||||
|
let invite_index = pending.iter().position(|p| p.0 == entity)?;
|
||||||
|
pending.swap_remove(invite_index);
|
||||||
|
// If no pending invites remain remove the component
|
||||||
|
if pending.is_empty() {
|
||||||
|
pending_invites.remove(inviter);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(inviter)
|
||||||
|
}) {
|
||||||
|
if let (Some(client), Some(target)) =
|
||||||
|
(clients.get_mut(inviter), uids.get(entity).copied())
|
||||||
|
{
|
||||||
|
client.notify(ServerMsg::InviteComplete {
|
||||||
|
target,
|
||||||
|
answer: InviteAnswer::Accepted,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
let mut group_manager = state.ecs().write_resource::<GroupManager>();
|
||||||
|
group_manager.add_group_member(
|
||||||
|
inviter,
|
||||||
|
entity,
|
||||||
|
&state.ecs().entities(),
|
||||||
|
&mut state.ecs().write_storage(),
|
||||||
|
&state.ecs().read_storage(),
|
||||||
|
&uids,
|
||||||
|
|entity, group_change| {
|
||||||
|
clients
|
||||||
|
.get_mut(entity)
|
||||||
|
.and_then(|c| {
|
||||||
|
group_change
|
||||||
|
.try_map(|e| uids.get(e).copied())
|
||||||
|
.map(|g| (g, c))
|
||||||
|
})
|
||||||
|
.map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g)));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
GroupManip::Decline => {
|
||||||
|
let mut clients = state.ecs().write_storage::<Client>();
|
||||||
|
let uids = state.ecs().read_storage::<sync::Uid>();
|
||||||
|
let mut invites = state.ecs().write_storage::<Invite>();
|
||||||
|
if let Some(inviter) = invites.remove(entity).and_then(|invite| {
|
||||||
|
let inviter = invite.0;
|
||||||
|
let mut pending_invites = state.ecs().write_storage::<PendingInvites>();
|
||||||
|
let pending = &mut pending_invites.get_mut(inviter)?.0;
|
||||||
|
// Check that inviter has a pending invite and remove it from the list
|
||||||
|
let invite_index = pending.iter().position(|p| p.0 == entity)?;
|
||||||
|
pending.swap_remove(invite_index);
|
||||||
|
// If no pending invites remain remove the component
|
||||||
|
if pending.is_empty() {
|
||||||
|
pending_invites.remove(inviter);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(inviter)
|
||||||
|
}) {
|
||||||
|
// Inform inviter of rejection
|
||||||
|
if let (Some(client), Some(target)) =
|
||||||
|
(clients.get_mut(inviter), uids.get(entity).copied())
|
||||||
|
{
|
||||||
|
client.notify(ServerMsg::InviteComplete {
|
||||||
|
target,
|
||||||
|
answer: InviteAnswer::Declined,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
GroupManip::Leave => {
|
||||||
|
let mut clients = state.ecs().write_storage::<Client>();
|
||||||
|
let uids = state.ecs().read_storage::<sync::Uid>();
|
||||||
|
let mut group_manager = state.ecs().write_resource::<GroupManager>();
|
||||||
|
group_manager.leave_group(
|
||||||
|
entity,
|
||||||
|
&mut state.ecs().write_storage(),
|
||||||
|
&state.ecs().read_storage(),
|
||||||
|
&uids,
|
||||||
|
&state.ecs().entities(),
|
||||||
|
&mut |entity, group_change| {
|
||||||
|
clients
|
||||||
|
.get_mut(entity)
|
||||||
|
.and_then(|c| {
|
||||||
|
group_change
|
||||||
|
.try_map(|e| uids.get(e).copied())
|
||||||
|
.map(|g| (g, c))
|
||||||
|
})
|
||||||
|
.map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g)));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
GroupManip::Kick(uid) => {
|
||||||
|
let mut clients = state.ecs().write_storage::<Client>();
|
||||||
|
let uids = state.ecs().read_storage::<sync::Uid>();
|
||||||
|
let alignments = state.ecs().read_storage::<comp::Alignment>();
|
||||||
|
|
||||||
|
let target = match state.ecs().entity_from_uid(uid.into()) {
|
||||||
|
Some(t) => t,
|
||||||
|
None => {
|
||||||
|
// Inform of failure
|
||||||
|
if let Some(client) = clients.get_mut(entity) {
|
||||||
|
client.notify(
|
||||||
|
ChatType::Meta
|
||||||
|
.server_msg("Kick failed, target does not exist.".to_owned()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Can't kick pet
|
||||||
|
if matches!(alignments.get(target), Some(comp::Alignment::Owned(owner)) if uids.get(target).map_or(true, |u| u != owner))
|
||||||
|
{
|
||||||
|
if let Some(client) = clients.get_mut(entity) {
|
||||||
|
client.notify(
|
||||||
|
ChatType::Meta.server_msg("Kick failed, you can't kick pets.".to_owned()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Can't kick yourself
|
||||||
|
if uids.get(entity).map_or(false, |u| *u == uid) {
|
||||||
|
if let Some(client) = clients.get_mut(entity) {
|
||||||
|
client.notify(
|
||||||
|
ChatType::Meta
|
||||||
|
.server_msg("Kick failed, you can't kick yourself.".to_owned()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut groups = state.ecs().write_storage::<group::Group>();
|
||||||
|
let mut group_manager = state.ecs().write_resource::<GroupManager>();
|
||||||
|
// Make sure kicker is the group leader
|
||||||
|
match groups
|
||||||
|
.get(target)
|
||||||
|
.and_then(|group| group_manager.group_info(*group))
|
||||||
|
{
|
||||||
|
Some(info) if info.leader == entity => {
|
||||||
|
// Remove target from group
|
||||||
|
group_manager.leave_group(
|
||||||
|
target,
|
||||||
|
&mut groups,
|
||||||
|
&state.ecs().read_storage(),
|
||||||
|
&uids,
|
||||||
|
&state.ecs().entities(),
|
||||||
|
&mut |entity, group_change| {
|
||||||
|
clients
|
||||||
|
.get_mut(entity)
|
||||||
|
.and_then(|c| {
|
||||||
|
group_change
|
||||||
|
.try_map(|e| uids.get(e).copied())
|
||||||
|
.map(|g| (g, c))
|
||||||
|
})
|
||||||
|
.map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g)));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Tell them the have been kicked
|
||||||
|
if let Some(client) = clients.get_mut(target) {
|
||||||
|
client.notify(
|
||||||
|
ChatType::Meta
|
||||||
|
.server_msg("You were removed from the group.".to_owned()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Tell kicker that they were succesful
|
||||||
|
if let Some(client) = clients.get_mut(entity) {
|
||||||
|
client.notify(ChatType::Meta.server_msg("Player kicked.".to_owned()));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Some(_) => {
|
||||||
|
// Inform kicker that they are not the leader
|
||||||
|
if let Some(client) = clients.get_mut(entity) {
|
||||||
|
client.notify(ChatType::Meta.server_msg(
|
||||||
|
"Kick failed: You are not the leader of the target's group.".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
// Inform kicker that the target is not in a group
|
||||||
|
if let Some(client) = clients.get_mut(entity) {
|
||||||
|
client.notify(
|
||||||
|
ChatType::Meta.server_msg(
|
||||||
|
"Kick failed: Your target is not in a group.".to_owned(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
GroupManip::AssignLeader(uid) => {
|
||||||
|
let mut clients = state.ecs().write_storage::<Client>();
|
||||||
|
let uids = state.ecs().read_storage::<sync::Uid>();
|
||||||
|
let target = match state.ecs().entity_from_uid(uid.into()) {
|
||||||
|
Some(t) => t,
|
||||||
|
None => {
|
||||||
|
// Inform of failure
|
||||||
|
if let Some(client) = clients.get_mut(entity) {
|
||||||
|
client.notify(ChatType::Meta.server_msg(
|
||||||
|
"Leadership transfer failed, target does not exist".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let groups = state.ecs().read_storage::<group::Group>();
|
||||||
|
let mut group_manager = state.ecs().write_resource::<GroupManager>();
|
||||||
|
// Make sure assigner is the group leader
|
||||||
|
match groups
|
||||||
|
.get(target)
|
||||||
|
.and_then(|group| group_manager.group_info(*group))
|
||||||
|
{
|
||||||
|
Some(info) if info.leader == entity => {
|
||||||
|
// Assign target as group leader
|
||||||
|
group_manager.assign_leader(
|
||||||
|
target,
|
||||||
|
&groups,
|
||||||
|
&state.ecs().entities(),
|
||||||
|
&state.ecs().read_storage(),
|
||||||
|
&uids,
|
||||||
|
|entity, group_change| {
|
||||||
|
clients
|
||||||
|
.get_mut(entity)
|
||||||
|
.and_then(|c| {
|
||||||
|
group_change
|
||||||
|
.try_map(|e| uids.get(e).copied())
|
||||||
|
.map(|g| (g, c))
|
||||||
|
})
|
||||||
|
.map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g)));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// Tell them they are the leader
|
||||||
|
if let Some(client) = clients.get_mut(target) {
|
||||||
|
client.notify(
|
||||||
|
ChatType::Meta.server_msg("You are the group leader now.".to_owned()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Tell the old leader that the transfer was succesful
|
||||||
|
if let Some(client) = clients.get_mut(target) {
|
||||||
|
client.notify(
|
||||||
|
ChatType::Meta
|
||||||
|
.server_msg("You are no longer the group leader.".to_owned()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Some(_) => {
|
||||||
|
// Inform transferer that they are not the leader
|
||||||
|
if let Some(client) = clients.get_mut(entity) {
|
||||||
|
client.notify(
|
||||||
|
ChatType::Meta.server_msg(
|
||||||
|
"Transfer failed: You are not the leader of the target's group."
|
||||||
|
.to_owned(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
// Inform transferer that the target is not in a group
|
||||||
|
if let Some(client) = clients.get_mut(entity) {
|
||||||
|
client.notify(ChatType::Meta.server_msg(
|
||||||
|
"Transfer failed: Your target is not in a group.".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,11 @@
|
|||||||
use crate::{Server, StateExt};
|
use crate::{client::Client, Server, StateExt};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{
|
comp::{
|
||||||
self, item,
|
self, item,
|
||||||
slot::{self, Slot},
|
slot::{self, Slot},
|
||||||
Pos, MAX_PICKUP_RANGE_SQR,
|
Pos, MAX_PICKUP_RANGE_SQR,
|
||||||
},
|
},
|
||||||
|
msg::ServerMsg,
|
||||||
recipe::default_recipe_book,
|
recipe::default_recipe_book,
|
||||||
sync::{Uid, WorldSyncExt},
|
sync::{Uid, WorldSyncExt},
|
||||||
terrain::block::Block,
|
terrain::block::Block,
|
||||||
@ -166,10 +167,10 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
|||||||
thrown_items.push((
|
thrown_items.push((
|
||||||
*pos,
|
*pos,
|
||||||
state
|
state
|
||||||
.read_component_cloned::<comp::Vel>(entity)
|
.read_component_copied::<comp::Vel>(entity)
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
state
|
state
|
||||||
.read_component_cloned::<comp::Ori>(entity)
|
.read_component_copied::<comp::Ori>(entity)
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
*kind,
|
*kind,
|
||||||
));
|
));
|
||||||
@ -184,7 +185,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
|||||||
state.read_storage::<comp::Pos>().get(entity)
|
state.read_storage::<comp::Pos>().get(entity)
|
||||||
{
|
{
|
||||||
let uid = state
|
let uid = state
|
||||||
.read_component_cloned(entity)
|
.read_component_copied(entity)
|
||||||
.expect("Expected player to have a UID");
|
.expect("Expected player to have a UID");
|
||||||
if (
|
if (
|
||||||
&state.read_storage::<comp::Alignment>(),
|
&state.read_storage::<comp::Alignment>(),
|
||||||
@ -222,6 +223,35 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
|||||||
.ecs()
|
.ecs()
|
||||||
.write_storage()
|
.write_storage()
|
||||||
.insert(tameable_entity, comp::Alignment::Owned(uid));
|
.insert(tameable_entity, comp::Alignment::Owned(uid));
|
||||||
|
|
||||||
|
// Add to group system
|
||||||
|
let mut clients = state.ecs().write_storage::<Client>();
|
||||||
|
let uids = state.ecs().read_storage::<Uid>();
|
||||||
|
let mut group_manager = state
|
||||||
|
.ecs()
|
||||||
|
.write_resource::<comp::group::GroupManager>(
|
||||||
|
);
|
||||||
|
group_manager.new_pet(
|
||||||
|
tameable_entity,
|
||||||
|
entity,
|
||||||
|
&mut state.ecs().write_storage(),
|
||||||
|
&state.ecs().entities(),
|
||||||
|
&state.ecs().read_storage(),
|
||||||
|
&uids,
|
||||||
|
&mut |entity, group_change| {
|
||||||
|
clients
|
||||||
|
.get_mut(entity)
|
||||||
|
.and_then(|c| {
|
||||||
|
group_change
|
||||||
|
.try_map(|e| uids.get(e).copied())
|
||||||
|
.map(|g| (g, c))
|
||||||
|
})
|
||||||
|
.map(|(g, c)| {
|
||||||
|
c.notify(ServerMsg::GroupUpdate(g))
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let _ = state
|
let _ = state
|
||||||
.ecs()
|
.ecs()
|
||||||
.write_storage()
|
.write_storage()
|
||||||
@ -311,7 +341,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
|||||||
dropped_items.push((
|
dropped_items.push((
|
||||||
*pos,
|
*pos,
|
||||||
state
|
state
|
||||||
.read_component_cloned::<comp::Ori>(entity)
|
.read_component_copied::<comp::Ori>(entity)
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
item,
|
item,
|
||||||
));
|
));
|
||||||
@ -343,10 +373,10 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
|||||||
for _ in 0..amount {
|
for _ in 0..amount {
|
||||||
dropped_items.push((
|
dropped_items.push((
|
||||||
state
|
state
|
||||||
.read_component_cloned::<comp::Pos>(entity)
|
.read_component_copied::<comp::Pos>(entity)
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
state
|
state
|
||||||
.read_component_cloned::<comp::Ori>(entity)
|
.read_component_copied::<comp::Ori>(entity)
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
item.clone(),
|
item.clone(),
|
||||||
));
|
));
|
||||||
@ -377,7 +407,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
|||||||
+ Vec3::unit_z() * 15.0
|
+ Vec3::unit_z() * 15.0
|
||||||
+ Vec3::<f32>::zero().map(|_| rand::thread_rng().gen::<f32>() - 0.5) * 4.0;
|
+ Vec3::<f32>::zero().map(|_| rand::thread_rng().gen::<f32>() - 0.5) * 4.0;
|
||||||
|
|
||||||
let uid = state.read_component_cloned::<Uid>(entity);
|
let uid = state.read_component_copied::<Uid>(entity);
|
||||||
|
|
||||||
let mut new_entity = state
|
let mut new_entity = state
|
||||||
.create_object(Default::default(), match kind {
|
.create_object(Default::default(), match kind {
|
||||||
|
@ -8,6 +8,7 @@ use entity_manipulation::{
|
|||||||
handle_damage, handle_destroy, handle_explosion, handle_land_on_ground, handle_level_up,
|
handle_damage, handle_destroy, handle_explosion, handle_land_on_ground, handle_level_up,
|
||||||
handle_respawn,
|
handle_respawn,
|
||||||
};
|
};
|
||||||
|
use group_manip::handle_group;
|
||||||
use interaction::{handle_lantern, handle_mount, handle_possess, handle_unmount};
|
use interaction::{handle_lantern, handle_mount, handle_possess, handle_unmount};
|
||||||
use inventory_manip::handle_inventory;
|
use inventory_manip::handle_inventory;
|
||||||
use player::{handle_client_disconnect, handle_exit_ingame};
|
use player::{handle_client_disconnect, handle_exit_ingame};
|
||||||
@ -15,6 +16,7 @@ use specs::{Entity as EcsEntity, WorldExt};
|
|||||||
|
|
||||||
mod entity_creation;
|
mod entity_creation;
|
||||||
mod entity_manipulation;
|
mod entity_manipulation;
|
||||||
|
mod group_manip;
|
||||||
mod interaction;
|
mod interaction;
|
||||||
mod inventory_manip;
|
mod inventory_manip;
|
||||||
mod player;
|
mod player;
|
||||||
@ -48,9 +50,12 @@ impl Server {
|
|||||||
|
|
||||||
for event in events {
|
for event in events {
|
||||||
match event {
|
match event {
|
||||||
ServerEvent::Explosion { pos, power, owner } => {
|
ServerEvent::Explosion {
|
||||||
handle_explosion(&self, pos, power, owner)
|
pos,
|
||||||
},
|
power,
|
||||||
|
owner,
|
||||||
|
friendly_damage,
|
||||||
|
} => handle_explosion(&self, pos, power, owner, friendly_damage),
|
||||||
ServerEvent::Shoot {
|
ServerEvent::Shoot {
|
||||||
entity,
|
entity,
|
||||||
dir,
|
dir,
|
||||||
@ -62,6 +67,7 @@ impl Server {
|
|||||||
ServerEvent::Damage { uid, change } => handle_damage(&self, uid, change),
|
ServerEvent::Damage { uid, change } => handle_damage(&self, uid, change),
|
||||||
ServerEvent::Destroy { entity, cause } => handle_destroy(self, entity, cause),
|
ServerEvent::Destroy { entity, cause } => handle_destroy(self, entity, cause),
|
||||||
ServerEvent::InventoryManip(entity, manip) => handle_inventory(self, entity, manip),
|
ServerEvent::InventoryManip(entity, manip) => handle_inventory(self, entity, manip),
|
||||||
|
ServerEvent::GroupManip(entity, manip) => handle_group(self, entity, manip),
|
||||||
ServerEvent::Respawn(entity) => handle_respawn(&self, entity),
|
ServerEvent::Respawn(entity) => handle_respawn(&self, entity),
|
||||||
ServerEvent::LandOnGround { entity, vel } => {
|
ServerEvent::LandOnGround { entity, vel } => {
|
||||||
handle_land_on_ground(&self, entity, vel)
|
handle_land_on_ground(&self, entity, vel)
|
||||||
|
@ -4,7 +4,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
comp,
|
comp,
|
||||||
comp::Player,
|
comp::{group, Player},
|
||||||
msg::{ClientState, PlayerListUpdate, ServerMsg},
|
msg::{ClientState, PlayerListUpdate, ServerMsg},
|
||||||
sync::{Uid, UidAllocator},
|
sync::{Uid, UidAllocator},
|
||||||
};
|
};
|
||||||
@ -20,8 +20,13 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) {
|
|||||||
// Note: If other `ServerEvent`s are referring to this entity they will be
|
// Note: If other `ServerEvent`s are referring to this entity they will be
|
||||||
// disrupted
|
// disrupted
|
||||||
let maybe_client = state.ecs().write_storage::<Client>().remove(entity);
|
let maybe_client = state.ecs().write_storage::<Client>().remove(entity);
|
||||||
let maybe_uid = state.read_component_cloned::<Uid>(entity);
|
let maybe_uid = state.read_component_copied::<Uid>(entity);
|
||||||
let maybe_player = state.ecs().write_storage::<comp::Player>().remove(entity);
|
let maybe_player = state.ecs().write_storage::<comp::Player>().remove(entity);
|
||||||
|
let maybe_group = state
|
||||||
|
.ecs()
|
||||||
|
.write_storage::<group::Group>()
|
||||||
|
.get(entity)
|
||||||
|
.cloned();
|
||||||
if let (Some(mut client), Some(uid), Some(player)) = (maybe_client, maybe_uid, maybe_player) {
|
if let (Some(mut client), Some(uid), Some(player)) = (maybe_client, maybe_uid, maybe_player) {
|
||||||
// Tell client its request was successful
|
// Tell client its request was successful
|
||||||
client.allow_state(ClientState::Registered);
|
client.allow_state(ClientState::Registered);
|
||||||
@ -29,13 +34,39 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) {
|
|||||||
client.notify(ServerMsg::ExitIngameCleanup);
|
client.notify(ServerMsg::ExitIngameCleanup);
|
||||||
|
|
||||||
let entity_builder = state.ecs_mut().create_entity().with(client).with(player);
|
let entity_builder = state.ecs_mut().create_entity().with(client).with(player);
|
||||||
|
|
||||||
|
let entity_builder = match maybe_group {
|
||||||
|
Some(group) => entity_builder.with(group),
|
||||||
|
None => entity_builder,
|
||||||
|
};
|
||||||
|
|
||||||
// Ensure UidAllocator maps this uid to the new entity
|
// Ensure UidAllocator maps this uid to the new entity
|
||||||
let uid = entity_builder
|
let uid = entity_builder
|
||||||
.world
|
.world
|
||||||
.write_resource::<UidAllocator>()
|
.write_resource::<UidAllocator>()
|
||||||
.allocate(entity_builder.entity, Some(uid.into()));
|
.allocate(entity_builder.entity, Some(uid.into()));
|
||||||
entity_builder.with(uid).build();
|
let new_entity = entity_builder.with(uid).build();
|
||||||
|
if let Some(group) = maybe_group {
|
||||||
|
let mut group_manager = state.ecs().write_resource::<group::GroupManager>();
|
||||||
|
if group_manager
|
||||||
|
.group_info(group)
|
||||||
|
.map(|info| info.leader == entity)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
group_manager.assign_leader(
|
||||||
|
new_entity,
|
||||||
|
&state.ecs().read_storage(),
|
||||||
|
&state.ecs().entities(),
|
||||||
|
&state.ecs().read_storage(),
|
||||||
|
&state.ecs().read_storage(),
|
||||||
|
// Nothing actually changing since Uid is transferred
|
||||||
|
|_, _| {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// Erase group component to avoid group restructure when deleting the entity
|
||||||
|
state.ecs().write_storage::<group::Group>().remove(entity);
|
||||||
// Delete old entity
|
// Delete old entity
|
||||||
if let Err(e) = state.delete_entity_recorded(entity) {
|
if let Err(e) = state.delete_entity_recorded(entity) {
|
||||||
error!(
|
error!(
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#![deny(unsafe_code)]
|
#![deny(unsafe_code)]
|
||||||
#![allow(clippy::option_map_unit_fn)]
|
#![allow(clippy::option_map_unit_fn)]
|
||||||
#![feature(drain_filter, option_zip)]
|
#![feature(bool_to_option, drain_filter, option_zip)]
|
||||||
|
|
||||||
pub mod alias_validator;
|
pub mod alias_validator;
|
||||||
pub mod chunk_generator;
|
pub mod chunk_generator;
|
||||||
@ -34,6 +34,7 @@ use common::{
|
|||||||
comp::{self, ChatType},
|
comp::{self, ChatType},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
msg::{server::WorldMapMsg, ClientState, ServerInfo, ServerMsg},
|
msg::{server::WorldMapMsg, ClientState, ServerInfo, ServerMsg},
|
||||||
|
outcome::Outcome,
|
||||||
recipe::default_recipe_book,
|
recipe::default_recipe_book,
|
||||||
state::{State, TimeOfDay},
|
state::{State, TimeOfDay},
|
||||||
sync::WorldSyncExt,
|
sync::WorldSyncExt,
|
||||||
@ -118,6 +119,7 @@ impl Server {
|
|||||||
state
|
state
|
||||||
.ecs_mut()
|
.ecs_mut()
|
||||||
.insert(comp::AdminList(settings.admins.clone()));
|
.insert(comp::AdminList(settings.admins.clone()));
|
||||||
|
state.ecs_mut().insert(Vec::<Outcome>::new());
|
||||||
|
|
||||||
// System timers for performance monitoring
|
// System timers for performance monitoring
|
||||||
state.ecs_mut().insert(sys::EntitySyncTimer::default());
|
state.ecs_mut().insert(sys::EntitySyncTimer::default());
|
||||||
@ -127,6 +129,7 @@ impl Server {
|
|||||||
state.ecs_mut().insert(sys::TerrainSyncTimer::default());
|
state.ecs_mut().insert(sys::TerrainSyncTimer::default());
|
||||||
state.ecs_mut().insert(sys::TerrainTimer::default());
|
state.ecs_mut().insert(sys::TerrainTimer::default());
|
||||||
state.ecs_mut().insert(sys::WaypointTimer::default());
|
state.ecs_mut().insert(sys::WaypointTimer::default());
|
||||||
|
state.ecs_mut().insert(sys::InviteTimeoutTimer::default());
|
||||||
state.ecs_mut().insert(sys::PersistenceTimer::default());
|
state.ecs_mut().insert(sys::PersistenceTimer::default());
|
||||||
|
|
||||||
// System schedulers to control execution of systems
|
// System schedulers to control execution of systems
|
||||||
@ -513,12 +516,18 @@ impl Server {
|
|||||||
.nanos as i64;
|
.nanos as i64;
|
||||||
let terrain_nanos = self.state.ecs().read_resource::<sys::TerrainTimer>().nanos as i64;
|
let terrain_nanos = self.state.ecs().read_resource::<sys::TerrainTimer>().nanos as i64;
|
||||||
let waypoint_nanos = self.state.ecs().read_resource::<sys::WaypointTimer>().nanos as i64;
|
let waypoint_nanos = self.state.ecs().read_resource::<sys::WaypointTimer>().nanos as i64;
|
||||||
|
let invite_timeout_nanos = self
|
||||||
|
.state
|
||||||
|
.ecs()
|
||||||
|
.read_resource::<sys::InviteTimeoutTimer>()
|
||||||
|
.nanos as i64;
|
||||||
let stats_persistence_nanos = self
|
let stats_persistence_nanos = self
|
||||||
.state
|
.state
|
||||||
.ecs()
|
.ecs()
|
||||||
.read_resource::<sys::PersistenceTimer>()
|
.read_resource::<sys::PersistenceTimer>()
|
||||||
.nanos as i64;
|
.nanos as i64;
|
||||||
let total_sys_ran_in_dispatcher_nanos = terrain_nanos + waypoint_nanos;
|
let total_sys_ran_in_dispatcher_nanos =
|
||||||
|
terrain_nanos + waypoint_nanos + invite_timeout_nanos;
|
||||||
|
|
||||||
// Report timing info
|
// Report timing info
|
||||||
self.tick_metrics
|
self.tick_metrics
|
||||||
@ -580,6 +589,10 @@ impl Server {
|
|||||||
.tick_time
|
.tick_time
|
||||||
.with_label_values(&["waypoint"])
|
.with_label_values(&["waypoint"])
|
||||||
.set(waypoint_nanos);
|
.set(waypoint_nanos);
|
||||||
|
self.tick_metrics
|
||||||
|
.tick_time
|
||||||
|
.with_label_values(&["invite timeout"])
|
||||||
|
.set(invite_timeout_nanos);
|
||||||
self.tick_metrics
|
self.tick_metrics
|
||||||
.tick_time
|
.tick_time
|
||||||
.with_label_values(&["persistence:stats"])
|
.with_label_values(&["persistence:stats"])
|
||||||
@ -689,6 +702,7 @@ impl Server {
|
|||||||
.create_entity_package(entity, None, None, None),
|
.create_entity_package(entity, None, None, None),
|
||||||
server_info: self.get_server_info(),
|
server_info: self.get_server_info(),
|
||||||
time_of_day: *self.state.ecs().read_resource(),
|
time_of_day: *self.state.ecs().read_resource(),
|
||||||
|
max_group_size: self.settings().max_player_group_size,
|
||||||
world_map: self.map.clone(),
|
world_map: self.map.clone(),
|
||||||
recipe_book: (&*default_recipe_book()).clone(),
|
recipe_book: (&*default_recipe_book()).clone(),
|
||||||
});
|
});
|
||||||
|
@ -26,6 +26,7 @@ pub struct ServerSettings {
|
|||||||
pub persistence_db_dir: String,
|
pub persistence_db_dir: String,
|
||||||
pub max_view_distance: Option<u32>,
|
pub max_view_distance: Option<u32>,
|
||||||
pub banned_words_files: Vec<PathBuf>,
|
pub banned_words_files: Vec<PathBuf>,
|
||||||
|
pub max_player_group_size: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ServerSettings {
|
impl Default for ServerSettings {
|
||||||
@ -65,6 +66,7 @@ impl Default for ServerSettings {
|
|||||||
persistence_db_dir: "saves".to_owned(),
|
persistence_db_dir: "saves".to_owned(),
|
||||||
max_view_distance: Some(30),
|
max_view_distance: Some(30),
|
||||||
banned_words_files: Vec::new(),
|
banned_words_files: Vec::new(),
|
||||||
|
max_player_group_size: 6,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ pub trait StateExt {
|
|||||||
/// Performed after loading component data from the database
|
/// Performed after loading component data from the database
|
||||||
fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents);
|
fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents);
|
||||||
/// Iterates over registered clients and send each `ServerMsg`
|
/// Iterates over registered clients and send each `ServerMsg`
|
||||||
fn send_chat(&self, msg: comp::ChatMsg);
|
fn send_chat(&self, msg: comp::UnresolvedChatMsg);
|
||||||
fn notify_registered_clients(&self, msg: ServerMsg);
|
fn notify_registered_clients(&self, msg: ServerMsg);
|
||||||
/// Delete an entity, recording the deletion in [`DeletedEntities`]
|
/// Delete an entity, recording the deletion in [`DeletedEntities`]
|
||||||
fn delete_entity_recorded(
|
fn delete_entity_recorded(
|
||||||
@ -173,7 +173,7 @@ impl StateExt for State {
|
|||||||
self.write_component(entity, comp::CharacterState::default());
|
self.write_component(entity, comp::CharacterState::default());
|
||||||
self.write_component(
|
self.write_component(
|
||||||
entity,
|
entity,
|
||||||
comp::Alignment::Owned(self.read_component_cloned(entity).unwrap()),
|
comp::Alignment::Owned(self.read_component_copied(entity).unwrap()),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Set the character id for the player
|
// Set the character id for the player
|
||||||
@ -213,7 +213,7 @@ impl StateExt for State {
|
|||||||
|
|
||||||
// Notify clients of a player list update
|
// Notify clients of a player list update
|
||||||
let client_uid = self
|
let client_uid = self
|
||||||
.read_component_cloned::<Uid>(entity)
|
.read_component_copied::<Uid>(entity)
|
||||||
.map(|u| u)
|
.map(|u| u)
|
||||||
.expect("Client doesn't have a Uid!!!");
|
.expect("Client doesn't have a Uid!!!");
|
||||||
|
|
||||||
@ -240,10 +240,18 @@ impl StateExt for State {
|
|||||||
|
|
||||||
/// Send the chat message to the proper players. Say and region are limited
|
/// Send the chat message to the proper players. Say and region are limited
|
||||||
/// by location. Faction and group are limited by component.
|
/// by location. Faction and group are limited by component.
|
||||||
fn send_chat(&self, msg: comp::ChatMsg) {
|
fn send_chat(&self, msg: comp::UnresolvedChatMsg) {
|
||||||
let ecs = self.ecs();
|
let ecs = self.ecs();
|
||||||
let is_within =
|
let is_within =
|
||||||
|target, a: &comp::Pos, b: &comp::Pos| a.0.distance_squared(b.0) < target * target;
|
|target, a: &comp::Pos, b: &comp::Pos| a.0.distance_squared(b.0) < target * target;
|
||||||
|
|
||||||
|
let group_manager = ecs.read_resource::<comp::group::GroupManager>();
|
||||||
|
let resolved_msg = msg.clone().map_group(|group_id| {
|
||||||
|
group_manager
|
||||||
|
.group_info(group_id)
|
||||||
|
.map_or_else(|| "???".into(), |i| i.name.clone())
|
||||||
|
});
|
||||||
|
|
||||||
match &msg.chat_type {
|
match &msg.chat_type {
|
||||||
comp::ChatType::Online
|
comp::ChatType::Online
|
||||||
| comp::ChatType::Offline
|
| comp::ChatType::Offline
|
||||||
@ -253,7 +261,7 @@ impl StateExt for State {
|
|||||||
| comp::ChatType::Kill
|
| comp::ChatType::Kill
|
||||||
| comp::ChatType::Meta
|
| comp::ChatType::Meta
|
||||||
| comp::ChatType::World(_) => {
|
| comp::ChatType::World(_) => {
|
||||||
self.notify_registered_clients(ServerMsg::ChatMsg(msg.clone()))
|
self.notify_registered_clients(ServerMsg::ChatMsg(resolved_msg))
|
||||||
},
|
},
|
||||||
comp::ChatType::Tell(u, t) => {
|
comp::ChatType::Tell(u, t) => {
|
||||||
for (client, uid) in (
|
for (client, uid) in (
|
||||||
@ -263,7 +271,7 @@ impl StateExt for State {
|
|||||||
.join()
|
.join()
|
||||||
{
|
{
|
||||||
if uid == u || uid == t {
|
if uid == u || uid == t {
|
||||||
client.notify(ServerMsg::ChatMsg(msg.clone()));
|
client.notify(ServerMsg::ChatMsg(resolved_msg.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -275,7 +283,7 @@ impl StateExt for State {
|
|||||||
if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
|
if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
|
||||||
for (client, pos) in (&mut ecs.write_storage::<Client>(), &positions).join() {
|
for (client, pos) in (&mut ecs.write_storage::<Client>(), &positions).join() {
|
||||||
if is_within(comp::ChatMsg::SAY_DISTANCE, pos, speaker_pos) {
|
if is_within(comp::ChatMsg::SAY_DISTANCE, pos, speaker_pos) {
|
||||||
client.notify(ServerMsg::ChatMsg(msg.clone()));
|
client.notify(ServerMsg::ChatMsg(resolved_msg.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -287,7 +295,7 @@ impl StateExt for State {
|
|||||||
if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
|
if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
|
||||||
for (client, pos) in (&mut ecs.write_storage::<Client>(), &positions).join() {
|
for (client, pos) in (&mut ecs.write_storage::<Client>(), &positions).join() {
|
||||||
if is_within(comp::ChatMsg::REGION_DISTANCE, pos, speaker_pos) {
|
if is_within(comp::ChatMsg::REGION_DISTANCE, pos, speaker_pos) {
|
||||||
client.notify(ServerMsg::ChatMsg(msg.clone()));
|
client.notify(ServerMsg::ChatMsg(resolved_msg.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -299,7 +307,7 @@ impl StateExt for State {
|
|||||||
if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
|
if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
|
||||||
for (client, pos) in (&mut ecs.write_storage::<Client>(), &positions).join() {
|
for (client, pos) in (&mut ecs.write_storage::<Client>(), &positions).join() {
|
||||||
if is_within(comp::ChatMsg::NPC_DISTANCE, pos, speaker_pos) {
|
if is_within(comp::ChatMsg::NPC_DISTANCE, pos, speaker_pos) {
|
||||||
client.notify(ServerMsg::ChatMsg(msg.clone()));
|
client.notify(ServerMsg::ChatMsg(resolved_msg.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -313,19 +321,19 @@ impl StateExt for State {
|
|||||||
.join()
|
.join()
|
||||||
{
|
{
|
||||||
if s == &faction.0 {
|
if s == &faction.0 {
|
||||||
client.notify(ServerMsg::ChatMsg(msg.clone()));
|
client.notify(ServerMsg::ChatMsg(resolved_msg.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
comp::ChatType::GroupMeta(s) | comp::ChatType::Group(_, s) => {
|
comp::ChatType::GroupMeta(g) | comp::ChatType::Group(_, g) => {
|
||||||
for (client, group) in (
|
for (client, group) in (
|
||||||
&mut ecs.write_storage::<Client>(),
|
&mut ecs.write_storage::<Client>(),
|
||||||
&ecs.read_storage::<comp::Group>(),
|
&ecs.read_storage::<comp::Group>(),
|
||||||
)
|
)
|
||||||
.join()
|
.join()
|
||||||
{
|
{
|
||||||
if s == &group.0 {
|
if g == group {
|
||||||
client.notify(ServerMsg::ChatMsg(msg.clone()));
|
client.notify(ServerMsg::ChatMsg(resolved_msg.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -346,6 +354,30 @@ impl StateExt for State {
|
|||||||
&mut self,
|
&mut self,
|
||||||
entity: EcsEntity,
|
entity: EcsEntity,
|
||||||
) -> Result<(), specs::error::WrongGeneration> {
|
) -> Result<(), specs::error::WrongGeneration> {
|
||||||
|
// Remove entity from a group if they are in one
|
||||||
|
{
|
||||||
|
let mut clients = self.ecs().write_storage::<Client>();
|
||||||
|
let uids = self.ecs().read_storage::<Uid>();
|
||||||
|
let mut group_manager = self.ecs().write_resource::<comp::group::GroupManager>();
|
||||||
|
group_manager.entity_deleted(
|
||||||
|
entity,
|
||||||
|
&mut self.ecs().write_storage(),
|
||||||
|
&self.ecs().read_storage(),
|
||||||
|
&uids,
|
||||||
|
&self.ecs().entities(),
|
||||||
|
&mut |entity, group_change| {
|
||||||
|
clients
|
||||||
|
.get_mut(entity)
|
||||||
|
.and_then(|c| {
|
||||||
|
group_change
|
||||||
|
.try_map(|e| uids.get(e).copied())
|
||||||
|
.map(|g| (g, c))
|
||||||
|
})
|
||||||
|
.map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g)));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let (maybe_uid, maybe_pos) = (
|
let (maybe_uid, maybe_pos) = (
|
||||||
self.ecs().read_storage::<Uid>().get(entity).copied(),
|
self.ecs().read_storage::<Uid>().get(entity).copied(),
|
||||||
self.ecs().read_storage::<comp::Pos>().get(entity).copied(),
|
self.ecs().read_storage::<comp::Pos>().get(entity).copied(),
|
||||||
|
@ -7,15 +7,19 @@ use crate::{
|
|||||||
Tick,
|
Tick,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Pos, Vel},
|
comp::{ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Player, Pos, Vel},
|
||||||
msg::ServerMsg,
|
msg::ServerMsg,
|
||||||
|
outcome::Outcome,
|
||||||
region::{Event as RegionEvent, RegionMap},
|
region::{Event as RegionEvent, RegionMap},
|
||||||
state::TimeOfDay,
|
state::TimeOfDay,
|
||||||
sync::{CompSyncPackage, Uid},
|
sync::{CompSyncPackage, Uid},
|
||||||
|
terrain::TerrainChunkSize,
|
||||||
|
vol::RectVolSize,
|
||||||
};
|
};
|
||||||
use specs::{
|
use specs::{
|
||||||
Entities, Entity as EcsEntity, Join, Read, ReadExpect, ReadStorage, System, Write, WriteStorage,
|
Entities, Entity as EcsEntity, Join, Read, ReadExpect, ReadStorage, System, Write, WriteStorage,
|
||||||
};
|
};
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
/// This system will send physics updates to the client
|
/// This system will send physics updates to the client
|
||||||
pub struct Sys;
|
pub struct Sys;
|
||||||
@ -33,6 +37,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
ReadStorage<'a, Ori>,
|
ReadStorage<'a, Ori>,
|
||||||
ReadStorage<'a, Inventory>,
|
ReadStorage<'a, Inventory>,
|
||||||
ReadStorage<'a, RegionSubscription>,
|
ReadStorage<'a, RegionSubscription>,
|
||||||
|
ReadStorage<'a, Player>,
|
||||||
WriteStorage<'a, Last<Pos>>,
|
WriteStorage<'a, Last<Pos>>,
|
||||||
WriteStorage<'a, Last<Vel>>,
|
WriteStorage<'a, Last<Vel>>,
|
||||||
WriteStorage<'a, Last<Ori>>,
|
WriteStorage<'a, Last<Ori>>,
|
||||||
@ -40,6 +45,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
WriteStorage<'a, ForceUpdate>,
|
WriteStorage<'a, ForceUpdate>,
|
||||||
WriteStorage<'a, InventoryUpdate>,
|
WriteStorage<'a, InventoryUpdate>,
|
||||||
Write<'a, DeletedEntities>,
|
Write<'a, DeletedEntities>,
|
||||||
|
Write<'a, Vec<Outcome>>,
|
||||||
TrackedComps<'a>,
|
TrackedComps<'a>,
|
||||||
ReadTrackers<'a>,
|
ReadTrackers<'a>,
|
||||||
);
|
);
|
||||||
@ -58,6 +64,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
orientations,
|
orientations,
|
||||||
inventories,
|
inventories,
|
||||||
subscriptions,
|
subscriptions,
|
||||||
|
players,
|
||||||
mut last_pos,
|
mut last_pos,
|
||||||
mut last_vel,
|
mut last_vel,
|
||||||
mut last_ori,
|
mut last_ori,
|
||||||
@ -65,6 +72,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
mut force_updates,
|
mut force_updates,
|
||||||
mut inventory_updates,
|
mut inventory_updates,
|
||||||
mut deleted_entities,
|
mut deleted_entities,
|
||||||
|
mut outcomes,
|
||||||
tracked_comps,
|
tracked_comps,
|
||||||
trackers,
|
trackers,
|
||||||
): Self::SystemData,
|
): Self::SystemData,
|
||||||
@ -316,6 +324,26 @@ impl<'a> System<'a> for Sys {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sync outcomes
|
||||||
|
for (client, player, pos) in (&mut clients, &players, positions.maybe()).join() {
|
||||||
|
let is_near = |o_pos: Vec3<f32>| {
|
||||||
|
pos.zip_with(player.view_distance, |pos, vd| {
|
||||||
|
pos.0.xy().distance_squared(o_pos.xy())
|
||||||
|
< (vd as f32 * TerrainChunkSize::RECT_SIZE.x as f32).powf(2.0)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let outcomes = outcomes
|
||||||
|
.iter()
|
||||||
|
.filter(|o| o.get_pos().and_then(&is_near).unwrap_or(true))
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if outcomes.len() > 0 {
|
||||||
|
client.notify(ServerMsg::Outcomes(outcomes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outcomes.clear();
|
||||||
|
|
||||||
// Remove all force flags.
|
// Remove all force flags.
|
||||||
force_updates.clear();
|
force_updates.clear();
|
||||||
inventory_updates.clear();
|
inventory_updates.clear();
|
||||||
|
71
server/src/sys/invite_timeout.rs
Normal file
71
server/src/sys/invite_timeout.rs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
use super::SysTimer;
|
||||||
|
use crate::client::Client;
|
||||||
|
use common::{
|
||||||
|
comp::group::{Invite, PendingInvites},
|
||||||
|
msg::{InviteAnswer, ServerMsg},
|
||||||
|
sync::Uid,
|
||||||
|
};
|
||||||
|
use specs::{Entities, Join, ReadStorage, System, Write, WriteStorage};
|
||||||
|
|
||||||
|
/// This system removes timed out group invites
|
||||||
|
pub struct Sys;
|
||||||
|
impl<'a> System<'a> for Sys {
|
||||||
|
#[allow(clippy::type_complexity)] // TODO: Pending review in #587
|
||||||
|
type SystemData = (
|
||||||
|
Entities<'a>,
|
||||||
|
WriteStorage<'a, Invite>,
|
||||||
|
WriteStorage<'a, PendingInvites>,
|
||||||
|
WriteStorage<'a, Client>,
|
||||||
|
ReadStorage<'a, Uid>,
|
||||||
|
Write<'a, SysTimer<Self>>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&mut self,
|
||||||
|
(entities, mut invites, mut pending_invites, mut clients, uids, mut timer): Self::SystemData,
|
||||||
|
) {
|
||||||
|
timer.start();
|
||||||
|
|
||||||
|
let now = std::time::Instant::now();
|
||||||
|
|
||||||
|
let timed_out_invites = (&entities, &invites)
|
||||||
|
.join()
|
||||||
|
.filter_map(|(invitee, Invite(inviter))| {
|
||||||
|
// Retrieve timeout invite from pending invites
|
||||||
|
let pending = &mut pending_invites.get_mut(*inviter)?.0;
|
||||||
|
let index = pending.iter().position(|p| p.0 == invitee)?;
|
||||||
|
|
||||||
|
// Stop if not timed out
|
||||||
|
if pending[index].1 > now {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove pending entry
|
||||||
|
pending.swap_remove(index);
|
||||||
|
|
||||||
|
// If no pending invites remain remove the component
|
||||||
|
if pending.is_empty() {
|
||||||
|
pending_invites.remove(*inviter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inform inviter of timeout
|
||||||
|
if let (Some(client), Some(target)) =
|
||||||
|
(clients.get_mut(*inviter), uids.get(invitee).copied())
|
||||||
|
{
|
||||||
|
client.notify(ServerMsg::InviteComplete {
|
||||||
|
target,
|
||||||
|
answer: InviteAnswer::TimedOut,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(invitee)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
for entity in timed_out_invites {
|
||||||
|
invites.remove(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
timer.end();
|
||||||
|
}
|
||||||
|
}
|
@ -5,8 +5,8 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{
|
comp::{
|
||||||
Admin, AdminList, CanBuild, ChatMode, ChatMsg, ChatType, ControlEvent, Controller,
|
Admin, AdminList, CanBuild, ChatMode, ChatType, ControlEvent, Controller, ForceUpdate, Ori,
|
||||||
ForceUpdate, Ori, Player, Pos, Stats, Vel,
|
Player, Pos, Stats, UnresolvedChatMsg, Vel,
|
||||||
},
|
},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
msg::{
|
msg::{
|
||||||
@ -32,7 +32,7 @@ impl Sys {
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn handle_client_msg(
|
async fn handle_client_msg(
|
||||||
server_emitter: &mut common::event::Emitter<'_, ServerEvent>,
|
server_emitter: &mut common::event::Emitter<'_, ServerEvent>,
|
||||||
new_chat_msgs: &mut Vec<(Option<specs::Entity>, ChatMsg)>,
|
new_chat_msgs: &mut Vec<(Option<specs::Entity>, UnresolvedChatMsg)>,
|
||||||
player_list: &HashMap<Uid, PlayerInfo>,
|
player_list: &HashMap<Uid, PlayerInfo>,
|
||||||
new_players: &mut Vec<specs::Entity>,
|
new_players: &mut Vec<specs::Entity>,
|
||||||
entity: specs::Entity,
|
entity: specs::Entity,
|
||||||
@ -202,7 +202,7 @@ impl Sys {
|
|||||||
// Only send login message if it wasn't already
|
// Only send login message if it wasn't already
|
||||||
// sent previously
|
// sent previously
|
||||||
if !client.login_msg_sent {
|
if !client.login_msg_sent {
|
||||||
new_chat_msgs.push((None, ChatMsg {
|
new_chat_msgs.push((None, UnresolvedChatMsg {
|
||||||
chat_type: ChatType::Online,
|
chat_type: ChatType::Online,
|
||||||
message: format!("[{}] is now online.", &player.alias), // TODO: Localize this
|
message: format!("[{}] is now online.", &player.alias), // TODO: Localize this
|
||||||
}));
|
}));
|
||||||
@ -461,7 +461,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
|
|
||||||
let mut server_emitter = server_event_bus.emitter();
|
let mut server_emitter = server_event_bus.emitter();
|
||||||
|
|
||||||
let mut new_chat_msgs: Vec<(Option<specs::Entity>, ChatMsg)> = Vec::new();
|
let mut new_chat_msgs = Vec::new();
|
||||||
|
|
||||||
// Player list to send new players.
|
// Player list to send new players.
|
||||||
let player_list = (&uids, &players, stats.maybe(), admins.maybe())
|
let player_list = (&uids, &players, stats.maybe(), admins.maybe())
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
pub mod entity_sync;
|
pub mod entity_sync;
|
||||||
|
pub mod invite_timeout;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
pub mod object;
|
pub mod object;
|
||||||
pub mod persistence;
|
pub mod persistence;
|
||||||
@ -21,6 +22,7 @@ pub type SubscriptionTimer = SysTimer<subscription::Sys>;
|
|||||||
pub type TerrainTimer = SysTimer<terrain::Sys>;
|
pub type TerrainTimer = SysTimer<terrain::Sys>;
|
||||||
pub type TerrainSyncTimer = SysTimer<terrain_sync::Sys>;
|
pub type TerrainSyncTimer = SysTimer<terrain_sync::Sys>;
|
||||||
pub type WaypointTimer = SysTimer<waypoint::Sys>;
|
pub type WaypointTimer = SysTimer<waypoint::Sys>;
|
||||||
|
pub type InviteTimeoutTimer = SysTimer<invite_timeout::Sys>;
|
||||||
pub type PersistenceTimer = SysTimer<persistence::Sys>;
|
pub type PersistenceTimer = SysTimer<persistence::Sys>;
|
||||||
pub type PersistenceScheduler = SysScheduler<persistence::Sys>;
|
pub type PersistenceScheduler = SysScheduler<persistence::Sys>;
|
||||||
|
|
||||||
@ -32,12 +34,14 @@ pub type PersistenceScheduler = SysScheduler<persistence::Sys>;
|
|||||||
//const TERRAIN_SYNC_SYS: &str = "server_terrain_sync_sys";
|
//const TERRAIN_SYNC_SYS: &str = "server_terrain_sync_sys";
|
||||||
const TERRAIN_SYS: &str = "server_terrain_sys";
|
const TERRAIN_SYS: &str = "server_terrain_sys";
|
||||||
const WAYPOINT_SYS: &str = "server_waypoint_sys";
|
const WAYPOINT_SYS: &str = "server_waypoint_sys";
|
||||||
|
const INVITE_TIMEOUT_SYS: &str = "server_invite_timeout_sys";
|
||||||
const PERSISTENCE_SYS: &str = "server_persistence_sys";
|
const PERSISTENCE_SYS: &str = "server_persistence_sys";
|
||||||
const OBJECT_SYS: &str = "server_object_sys";
|
const OBJECT_SYS: &str = "server_object_sys";
|
||||||
|
|
||||||
pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
|
pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
|
||||||
dispatch_builder.add(terrain::Sys, TERRAIN_SYS, &[]);
|
dispatch_builder.add(terrain::Sys, TERRAIN_SYS, &[]);
|
||||||
dispatch_builder.add(waypoint::Sys, WAYPOINT_SYS, &[]);
|
dispatch_builder.add(waypoint::Sys, WAYPOINT_SYS, &[]);
|
||||||
|
dispatch_builder.add(invite_timeout::Sys, INVITE_TIMEOUT_SYS, &[]);
|
||||||
dispatch_builder.add(persistence::Sys, PERSISTENCE_SYS, &[]);
|
dispatch_builder.add(persistence::Sys, PERSISTENCE_SYS, &[]);
|
||||||
dispatch_builder.add(object::Sys, OBJECT_SYS, &[]);
|
dispatch_builder.add(object::Sys, OBJECT_SYS, &[]);
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
pos: pos.0,
|
pos: pos.0,
|
||||||
power: 4.0,
|
power: 4.0,
|
||||||
owner: *owner,
|
owner: *owner,
|
||||||
|
friendly_damage: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use super::SysTimer;
|
use super::SysTimer;
|
||||||
use common::{
|
use common::{
|
||||||
comp::{
|
comp::{
|
||||||
Alignment, Body, CanBuild, CharacterState, Collider, Energy, Gravity, Item, LightEmitter,
|
Body, CanBuild, CharacterState, Collider, Energy, Gravity, Group, Item, LightEmitter,
|
||||||
Loadout, Mass, MountState, Mounting, Ori, Player, Pos, Scale, Stats, Sticky, Vel,
|
Loadout, Mass, MountState, Mounting, Ori, Player, Pos, Scale, Stats, Sticky, Vel,
|
||||||
},
|
},
|
||||||
msg::EcsCompPacket,
|
msg::EcsCompPacket,
|
||||||
@ -48,7 +48,7 @@ pub struct TrackedComps<'a> {
|
|||||||
pub scale: ReadStorage<'a, Scale>,
|
pub scale: ReadStorage<'a, Scale>,
|
||||||
pub mounting: ReadStorage<'a, Mounting>,
|
pub mounting: ReadStorage<'a, Mounting>,
|
||||||
pub mount_state: ReadStorage<'a, MountState>,
|
pub mount_state: ReadStorage<'a, MountState>,
|
||||||
pub alignment: ReadStorage<'a, Alignment>,
|
pub group: ReadStorage<'a, Group>,
|
||||||
pub mass: ReadStorage<'a, Mass>,
|
pub mass: ReadStorage<'a, Mass>,
|
||||||
pub collider: ReadStorage<'a, Collider>,
|
pub collider: ReadStorage<'a, Collider>,
|
||||||
pub sticky: ReadStorage<'a, Sticky>,
|
pub sticky: ReadStorage<'a, Sticky>,
|
||||||
@ -105,7 +105,7 @@ impl<'a> TrackedComps<'a> {
|
|||||||
.get(entity)
|
.get(entity)
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|c| comps.push(c.into()));
|
.map(|c| comps.push(c.into()));
|
||||||
self.alignment
|
self.group
|
||||||
.get(entity)
|
.get(entity)
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|c| comps.push(c.into()));
|
.map(|c| comps.push(c.into()));
|
||||||
@ -151,7 +151,7 @@ pub struct ReadTrackers<'a> {
|
|||||||
pub scale: ReadExpect<'a, UpdateTracker<Scale>>,
|
pub scale: ReadExpect<'a, UpdateTracker<Scale>>,
|
||||||
pub mounting: ReadExpect<'a, UpdateTracker<Mounting>>,
|
pub mounting: ReadExpect<'a, UpdateTracker<Mounting>>,
|
||||||
pub mount_state: ReadExpect<'a, UpdateTracker<MountState>>,
|
pub mount_state: ReadExpect<'a, UpdateTracker<MountState>>,
|
||||||
pub alignment: ReadExpect<'a, UpdateTracker<Alignment>>,
|
pub group: ReadExpect<'a, UpdateTracker<Group>>,
|
||||||
pub mass: ReadExpect<'a, UpdateTracker<Mass>>,
|
pub mass: ReadExpect<'a, UpdateTracker<Mass>>,
|
||||||
pub collider: ReadExpect<'a, UpdateTracker<Collider>>,
|
pub collider: ReadExpect<'a, UpdateTracker<Collider>>,
|
||||||
pub sticky: ReadExpect<'a, UpdateTracker<Sticky>>,
|
pub sticky: ReadExpect<'a, UpdateTracker<Sticky>>,
|
||||||
@ -184,7 +184,7 @@ impl<'a> ReadTrackers<'a> {
|
|||||||
.with_component(&comps.uid, &*self.scale, &comps.scale, filter)
|
.with_component(&comps.uid, &*self.scale, &comps.scale, filter)
|
||||||
.with_component(&comps.uid, &*self.mounting, &comps.mounting, filter)
|
.with_component(&comps.uid, &*self.mounting, &comps.mounting, filter)
|
||||||
.with_component(&comps.uid, &*self.mount_state, &comps.mount_state, filter)
|
.with_component(&comps.uid, &*self.mount_state, &comps.mount_state, filter)
|
||||||
.with_component(&comps.uid, &*self.alignment, &comps.alignment, filter)
|
.with_component(&comps.uid, &*self.group, &comps.group, filter)
|
||||||
.with_component(&comps.uid, &*self.mass, &comps.mass, filter)
|
.with_component(&comps.uid, &*self.mass, &comps.mass, filter)
|
||||||
.with_component(&comps.uid, &*self.collider, &comps.collider, filter)
|
.with_component(&comps.uid, &*self.collider, &comps.collider, filter)
|
||||||
.with_component(&comps.uid, &*self.sticky, &comps.sticky, filter)
|
.with_component(&comps.uid, &*self.sticky, &comps.sticky, filter)
|
||||||
@ -214,7 +214,7 @@ pub struct WriteTrackers<'a> {
|
|||||||
scale: WriteExpect<'a, UpdateTracker<Scale>>,
|
scale: WriteExpect<'a, UpdateTracker<Scale>>,
|
||||||
mounting: WriteExpect<'a, UpdateTracker<Mounting>>,
|
mounting: WriteExpect<'a, UpdateTracker<Mounting>>,
|
||||||
mount_state: WriteExpect<'a, UpdateTracker<MountState>>,
|
mount_state: WriteExpect<'a, UpdateTracker<MountState>>,
|
||||||
alignment: WriteExpect<'a, UpdateTracker<Alignment>>,
|
group: WriteExpect<'a, UpdateTracker<Group>>,
|
||||||
mass: WriteExpect<'a, UpdateTracker<Mass>>,
|
mass: WriteExpect<'a, UpdateTracker<Mass>>,
|
||||||
collider: WriteExpect<'a, UpdateTracker<Collider>>,
|
collider: WriteExpect<'a, UpdateTracker<Collider>>,
|
||||||
sticky: WriteExpect<'a, UpdateTracker<Sticky>>,
|
sticky: WriteExpect<'a, UpdateTracker<Sticky>>,
|
||||||
@ -236,7 +236,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
|
|||||||
trackers.scale.record_changes(&comps.scale);
|
trackers.scale.record_changes(&comps.scale);
|
||||||
trackers.mounting.record_changes(&comps.mounting);
|
trackers.mounting.record_changes(&comps.mounting);
|
||||||
trackers.mount_state.record_changes(&comps.mount_state);
|
trackers.mount_state.record_changes(&comps.mount_state);
|
||||||
trackers.alignment.record_changes(&comps.alignment);
|
trackers.group.record_changes(&comps.group);
|
||||||
trackers.mass.record_changes(&comps.mass);
|
trackers.mass.record_changes(&comps.mass);
|
||||||
trackers.collider.record_changes(&comps.collider);
|
trackers.collider.record_changes(&comps.collider);
|
||||||
trackers.sticky.record_changes(&comps.sticky);
|
trackers.sticky.record_changes(&comps.sticky);
|
||||||
@ -291,7 +291,7 @@ pub fn register_trackers(world: &mut World) {
|
|||||||
world.register_tracker::<Scale>();
|
world.register_tracker::<Scale>();
|
||||||
world.register_tracker::<Mounting>();
|
world.register_tracker::<Mounting>();
|
||||||
world.register_tracker::<MountState>();
|
world.register_tracker::<MountState>();
|
||||||
world.register_tracker::<Alignment>();
|
world.register_tracker::<Group>();
|
||||||
world.register_tracker::<Mass>();
|
world.register_tracker::<Mass>();
|
||||||
world.register_tracker::<Collider>();
|
world.register_tracker::<Collider>();
|
||||||
world.register_tracker::<Sticky>();
|
world.register_tracker::<Sticky>();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use common::{
|
use common::{
|
||||||
generation::{ChunkSupplement, EntityInfo, EntityKind},
|
generation::{ChunkSupplement, EntityInfo},
|
||||||
terrain::{Block, BlockKind, MapSizeLg, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
|
terrain::{Block, BlockKind, MapSizeLg, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
|
||||||
vol::{ReadVol, RectVolSize, Vox, WriteVol},
|
vol::{ReadVol, RectVolSize, Vox, WriteVol},
|
||||||
};
|
};
|
||||||
@ -38,17 +38,10 @@ impl World {
|
|||||||
|
|
||||||
let mut supplement = ChunkSupplement::default();
|
let mut supplement = ChunkSupplement::default();
|
||||||
|
|
||||||
if chunk_pos.map(|e| e % 8 == 0).reduce_and() {
|
|
||||||
supplement = supplement.with_entity(EntityInfo {
|
|
||||||
pos: Vec3::<f32>::from(chunk_pos.map(|e| e as f32 * 32.0)) + Vec3::unit_z() * 256.0,
|
|
||||||
kind: EntityKind::Waypoint,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
TerrainChunk::new(
|
TerrainChunk::new(
|
||||||
256 + if rng.gen::<u8>() < 64 { height } else { 0 },
|
256 + if rng.gen::<u8>() < 64 { height } else { 0 },
|
||||||
Block::new(BlockKind::Dense, Rgb::new(200, 220, 255)),
|
Block::new(BlockKind::Grass, Rgb::new(11, 102, 35)),
|
||||||
Block::empty(),
|
Block::empty(),
|
||||||
TerrainChunkMeta::void(),
|
TerrainChunkMeta::void(),
|
||||||
),
|
),
|
||||||
|
@ -60,7 +60,7 @@ directories-next = "1.0.1"
|
|||||||
num = "0.2"
|
num = "0.2"
|
||||||
backtrace = "0.3.40"
|
backtrace = "0.3.40"
|
||||||
rand = "0.7"
|
rand = "0.7"
|
||||||
treeculler = { git = "https://gitlab.com/yusdacra/treeculler.git" }
|
treeculler = "0.1.0"
|
||||||
rodio = { version = "0.11", default-features = false, features = ["wav", "vorbis"] }
|
rodio = { version = "0.11", default-features = false, features = ["wav", "vorbis"] }
|
||||||
cpal = "0.11"
|
cpal = "0.11"
|
||||||
crossbeam = "=0.7.2"
|
crossbeam = "=0.7.2"
|
||||||
@ -71,6 +71,7 @@ deunicode = "1.0"
|
|||||||
uvth = "3.1.1"
|
uvth = "3.1.1"
|
||||||
# vec_map = { version = "0.8.2" }
|
# vec_map = { version = "0.8.2" }
|
||||||
const-tweaker = { version = "0.3.1", optional = true }
|
const-tweaker = { version = "0.3.1", optional = true }
|
||||||
|
itertools = "0.9.0"
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
|
@ -8,7 +8,7 @@ edition = "2018"
|
|||||||
name = "voxygen_anim"
|
name = "voxygen_anim"
|
||||||
# Uncomment to use animation hot reloading
|
# Uncomment to use animation hot reloading
|
||||||
# Note: this breaks `cargo test`
|
# Note: this breaks `cargo test`
|
||||||
# crate-type = ["lib", "cdylib"]
|
#crate-type = ["lib", "cdylib"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
use-dyn-lib = ["libloading", "notify", "lazy_static", "tracing", "find_folder"]
|
use-dyn-lib = ["libloading", "notify", "lazy_static", "tracing", "find_folder"]
|
||||||
|
@ -39,6 +39,8 @@ impl Animation for IdleAnimation {
|
|||||||
* 0.25,
|
* 0.25,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let wave_slow = (anim_time as f32 * 0.8).sin();
|
||||||
|
|
||||||
next.head.position = Vec3::new(
|
next.head.position = Vec3::new(
|
||||||
0.0,
|
0.0,
|
||||||
skeleton_attr.head.0,
|
skeleton_attr.head.0,
|
||||||
@ -64,10 +66,27 @@ impl Animation for IdleAnimation {
|
|||||||
next.lower_torso.orientation = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(0.0);
|
next.lower_torso.orientation = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(0.0);
|
||||||
next.lower_torso.scale = Vec3::one() * 1.02;
|
next.lower_torso.scale = Vec3::one() * 1.02;
|
||||||
|
|
||||||
|
next.jaw.position = Vec3::new(0.0, skeleton_attr.jaw.0, skeleton_attr.jaw.1);
|
||||||
|
next.jaw.orientation = Quaternion::rotation_x(wave_slow * 0.09);
|
||||||
|
next.jaw.scale = Vec3::one();
|
||||||
|
|
||||||
|
next.tail.position = Vec3::new(
|
||||||
|
0.0,
|
||||||
|
skeleton_attr.tail.0,
|
||||||
|
skeleton_attr.tail.1 + torso * 0.0,
|
||||||
|
);
|
||||||
|
next.tail.orientation = Quaternion::rotation_z(0.0);
|
||||||
|
next.tail.scale = Vec3::one();
|
||||||
|
|
||||||
next.control.position = Vec3::new(0.0, 0.0, 0.0);
|
next.control.position = Vec3::new(0.0, 0.0, 0.0);
|
||||||
next.control.orientation = Quaternion::rotation_z(0.0);
|
next.control.orientation = Quaternion::rotation_z(0.0);
|
||||||
next.control.scale = Vec3::one();
|
next.control.scale = Vec3::one();
|
||||||
|
|
||||||
|
next.second.position = Vec3::new(0.0, 0.0, 0.0);
|
||||||
|
next.second.orientation =
|
||||||
|
Quaternion::rotation_x(PI) * Quaternion::rotation_y(0.0) * Quaternion::rotation_z(0.0);
|
||||||
|
next.second.scale = Vec3::one() * 0.0;
|
||||||
|
|
||||||
next.main.position = Vec3::new(-5.0, -7.0, 7.0);
|
next.main.position = Vec3::new(-5.0, -7.0, 7.0);
|
||||||
next.main.orientation =
|
next.main.orientation =
|
||||||
Quaternion::rotation_x(PI) * Quaternion::rotation_y(0.6) * Quaternion::rotation_z(1.57);
|
Quaternion::rotation_x(PI) * Quaternion::rotation_y(0.6) * Quaternion::rotation_z(1.57);
|
||||||
|
@ -41,6 +41,14 @@ impl Animation for JumpAnimation {
|
|||||||
next.lower_torso.orientation = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(0.0);
|
next.lower_torso.orientation = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(0.0);
|
||||||
next.lower_torso.scale = Vec3::one() * 1.02;
|
next.lower_torso.scale = Vec3::one() * 1.02;
|
||||||
|
|
||||||
|
next.jaw.position = Vec3::new(0.0, skeleton_attr.jaw.0, skeleton_attr.jaw.1);
|
||||||
|
next.jaw.orientation = Quaternion::rotation_z(0.0);
|
||||||
|
next.jaw.scale = Vec3::one();
|
||||||
|
|
||||||
|
next.tail.position = Vec3::new(0.0, skeleton_attr.tail.0, skeleton_attr.tail.1 * 0.0);
|
||||||
|
next.tail.orientation = Quaternion::rotation_z(0.0);
|
||||||
|
next.tail.scale = Vec3::one();
|
||||||
|
|
||||||
next.shoulder_l.position = Vec3::new(
|
next.shoulder_l.position = Vec3::new(
|
||||||
-skeleton_attr.shoulder.0,
|
-skeleton_attr.shoulder.0,
|
||||||
skeleton_attr.shoulder.1,
|
skeleton_attr.shoulder.1,
|
||||||
|
@ -14,9 +14,12 @@ use core::convert::TryFrom;
|
|||||||
|
|
||||||
skeleton_impls!(struct BipedLargeSkeleton {
|
skeleton_impls!(struct BipedLargeSkeleton {
|
||||||
+ head,
|
+ head,
|
||||||
|
+ jaw,
|
||||||
+ upper_torso,
|
+ upper_torso,
|
||||||
+ lower_torso,
|
+ lower_torso,
|
||||||
|
+ tail,
|
||||||
+ main,
|
+ main,
|
||||||
|
+ second,
|
||||||
+ shoulder_l,
|
+ shoulder_l,
|
||||||
+ shoulder_r,
|
+ shoulder_r,
|
||||||
+ hand_l,
|
+ hand_l,
|
||||||
@ -32,7 +35,7 @@ skeleton_impls!(struct BipedLargeSkeleton {
|
|||||||
impl Skeleton for BipedLargeSkeleton {
|
impl Skeleton for BipedLargeSkeleton {
|
||||||
type Attr = SkeletonAttr;
|
type Attr = SkeletonAttr;
|
||||||
|
|
||||||
const BONE_COUNT: usize = 12;
|
const BONE_COUNT: usize = 15;
|
||||||
#[cfg(feature = "use-dyn-lib")]
|
#[cfg(feature = "use-dyn-lib")]
|
||||||
const COMPUTE_FN: &'static [u8] = b"biped_large_compute_mats\0";
|
const COMPUTE_FN: &'static [u8] = b"biped_large_compute_mats\0";
|
||||||
|
|
||||||
@ -48,12 +51,16 @@ impl Skeleton for BipedLargeSkeleton {
|
|||||||
let control_mat = torso_mat * Mat4::<f32>::from(self.control) * upper_torso;
|
let control_mat = torso_mat * Mat4::<f32>::from(self.control) * upper_torso;
|
||||||
let upper_torso_mat = torso_mat * upper_torso;
|
let upper_torso_mat = torso_mat * upper_torso;
|
||||||
let lower_torso_mat = upper_torso_mat * Mat4::<f32>::from(self.lower_torso);
|
let lower_torso_mat = upper_torso_mat * Mat4::<f32>::from(self.lower_torso);
|
||||||
|
let head_mat = upper_torso_mat * Mat4::<f32>::from(self.head);
|
||||||
|
|
||||||
*(<&mut [_; Self::BONE_COUNT]>::try_from(&mut buf[0..Self::BONE_COUNT]).unwrap()) = [
|
*(<&mut [_; Self::BONE_COUNT]>::try_from(&mut buf[0..Self::BONE_COUNT]).unwrap()) = [
|
||||||
make_bone(upper_torso_mat * Mat4::<f32>::from(self.head)),
|
make_bone(head_mat),
|
||||||
|
make_bone(head_mat * Mat4::<f32>::from(self.jaw)),
|
||||||
make_bone(upper_torso_mat),
|
make_bone(upper_torso_mat),
|
||||||
make_bone(lower_torso_mat),
|
make_bone(lower_torso_mat),
|
||||||
|
make_bone(lower_torso_mat * Mat4::<f32>::from(self.tail)),
|
||||||
make_bone(control_mat * Mat4::<f32>::from(self.main)),
|
make_bone(control_mat * Mat4::<f32>::from(self.main)),
|
||||||
|
make_bone(control_mat * Mat4::<f32>::from(self.second)),
|
||||||
make_bone(upper_torso_mat * Mat4::<f32>::from(self.shoulder_l)),
|
make_bone(upper_torso_mat * Mat4::<f32>::from(self.shoulder_l)),
|
||||||
make_bone(upper_torso_mat * Mat4::<f32>::from(self.shoulder_r)),
|
make_bone(upper_torso_mat * Mat4::<f32>::from(self.shoulder_r)),
|
||||||
make_bone(control_mat * Mat4::<f32>::from(self.hand_l)),
|
make_bone(control_mat * Mat4::<f32>::from(self.hand_l)),
|
||||||
@ -69,8 +76,10 @@ impl Skeleton for BipedLargeSkeleton {
|
|||||||
|
|
||||||
pub struct SkeletonAttr {
|
pub struct SkeletonAttr {
|
||||||
head: (f32, f32),
|
head: (f32, f32),
|
||||||
|
jaw: (f32, f32),
|
||||||
upper_torso: (f32, f32),
|
upper_torso: (f32, f32),
|
||||||
lower_torso: (f32, f32),
|
lower_torso: (f32, f32),
|
||||||
|
tail: (f32, f32),
|
||||||
shoulder: (f32, f32, f32),
|
shoulder: (f32, f32, f32),
|
||||||
hand: (f32, f32, f32),
|
hand: (f32, f32, f32),
|
||||||
leg: (f32, f32, f32),
|
leg: (f32, f32, f32),
|
||||||
@ -92,8 +101,10 @@ impl Default for SkeletonAttr {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
head: (0.0, 0.0),
|
head: (0.0, 0.0),
|
||||||
|
jaw: (0.0, 0.0),
|
||||||
upper_torso: (0.0, 0.0),
|
upper_torso: (0.0, 0.0),
|
||||||
lower_torso: (0.0, 0.0),
|
lower_torso: (0.0, 0.0),
|
||||||
|
tail: (0.0, 0.0),
|
||||||
shoulder: (0.0, 0.0, 0.0),
|
shoulder: (0.0, 0.0, 0.0),
|
||||||
hand: (0.0, 0.0, 0.0),
|
hand: (0.0, 0.0, 0.0),
|
||||||
leg: (0.0, 0.0, 0.0),
|
leg: (0.0, 0.0, 0.0),
|
||||||
@ -113,6 +124,13 @@ impl<'a> From<&'a comp::biped_large::Body> for SkeletonAttr {
|
|||||||
(Troll, _) => (6.0, 10.0),
|
(Troll, _) => (6.0, 10.0),
|
||||||
(Dullahan, _) => (3.0, 6.0),
|
(Dullahan, _) => (3.0, 6.0),
|
||||||
},
|
},
|
||||||
|
jaw: match (body.species, body.body_type) {
|
||||||
|
(Ogre, _) => (0.0, 0.0),
|
||||||
|
(Cyclops, _) => (0.0, 0.0),
|
||||||
|
(Wendigo, _) => (0.0, 0.0),
|
||||||
|
(Troll, _) => (2.0, -4.0),
|
||||||
|
(Dullahan, _) => (0.0, 0.0),
|
||||||
|
},
|
||||||
upper_torso: match (body.species, body.body_type) {
|
upper_torso: match (body.species, body.body_type) {
|
||||||
(Ogre, _) => (0.0, 19.0),
|
(Ogre, _) => (0.0, 19.0),
|
||||||
(Cyclops, _) => (-2.0, 27.0),
|
(Cyclops, _) => (-2.0, 27.0),
|
||||||
@ -127,11 +145,18 @@ impl<'a> From<&'a comp::biped_large::Body> for SkeletonAttr {
|
|||||||
(Troll, _) => (1.0, -10.5),
|
(Troll, _) => (1.0, -10.5),
|
||||||
(Dullahan, _) => (0.0, -6.5),
|
(Dullahan, _) => (0.0, -6.5),
|
||||||
},
|
},
|
||||||
|
tail: match (body.species, body.body_type) {
|
||||||
|
(Ogre, _) => (0.0, 0.0),
|
||||||
|
(Cyclops, _) => (0.0, 0.0),
|
||||||
|
(Wendigo, _) => (0.0, 0.0),
|
||||||
|
(Troll, _) => (0.0, 0.0),
|
||||||
|
(Dullahan, _) => (0.0, 0.0),
|
||||||
|
},
|
||||||
shoulder: match (body.species, body.body_type) {
|
shoulder: match (body.species, body.body_type) {
|
||||||
(Ogre, _) => (6.1, 0.5, 2.5),
|
(Ogre, _) => (6.1, 0.5, 2.5),
|
||||||
(Cyclops, _) => (9.5, 2.5, 2.5),
|
(Cyclops, _) => (9.5, 2.5, 2.5),
|
||||||
(Wendigo, _) => (9.0, 0.5, -0.5),
|
(Wendigo, _) => (9.0, 0.5, -0.5),
|
||||||
(Troll, _) => (11.0, 0.5, -2.5),
|
(Troll, _) => (11.0, 0.5, -1.5),
|
||||||
(Dullahan, _) => (14.0, 0.5, 4.5),
|
(Dullahan, _) => (14.0, 0.5, 4.5),
|
||||||
},
|
},
|
||||||
hand: match (body.species, body.body_type) {
|
hand: match (body.species, body.body_type) {
|
||||||
|
@ -81,6 +81,19 @@ impl Animation for RunAnimation {
|
|||||||
Quaternion::rotation_z(short * 0.15) * Quaternion::rotation_x(0.14);
|
Quaternion::rotation_z(short * 0.15) * Quaternion::rotation_x(0.14);
|
||||||
next.lower_torso.scale = Vec3::one() * 1.02;
|
next.lower_torso.scale = Vec3::one() * 1.02;
|
||||||
|
|
||||||
|
next.jaw.position = Vec3::new(0.0, skeleton_attr.jaw.0, skeleton_attr.jaw.1);
|
||||||
|
next.jaw.orientation = Quaternion::rotation_z(0.0);
|
||||||
|
next.jaw.scale = Vec3::one();
|
||||||
|
|
||||||
|
next.tail.position = Vec3::new(0.0, skeleton_attr.tail.0, skeleton_attr.tail.1 * 0.0);
|
||||||
|
next.tail.orientation = Quaternion::rotation_z(0.0);
|
||||||
|
next.tail.scale = Vec3::one();
|
||||||
|
|
||||||
|
next.second.position = Vec3::new(0.0, 0.0, 0.0);
|
||||||
|
next.second.orientation =
|
||||||
|
Quaternion::rotation_x(PI) * Quaternion::rotation_y(0.0) * Quaternion::rotation_z(0.0);
|
||||||
|
next.second.scale = Vec3::one() * 0.0;
|
||||||
|
|
||||||
next.control.position = Vec3::new(0.0, 0.0, 0.0);
|
next.control.position = Vec3::new(0.0, 0.0, 0.0);
|
||||||
next.control.orientation = Quaternion::rotation_z(0.0);
|
next.control.orientation = Quaternion::rotation_z(0.0);
|
||||||
next.control.scale = Vec3::one();
|
next.control.scale = Vec3::one();
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user