diff --git a/.gitignore b/.gitignore index f380e795cb..ad3297ca3c 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ *.rar *.log settings.ron +server_settings.ron run.sh maps screenshots diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 56148a3102..203c56a70b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -105,6 +105,7 @@ unittests: coverage: stage: build-post + allow_failure: true when: delayed start_in: 5 seconds tags: diff --git a/CHANGELOG.md b/CHANGELOG.md index ada5e7fbfc..628f68ae7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added daily Mac builds - Allow spawning individual pet species, not just generic body kinds. - Configurable fonts +- Configurable keybindings from the Controls menu - Tanslation status tracking - Added gamma setting - Added new orc hairstyles @@ -40,6 +41,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fleshed out "attack" animation into alpha, beta and spin type attacks - Fleshed out range attack into charging and shooting anims for staff/bow - Customized attack animation for hammers and axes +- German translation +- Added a silhouette for players when they are occluded +- Added transparency to the player when zooming in +- Made armor and hotbar slots actually function +- Added dragging and right-click to use functionality to inventory, armor & hotbar slots +- Added capes, lanterns, tabards, rings, helmets & necklaces as equippable armor +- 6 new music tracks +- Added basic world and civilisation simulation +- Added overhauled towns +- Added fields, crops and scarecrows +- Added paths +- Added bridges +- Added procedural house generation +- Added lampposts +- Added NPCs that spawn in towns +- Added simple dungeons +- Added sub-voxel noise effect +- Added waypoints next to dungeons +- Made players spawn in towns +- Added non-uniform block heights ### Changed @@ -54,7 +75,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Overhauled icon art - Asset cleanup to lower client-size - Rewrote the humanoid skeleton to be more ideal for attack animations - +- Arrows can no longer hurt their owners ### Removed diff --git a/Cargo.toml b/Cargo.toml index f4a8194459..98990c57d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,10 +14,10 @@ members = [ # default profile for devs, fast to compile, okay enough to run, no debug information [profile.dev] opt-level = 2 -overflow-checks = true +overflow-checks = true debug-assertions = true panic = "abort" -debug = false +debug = false codegen-units = 8 lto = false incremental = true diff --git a/assets/common/items/armor/back/admin.ron b/assets/common/items/armor/back/admin.ron new file mode 100644 index 0000000000..04b45466e3 --- /dev/null +++ b/assets/common/items/armor/back/admin.ron @@ -0,0 +1,10 @@ +Item( + name: "Admin's Cape", + description: " + With great power comes + great responsibility. ", + kind: Armor( + kind: Back(Admin), + stats: (20), + ), +) diff --git a/assets/common/items/armor/back/short_0.ron b/assets/common/items/armor/back/short_0.ron new file mode 100644 index 0000000000..953cb40e13 --- /dev/null +++ b/assets/common/items/armor/back/short_0.ron @@ -0,0 +1,8 @@ +Item( + name: "Short leather Cape", + description: "", + kind: Armor( + kind: Back(Short0), + stats: (20), + ), +) diff --git a/assets/common/items/armor/head/assa_mask_0.ron b/assets/common/items/armor/head/assa_mask_0.ron new file mode 100644 index 0000000000..34f242c778 --- /dev/null +++ b/assets/common/items/armor/head/assa_mask_0.ron @@ -0,0 +1,8 @@ +Item( + name: "Dark Assassin Mask", + description: "WIP", + kind: Armor( + kind: Head(AssaMask0), + stats: (20), + ), +) diff --git a/assets/common/items/armor/head/leather_0.ron b/assets/common/items/armor/head/leather_0.ron new file mode 100644 index 0000000000..aa0ffd33d6 --- /dev/null +++ b/assets/common/items/armor/head/leather_0.ron @@ -0,0 +1,8 @@ +Item( + name: "Swift Leather Cap", + description: "WIP", + kind: Armor( + kind: Head(Leather0), + stats: (20), + ), +) diff --git a/assets/common/items/armor/neck/neck_0.ron b/assets/common/items/armor/neck/neck_0.ron new file mode 100644 index 0000000000..28125345bc --- /dev/null +++ b/assets/common/items/armor/neck/neck_0.ron @@ -0,0 +1,8 @@ +Item( + name: "Plain Necklace", + description: "", + kind: Armor( + kind: Neck(Neck0), + stats: (20), + ), +) diff --git a/assets/common/items/armor/ring/ring_0.ron b/assets/common/items/armor/ring/ring_0.ron new file mode 100644 index 0000000000..d094e21af8 --- /dev/null +++ b/assets/common/items/armor/ring/ring_0.ron @@ -0,0 +1,8 @@ +Item( + name: "Scratched Ring", + description: "Barely fits your finger.", + kind: Armor( + kind: Ring(Ring0), + stats: (20), + ), +) diff --git a/assets/common/items/armor/starter/rugged_chest.ron b/assets/common/items/armor/starter/rugged_chest.ron new file mode 100644 index 0000000000..63ff3e0835 --- /dev/null +++ b/assets/common/items/armor/starter/rugged_chest.ron @@ -0,0 +1,8 @@ +Item( + name: "Rugged Shirt", + description: "Smells like Adventure.", + kind: Armor( + kind: Chest(Rugged0), + stats: (20), + ), +) diff --git a/assets/common/items/armor/starter/rugged_pants.ron b/assets/common/items/armor/starter/rugged_pants.ron new file mode 100644 index 0000000000..6212774135 --- /dev/null +++ b/assets/common/items/armor/starter/rugged_pants.ron @@ -0,0 +1,8 @@ +Item( + name: "Rugged Commoner's Pants", + description: "They remind you of the old days.", + kind: Armor( + kind: Pants(Rugged0), + stats: (20), + ), +) diff --git a/assets/common/items/armor/starter/sandals_0.ron b/assets/common/items/armor/starter/sandals_0.ron new file mode 100644 index 0000000000..6f4105fb26 --- /dev/null +++ b/assets/common/items/armor/starter/sandals_0.ron @@ -0,0 +1,8 @@ +Item( + name: "Worn out Sandals", + description: "Loyal companions.", + kind: Armor( + kind: Foot(Sandal0), + stats: (20), + ), +) diff --git a/assets/common/items/armor/tabard/admin.ron b/assets/common/items/armor/tabard/admin.ron new file mode 100644 index 0000000000..5bb3fd22fb --- /dev/null +++ b/assets/common/items/armor/tabard/admin.ron @@ -0,0 +1,10 @@ +Item( + name: "Admin's Tabard", + description: " + With great power comes + great responsibility. ", + kind: Armor( + kind: Tabard(Admin), + stats: (20), + ), +) diff --git a/assets/common/items/coconut.ron b/assets/common/items/coconut.ron new file mode 100644 index 0000000000..219af26991 --- /dev/null +++ b/assets/common/items/coconut.ron @@ -0,0 +1,13 @@ +Item( + name: "Coconut", + description: "Reliable source of water and fat. + +Restores 30 health.", + kind: Consumable( + kind: Coconut, + effect: Health(( + amount: 30, + cause: Item, + )), + ), +) diff --git a/assets/common/items/debug/admin_back.ron b/assets/common/items/debug/admin_back.ron new file mode 100644 index 0000000000..fc8f427533 --- /dev/null +++ b/assets/common/items/debug/admin_back.ron @@ -0,0 +1,9 @@ +Item( + name: "Admin's Cape", + description: "With great power comes + great responsibility. ", + kind: Armor( + kind: Back(Admin), + stats: (20), + ), +) diff --git a/assets/common/items/debug/admin_tabard.ron b/assets/common/items/debug/admin_tabard.ron new file mode 100644 index 0000000000..9ad427f3ed --- /dev/null +++ b/assets/common/items/debug/admin_tabard.ron @@ -0,0 +1,9 @@ +Item( + name: "Admin's Tabard", + description: "With great power comes + great responsibility. ", + kind: Armor( + kind: Tabard(Admin), + stats: (20), + ), +) diff --git a/assets/common/items/debug/green_0.ron b/assets/common/items/debug/green_0.ron new file mode 100644 index 0000000000..ea7d5cbcc7 --- /dev/null +++ b/assets/common/items/debug/green_0.ron @@ -0,0 +1,5 @@ +Item( + name: "Lime Zest Lantern", + description: "It has an opening that could fit a ring...", + kind: Lantern(Green0), +) diff --git a/assets/common/items/debug/leather_0.ron b/assets/common/items/debug/leather_0.ron new file mode 100644 index 0000000000..aa0ffd33d6 --- /dev/null +++ b/assets/common/items/debug/leather_0.ron @@ -0,0 +1,8 @@ +Item( + name: "Swift Leather Cap", + description: "WIP", + kind: Armor( + kind: Head(Leather0), + stats: (20), + ), +) diff --git a/assets/common/items/debug/neck_0.ron b/assets/common/items/debug/neck_0.ron new file mode 100644 index 0000000000..4dbd32ae6f --- /dev/null +++ b/assets/common/items/debug/neck_0.ron @@ -0,0 +1,8 @@ +Item( + name: "Plain Necklace", + description: "WIP", + kind: Armor( + kind: Neck(Neck0), + stats: (20), + ), +) diff --git a/assets/common/items/debug/plate_belt.ron b/assets/common/items/debug/plate_belt.ron new file mode 100644 index 0000000000..2c18113d1a --- /dev/null +++ b/assets/common/items/debug/plate_belt.ron @@ -0,0 +1,8 @@ +Item( + name: "Iron Belt", + description: "WIP", + kind: Armor( + kind: Belt(Plate0), + stats: (20), + ), +) diff --git a/assets/common/items/debug/plate_chest.ron b/assets/common/items/debug/plate_chest.ron new file mode 100644 index 0000000000..92bb5fb664 --- /dev/null +++ b/assets/common/items/debug/plate_chest.ron @@ -0,0 +1,8 @@ +Item( + name: "Iron Chestplate", + description: "Arrows to the stomach are soooo last update.", + kind: Armor( + kind: Chest(PlateGreen0), + stats: (20), + ), +) diff --git a/assets/common/items/debug/plate_feet.ron b/assets/common/items/debug/plate_feet.ron new file mode 100644 index 0000000000..12f95dcd4e --- /dev/null +++ b/assets/common/items/debug/plate_feet.ron @@ -0,0 +1,8 @@ +Item( + name: "Iron Feet", + description: "WIP", + kind: Armor( + kind: Foot(Plate0), + stats: (20), + ), +) diff --git a/assets/common/items/debug/plate_hands.ron b/assets/common/items/debug/plate_hands.ron new file mode 100644 index 0000000000..1fdbb6d6da --- /dev/null +++ b/assets/common/items/debug/plate_hands.ron @@ -0,0 +1,8 @@ +Item( + name: "Iron Handguards", + description: "WIP", + kind: Armor( + kind: Hand(Plate0), + stats: (20), + ), +) diff --git a/assets/common/items/debug/plate_legs.ron b/assets/common/items/debug/plate_legs.ron new file mode 100644 index 0000000000..51c3620338 --- /dev/null +++ b/assets/common/items/debug/plate_legs.ron @@ -0,0 +1,8 @@ +Item( + name: "Iron Legguards", + description: "WIP", + kind: Armor( + kind: Pants(PlateGreen0), + stats: (20), + ), +) diff --git a/assets/common/items/debug/plate_shoulder.ron b/assets/common/items/debug/plate_shoulder.ron new file mode 100644 index 0000000000..3323278ecb --- /dev/null +++ b/assets/common/items/debug/plate_shoulder.ron @@ -0,0 +1,8 @@ +Item( + name: "Iron Shoulderguards", + description: "A strong shoulder to lean on.", + kind: Armor( + kind: Shoulder(Plate0), + stats: (20), + ), +) diff --git a/assets/common/items/debug/ring_0.ron b/assets/common/items/debug/ring_0.ron new file mode 100644 index 0000000000..8544765246 --- /dev/null +++ b/assets/common/items/debug/ring_0.ron @@ -0,0 +1,8 @@ +Item( + name: "Scratched Ring", + description: "WIP", + kind: Armor( + kind: Ring(Ring0), + stats: (20), + ), +) diff --git a/assets/common/items/lantern/black_0.ron b/assets/common/items/lantern/black_0.ron new file mode 100644 index 0000000000..cabb197911 --- /dev/null +++ b/assets/common/items/lantern/black_0.ron @@ -0,0 +1,5 @@ +Item( + name: "Black Lantern", + description: "Used by city guards.", + kind: Lantern(Black0), +) diff --git a/assets/common/items/lantern/green_0.ron b/assets/common/items/lantern/green_0.ron new file mode 100644 index 0000000000..ea7d5cbcc7 --- /dev/null +++ b/assets/common/items/lantern/green_0.ron @@ -0,0 +1,5 @@ +Item( + name: "Lime Zest Lantern", + description: "It has an opening that could fit a ring...", + kind: Lantern(Green0), +) diff --git a/assets/voxygen/audio/soundtrack.ron b/assets/voxygen/audio/soundtrack.ron index 0ac60d9bde..a082e43cc0 100644 --- a/assets/voxygen/audio/soundtrack.ron +++ b/assets/voxygen/audio/soundtrack.ron @@ -41,6 +41,48 @@ length: 173.0, timing: Some(Night), artist: "Aeronic", - ) + ), + ( + title: "Rest Assured", + path: "voxygen.audio.soundtrack.rest_assured", + length: 185.0, + timing: Some(Day), + artist: "badbbad", + ), + ( + title: "Just The Beginning", + path: "voxygen.audio.soundtrack.just_the_beginning", + length: 188.0, + timing: Some(Day), + artist: "badbbad", + ), + ( + title: "Campfire Stories", + path: "voxygen.audio.soundtrack.campfire_stories", + length: 100.0, + timing: Some(Night), + artist: "badbbad", + ), + ( + title: "Limits", + path: "voxygen.audio.soundtrack.limits", + length: 203.0, + timing: Some(Night), + artist: "badbbad", + ), + ( + title: "Down The Rabbit Hole", + path: "voxygen.audio.soundtrack.down_the_rabbit_hole", + length: 244.0, + timing: Some(Night), + artist: "badbbad", + ), + ( + title: "Between The Fairies", + path: "voxygen.audio.soundtrack.between_the_fairies", + length: 175.0, + timing: Some(Night), + artist: "badbbad", + ), ] ) \ No newline at end of file diff --git a/assets/voxygen/audio/soundtrack/between_the_fairies.ogg b/assets/voxygen/audio/soundtrack/between_the_fairies.ogg new file mode 100644 index 0000000000..014c9e9b13 --- /dev/null +++ b/assets/voxygen/audio/soundtrack/between_the_fairies.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b1ffa266d056e23b980ca5a18970a77376f001c407d0290c95bbfe5e1461dbc3 +size 3935072 diff --git a/assets/voxygen/audio/soundtrack/campfire_stories.ogg b/assets/voxygen/audio/soundtrack/campfire_stories.ogg new file mode 100644 index 0000000000..bc90d1166c --- /dev/null +++ b/assets/voxygen/audio/soundtrack/campfire_stories.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0f98af47d74e6e38e9fd00d19554f3666bf5bee22e71b039ac28d8a3b3e79b5b +size 2201901 diff --git a/assets/voxygen/audio/soundtrack/down_the_rabbit_hole.ogg b/assets/voxygen/audio/soundtrack/down_the_rabbit_hole.ogg new file mode 100644 index 0000000000..b99bc8f4ae --- /dev/null +++ b/assets/voxygen/audio/soundtrack/down_the_rabbit_hole.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6cc60e9717a9e07a0b40788502f42036b8e01f9f4aae66ad1ab5cf78f9ad976b +size 4685453 diff --git a/assets/voxygen/audio/soundtrack/just_the_beginning.ogg b/assets/voxygen/audio/soundtrack/just_the_beginning.ogg new file mode 100644 index 0000000000..cfe059c28c --- /dev/null +++ b/assets/voxygen/audio/soundtrack/just_the_beginning.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c59c8a95fd07283a028c09010ad05d44d3a620a12f202b52c59f22a66411f9e8 +size 4772973 diff --git a/assets/voxygen/audio/soundtrack/limits.ogg b/assets/voxygen/audio/soundtrack/limits.ogg new file mode 100644 index 0000000000..3d00d86ffd --- /dev/null +++ b/assets/voxygen/audio/soundtrack/limits.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:943e4199b3a23623150c8e58c7de432ff0c3b2f4634efef807a8f1e6fddfc865 +size 3641997 diff --git a/assets/voxygen/audio/soundtrack/rest_assured.ogg b/assets/voxygen/audio/soundtrack/rest_assured.ogg new file mode 100644 index 0000000000..b256b58a50 --- /dev/null +++ b/assets/voxygen/audio/soundtrack/rest_assured.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:853a0eeb0d6195a92ee9a33115483ab2d221af9733e91c9588bc80b42c0805a3 +size 2741191 diff --git a/assets/voxygen/background/bg_10.png b/assets/voxygen/background/bg_10.png new file mode 100644 index 0000000000..8d27d84d83 --- /dev/null +++ b/assets/voxygen/background/bg_10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d34cd8aa7655efae1ab92b86dfa6916560eeed22d60e7ce45de6ff4d77e4340 +size 1175394 diff --git a/assets/voxygen/background/bg_11.png b/assets/voxygen/background/bg_11.png new file mode 100644 index 0000000000..3aa49f8c2d --- /dev/null +++ b/assets/voxygen/background/bg_11.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5cc0e614d52708cba30d71f658f0c290395371d4333f2f6a866c207a8f471878 +size 1301002 diff --git a/assets/voxygen/element/buttons/armor_slot.png b/assets/voxygen/element/buttons/armor_slot.png index 2eb79d4449..e3b9af76dd 100644 --- a/assets/voxygen/element/buttons/armor_slot.png +++ b/assets/voxygen/element/buttons/armor_slot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a2fb5c403dced0781cff9dc7ea824a290178a114db9199e50cd762f8540f80c9 -size 2002 +oid sha256:d169cbf619c0fbd2acfc006845e0e119d22688ac4b640e7347aa833250dfe2f7 +size 1932 diff --git a/assets/voxygen/element/buttons/armor_slot_empty.png b/assets/voxygen/element/buttons/armor_slot_empty.png new file mode 100644 index 0000000000..2eb79d4449 --- /dev/null +++ b/assets/voxygen/element/buttons/armor_slot_empty.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a2fb5c403dced0781cff9dc7ea824a290178a114db9199e50cd762f8540f80c9 +size 2002 diff --git a/assets/voxygen/element/buttons/armor_slot_selected.png b/assets/voxygen/element/buttons/armor_slot_selected.png new file mode 100644 index 0000000000..ebe4865fe6 --- /dev/null +++ b/assets/voxygen/element/buttons/armor_slot_selected.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e37f9d7b52882f983e476538a26c38d8795082c114348bc56f995e96af5960d +size 2151 diff --git a/assets/voxygen/element/icons/head_leather-0.png b/assets/voxygen/element/icons/head_leather-0.png new file mode 100644 index 0000000000..f501315484 --- /dev/null +++ b/assets/voxygen/element/icons/head_leather-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7f0bf90c6fc76fb9a882324d59255881702648bc995a5800679d19bd8543778 +size 346 diff --git a/assets/voxygen/element/icons/item_coconut.png b/assets/voxygen/element/icons/item_coconut.png new file mode 100644 index 0000000000..69f5f9046e --- /dev/null +++ b/assets/voxygen/element/icons/item_coconut.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0307232e80cbbc31cf5ec9d03483cbd0851334caffe09d3a4d7bcb71a77e5097 +size 344 diff --git a/assets/voxygen/element/icons/lantern.png b/assets/voxygen/element/icons/lantern.png new file mode 100644 index 0000000000..765a2f59ff --- /dev/null +++ b/assets/voxygen/element/icons/lantern.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ee62daa0fa231136722b14bca820a04ba61732230937c3771f37c09a3da9f8f +size 776 diff --git a/assets/voxygen/element/icons/lantern_black-0.png b/assets/voxygen/element/icons/lantern_black-0.png new file mode 100644 index 0000000000..a7bcf612ab --- /dev/null +++ b/assets/voxygen/element/icons/lantern_black-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4e538baa8be2c32aa45d0d62063823e94f25e21526089ca09ccbbd9a2878d0f8 +size 8703 diff --git a/assets/voxygen/element/icons/lantern_green-0.png b/assets/voxygen/element/icons/lantern_green-0.png new file mode 100644 index 0000000000..fe92d008bb --- /dev/null +++ b/assets/voxygen/element/icons/lantern_green-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f5eb64fa4e321b9e9d357d3fccd59ba269821684ace9b182f60c3c438e2288d2 +size 1207 diff --git a/assets/voxygen/element/icons/lantern_grey-0.png b/assets/voxygen/element/icons/lantern_grey-0.png new file mode 100644 index 0000000000..c60f08633a --- /dev/null +++ b/assets/voxygen/element/icons/lantern_grey-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e1a87140fb1df927fdea819a72e67bd974847da5165eb24ccd3696bcfc461480 +size 1135 diff --git a/assets/voxygen/element/icons/neck-0.png b/assets/voxygen/element/icons/neck-0.png new file mode 100644 index 0000000000..a7a1f6ca5e --- /dev/null +++ b/assets/voxygen/element/icons/neck-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:344387aec10b94e881c13b8d1ac5bb39aa085830c9b72a61f8b517c4e00684e5 +size 560 diff --git a/assets/voxygen/element/icons/ring-0.png b/assets/voxygen/element/icons/ring-0.png new file mode 100644 index 0000000000..82a12b7e49 --- /dev/null +++ b/assets/voxygen/element/icons/ring-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8231828a67bc4d6aed4254bb3f8ae12ae3a40479bfe3c260d412667ef94f98ae +size 652 diff --git a/assets/voxygen/element/icons/tabard_admin.png b/assets/voxygen/element/icons/tabard_admin.png new file mode 100644 index 0000000000..2061bd402e --- /dev/null +++ b/assets/voxygen/element/icons/tabard_admin.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7e4c27c15fdb78ecc91d3f7635bbb5f6db6ad8d7d550ded9d454f5e2955ec2e7 +size 662 diff --git a/assets/voxygen/element/misc_bg/level_down.png b/assets/voxygen/element/misc_bg/level_down.png new file mode 100644 index 0000000000..bd773e7862 --- /dev/null +++ b/assets/voxygen/element/misc_bg/level_down.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3faab75ba08e40084ca98966999883db632bf2e7ade80802ef5aadbf9c47d51d +size 256 diff --git a/assets/voxygen/element/misc_bg/level_down.vox b/assets/voxygen/element/misc_bg/level_down.vox deleted file mode 100644 index e04998a5e6..0000000000 --- a/assets/voxygen/element/misc_bg/level_down.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:073c4a5b0b5b507a07839da0f16f8988ca6dc1c46c78ee371291ee1bec012511 -size 2500 diff --git a/assets/voxygen/element/misc_bg/level_up.png b/assets/voxygen/element/misc_bg/level_up.png new file mode 100644 index 0000000000..90fe596915 --- /dev/null +++ b/assets/voxygen/element/misc_bg/level_up.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1939158216f4b31a6769615efdd958c6eb7f98c42678e2423cde8c160d349188 +size 235 diff --git a/assets/voxygen/element/misc_bg/level_up.vox b/assets/voxygen/element/misc_bg/level_up.vox deleted file mode 100644 index 437b88ada9..0000000000 --- a/assets/voxygen/element/misc_bg/level_up.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0b6dce293f76c863983a545f4d4ecdfbc855aee7ca2e493212a4954d8be7a741 -size 2500 diff --git a/assets/voxygen/element/skillbar/bar_content.png b/assets/voxygen/element/skillbar/bar_content.png new file mode 100644 index 0000000000..740e497fd2 --- /dev/null +++ b/assets/voxygen/element/skillbar/bar_content.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:554ab7e7ab2b4711dde9d63f1b176a589e978036c96df44af8e34213ca308d3c +size 90 diff --git a/assets/voxygen/element/skillbar/bar_content.vox b/assets/voxygen/element/skillbar/bar_content.vox deleted file mode 100644 index 4ccf6ac7e4..0000000000 --- a/assets/voxygen/element/skillbar/bar_content.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cc69560a401ac2b659c9d19b3942b61018edec034ea0e955d3d99afd02757adf -size 44228 diff --git a/assets/voxygen/element/skillbar/energybar_bg.png b/assets/voxygen/element/skillbar/energybar_bg.png new file mode 100644 index 0000000000..ce25e47252 --- /dev/null +++ b/assets/voxygen/element/skillbar/energybar_bg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e74fb3a0d89ebd0e183881aa29e1315fe9aa0417099d5d0f24a3d7244ed82f66 +size 193 diff --git a/assets/voxygen/element/skillbar/energybar_bg.vox b/assets/voxygen/element/skillbar/energybar_bg.vox deleted file mode 100644 index d69b3c5f0e..0000000000 --- a/assets/voxygen/element/skillbar/energybar_bg.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c4490310e4be1b454e39619384dac40ef9c0abe3715e951deacd6f06955ddba0 -size 54020 diff --git a/assets/voxygen/element/skillbar/healthbar_bg.png b/assets/voxygen/element/skillbar/healthbar_bg.png new file mode 100644 index 0000000000..85e206287c --- /dev/null +++ b/assets/voxygen/element/skillbar/healthbar_bg.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99db830a62bcc2a77867df849e5c5eb3689514782ec95b1b0f62c0b096d21bcd +size 190 diff --git a/assets/voxygen/element/skillbar/healthbar_bg.vox b/assets/voxygen/element/skillbar/healthbar_bg.vox deleted file mode 100644 index 985fe3ff05..0000000000 --- a/assets/voxygen/element/skillbar/healthbar_bg.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:550437f73578788b1520e66ad90e0f8459cd8e0f441fbebf183adfc9d10b0b23 -size 54004 diff --git a/assets/voxygen/element/skillbar/skillbar_slot.png b/assets/voxygen/element/skillbar/skillbar_slot.png new file mode 100644 index 0000000000..b23b25967f --- /dev/null +++ b/assets/voxygen/element/skillbar/skillbar_slot.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e684e902c11fe48c4875997799834fa597cc14a7eab86cf48d63c56de1191f11 +size 610 diff --git a/assets/voxygen/element/skillbar/skillbar_slot.vox b/assets/voxygen/element/skillbar/skillbar_slot.vox deleted file mode 100644 index 4578c91645..0000000000 --- a/assets/voxygen/element/skillbar/skillbar_slot.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:850e4bda7520d9055e61a39cef6a8989f89e532a43496bd12bc90a3fd577a94c -size 44796 diff --git a/assets/voxygen/element/skillbar/skillbar_slot_active.png b/assets/voxygen/element/skillbar/skillbar_slot_active.png new file mode 100644 index 0000000000..97dbb17702 --- /dev/null +++ b/assets/voxygen/element/skillbar/skillbar_slot_active.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11caed86e7b99aa9a552bfe2f3bce3ea84c175e39069a909b5bc7e2ac2cbfe7e +size 3533 diff --git a/assets/voxygen/element/skillbar/skillbar_slot_active.vox b/assets/voxygen/element/skillbar/skillbar_slot_active.vox deleted file mode 100644 index 3ffc53119a..0000000000 --- a/assets/voxygen/element/skillbar/skillbar_slot_active.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b3349a0ac930db681061036315a4136ba2a0009014ce659e287e1ea8c882915e -size 56172 diff --git a/assets/voxygen/element/skillbar/skillbar_slot_bg.vox b/assets/voxygen/element/skillbar/skillbar_slot_bg.vox deleted file mode 100644 index 9cfb3425a4..0000000000 --- a/assets/voxygen/element/skillbar/skillbar_slot_bg.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7e46b2e376ee048ed6dfda089e104b983e456c7ed3743a2aab702d1d27813246 -size 45484 diff --git a/assets/voxygen/element/skillbar/skillbar_slot_big.png b/assets/voxygen/element/skillbar/skillbar_slot_big.png new file mode 100644 index 0000000000..8638974e52 --- /dev/null +++ b/assets/voxygen/element/skillbar/skillbar_slot_big.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82aa46ad7cd827151206e79180200d40b72755b9ec3ba2016f081a9c38f3100c +size 243 diff --git a/assets/voxygen/element/skillbar/skillbar_slot_big.vox b/assets/voxygen/element/skillbar/skillbar_slot_big.vox deleted file mode 100644 index 02bdf095c0..0000000000 --- a/assets/voxygen/element/skillbar/skillbar_slot_big.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1fc7a52c394969779da4f2bb548bbfabd84b0e979ecaf94ebf239efe8d90f3fe -size 46732 diff --git a/assets/voxygen/element/skillbar/skillbar_slot_big_active.vox b/assets/voxygen/element/skillbar/skillbar_slot_big_active.vox deleted file mode 100644 index aa30d8875a..0000000000 --- a/assets/voxygen/element/skillbar/skillbar_slot_big_active.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ad9d070daefe97358d640d23f92651353e8185300c995a6a196b253e200097b4 -size 58108 diff --git a/assets/voxygen/element/skillbar/skillbar_slot_big_bg.vox b/assets/voxygen/element/skillbar/skillbar_slot_big_bg.vox deleted file mode 100644 index a734a2a34c..0000000000 --- a/assets/voxygen/element/skillbar/skillbar_slot_big_bg.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f2f338b9c04bfcca21e2eff745c2d4b7f209c1ade2797806a8b3faf02d8f6d95 -size 49340 diff --git a/assets/voxygen/element/skillbar/skillbar_slot_l.png b/assets/voxygen/element/skillbar/skillbar_slot_l.png new file mode 100644 index 0000000000..615e91ffd2 --- /dev/null +++ b/assets/voxygen/element/skillbar/skillbar_slot_l.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0308eac5f6ca52d9e1425ae5c7f17c0451f628d2b8443d699eece95e3480adfd +size 607 diff --git a/assets/voxygen/element/skillbar/skillbar_slot_l.vox b/assets/voxygen/element/skillbar/skillbar_slot_l.vox deleted file mode 100644 index 97f44dc171..0000000000 --- a/assets/voxygen/element/skillbar/skillbar_slot_l.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:592fc1ed4126ea63b1c851f294eaee0b587113320cec61acc0b29f5029f94f42 -size 44788 diff --git a/assets/voxygen/element/skillbar/skillbar_slot_l_active.png b/assets/voxygen/element/skillbar/skillbar_slot_l_active.png new file mode 100644 index 0000000000..3e629d7c6c --- /dev/null +++ b/assets/voxygen/element/skillbar/skillbar_slot_l_active.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:48ed7345f1920bc9a6105771659ebef797a07d690369cdb2bb299d92bb583e65 +size 3533 diff --git a/assets/voxygen/element/skillbar/skillbar_slot_l_active.vox b/assets/voxygen/element/skillbar/skillbar_slot_l_active.vox deleted file mode 100644 index af526240ec..0000000000 --- a/assets/voxygen/element/skillbar/skillbar_slot_l_active.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2330b4bb03aa2eff449dcd55640198bdc805020080c0f81055eae5a866d49774 -size 56164 diff --git a/assets/voxygen/element/skillbar/skillbar_slot_r.png b/assets/voxygen/element/skillbar/skillbar_slot_r.png new file mode 100644 index 0000000000..16591871c0 --- /dev/null +++ b/assets/voxygen/element/skillbar/skillbar_slot_r.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56dbe92fe64b64b59b554ada0d1eb0dbb537593e3a1d87dfccd721c6519e521d +size 608 diff --git a/assets/voxygen/element/skillbar/skillbar_slot_r.vox b/assets/voxygen/element/skillbar/skillbar_slot_r.vox deleted file mode 100644 index 54f3148e0c..0000000000 --- a/assets/voxygen/element/skillbar/skillbar_slot_r.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:16d8f4c48dc9046358e9c8e66f5f892033ee9fc9aa737b8b6bd9e14c140379eb -size 44788 diff --git a/assets/voxygen/element/skillbar/skillbar_slot_r_active.png b/assets/voxygen/element/skillbar/skillbar_slot_r_active.png new file mode 100644 index 0000000000..6ba1610d29 --- /dev/null +++ b/assets/voxygen/element/skillbar/skillbar_slot_r_active.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3cfe0bef782ebdff6665e866e1ec659f139d36ded000a3c9fac298037ae86787 +size 3533 diff --git a/assets/voxygen/element/skillbar/skillbar_slot_r_active.vox b/assets/voxygen/element/skillbar/skillbar_slot_r_active.vox deleted file mode 100644 index 9c4cadf52b..0000000000 --- a/assets/voxygen/element/skillbar/skillbar_slot_r_active.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3e87dca4630698130ff836d412a4e18d43d398169dbb446c375433b8cf102138 -size 56164 diff --git a/assets/voxygen/element/skillbar/stamina_wheel-0.vox b/assets/voxygen/element/skillbar/stamina_wheel-0.vox deleted file mode 100644 index 3a88fc752a..0000000000 --- a/assets/voxygen/element/skillbar/stamina_wheel-0.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:428131eff788ab1ad5400e0d82a6d3306671a9f6eed9ac43452e46f967614e4e -size 4236 diff --git a/assets/voxygen/element/skillbar/stamina_wheel-1.vox b/assets/voxygen/element/skillbar/stamina_wheel-1.vox deleted file mode 100644 index 0867fb8665..0000000000 --- a/assets/voxygen/element/skillbar/stamina_wheel-1.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c1d4b7afaf0a325c357b2865bffd7abc13bbe3874e6fd940ce2aa4ef6ac93524 -size 4240 diff --git a/assets/voxygen/element/skillbar/stamina_wheel-2.vox b/assets/voxygen/element/skillbar/stamina_wheel-2.vox deleted file mode 100644 index af761fc249..0000000000 --- a/assets/voxygen/element/skillbar/stamina_wheel-2.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a2cb03507bef8d50ee03bceec8c38ade39ace39b926cf1cfe95c3996ca81ec93 -size 4236 diff --git a/assets/voxygen/element/skillbar/stamina_wheel-3.vox b/assets/voxygen/element/skillbar/stamina_wheel-3.vox deleted file mode 100644 index 908869b087..0000000000 --- a/assets/voxygen/element/skillbar/stamina_wheel-3.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7937cf8728cd5aa5c1026c70d2c920b8100a1fb241582c94b5f224a88a4b8c65 -size 4240 diff --git a/assets/voxygen/element/skillbar/stamina_wheel-4.vox b/assets/voxygen/element/skillbar/stamina_wheel-4.vox deleted file mode 100644 index 9190122bb5..0000000000 --- a/assets/voxygen/element/skillbar/stamina_wheel-4.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ca98b3e5880cc379176b1f6d1a8aafad3918cccabe374ce95c370b9abf19e995 -size 4236 diff --git a/assets/voxygen/element/skillbar/stamina_wheel-5.vox b/assets/voxygen/element/skillbar/stamina_wheel-5.vox deleted file mode 100644 index 58eba1edad..0000000000 --- a/assets/voxygen/element/skillbar/stamina_wheel-5.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2ff7ec15effe1d73cf6cf1cbde423192d5d2e80177ebea28ec373a3c9039a222 -size 4272 diff --git a/assets/voxygen/element/skillbar/stamina_wheel-6.vox b/assets/voxygen/element/skillbar/stamina_wheel-6.vox deleted file mode 100644 index 7ae3439101..0000000000 --- a/assets/voxygen/element/skillbar/stamina_wheel-6.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6166f65840900908a1817adf83a36f6f892e8a4462a60eb891c52ef6faacf103 -size 4288 diff --git a/assets/voxygen/element/skillbar/stamina_wheel-7.vox b/assets/voxygen/element/skillbar/stamina_wheel-7.vox deleted file mode 100644 index 7dad13a842..0000000000 --- a/assets/voxygen/element/skillbar/stamina_wheel-7.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6091d44c6e1ed205d1ecf15f84aec2df8793bb49e3a307a56f1320ec2798c2a3 -size 4200 diff --git a/assets/voxygen/element/skillbar/stamina_wheel-empty.vox b/assets/voxygen/element/skillbar/stamina_wheel-empty.vox deleted file mode 100644 index 0740042dd6..0000000000 --- a/assets/voxygen/element/skillbar/stamina_wheel-empty.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:32ad9d58f8c34a3fdfdeedf18c0c6efc93b5baf7348fce320d462671bda550b4 -size 58684 diff --git a/assets/voxygen/element/skillbar/xp_bar_content.png b/assets/voxygen/element/skillbar/xp_bar_content.png new file mode 100644 index 0000000000..87d1f20c4c --- /dev/null +++ b/assets/voxygen/element/skillbar/xp_bar_content.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c547c12c27a835c845a1b6e20c6e66b632fcba64e2cfad5a0df635263191dbe +size 111 diff --git a/assets/voxygen/element/skillbar/xp_bar_content.vox b/assets/voxygen/element/skillbar/xp_bar_content.vox deleted file mode 100644 index b43e07f3d3..0000000000 --- a/assets/voxygen/element/skillbar/xp_bar_content.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f6826c8887d99eec69c3858df354b86ccf183eba945ccfd56ff1d16b4dec3b35 -size 44228 diff --git a/assets/voxygen/element/skillbar/xp_bar_left.png b/assets/voxygen/element/skillbar/xp_bar_left.png new file mode 100644 index 0000000000..5f045d8ce8 --- /dev/null +++ b/assets/voxygen/element/skillbar/xp_bar_left.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5bf8eaeecd8a80e343951be5d9e2ef215f897bc2b25bac1681d14203f29e6a41 +size 170 diff --git a/assets/voxygen/element/skillbar/xp_bar_left.vox b/assets/voxygen/element/skillbar/xp_bar_left.vox deleted file mode 100644 index 191de615c6..0000000000 --- a/assets/voxygen/element/skillbar/xp_bar_left.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:10ebad96b2076b0acb72e5894bb0993c9b73ca4613aeb1db4830a0db414b8891 -size 49011 diff --git a/assets/voxygen/element/skillbar/xp_bar_mid.png b/assets/voxygen/element/skillbar/xp_bar_mid.png new file mode 100644 index 0000000000..36e5211848 --- /dev/null +++ b/assets/voxygen/element/skillbar/xp_bar_mid.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:893cb632175e00fe32f83c1de7232cbfdad5a31cf251de5948895781ab870922 +size 133 diff --git a/assets/voxygen/element/skillbar/xp_bar_mid.vox b/assets/voxygen/element/skillbar/xp_bar_mid.vox deleted file mode 100644 index 6b2e358e56..0000000000 --- a/assets/voxygen/element/skillbar/xp_bar_mid.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:77ca8cecfd2f246f51fd86b583a8ac823b174045a218a46d040ee79ead5fe138 -size 48043 diff --git a/assets/voxygen/element/skillbar/xp_bar_right.png b/assets/voxygen/element/skillbar/xp_bar_right.png new file mode 100644 index 0000000000..6b9edbb223 --- /dev/null +++ b/assets/voxygen/element/skillbar/xp_bar_right.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f2cb46b07a8f7fa0f6ecf029bda641abe17c107a3a0558013ac31d3fec3a93bf +size 159 diff --git a/assets/voxygen/element/skillbar/xp_bar_right.vox b/assets/voxygen/element/skillbar/xp_bar_right.vox deleted file mode 100644 index 4785c3fba3..0000000000 --- a/assets/voxygen/element/skillbar/xp_bar_right.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2b820db766297c456d7a9982a5908a4e6d92e1e79eff4438bf2f0149992c35b2 -size 49011 diff --git a/assets/voxygen/font/haxrcorp_4089_cyrillic_altgr_extended.ttf b/assets/voxygen/font/haxrcorp_4089_cyrillic_altgr_extended.ttf index f1287a32c0..5f7706d6b0 100644 --- a/assets/voxygen/font/haxrcorp_4089_cyrillic_altgr_extended.ttf +++ b/assets/voxygen/font/haxrcorp_4089_cyrillic_altgr_extended.ttf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d2fcb65f9c3956f91ddebc350729221704c75b3559d120772d4c9bd1898d720 -size 26232 +oid sha256:a0cb7b143acfd0fe16def3d0755cb54ca31dbe0c88cc832bb987ea41d5b9fb9d +size 26316 diff --git a/assets/voxygen/i18n/de_DE.ron b/assets/voxygen/i18n/de_DE.ron new file mode 100644 index 0000000000..5c9e7b21d8 --- /dev/null +++ b/assets/voxygen/i18n/de_DE.ron @@ -0,0 +1,379 @@ +/// Translation document instructions +/// +/// In order to keep localization documents readible please follow the following +/// rules: +/// - separate the string map sections using a commentary describing the purpose +/// of the next section +/// - prepend multi-line strings with a commentary +/// - append one blank lines after a multi-line strings and two after sections +/// +/// To add a new language in Veloren, juste write an additional `.ron` file in +/// `assets/voxygen/i18n` and that's it! + +/// Lokalisation für Deutsch/Deutschland +VoxygenLocalization( + metadata: ( + language_name: "Deutsch", + language_identifier: "de_DE", + ), + convert_utf8_to_ascii: false, + fonts: { + "opensans": Font ( + asset_key: "voxygen.font.OpenSans-Regular", + scale_ratio: 1.0, + ), + "metamorph": Font ( + asset_key: "voxygen.font.Metamorphous-Regular", + scale_ratio: 1.0, + ), + "alkhemi": Font ( + asset_key: "voxygen.font.Alkhemikal", + scale_ratio: 1.0, + ), + "wizard": Font ( + asset_key: "voxygen.font.wizard", + scale_ratio: 1.0, + ), + "cyri": Font ( + asset_key: "voxygen.font.haxrcorp_4089_cyrillic_altgr_extended", + scale_ratio: 1.0, + ), + }, + string_map: { + /// Start Common section + /// Texts used in multiple locations with the same formatting + "common.username": "Benutzername", + "common.singleplayer": "Einzelspieler", + "common.multiplayer": "Mehrspieler", + "common.servers": "Server", + "common.quit": "Beenden", + "common.settings": "Einstellungen", + "common.languages": "Sprache", + "common.interface": "Interface", + "common.gameplay": "Spiel", + "common.controls": "Tastenbelegung", + "common.video": "Grafik", + "common.sound": "Audio", + "common.resume": "Weiter", + "common.characters": "Charaktere", + "common.close": "Schließen", + "common.yes": "Ja", + "common.no": "Nein", + "common.back": "Zurück", + "common.create": "Erstellen", + "common.okay": "Okay", + "common.accept": "Annehmen", + "common.disclaimer": "Disclaimer", + "common.cancel": "Abbrechen", + "common.none": "Kein", + "common.error": "Fehler", + "common.fatal_error": "Fataler Fehler", + /// End Common section + + // Message when connection to the server is lost + "common.connection_lost": r#"Verbindung unterbrochen."#, + + + "common.races.orc": "Orc", + "common.races.human": "Mensch", + "common.races.dwarf": "Zwerg", + "common.races.elf": "Elf", + "common.races.undead": "Untoter", + "common.races.danari": "Danari", + + "common.weapons.axe": "Axt", + "common.weapons.sword": "Schwert", + "common.weapons.staff": "Stab", + "common.weapons.bow": "Bogen", + "common.weapons.hammer": "Hammer", + /// End Common section + + + /// Start Main screen section + "main.connecting": "Verbinde ", + "main.creating_world": "Erschaffe Welt ", + + + /// Start Main screen section + /// Welcome notice that appears the first time Veloren is started + "main.notice": r#"Willkommen zur Veloren Alpha! + +Bevor es losgeht noch einige Infos: + +Dies ist eine frühe Alpha. Ihr werdet auf Bugs, unfertiges Gameplay und Mechaniken, sowie fehlende Features sto�en. + +Für konstruktives Feedback und Bug-Reports könnt ihr uns via Reddit, Gitlab oder unseren Discord Server kontaktieren. + +Veloren hat die GPL 3 Open-Source Lizenz. Das heißt ihr könnt es kostenlos spielen, aber auch modifizieren (solange die Mods auch die selbe Lizenz tragen) und das Spiel an andere weiterschicken. + +Veloren ist ein Non-Profit Community Projekt und jeder Mitarbeiter entwickelt es als Hobby in seiner Freizeit. + +Wenn euch die Idee gefällt, dann schließt euch doch einfach unserem Dev- oder Art-Team an! + +Voxel RPGs haben sich (Genau wie First Person Shooter, die lange nur Doom-Klone genannt wurden) zu einem eigenen Genre entwickelt. +Dieses Spiel ist alles andere, als ein Klon bereits vorhandener Spiele und wird sich in seine ganz eigene Richtung entwickeln. + +Danke, dass ihr euch die Zeit genommen habt diese Zeilen zu lesen und wir hoffen, dass euch Veloren gefällt! + +~ Die Entwickler"#, + + /// Login process description + "main.login_process": r#"Information zum Login: + +Zum Spielen wird ein Account benötigt. + +Diesen könnt ihr euch hier erstellen: + +https://account.veloren.net. + +Aktuell wird nur das Aussehen +eurer erstellen Charaktere gespeichert."#, +"main.login.server_not_found": "Server nicht gefunden.", + "main.login.authentication_error": "Authentifizierung fehlgeschlagen", + "main.login.server_full": "Server ist voll", + "main.login.untrusted_auth_server": "Dem Auth. Server wird nicht vertraut", + "main.login.outdated_client_or_server": "Inkompatible Version", + "main.login.timeout": "Zeitüberschreitung", + "main.login.server_shut_down": "Server heruntergefahren", + "main.login.already_logged_in": "Ihr seid bereits eingelogged", + "main.login.network_error": "Netzwerkfehler", + "main.login.failed_sending_request": "Authentifizierung fehlgeschlagen", + "main.login.client_crashed": "Client abgestürzt", + + /// End Main screen section + + + /// Start HUD Section + "hud.do_not_show_on_startup": "Nicht wieder anzeigen.", + "hud.show_tips": "Tips zeigen.", + "hud.quests": "Quests", + "hud.you_died": "Ihr seid gestorben.", + + "hud.press_key_to_show_keybindings_fmt": "Drückt {key} um die Tastenbelegung zu zeigen", + "hud.press_key_to_show_debug_info_fmt": "Drückt {key} um die Debug-Info zu zeigen", + "hud.press_key_to_toggle_keybindings_fmt": "Drückt {key} um die Tastenbelegung zu zeigen", + "hud.press_key_to_toggle_debug_info_fmt": "Drückt {key} um die Debug-Info zu zeigen", + + /// Respawn message + "hud.press_key_to_respawn": r#"Drückt {key} um am letzten Lagerfeuer wiederbelebt zu werden."#, + + /// Welcome message + "hud.welcome": r#"Willkommen zur Veloren Alpha. + + +Einige Tipps bevor ihr startet: + + +Drückt F1, um die Tastenbelegungen zu sehen. + +Um Chat-Kommandos zu sehen gebt /help in den Chat ein. + + +Überall in der Welt erscheinen Kisten und andere Gegenstände. + +Sammelt diese mit Rechts-Klick auf. + +Um diese zu nutzen öffnet euer Inventar mit 'B'. + +Doppelklickt den Gegenstand in eurer Tasche, um dieses zu nutzen. + +Um Items wegzuwerfen klickt sie einmal im Inventar an + +und klickt dann außerhalb der Tasche. + + +Die Nächte in Veloren können sehr dunkel werden. + +Um eure Laterne anzuzünden gebt /lantern in den Chat ein. + + +Ihr wollt endlich spielen und dafür euren Cursor befreien, + +um dieses Fenster zu schließen? Drückt 'TAB'! + + +Viel Spaß in der Welt von Veloren, Abenteurer!"#, + + // Inventory + "hud.bag.inventory": "{playername}s Inventar", + "hud.bag.stats_title": "{playername}s Werte", + "hud.bag.exp": "Erf", + "hud.bag.armor": "Rüstung", + "hud.bag.stats": "Werte", + "hud.bag.head": "Kopf", + "hud.bag.neck": "Hals", + "hud.bag.tabard": "Wappenrock", + "hud.bag.shoulders": "Schultern", + "hud.bag.chest": "Brust", + "hud.bag.hands": "Hände", + "hud.bag.lantern": "Laterne", + "hud.bag.belt": "Gürtel", + "hud.bag.ring": "Ring", + "hud.bag.back": "Rücken", + "hud.bag.legs": "Beine", + "hud.bag.feet": "Füße", + "hud.bag.mainhand": "Haupthand", + "hud.bag.offhand": "Nebenhand", + + // Map and Questlog + "hud.map.map_title": "Karte", + "hud.map.qlog_title": "Aufgaben", + + // Settings + "hud.settings.general": "Allgemein", + "hud.settings.none": "Keine", + "hud.settings.press_behavior.toggle": "Umschalten", + "hud.settings.press_behavior.hold": "Halten", + "hud.settings.help_window": "Hilfe", + "hud.settings.debug_info": "Debug Info", + "hud.settings.tips_on_startup": "Tips", + "hud.settings.ui_scale": "UI-Skalierung", + "hud.settings.relative_scaling": "Relative Skalierung", + "hud.settings.custom_scaling": "Freie Skalierung", + "hud.settings.crosshair": "Fadenkreuz", + "hud.settings.transparency": "Transparenz", + "hud.settings.hotbar": "Hotbar", + "hud.settings.toggle_shortcuts": "Tastenbelegung", + "hud.settings.toggle_bar_experience": "Erfahrungsleiste", + "hud.settings.scrolling_combat_text": "Aufsteigende Kampfwerte", + "hud.settings.single_damage_number": "Einzelne Schadenszahlen", + "hud.settings.cumulated_damage": "Addierter Schaden", + "hud.settings.incoming_damage": "Erlittener Schaden", + "hud.settings.cumulated_incoming_damage": "Addierter erlittener Schaden", + "hud.settings.energybar_numbers": "Zahlen auf Ressourcenanzeige", + "hud.settings.values": "Werte", + "hud.settings.percentages": "Prozent", + "hud.settings.chat": "Chat", + "hud.settings.background_transparency": "Hintergrund Sichtbarkeit", + + "hud.settings.pan_sensitivity": "Schwenk Sensibilität", + "hud.settings.zoom_sensitivity": "Zoom Sensibilität", + "hud.settings.invert_scroll_zoom": "Scroll-Zoom invertieren", + "hud.settings.invert_mouse_y_axis": "Maus Y-Achse invertieren", + "hud.settings.free_look_behavior": "Freies Umsehen", + + "hud.settings.view_distance": "Sichtweite", + "hud.settings.maximum_fps": "Maximale FPS", + "hud.settings.fov": "Sichtfeld (Grad)", + "hud.settings.gamma": "Gamma", + "hud.settings.antialiasing_mode": "Anti-Alias Modus", + "hud.settings.cloud_rendering_mode": "Wolken Detail", + "hud.settings.fluid_rendering_mode": "Flüssigkeits Detail", + "hud.settings.fluid_rendering_mode.cheap": "Niedrig", + "hud.settings.fluid_rendering_mode.shiny": "Hoch", + "hud.settings.cloud_rendering_mode.regular": "Realistisch", + "hud.settings.fullscreen": "Vollbild", + "hud.settings.save_window_size": "Größe speichern", + + "hud.settings.music_volume": "Musik Lautstärke", + "hud.settings.sound_effect_volume": "Geräusch Lautstärke", + "hud.settings.audio_device": "Ausgabegerät", + + "hud.settings.awaitingkey": "Drückt eine Taste...", + + "hud.social": "Sozial", + "hud.social.online": "Online", + "hud.social.friends": "Freunde", + "hud.social.not_yet_available": "Noch nicht verfügbar", + "hud.social.faction": "Fraktion", + "hud.social.play_online_fmt": "{nb_player} Spieler online", + + "hud.spell": "Zauber", + + "hud.free_look_indicator": "Freie Sicht aktiv", + + + /// End HUD section + /// Start GameInput section + + "gameinput.primary": "Linker mittlerer Slot", + "gameinput.secondary": "Rechter mittlerer Slot", + "gameinput.slot1": "Hotbar Slot 1", + "gameinput.slot2": "Hotbar Slot 2", + "gameinput.slot3": "Hotbar Slot 3", + "gameinput.slot4": "Hotbar Slot 4", + "gameinput.slot5": "Hotbar Slot 5", + "gameinput.slot6": "Hotbar Slot 6", + "gameinput.slot7": "Hotbar Slot 7", + "gameinput.slot8": "Hotbar Slot 8", + "gameinput.slot9": "Hotbar Slot 9", + "gameinput.slot10": "Hotbar Slot 10", + "gameinput.swaploadout": "Waffe wechseln", + "gameinput.togglecursor": "Mauszeiger zeigen/verstecken", + "gameinput.help": "Hilfe anzeigen", + "gameinput.toggleinterface": "Interface verstecken/zeigen", + "gameinput.toggledebug": "FPS und Debug Informationen", + "gameinput.screenshot": "Bildschirmaufnahme", + "gameinput.toggleingameui": "Namensplaketten zeigen", + "gameinput.fullscreen": "Vollbildschirm", + "gameinput.moveforward": "Vorwärts bewegen", + "gameinput.moveleft": "Nach Links bewegen", + "gameinput.moveright": "Nach Rechts bewegen", + "gameinput.moveback": "Rückwärts bewegen", + "gameinput.jump": "Springen", + "gameinput.glide": "Gleiter", + "gameinput.roll": "Rollen", + "gameinput.climb": "Klettern", + "gameinput.climbdown": "Runter klettern", + "gameinput.wallleap": "Wandsprung", + "gameinput.mount": "Aufsteigen", + "gameinput.enter": "Betreten", + "gameinput.command": "Befehl", + "gameinput.escape": "Escape", + "gameinput.map": "Karte", + "gameinput.bag": "Inventar", + "gameinput.social": "Sozial", + "gameinput.sit": "Sitzen", + "gameinput.spellbook": "Zauber", + "gameinput.settings": "Einstellungen", + "gameinput.respawn": "Wiederbeleben", + "gameinput.charge": "Anstürmen", + "gameinput.togglewield": "Waffe ziehen/wegstecken", + "gameinput.interact": "Interagieren", + "gameinput.freelook": "Freie Sicht", + + /// End GameInput section + + /// Start chracter selection section + "char_selection.delete_permanently": "Diesen Charakter unwiderruflich löschen?", + "char_selection.change_server": "Server wechseln.", + "char_selection.enter_world": "Betreten", + "char_selection.logout": "Logout", + "char_selection.create_charater": "Charakter erstellen", + "char_selection.character_creation": "Charakter Erstellung", + "char_selection.create_new_charater": "Neuen Charakter erstellen", + + "char_selection.human_default": "Human Default", + "char_selection.level_fmt": "Level {level_nb}", + "char_selection.uncanny_valley": "Wildniss", + "char_selection.plains_of_uncertainty": "Wildniss", + "char_selection.beard": "Bart", + "char_selection.hair_style": "Frisur", + "char_selection.hair_color": "Haarfarbe", + "char_selection.chest_color": "Brustrüstung", + "char_selection.eye_color": "Augenfarbe", + "char_selection.skin": "Hautton", + "char_selection.eyebrows": "Augenbrauen", + "char_selection.accessories": "Accessoires", + /// End chracter selection section + /// Start character window section + "character_window.character_name": "Charakter", + // Charater stats + // Charater stats + "character_window.character_stats": r#"Ausdauer + +Beweglichkeit + +Willenskraft +"#, + + + /// Start character window section + + + /// Start Escape Menu Section + "esc_menu.logout": "Ausloggen", + "esc_menu.quit_game": "Desktop", + /// End Escape Menu Section + } +) \ No newline at end of file diff --git a/assets/voxygen/i18n/en.ron b/assets/voxygen/i18n/en.ron index a7bc51a228..6e39d56c43 100644 --- a/assets/voxygen/i18n/en.ron +++ b/assets/voxygen/i18n/en.ron @@ -9,6 +9,8 @@ /// /// To add a new language in Veloren, just write an additional `.ron` file in /// `assets/voxygen/i18n` and that's it! +/// +/// WARNING: Localization files shall be saved in UTF-8 format without BOM /// Localization for "global" English VoxygenLocalization( @@ -147,24 +149,19 @@ https://account.veloren.net."#, "hud.press_key_to_show_keybindings_fmt": "Press {key} to show keybindings", "hud.press_key_to_show_debug_info_fmt": "Press {key} to show debug info", - "hud.press_key_to_toggle_keybindings_fmt": "Press {key} to toogle keybindings", - "hud.press_key_to_toggle_debug_info_fmt": "Press {key} to toogle debug info", + "hud.press_key_to_toggle_keybindings_fmt": "Press {key} to toggle keybindings", + "hud.press_key_to_toggle_debug_info_fmt": "Press {key} to toggle debug info", // Respawn message "hud.press_key_to_respawn": r#"Press {key} to respawn at the last campfire you visited."#, // Welcome message - "hud.welcome": r#"Welcome to the Veloren Alpha!, + "hud.welcome": r#"Welcome to the Veloren Alpha! Some tips before you start: -MOST IMPORTANTLY: To set your respawn point type /waypoint into the chat. - -This can also be done when you are already dead! - - Press F1 to see the available key commands. Type /help into the chat to see chat commands @@ -193,11 +190,26 @@ Enjoy your stay in the World of Veloren."#, // Inventory - "hud.bag.inventory": "'s Inventory", - "hud.bag.stats_title": "'s Stats", + "hud.bag.inventory": "{playername}'s Inventory", + "hud.bag.stats_title": "{playername}'s Stats", "hud.bag.exp": "Exp", "hud.bag.armor": "Armor", "hud.bag.stats": "Stats", + "hud.bag.head": "Head", + "hud.bag.neck": "Neck", + "hud.bag.tabard": "Tabard", + "hud.bag.shoulders": "Shoulders", + "hud.bag.chest": "Chest", + "hud.bag.hands": "Hands", + "hud.bag.lantern": "Lantern", + "hud.bag.belt": "Belt", + "hud.bag.ring": "Ring", + "hud.bag.back": "Back", + "hud.bag.legs": "Legs", + "hud.bag.feet": "Feet", + "hud.bag.mainhand": "Mainhand", + "hud.bag.offhand": "Offhand", + // Map and Questlog "hud.map.map_title": "Map", @@ -228,13 +240,13 @@ Enjoy your stay in the World of Veloren."#, "hud.settings.values": "Values", "hud.settings.percentages": "Percentages", "hud.settings.chat": "Chat", - "hud.settings.background_transparency": "Background Transparency", - "hud.settings.none": "None", + "hud.settings.background_transparency": "Background Transparency", "hud.settings.pan_sensitivity": "Pan Sensitivity", "hud.settings.zoom_sensitivity": "Zoom Sensitivity", "hud.settings.invert_scroll_zoom": "Invert Scroll Zoom", "hud.settings.invert_mouse_y_axis": "Invert Mouse Y Axis", + "hud.settings.enable_mouse_smoothing": "Camera Smoothing", "hud.settings.free_look_behavior": "Free look behavior", "hud.settings.view_distance": "View Distance", @@ -254,90 +266,7 @@ Enjoy your stay in the World of Veloren."#, "hud.settings.sound_effect_volume": "Sound Effects Volume", "hud.settings.audio_device": "Audio Device", - // Control list - "hud.settings.control_names": r#"Free Cursor -Toggle Help Window -Toggle Interface -Toggle FPS and Debug Info -Take Screenshot -Toggle Nametags -Toggle Fullscreen - - -Move Forward -Move Left -Move Right -Move Backwards - -Jump - -Glider - -Dodge - -Roll - -Climb - -Climb down - -Auto Walk - -Sheathe/Draw Weapons - -Put on/Remove Helmet - -Sit - -Mount - -Interact - - -Basic Attack -Secondary Attack/Block/Aim - - -Skillbar Slot 1 -Skillbar Slot 2 -Skillbar Slot 3 -Skillbar Slot 4 -Skillbar Slot 5 -Skillbar Slot 6 -Skillbar Slot 7 -Skillbar Slot 8 -Skillbar Slot 9 -Skillbar Slot 10 - - -Pause Menu -Settings -Social -Map -Spellbook -Character -Questlog -Bag - - - -Send Chat Message -Scroll Chat - - -Free look - - -Chat commands: - -/alias [Name] - Change your Chat Name -/tp [Name] - Teleports you to another player -/jump - Offset your position -/goto - Teleport to a position -/kill - Kill yourself -/pig - Spawn pig NPC -/wolf - Spawn wolf NPC -/help - Display chat commands"#, + "hud.settings.awaitingkey": "Press a key...", "hud.social": "Social", "hud.social.online": "Online", @@ -348,10 +277,62 @@ Chat commands: "hud.spell": "Spell", - "hud.free_look_indicator": "Free look active", + "hud.free_look_indicator": "Free look active", + /// End HUD section + /// Start GameInput section + + "gameinput.primary": "Basic Attack", + "gameinput.secondary": "Secondary Attack/Block/Aim", + "gameinput.slot1": "Hotbar Slot 1", + "gameinput.slot2": "Hotbar Slot 2", + "gameinput.slot3": "Hotbar Slot 3", + "gameinput.slot4": "Hotbar Slot 4", + "gameinput.slot5": "Hotbar Slot 5", + "gameinput.slot6": "Hotbar Slot 6", + "gameinput.slot7": "Hotbar Slot 7", + "gameinput.slot8": "Hotbar Slot 8", + "gameinput.slot9": "Hotbar Slot 9", + "gameinput.slot10": "Hotbar Slot 10", + "gameinput.swaploadout": "Swap Loadout", + "gameinput.togglecursor": "Toggle Cursor", + "gameinput.help": "Toggle Help Window", + "gameinput.toggleinterface": "Toggle Interface", + "gameinput.toggledebug": "Toggle FPS and Debug Info", + "gameinput.screenshot": "Take Screenshot", + "gameinput.toggleingameui": "Toggle Nametags", + "gameinput.fullscreen": "Toggle Fullscreen", + "gameinput.moveforward": "Move Forward", + "gameinput.moveleft": "Move Left", + "gameinput.moveright": "Move Right", + "gameinput.moveback": "Move Backwards", + "gameinput.jump": "Jump", + "gameinput.glide": "Glider", + "gameinput.roll": "Roll", + "gameinput.climb": "Climb", + "gameinput.climbdown": "Climb Down", + "gameinput.wallleap": "Wall Leap", + "gameinput.mount": "Mount", + "gameinput.enter": "Enter", + "gameinput.command": "Command", + "gameinput.escape": "Escape", + "gameinput.map": "Map", + "gameinput.bag": "Bag", + "gameinput.social": "Social", + "gameinput.sit": "Sit", + "gameinput.spellbook": "Spells", + "gameinput.settings": "Settings", + "gameinput.respawn": "Respawn", + "gameinput.charge": "Charge", + "gameinput.togglewield": "Toggle Wield", + "gameinput.interact": "Interact", + "gameinput.freelook": "Free Look", + + /// End GameInput section + + /// Start chracter selection section "char_selection.delete_permanently": "Permanently delete this Character?", "char_selection.change_server": "Change Server", diff --git a/assets/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index f9c40cf4ec..a2c33550b6 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -54,12 +54,31 @@ "voxel.weapon.shield.wood-0", (0.0, 0.0, 0.0), (-90.0, 90.0, 0.0), 2.4, ), + // Lanterns + Lantern(Black0): Png( + "element.icons.lantern_black-0", + ), + Lantern(Green0): Png( + "element.icons.lantern_green-0", + ), // Other Utility(Collar): Png( "element.icons.collar", - ), - + ), // Armor + // Starter Parts + Armor(Foot(Sandal0)): VoxTrans( + "voxel.armor.foot.cloth_sandals", + (0.0, 0.0, 0.0), (-95.0, 140.0, 0.0), 1.1, + ), + Armor(Pants(Rugged0)): VoxTrans( + "voxel.armor.pants.rugged-0", + (0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.2, + ), + Armor(Chest(Rugged0)): VoxTrans( + "voxel.armor.chest.rugged-0", + (0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.2, + ), // Assassin Set Armor(Chest(Assassin)): VoxTrans( "voxel.armor.chest.assa", @@ -222,10 +241,44 @@ "voxel.armor.shoulder.cloth_purple_right-0", (0.0, 0.0, 0.0), (-90.0, 130.0, 0.0), 1.2, ), + // Backs + Armor(Back(Short0)): VoxTrans( + "voxel.armor.back.short-0", + (0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.0, + ), + Armor(Back(Admin)): VoxTrans( + "voxel.armor.back.admin", + (0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.0, + ), + // Rings + Armor(Ring(Ring0)): Png( + "element.icons.ring-0", + ), + // Necks + Armor(Neck(Neck0)): Png( + "element.icons.neck-0", + ), + // Tabards + Armor(Tabard(Admin)): Png( + "element.icons.tabard_admin", + ), + // Heads + Armor(Head(Leather0)): VoxTrans( + "voxel.armor.head.leather-0", + (0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.0, + ), + Armor(Head(AssaMask0)): VoxTrans( + "voxel.armor.head.assa_mask-0", + (0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.0, + ), // Consumables - Consumable(Apple): VoxTrans( + Consumable(Apple): + VoxTrans( "element.icons.item_apple", (0.0, 0.0, 0.0), (-90.0, 90.0, 0.0), 1.0, + ), + Consumable(Coconut): Png( + "element.icons.item_coconut", ), Consumable(PotionMinor): VoxTrans( "voxel.object.potion_red", diff --git a/assets/voxygen/net.veloren.veloren.appdata.xml b/assets/voxygen/net.veloren.veloren.appdata.xml deleted file mode 100644 index 1192abe8fc..0000000000 --- a/assets/voxygen/net.veloren.veloren.appdata.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - net.veloren.veloren.desktop - CC0-1.0 - GPL-3.0-or-later - - intense - mild - mild - mild - - Veloren - - Veloren is a multiplayer voxel RPG written in Rust. It is inspired - by games such as Cube World, Legend of Zelda: Breath of the Wild, - Dwarf Fortress and Minecraft. - - -

- Welcome To Veloren! -

- Veloren is a multiplayer voxel RPG written in Rust. Veloren takes - inspiration from games such as Cube World, Minecraft and Dwarf - Fortress. The game is currently under heavy development, but is - playable. -

- Development -

- Currently the communication of contributors happens mainly on our - official Discord server (https://discord.gg/kjwJwjK). You can join - it to keep up with the development, talk to us or contribute - something yourself. Anyone who shows genuine effort to help is - welcome in our team. You don't have to know how to program to - contribute! -

-
- - - https://media.discordapp.net/attachments/634860358623821835/643034796548947968/screenshot_1573381825305.png - - - https://media.discordapp.net/attachments/597826574095613962/643102462781423616/screenshot_1573397958545.png - - - https://cdn.discordapp.com/attachments/634860358623821835/646518917577310219/screenshot_1574211401431.png - - - - sandbox - world - multiplayer - - https://veloren.net - https://gitlab.com/veloren/veloren/issues - https://gitlab.com/veloren/veloren#faq - https://book.veloren.net/ - - veloren-voxygen - -
diff --git a/assets/voxygen/net.veloren.veloren.desktop b/assets/voxygen/net.veloren.veloren.desktop index 5e4f2cd02e..c6e434432a 100644 --- a/assets/voxygen/net.veloren.veloren.desktop +++ b/assets/voxygen/net.veloren.veloren.desktop @@ -5,5 +5,5 @@ Comment=Veloren is a multiplayer voxel RPG written in Rust Exec=veloren-voxygen Categories=Game;Simulation; Keywords=veloren;sandbox;world;blocks;nodes;multiplayer;roleplaying; -Icon=net.veloren.veloren.png +Icon=net.veloren.veloren Terminal=false diff --git a/assets/voxygen/net.veloren.veloren.metainfo.xml b/assets/voxygen/net.veloren.veloren.metainfo.xml new file mode 100644 index 0000000000..70b0f342b9 --- /dev/null +++ b/assets/voxygen/net.veloren.veloren.metainfo.xml @@ -0,0 +1,73 @@ + + + + net.veloren.veloren.desktop + CC0-1.0 + GPL-3.0-or-later + + Veloren + + veloren-voxygen + + + + intense + mild + mild + mild + + + + Veloren is a multiplayer voxel RPG written in Rust. It is inspired by games + such as Cube World, Legend of Zelda: Breath of the Wild, Dwarf Fortress and + Minecraft. + + + +

+ Welcome To Veloren! +

+ Veloren is a multiplayer voxel RPG written in Rust. Veloren takes + inspiration from games such as Cube World, Minecraft and Dwarf Fortress. + The game is currently under heavy development, but is playable. +

+ Development +

+ Currently the communication of contributors happens mainly on our official + Discord server (https://discord.gg/kjwJwjK). You can join it to keep up + with the development, talk to us or contribute something yourself. Anyone + who shows genuine effort to help is welcome in our team. You don't have to + know how to program to contribute! +

+
+ + + + + + + + https://veloren.net/screenshot1.png + + + https://veloren.net/screenshot2.png + + + https://veloren.net/screenshot3.png + + + https://veloren.net/screenshot4.png + + + + + multiplayer + sandbox + world + + + https://gitlab.com/veloren/veloren/issues + https://gitlab.com/veloren/veloren#faq + https://book.veloren.net/ + https://veloren.net +
diff --git a/assets/voxygen/shaders/figure-frag.glsl b/assets/voxygen/shaders/figure-frag.glsl index ace255e3d5..9e98c6fef7 100644 --- a/assets/voxygen/shaders/figure-frag.glsl +++ b/assets/voxygen/shaders/figure-frag.glsl @@ -4,6 +4,7 @@ in vec3 f_pos; in vec3 f_col; +in float f_ao; flat in vec3 f_norm; in float f_alt; in vec4 f_shadow; @@ -12,6 +13,9 @@ layout (std140) uniform u_locals { mat4 model_mat; vec4 model_col; + // bit 0 - is player + // bit 1-31 - unused + int flags; }; struct BoneData { @@ -72,6 +76,11 @@ void main() { emitted_light *= point_shadow; lights_at(f_pos, f_norm, view_dir, k_a, k_d, k_s, alpha, emitted_light, reflected_light); + + float ao = pow(f_ao, 0.5) * 0.85 + 0.15; + + reflected_light *= ao; + emitted_light *= ao; /* vec3 point_light = light_at(f_pos, f_norm); emitted_light += point_light; reflected_light += point_light; */ @@ -92,5 +101,15 @@ void main() { vec3 fog_color = get_sky_color(cam_to_frag/*view_dir*/, time_of_day.x, cam_pos.xyz, f_pos, 0.5, true, clouds); vec3 color = mix(mix(surf_color, fog_color, fog_level), clouds.rgb, clouds.a); + if ((flags & 1) == 1 && int(cam_mode) == 1) { + float distance = distance(vec3(cam_pos), vec3(model_mat * vec4(vec3(0), 1))) - 2; + + float opacity = clamp(distance / distance_divider, 0, 1); + + if(threshold_matrix[int(gl_FragCoord.x) % 4][int(gl_FragCoord.y) % 4] > opacity) { + discard; + } + } + tgt_color = vec4(color, 1.0); } diff --git a/assets/voxygen/shaders/figure-vert.glsl b/assets/voxygen/shaders/figure-vert.glsl index 29ebec2b99..62c41a0042 100644 --- a/assets/voxygen/shaders/figure-vert.glsl +++ b/assets/voxygen/shaders/figure-vert.glsl @@ -6,12 +6,16 @@ in vec3 v_pos; in vec3 v_norm; in vec3 v_col; +in float v_ao; in uint v_bone_idx; layout (std140) uniform u_locals { mat4 model_mat; vec4 model_col; + // bit 0 - is player + // bit 1-31 - unused + int flags; }; struct BoneData { @@ -25,6 +29,7 @@ uniform u_bones { out vec3 f_pos; out vec3 f_col; +out float f_ao; flat out vec3 f_norm; out float f_alt; out vec4 f_shadow; @@ -39,6 +44,8 @@ void main() { f_col = srgb_to_linear(v_col); + f_ao = v_ao; + // Calculate normal here rather than for each pixel in the fragment shader f_norm = normalize(( combined_mat * diff --git a/assets/voxygen/shaders/fluid-frag/cheap.glsl b/assets/voxygen/shaders/fluid-frag/cheap.glsl index 410c8abab8..db0ba83378 100644 --- a/assets/voxygen/shaders/fluid-frag/cheap.glsl +++ b/assets/voxygen/shaders/fluid-frag/cheap.glsl @@ -97,10 +97,10 @@ void main() { vec4 clouds; vec3 fog_color = get_sky_color(cam_to_frag, time_of_day.x, cam_pos.xyz, f_pos, 0.25, true, clouds); - float passthrough = pow(dot(faceforward(f_norm, f_norm, cam_to_frag/*view_dir*/), -cam_to_frag/*view_dir*/), 0.5); + float passthrough = /*pow(*/dot(faceforward(f_norm, f_norm, cam_to_frag/*view_dir*/), -cam_to_frag/*view_dir*/)/*, 0.5)*/; vec3 surf_color = illuminate(water_color * emitted_light, /*surf_color * */reflected_light); vec4 color = mix(vec4(surf_color, 1.0), vec4(surf_color, 1.0 / (1.0 + /*diffuse_light*//*(f_light * point_shadow + point_light)*/reflected_light_point/* * 0.25*/)), passthrough); - tgt_color = mix(mix(color, vec4(fog_color, 0.0), fog_level), vec4(clouds.rgb, 0.0), clouds.a); + tgt_color = mix(color, vec4(fog_color, 0.0), 0.0); } diff --git a/assets/voxygen/shaders/fluid-frag/shiny.glsl b/assets/voxygen/shaders/fluid-frag/shiny.glsl index c4f2f300ee..99f0cf445f 100644 --- a/assets/voxygen/shaders/fluid-frag/shiny.glsl +++ b/assets/voxygen/shaders/fluid-frag/shiny.glsl @@ -31,30 +31,33 @@ vec3 warp_normal(vec3 norm, vec3 pos, float time) { } float wave_height(vec3 pos) { + float timer = tick.x * 0.75; + + pos *= 0.5; vec3 big_warp = ( - texture(t_waves, fract(pos.xy * 0.03 + tick.x * 0.01)).xyz * 0.5 + - texture(t_waves, fract(pos.yx * 0.03 - tick.x * 0.01)).xyz * 0.5 + + texture(t_waves, fract(pos.xy * 0.03 + timer * 0.01)).xyz * 0.5 + + texture(t_waves, fract(pos.yx * 0.03 - timer * 0.01)).xyz * 0.5 + vec3(0) ); vec3 warp = ( - texture(t_noise, fract(pos.yx * 0.1 + tick.x * 0.02)).xyz * 0.3 + - texture(t_noise, fract(pos.yx * 0.1 - tick.x * 0.02)).xyz * 0.3 + + texture(t_noise, fract(pos.yx * 0.1 + timer * 0.02)).xyz * 0.3 + + texture(t_noise, fract(pos.yx * 0.1 - timer * 0.02)).xyz * 0.3 + vec3(0) ); float height = ( - (texture(t_noise, pos.xy * 0.03 + big_warp.xy + tick.x * 0.05).y - 0.5) * 1.0 + - (texture(t_noise, pos.yx * 0.03 + big_warp.yx - tick.x * 0.05).y - 0.5) * 1.0 + - (texture(t_waves, pos.xy * 0.1 + warp.xy + tick.x * 0.1).x - 0.5) * 0.5 + - (texture(t_waves, pos.yx * 0.1 + warp.yx - tick.x * 0.1).x - 0.5) * 0.5 + - (texture(t_noise, pos.yx * 0.3 + warp.xy * 0.5 + tick.x * 0.1).x - 0.5) * 0.2 + - (texture(t_noise, pos.yx * 0.3 + warp.yx * 0.5 - tick.x * 0.1).x - 0.5) * 0.2 + - (texture(t_noise, pos.yx * 1.0 + warp.yx * 0.0 - tick.x * 0.1).x - 0.5) * 0.05 + + (texture(t_noise, pos.xy * 0.03 + big_warp.xy + timer * 0.05).y - 0.5) * 1.0 + + (texture(t_noise, pos.yx * 0.03 + big_warp.yx - timer * 0.05).y - 0.5) * 1.0 + + (texture(t_waves, pos.xy * 0.1 + warp.xy + timer * 0.1).x - 0.5) * 0.5 + + (texture(t_waves, pos.yx * 0.1 + warp.yx - timer * 0.1).x - 0.5) * 0.5 + + (texture(t_noise, pos.yx * 0.3 + warp.xy * 0.5 + timer * 0.1).x - 0.5) * 0.2 + + (texture(t_noise, pos.yx * 0.3 + warp.yx * 0.5 - timer * 0.1).x - 0.5) * 0.2 + + (texture(t_noise, pos.yx * 1.0 + warp.yx * 0.0 - timer * 0.1).x - 0.5) * 0.05 + 0.0 ); - return pow(abs(height), 0.5) * sign(height) * 5.5; + return pow(abs(height), 0.5) * sign(height) * 10.5; } void main() { @@ -95,10 +98,9 @@ void main() { 0.1 / slope ); - nmap = mix(vec3(0, 0, 1), normalize(nmap), min(1.0 / pow(frag_dist, 0.75), 1)); - - vec3 norm = f_norm * nmap.z + b_norm * nmap.x + c_norm * nmap.y; + nmap = mix(f_norm, normalize(nmap), min(1.0 / pow(frag_dist, 0.75), 1)); + vec3 norm = vec3(0, 0, 1) * nmap.z + b_norm * nmap.x + c_norm * nmap.y; vec4 _clouds; vec3 reflect_ray_dir = reflect(cam_to_frag/*-view_dir*/, norm); @@ -127,7 +129,7 @@ void main() { float point_shadow = shadow_at(f_pos, f_norm); // vec3 light_frac = /*vec3(1.0);*/light_reflection_factor(f_norm/*vec3(0, 0, 1.0)*/, view_dir, vec3(0, 0, -1.0), vec3(1.0), vec3(R_s), alpha); // 0 = 100% reflection, 1 = translucent water - float passthrough = pow(dot(faceforward(f_norm, f_norm, cam_to_frag/*view_dir*/), -cam_to_frag/*view_dir*/), 0.5); + float passthrough = /*pow(*/dot(faceforward(f_norm, f_norm, cam_to_frag/*view_dir*/), -cam_to_frag/*view_dir*/)/*, 0.5)*/; get_sun_diffuse2(norm, /*time_of_day.x*/sun_dir, moon_dir, view_dir, k_a/* * (shade_frac * 0.5 + light_frac * 0.5)*/, vec3(0.0), /*vec3(f_light * point_shadow)*//*reflect_color*/k_s, alpha, emitted_light, reflected_light); reflected_light *= reflect_color * f_light * point_shadow * shade_frac; @@ -176,6 +178,11 @@ void main() { //vec4 color = vec4(surf_color, 1.0); // vec4 color = mix(vec4(reflect_color, 1.0), vec4(surf_color, 1.0 / (1.0 + /*diffuse_light*/(/*f_light * point_shadow*/reflected_light_point/* + point_light*//*reflected_light*/))), passthrough); vec4 color = vec4(surf_color, mix(1.0, 1.0 / (1.0 + /*0.25 * *//*diffuse_light*/(/*f_light * point_shadow*/reflected_light_point)), passthrough)); + /* reflect_color = reflect_color * 0.5 * (diffuse_light + ambient_light); + // 0 = 100% reflection, 1 = translucent water + float passthrough = dot(faceforward(f_norm, f_norm, cam_to_frag), -cam_to_frag); + + vec4 color = mix(vec4(reflect_color, 1.0), vec4(vec3(0), 1.0 / (1.0 + diffuse_light * 0.25)), passthrough); */ tgt_color = mix(mix(color, vec4(fog_color, 0.0), fog_level), vec4(clouds.rgb, 0.0), clouds.a); } diff --git a/assets/voxygen/shaders/fluid-vert.glsl b/assets/voxygen/shaders/fluid-vert.glsl index 9d5275605a..ea30688130 100644 --- a/assets/voxygen/shaders/fluid-vert.glsl +++ b/assets/voxygen/shaders/fluid-vert.glsl @@ -18,17 +18,16 @@ flat out vec3 f_norm; out vec3 f_col; out float f_light; +const float EXTRA_NEG_Z = 65536.0; + void main() { - f_pos = vec3( - float((v_pos_norm >> 0) & 0x00FFu), - float((v_pos_norm >> 8) & 0x00FFu), - float((v_pos_norm >> 16) & 0x1FFFu) - ) + model_offs; - f_pos.z *= min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.0); - f_pos.z -= min(32.0, pow(distance(focus_pos.xy, f_pos.xy) / view_distance.x, 20.0)); + f_pos = vec3((uvec3(v_pos_norm) >> uvec3(0, 6, 12)) & uvec3(0x3Fu, 0x3Fu, 0x1FFFFu)) - vec3(0, 0, EXTRA_NEG_Z) + model_offs; + f_pos.z -= 250.0 * (1.0 - min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.0)); + f_pos.z -= min(32.0, 25.0 * pow(distance(focus_pos.xy, f_pos.xy) / view_distance.x, 20.0)); // Small waves - f_pos.z -= 0.05 + 0.05 * (sin(tick.x * 2.0 + f_pos.x * 2.0 + f_pos.y * 2.0) + 1.0) * 0.5; + f_pos.xy += 0.01; // Avoid z-fighting + f_pos.z -= 0.1 + 0.1 * (sin(tick.x * 2.0 + f_pos.x * 2.0 + f_pos.y * 2.0) + 1.0) * 0.5; f_col = vec3( float((v_col_light >> 8) & 0xFFu), diff --git a/assets/voxygen/shaders/include/cloud/regular.glsl b/assets/voxygen/shaders/include/cloud/regular.glsl index fa241405a9..71249695b9 100644 --- a/assets/voxygen/shaders/include/cloud/regular.glsl +++ b/assets/voxygen/shaders/include/cloud/regular.glsl @@ -1,9 +1,6 @@ #include -// float CLOUD_AVG_HEIGHT = /*1025.0*/view_distance.z + 0.7 + view_distance.w; -// float CLOUD_HEIGHT_MIN = CLOUD_AVG_HEIGHT - 50.0; -// float CLOUD_HEIGHT_MAX = CLOUD_AVG_HEIGHT + 50.0; -const float CLOUD_THRESHOLD = 0.25; +const float CLOUD_THRESHOLD = 0.27; const float CLOUD_SCALE = 5.0; const float CLOUD_DENSITY = 100.0; @@ -13,8 +10,8 @@ float vsum(vec3 v) { vec3 get_cloud_heights() { float CLOUD_AVG_HEIGHT = /*1025.0*/view_distance.z + 0.7 * view_distance.w; - float CLOUD_HEIGHT_MIN = CLOUD_AVG_HEIGHT - 50.0; - float CLOUD_HEIGHT_MAX = CLOUD_AVG_HEIGHT + 50.0; + float CLOUD_HEIGHT_MIN = CLOUD_AVG_HEIGHT - 60.0; + float CLOUD_HEIGHT_MAX = CLOUD_AVG_HEIGHT + 60.0; return vec3(CLOUD_AVG_HEIGHT, CLOUD_HEIGHT_MIN, CLOUD_HEIGHT_MAX); } @@ -23,27 +20,24 @@ vec2 cloud_at(vec3 pos) { vec2 scaled_pos = pos.xy / CLOUD_SCALE; float tick_offs = 0.0 - + texture(t_noise, scaled_pos * 0.0005 - time_of_day.x * 0.00002).x * 0.5 - + texture(t_noise, scaled_pos * 0.000015).x * 5.0; + + texture(t_noise, scaled_pos * 0.0005 - time_of_day.x * 0.00001).x * 0.5 + + texture(t_noise, scaled_pos * 0.0015).x * 0.15; float value = ( 0.0 + texture(t_noise, scaled_pos * 0.0003 + tick_offs).x + texture(t_noise, scaled_pos * 0.0015 - tick_offs * 2.0).x * 0.5 - //+ texture(t_noise, scaled_pos * 0.0025 - time_of_day.x * 0.0002).x * 0.25 - //+ texture(t_noise, scaled_pos * 0.008 + time_of_day.x * 0.0004).x * 0.15 - //+ texture(t_noise, scaled_pos * 0.02 + tick_offs + time_of_day.x * 0.0004).x * 0.2 ) / 3.0; value += (0.0 - + texture(t_noise, scaled_pos * 0.008 + time_of_day.x * 0.0004).x * 0.25 - + texture(t_noise, scaled_pos * 0.02 + tick_offs + time_of_day.x * 0.0004).x * 0.15 + + texture(t_noise, scaled_pos * 0.008 + time_of_day.x * 0.0002).x * 0.25 + + texture(t_noise, scaled_pos * 0.02 + tick_offs + time_of_day.x * 0.0002).x * 0.15 ) * value; - float density = max((value - CLOUD_THRESHOLD) - abs(pos.z - max_heights.x) / 400.0, 0.0) * CLOUD_DENSITY; + float density = max((value - CLOUD_THRESHOLD) - abs(pos.z - max_heights.x) / 200.0, 0.0) * CLOUD_DENSITY; float SHADE_GRADIENT = 1.5 / (max_heights.x - max_heights.y); - float shade = ((pos.z - max_heights.x) / (max_heights.z - max_heights.y)) * 2.5 + 0.7; + float shade = ((pos.z - max_heights.x) / (max_heights.z - max_heights.y)) * 5.0 + 0.3; return vec2(shade, density / (1.0 + vsum(abs(pos - cam_pos.xyz)) / 5000)); } @@ -76,7 +70,7 @@ vec4 get_cloud_color(vec3 dir, vec3 origin, float time_of_day, float max_dist, f cloud_shade = mix(cloud_shade, sample.x, passthrough * integral); dist += INCR * delta; - if (passthrough < 0.05 || (passthrough > 0.8 && dist > (maxd + mind) * 0.7)) { + if (passthrough < 0.1) { break; } } diff --git a/assets/voxygen/shaders/include/globals.glsl b/assets/voxygen/shaders/include/globals.glsl index e0e97949e2..b35fab1228 100644 --- a/assets/voxygen/shaders/include/globals.glsl +++ b/assets/voxygen/shaders/include/globals.glsl @@ -13,4 +13,17 @@ uniform u_globals { uvec4 medium; ivec4 select_pos; vec4 gamma; + // 0 - FirstPerson + // 1 - ThirdPerson + uint cam_mode; }; + +// Specifies the pattern used in the player dithering +mat4 threshold_matrix = mat4( + vec4(1.0 / 17.0, 9.0 / 17.0, 3.0 / 17.0, 11.0 / 17.0), + vec4(13.0 / 17.0, 5.0 / 17.0, 15.0 / 17.0, 7.0 / 17.0), + vec4(4.0 / 17.0, 12.0 / 17.0, 2.0 / 17.0, 10.0 / 17.0), + vec4(16.0 / 17.0, 8.0 / 17.0, 14.0 / 17.0, 6.0 / 17.0) +); +float distance_divider = 2; +float shadow_dithering = 0.5; \ No newline at end of file diff --git a/assets/voxygen/shaders/include/light.glsl b/assets/voxygen/shaders/include/light.glsl index 39f3f16801..6a33a98f13 100644 --- a/assets/voxygen/shaders/include/light.glsl +++ b/assets/voxygen/shaders/include/light.glsl @@ -20,9 +20,9 @@ uniform u_shadows { }; float attenuation_strength(vec3 rpos) { - // Idea: we start off with attenuation strength equal to 1 at the source of the point light. - // return 1.0 / (1.0 + /*pow*/(rpos.x * rpos.x + rpos.y * rpos.y + rpos.z * rpos.z/*, 0.6*/)); - return 1.0 / (/*pow*/(rpos.x * rpos.x + rpos.y * rpos.y + rpos.z * rpos.z/*, 0.6*/)); + // This is not how light attenuation works at all, but it produces visually pleasing and mechanically useful properties + float d2 = rpos.x * rpos.x + rpos.y * rpos.y + rpos.z * rpos.z; + return max(2.0 / pow(d2 + 10, 0.35) - pow(d2 / 50000.0, 0.8), 0.0); } vec3 light_at(vec3 wpos, vec3 wnorm) { @@ -40,7 +40,7 @@ vec3 light_at(vec3 wpos, vec3 wnorm) { // Pre-calculate difference between light and fragment vec3 difference = light_pos - wpos; - float strength = pow(attenuation_strength(difference), 0.6); + float strength = attenuation_strength(difference); // Multiply the vec3 only once vec3 color = srgb_to_linear(L.light_col.rgb) * (strength * L.light_col.a); diff --git a/assets/voxygen/shaders/include/random.glsl b/assets/voxygen/shaders/include/random.glsl index 694758bb69..8298012000 100644 --- a/assets/voxygen/shaders/include/random.glsl +++ b/assets/voxygen/shaders/include/random.glsl @@ -1,9 +1,9 @@ uniform sampler2D t_noise; float hash(vec4 p) { - p = fract( p*0.3183099+.1); + p = fract(p * 0.3183099 + 0.1); 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; } float snoise(in vec4 x) { @@ -35,7 +35,7 @@ float snoise(in vec4 x) { } vec3 rand_perm_3(vec3 pos) { - return sin(pos * vec3(1473.7 * pos.z + 472.3, 8891.1 * pos.x + 723.1, 3813.3 * pos.y + 982.5)); + return abs(sin(pos * vec3(1473.7 * pos.z + 472.3, 8891.1 * pos.x + 723.1, 3813.3 * pos.y + 982.5))); } vec4 rand_perm_4(vec4 pos) { diff --git a/assets/voxygen/shaders/include/sky.glsl b/assets/voxygen/shaders/include/sky.glsl index ac92f1cff4..8818324c7f 100644 --- a/assets/voxygen/shaders/include/sky.glsl +++ b/assets/voxygen/shaders/include/sky.glsl @@ -4,8 +4,8 @@ const float PI = 3.141592; -const vec3 SKY_DAY_TOP = vec3(0.1, 0.2, 0.9); -const vec3 SKY_DAY_MID = vec3(0.02, 0.08, 0.8); +const vec3 SKY_DAY_TOP = vec3(0.1, 0.5, 0.9); +const vec3 SKY_DAY_MID = vec3(0.02, 0.28, 0.8); const vec3 SKY_DAY_BOT = vec3(0.1, 0.2, 0.3); const vec3 DAY_LIGHT = vec3(1.2, 1.0, 1.0) * 1.8;//3.0; const vec3 SUN_HALO_DAY = vec3(0.35, 0.35, 0.0); @@ -270,7 +270,7 @@ vec3 get_sky_color(vec3 dir, float time_of_day, vec3 origin, vec3 f_pos, float q // Clouds clouds = get_cloud_color(dir, origin, time_of_day, f_dist, quality); - clouds.rgb *= get_sun_brightness(sun_dir) * (sun_halo * 1.5 + get_sun_color(sun_dir)) + get_moon_brightness(moon_dir) * (moon_halo * 80.0 + get_moon_color(moon_dir)); + clouds.rgb *= get_sun_brightness(sun_dir) * (sun_halo * 1.5 + get_sun_color(sun_dir)) + get_moon_brightness(moon_dir) * (moon_halo * 80.0 + get_moon_color(moon_dir) + 0.25); if (f_dist > 5000.0) { sky_color += sun_light + moon_light; diff --git a/assets/voxygen/shaders/player-shadow-frag.glsl b/assets/voxygen/shaders/player-shadow-frag.glsl new file mode 100644 index 0000000000..25bd05ed0a --- /dev/null +++ b/assets/voxygen/shaders/player-shadow-frag.glsl @@ -0,0 +1,45 @@ +#version 330 core + +#include + +in vec3 f_pos; +in vec3 f_col; +flat in vec3 f_norm; + +layout (std140) +uniform u_locals { + mat4 model_mat; + vec4 model_col; + int flags; +}; + +struct BoneData { + mat4 bone_mat; +}; + +layout (std140) +uniform u_bones { + BoneData bones[16]; +}; + +#include +#include +#include + +out vec4 tgt_color; + +void main() { + float distance = distance(vec3(cam_pos), vec3(model_mat * vec4(vec3(0), 1))) - 2; + + float opacity = clamp(distance / distance_divider, 0, 1); + + if(threshold_matrix[int(gl_FragCoord.x) % 4][int(gl_FragCoord.y) % 4] > opacity) { + discard; + } + + if(threshold_matrix[int(gl_FragCoord.x) % 4][int(gl_FragCoord.y) % 4] > shadow_dithering) { + discard; + } + + tgt_color = vec4(0.0,0.0,0.0, 1.0); +} \ No newline at end of file diff --git a/assets/voxygen/shaders/postprocess-frag.glsl b/assets/voxygen/shaders/postprocess-frag.glsl index cd0c87a772..1a3c12cbbe 100644 --- a/assets/voxygen/shaders/postprocess-frag.glsl +++ b/assets/voxygen/shaders/postprocess-frag.glsl @@ -36,7 +36,6 @@ void main() { uv = clamp(uv + vec2(sin(uv.y * 16.0 + tick.x), sin(uv.x * 24.0 + tick.x)) * 0.005, 0, 1); } - vec4 aa_color = aa_apply(src_color, uv * screen_res.xy, screen_res.xy); //vec4 hsva_color = vec4(rgb2hsv(fxaa_color.rgb), fxaa_color.a); @@ -44,7 +43,7 @@ void main() { //hsva_color.z *= 0.85; //hsva_color.z = 1.0 - 1.0 / (1.0 * hsva_color.z + 1.0); //vec4 final_color = vec4(hsv2rgb(hsva_color.rgb), hsva_color.a); - + vec4 final_color = pow(aa_color, gamma); if (medium.x == 1u) { diff --git a/assets/voxygen/shaders/sprite-frag.glsl b/assets/voxygen/shaders/sprite-frag.glsl index ccd0821bda..6d04498611 100644 --- a/assets/voxygen/shaders/sprite-frag.glsl +++ b/assets/voxygen/shaders/sprite-frag.glsl @@ -5,6 +5,7 @@ in vec3 f_pos; flat in vec3 f_norm; in vec3 f_col; +in float f_ao; in float f_light; out vec4 tgt_color; @@ -77,6 +78,10 @@ void main() { emitted_light += point_light; reflected_light += point_light; */ + float ao = pow(f_ao, 0.5) * 0.85 + 0.15; + emitted_light *= ao; + reflected_light *= ao; + surf_color = illuminate(surf_color * emitted_light, surf_color * reflected_light); // vec3 surf_color = illuminate(f_col, light, diffuse_light, ambient_light); diff --git a/assets/voxygen/shaders/sprite-vert.glsl b/assets/voxygen/shaders/sprite-vert.glsl index aa7ec1b313..11475a9155 100644 --- a/assets/voxygen/shaders/sprite-vert.glsl +++ b/assets/voxygen/shaders/sprite-vert.glsl @@ -6,6 +6,7 @@ in vec3 v_pos; in vec3 v_norm; in vec3 v_col; +in float v_ao; in vec4 inst_mat0; in vec4 inst_mat1; in vec4 inst_mat2; @@ -16,6 +17,7 @@ in float inst_wind_sway; 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; @@ -42,6 +44,7 @@ void main() { f_norm = (inst_mat * vec4(v_norm, 0)).xyz; f_col = srgb_to_linear(v_col) * srgb_to_linear(inst_col); + f_ao = v_ao; // Select glowing if (select_pos.w > 0 && select_pos.xyz == floor(sprite_pos)) { diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl index 8817944b8e..94fb4bac0c 100644 --- a/assets/voxygen/shaders/terrain-frag.glsl +++ b/assets/voxygen/shaders/terrain-frag.glsl @@ -1,13 +1,16 @@ #version 330 core #include +#include in vec3 f_pos; +in vec3 f_chunk_pos; flat in uint f_pos_norm; in float f_alt; in vec4 f_shadow; in vec3 f_col; in float f_light; +in float f_ao; out vec4 tgt_color; @@ -65,6 +68,10 @@ void main() { reflected_light *= f_light * point_shadow * shade_frac; max_light += lights_at(f_pos, f_norm, view_dir, k_a, k_d, k_s, alpha, emitted_light, reflected_light); + + float ao = pow(f_ao, 0.5) * 0.9 + 0.1; + emitted_light *= ao; + reflected_light *= ao; /* vec3 point_light = light_at(f_pos, f_norm); emitted_light += point_light; reflected_light += point_light; */ @@ -87,7 +94,8 @@ void main() { // light_reflection_factorplight_reflection_factor // vec3 surf_color = illuminate(srgb_to_linear(f_col), light, diffuse_light, ambient_light); - surf_color = illuminate(f_col * emitted_light, f_col * reflected_light); + vec3 col = srgb_to_linear(f_col + hash(vec4(floor(f_chunk_pos * 3.0 - f_norm * 0.5), 0)) * 0.02); // Small-scale noise + surf_color = illuminate(col * emitted_light, col * reflected_light); float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x); vec4 clouds; diff --git a/assets/voxygen/shaders/terrain-vert.glsl b/assets/voxygen/shaders/terrain-vert.glsl index c210788c03..86d6d37bc9 100644 --- a/assets/voxygen/shaders/terrain-vert.glsl +++ b/assets/voxygen/shaders/terrain-vert.glsl @@ -14,21 +14,27 @@ uniform u_locals { }; out vec3 f_pos; +out vec3 f_chunk_pos; flat out uint f_pos_norm; out float f_alt; out vec4 f_shadow; out vec3 f_col; out float f_light; +out float f_ao; + +const int EXTRA_NEG_Z = 65536; void main() { - f_pos = vec3((uvec3(v_pos_norm) >> uvec3(0, 8, 16)) & uvec3(0xFFu, 0xFFu, 0x1FFFu)) + model_offs; + f_chunk_pos = vec3(ivec3((uvec3(v_pos_norm) >> uvec3(0, 6, 12)) & uvec3(0x3Fu, 0x3Fu, 0x1FFFFu)) - ivec3(0, 0, EXTRA_NEG_Z)); + f_pos = f_chunk_pos + model_offs; - f_pos.z *= min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.0); - f_pos.z -= min(32.0, pow(distance(focus_pos.xy, f_pos.xy) / (view_distance.x * 0.95), 20.0)); + f_pos.z -= 250.0 * (1.0 - min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.0)); + f_pos.z -= min(32.0, 25.0 * pow(distance(focus_pos.xy, f_pos.xy) / view_distance.x, 20.0)); - f_col = srgb_to_linear(vec3((uvec3(v_col_light) >> uvec3(8, 16, 24)) & uvec3(0xFFu)) / 255.0); + f_col = vec3((uvec3(v_col_light) >> uvec3(8, 16, 24)) & uvec3(0xFFu)) / 255.0; - f_light = float(v_col_light & 0xFFu) / 255.0; + f_light = float(v_col_light & 0x3Fu) / 64.0; + f_ao = float((v_col_light >> 6u) & 3u) / 4.0; f_pos_norm = v_pos_norm; diff --git a/assets/voxygen/voxel/armor/back/admin.vox b/assets/voxygen/voxel/armor/back/admin.vox new file mode 100644 index 0000000000..3f11d5f1da --- /dev/null +++ b/assets/voxygen/voxel/armor/back/admin.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37ccc4f22a88b5cf3a4ad9017297458d2444d0eb5f71c0505e90907e19b1dc53 +size 1496 diff --git a/assets/voxygen/voxel/armor/back/short-0.vox b/assets/voxygen/voxel/armor/back/short-0.vox index b9c5362a58..135b2afb14 100644 --- a/assets/voxygen/voxel/armor/back/short-0.vox +++ b/assets/voxygen/voxel/armor/back/short-0.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9c9e63b1debc909844e95ad7eb8b16c3dc94d1362b541fca7028f24dc2398fbd -size 44573 +oid sha256:d6d796f4f333329f6b84d63748bd217e055a3f0b4e22342eef59566e8fff0daf +size 1456 diff --git a/assets/voxygen/voxel/armor/chest/rugged-0.vox b/assets/voxygen/voxel/armor/chest/rugged-0.vox new file mode 100644 index 0000000000..f042bf2d91 --- /dev/null +++ b/assets/voxygen/voxel/armor/chest/rugged-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f1b5e831f03af420439b79abb1b71d33846823acbeffd73f54bc2119bd376576 +size 2664 diff --git a/assets/voxygen/voxel/armor/foot/cloth_sandals.vox b/assets/voxygen/voxel/armor/foot/cloth_sandals.vox index 10819d8277..e56314497f 100644 --- a/assets/voxygen/voxel/armor/foot/cloth_sandals.vox +++ b/assets/voxygen/voxel/armor/foot/cloth_sandals.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5471fbe958ae4bc347fc5e96edf680b7cc908952c9559f890e04d55a9b045ce8 -size 55899 +oid sha256:b857b019bd55b00e4027e6dcaefcc924429a8cfa3e2515ed6085265bed68b58d +size 1376 diff --git a/assets/voxygen/voxel/armor/head/assa_mask.vox b/assets/voxygen/voxel/armor/head/assa_mask-0.vox similarity index 100% rename from assets/voxygen/voxel/armor/head/assa_mask.vox rename to assets/voxygen/voxel/armor/head/assa_mask-0.vox diff --git a/assets/voxygen/voxel/armor/head/leather-0.vox b/assets/voxygen/voxel/armor/head/leather-0.vox new file mode 100644 index 0000000000..38c96da432 --- /dev/null +++ b/assets/voxygen/voxel/armor/head/leather-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a3bbe6cec3a39918de1b9e2be5f1a99d589599bd291dbd90ea6a775d267e0e2 +size 2864 diff --git a/assets/voxygen/voxel/armor/lantern/black-0.vox b/assets/voxygen/voxel/armor/lantern/black-0.vox new file mode 100644 index 0000000000..620f002bf3 --- /dev/null +++ b/assets/voxygen/voxel/armor/lantern/black-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3057877edf5bd955d80fed44b00f64c0fae9287f4cb9cb417b3cdcefc899d424 +size 1384 diff --git a/assets/voxygen/voxel/armor/lantern/green-0.vox b/assets/voxygen/voxel/armor/lantern/green-0.vox new file mode 100644 index 0000000000..6305f11288 --- /dev/null +++ b/assets/voxygen/voxel/armor/lantern/green-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ee771034347313e31f4518019911b832deb71f074494225e9b771b485826257 +size 1384 diff --git a/assets/voxygen/voxel/armor/pants/rugged-0.vox b/assets/voxygen/voxel/armor/pants/rugged-0.vox new file mode 100644 index 0000000000..9a28595d1f --- /dev/null +++ b/assets/voxygen/voxel/armor/pants/rugged-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7ca447e4c5668329b0c1e8885af9507aee103e4ac7031b43544f7d4cb0a601b +size 1880 diff --git a/assets/voxygen/voxel/biped_large_lateral_manifest.ron b/assets/voxygen/voxel/biped_large_lateral_manifest.ron index 764872c8ef..31855a5fbd 100644 --- a/assets/voxygen/voxel/biped_large_lateral_manifest.ron +++ b/assets/voxygen/voxel/biped_large_lateral_manifest.ron @@ -9,27 +9,27 @@ lateral: ("npc.giant.male.shoulder_r"), ), hand_l: ( - offset: (-3.0, -2.5, -14.0), + offset: (-2.5, -2.5, -14.0), lateral: ("npc.giant.male.hand_l"), ), hand_r: ( - offset: (-3.0, -2.5, -14.0), + offset: (-2.5, -2.5, -14.0), lateral: ("npc.giant.male.hand_r"), ), leg_l: ( - offset: (-3.0, -3.5, -7.0), + offset: (-6.0, -3.5, -7.0), lateral: ("npc.giant.male.leg_l"), ), leg_r: ( - offset: (-3.0, -3.5, -7.0), + offset: (0.0, -3.5, -7.0), lateral: ("npc.giant.male.leg_r"), ), foot_l: ( - offset: (-3.0, -5.5, -10.5), + offset: (-3.0, -5.0, -2.5), lateral: ("npc.giant.male.foot_l"), ), foot_r: ( - offset: (-3.0, -5.5, -10.5), + offset: (-3.0, -5.0, -2.5), lateral: ("npc.giant.male.foot_r"), ) ), @@ -43,27 +43,27 @@ lateral: ("npc.giant.female.shoulder_r"), ), hand_l: ( - offset: (-3.0, -2.5, -14.0), + offset: (-2.5, -2.5, -14.0), lateral: ("npc.giant.female.hand_l"), ), hand_r: ( - offset: (-3.0, -2.5, -14.0), + offset: (-2.5, -2.5, -14.0), lateral: ("npc.giant.female.hand_r"), ), leg_l: ( - offset: (-3.0, -3.5, -7.0), + offset: (-6.0, -3.5, -7.0), lateral: ("npc.giant.female.leg_l"), ), leg_r: ( - offset: (-3.0, -3.5, -7.0), + offset: (0.0, -3.5, -7.0), lateral: ("npc.giant.female.leg_r"), ), foot_l: ( - offset: (-3.0, -5.5, -10.5), + offset: (-3.0, -5.0, -2.5), lateral: ("npc.giant.female.foot_l"), ), foot_r: ( - offset: (-3.0, -5.5, -10.5), + offset: (-3.0, -5.0, -2.5), lateral: ("npc.giant.female.foot_r"), ) ), diff --git a/assets/voxygen/voxel/figure/accessory/dwarf/earring-0.vox b/assets/voxygen/voxel/figure/accessory/dwarf/earring-0.vox new file mode 100644 index 0000000000..660da2a342 --- /dev/null +++ b/assets/voxygen/voxel/figure/accessory/dwarf/earring-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:825f48525b0775ab97acc925f88a8710fa741a5c069f64322dc3b1debb36e15c +size 1100 diff --git a/assets/voxygen/voxel/figure/accessory/dwarf/earring-1.vox b/assets/voxygen/voxel/figure/accessory/dwarf/earring-1.vox new file mode 100644 index 0000000000..e6c0c7506e --- /dev/null +++ b/assets/voxygen/voxel/figure/accessory/dwarf/earring-1.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4b759ac1e5bd7b85fbd6bf1d170f523f581fb5e4d94ce941adba9a0c7557624 +size 1116 diff --git a/assets/voxygen/voxel/figure/accessory/dwarf/earrings-0.vox b/assets/voxygen/voxel/figure/accessory/dwarf/earrings-0.vox new file mode 100644 index 0000000000..0a85b77949 --- /dev/null +++ b/assets/voxygen/voxel/figure/accessory/dwarf/earrings-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12b9e1cdf37b5287cf20770bc410e1d5cc10f2952ebb7668e057cb4dab85d6cb +size 1112 diff --git a/assets/voxygen/voxel/figure/accessory/dwarf/earrings-1.vox b/assets/voxygen/voxel/figure/accessory/dwarf/earrings-1.vox new file mode 100644 index 0000000000..7b02eb5994 --- /dev/null +++ b/assets/voxygen/voxel/figure/accessory/dwarf/earrings-1.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:924a8f01bfa5a76b62662cc65e0a7b720ac83b98e63406fbc2384baf052e43eb +size 1104 diff --git a/assets/voxygen/voxel/figure/accessory/dwarf/earrings-2.vox b/assets/voxygen/voxel/figure/accessory/dwarf/earrings-2.vox new file mode 100644 index 0000000000..6b4b711489 --- /dev/null +++ b/assets/voxygen/voxel/figure/accessory/dwarf/earrings-2.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10138059c10f600fcea7edd953a918ab1926eb5198a96553bca5d489e6c338d6 +size 1136 diff --git a/assets/voxygen/voxel/figure/accessory/dwarf/earrings-3.vox b/assets/voxygen/voxel/figure/accessory/dwarf/earrings-3.vox new file mode 100644 index 0000000000..94af6ecec1 --- /dev/null +++ b/assets/voxygen/voxel/figure/accessory/dwarf/earrings-3.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4afbdb84c194b2e33bf11ca4780e9aa2ccb287e0a2f6192a7537f7780e5acba +size 1136 diff --git a/assets/voxygen/voxel/figure/accessory/dwarf/warpaint-0.vox b/assets/voxygen/voxel/figure/accessory/dwarf/warpaint-0.vox index 6384b30de5..3fc71e8bd5 100644 --- a/assets/voxygen/voxel/figure/accessory/dwarf/warpaint-0.vox +++ b/assets/voxygen/voxel/figure/accessory/dwarf/warpaint-0.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ee5ef8c75028aac457778b2f5fb20be0c8f2bc71eb71787f506ece2aa27d6583 -size 1304 +oid sha256:ffd9a866608e2fb86dc61fdfb64a7479a2ec30d1917cae1acb55d25087e29078 +size 1232 diff --git a/assets/voxygen/voxel/humanoid_armor_back_manifest.ron b/assets/voxygen/voxel/humanoid_armor_back_manifest.ron new file mode 100644 index 0000000000..5165820bc9 --- /dev/null +++ b/assets/voxygen/voxel/humanoid_armor_back_manifest.ron @@ -0,0 +1,16 @@ +(( + default: ( + vox_spec: ("armor.empty", (0.0, 0.0, 0.0)), + color: None + ), + map: { + Short0: ( + vox_spec: ("armor.back.short-0", (-5.0, -1.0, -11.0)), + color: None + ), + Admin: ( + vox_spec: ("armor.back.admin", (-5.0, -1.0, -11.0)), + color: None + ), + }, +)) diff --git a/assets/voxygen/voxel/humanoid_armor_chest_manifest.ron b/assets/voxygen/voxel/humanoid_armor_chest_manifest.ron index a311ef9ae7..f64477d328 100644 --- a/assets/voxygen/voxel/humanoid_armor_chest_manifest.ron +++ b/assets/voxygen/voxel/humanoid_armor_chest_manifest.ron @@ -56,5 +56,9 @@ vox_spec: ("armor.chest.cloth_green-0", (-7.0, -3.5, 1.0)), color: None ), + Rugged0:( + vox_spec: ("armor.chest.rugged-0", (-7.0, -3.5, 2.0)), + color: None + ), }, )) diff --git a/assets/voxygen/voxel/humanoid_armor_foot_manifest.ron b/assets/voxygen/voxel/humanoid_armor_foot_manifest.ron index 99f2e8580e..ed7d87f038 100644 --- a/assets/voxygen/voxel/humanoid_armor_foot_manifest.ron +++ b/assets/voxygen/voxel/humanoid_armor_foot_manifest.ron @@ -11,11 +11,7 @@ Assassin: ( vox_spec: ("armor.foot.assa", (-2.5, -3.5, -9.0)), color: None - ), - Sandal: ( - vox_spec: ("armor.foot.cloth_sandals", (-2.5, -2.5, -9.0)), - color: None - ), + ), Jester: ( vox_spec: ("armor.foot.dark_jester-elf_shoe", (-2.5, -3.0, -9.0)), color: None @@ -40,5 +36,9 @@ vox_spec: ("armor.foot.cloth_green-0", (-2.5, -3.5, -9.0)), color: None ), + Sandal0:( + vox_spec: ("armor.foot.cloth_sandals", (-2.5, -3.5, -9.0)), + color: None + ), }, )) diff --git a/assets/voxygen/voxel/humanoid_armor_pants_manifest.ron b/assets/voxygen/voxel/humanoid_armor_pants_manifest.ron index 8d04e54fa2..9ddb0f00f4 100644 --- a/assets/voxygen/voxel/humanoid_armor_pants_manifest.ron +++ b/assets/voxygen/voxel/humanoid_armor_pants_manifest.ron @@ -52,5 +52,9 @@ vox_spec: ("armor.pants.cloth_green-0", (-5.0, -3.5, 0.0)), color: None ), + Rugged0:( + vox_spec: ("armor.pants.rugged-0", (-5.0, -3.5, 1.0)), + color: None + ), }, )) diff --git a/assets/voxygen/voxel/humanoid_head_manifest.ron b/assets/voxygen/voxel/humanoid_head_manifest.ron index ea061ff7f2..483a191315 100644 --- a/assets/voxygen/voxel/humanoid_head_manifest.ron +++ b/assets/voxygen/voxel/humanoid_head_manifest.ron @@ -194,8 +194,14 @@ Some(("figure.beard.dwarf.dwarf-20", (1, 7,-4))), ], accessory: [ - None, - Some(("figure.accessory.elf.warpaint-0", (6, 9, 4))), ] + None, + Some(("figure.accessory.dwarf.earring-0", (0, 3, 0))), + Some(("figure.accessory.dwarf.earring-1", (0, 3, 0))), + Some(("figure.accessory.dwarf.earrings-0", (0, 3, 0))), + Some(("figure.accessory.dwarf.earrings-1", (0, 3, 0))), + Some(("figure.accessory.dwarf.earrings-2", (0, 3, 0))), + Some(("figure.accessory.dwarf.earrings-3", (0, 3, 0))), + ] ), (Dwarf, Female): ( offset: (-6.0, -4.5, -6.0), @@ -209,7 +215,14 @@ ], beard: [None], accessory: [ - None] + None, + Some(("figure.accessory.dwarf.earring-0", (0, 3, 0))), + Some(("figure.accessory.dwarf.earring-1", (0, 3, 0))), + Some(("figure.accessory.dwarf.earrings-0", (0, 3, 0))), + Some(("figure.accessory.dwarf.earrings-1", (0, 3, 0))), + Some(("figure.accessory.dwarf.earrings-2", (0, 3, 0))), + Some(("figure.accessory.dwarf.earrings-3", (0, 3, 0))), + ] ), (Undead, Male): ( offset: (-5.0, -4.0, -6.75), diff --git a/assets/voxygen/voxel/humanoid_lantern_manifest.ron b/assets/voxygen/voxel/humanoid_lantern_manifest.ron new file mode 100644 index 0000000000..2ad5c02a5b --- /dev/null +++ b/assets/voxygen/voxel/humanoid_lantern_manifest.ron @@ -0,0 +1,16 @@ +(( + default: ( + vox_spec: ("armor.empty", (0.0, 0.0, 0.0)), + color: None + ), + map: { + Green0: ( + vox_spec: ("armor.lantern.green-0", (-2.0, -2.0, -7.0)), + color: None + ), + Black0: ( + vox_spec: ("armor.lantern.black-0", (-2.0, -2.0, -7.0)), + color: None + ), + }, +)) diff --git a/assets/voxygen/voxel/humanoid_main_weapon_manifest.ron b/assets/voxygen/voxel/humanoid_main_weapon_manifest.ron index 5a689b103d..04e8a06384 100644 --- a/assets/voxygen/voxel/humanoid_main_weapon_manifest.ron +++ b/assets/voxygen/voxel/humanoid_main_weapon_manifest.ron @@ -55,4 +55,8 @@ vox_spec: ("weapon.debug_wand", (-1.5, -9.5, -4.0)), color: None ), + Empty: ( + vox_spec: ("armor.empty", (-3.0, -3.5, 1.0)), + color: None + ), }) diff --git a/assets/voxygen/voxel/lantern/black-0.vox b/assets/voxygen/voxel/lantern/black-0.vox new file mode 100644 index 0000000000..2251bf2726 --- /dev/null +++ b/assets/voxygen/voxel/lantern/black-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3fa40e891c67261cae80c21223f6ec1ef7d02b0e2546396e07547c2e19079857 +size 56375 diff --git a/assets/voxygen/voxel/lantern/green-0.vox b/assets/voxygen/voxel/lantern/green-0.vox new file mode 100644 index 0000000000..a3ab8e8da7 --- /dev/null +++ b/assets/voxygen/voxel/lantern/green-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e218a41ce27b3dd7af39c67d657881f304d39a5cda0fffe1f0958222a35d577b +size 44999 diff --git a/assets/voxygen/voxel/not_found.vox b/assets/voxygen/voxel/not_found.vox index c80a4bc22e..ed0d8f67c2 100644 --- a/assets/voxygen/voxel/not_found.vox +++ b/assets/voxygen/voxel/not_found.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9e7a3fff54d0f4ba4f32a2bcd8aec0248728c404ff76de43206b24a2e794b1ff -size 49528 +oid sha256:32132c9ad676cf436ed2ec1540b17378587cc94de6d2d8f03bc88c057fcf1310 +size 6420 diff --git a/assets/voxygen/voxel/npc/giant/female/foot_l.vox b/assets/voxygen/voxel/npc/giant/female/foot_l.vox index a6e7f95b14..6a6f33e1e8 100644 --- a/assets/voxygen/voxel/npc/giant/female/foot_l.vox +++ b/assets/voxygen/voxel/npc/giant/female/foot_l.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0381fb00349446b2b7bb9b3569037bc745699bfaf2ade019efde70e233a5c4c3 -size 1964 +oid sha256:28d297cc21308270571f67ffd2d89d9f2019e24577fea3f17bbe5f18d72f6dca +size 1920 diff --git a/assets/voxygen/voxel/npc/giant/female/foot_r.vox b/assets/voxygen/voxel/npc/giant/female/foot_r.vox index a6e7f95b14..08efe9ec67 100644 --- a/assets/voxygen/voxel/npc/giant/female/foot_r.vox +++ b/assets/voxygen/voxel/npc/giant/female/foot_r.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0381fb00349446b2b7bb9b3569037bc745699bfaf2ade019efde70e233a5c4c3 -size 1964 +oid sha256:f399f33fd5f0f3abdb6b6466ee28fed54e323fe4dee2f2c5accc4fac375fa00f +size 1920 diff --git a/assets/voxygen/voxel/npc/giant/female/hand_l.vox b/assets/voxygen/voxel/npc/giant/female/hand_l.vox index 25decb62bc..3209a770c9 100644 --- a/assets/voxygen/voxel/npc/giant/female/hand_l.vox +++ b/assets/voxygen/voxel/npc/giant/female/hand_l.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:944eddaefc49572e18222028419cc2ecf492bc90a665c43ab7ecb1d8c8bd8044 -size 1392 +oid sha256:e4d18bfbe4f6fd079d295238bae40a491c9b2c13c7a6d538b4c3adf94b5ce88c +size 1576 diff --git a/assets/voxygen/voxel/npc/giant/female/hand_r.vox b/assets/voxygen/voxel/npc/giant/female/hand_r.vox index 958ee44410..7447d81403 100644 --- a/assets/voxygen/voxel/npc/giant/female/hand_r.vox +++ b/assets/voxygen/voxel/npc/giant/female/hand_r.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7f35050d1534a1add47893c9651ec220aac67e6190747a4b3c947744c9a9a36 -size 1392 +oid sha256:5492321b3af03ffc78f162a0f40e7fa1e1fb6f7285327ca47af043b3c43d63ac +size 1576 diff --git a/assets/voxygen/voxel/npc/giant/female/head.vox b/assets/voxygen/voxel/npc/giant/female/head.vox index c8788e4449..0ab6f106de 100644 --- a/assets/voxygen/voxel/npc/giant/female/head.vox +++ b/assets/voxygen/voxel/npc/giant/female/head.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bbeea43aec6edc007c8d271271837664edbd46400200bf5f0bbfe2332f5c6675 -size 5756 +oid sha256:8ccc48349b2beb4a9c5a416e460bfe31ec0246c7aab0ad65b3c176eb27380c1d +size 5860 diff --git a/assets/voxygen/voxel/npc/giant/female/leg_l.vox b/assets/voxygen/voxel/npc/giant/female/leg_l.vox index 8172ab59a3..250ae798fb 100644 --- a/assets/voxygen/voxel/npc/giant/female/leg_l.vox +++ b/assets/voxygen/voxel/npc/giant/female/leg_l.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c3565904f0822154d0474ea6498a02a9ee3604fa919f6417af0fdaf49014ed25 -size 1576 +oid sha256:dfb9da63ede02f3d4cac323d7152d7764b3144e517cfc46fe1ceb1f9accf0378 +size 1704 diff --git a/assets/voxygen/voxel/npc/giant/female/leg_r.vox b/assets/voxygen/voxel/npc/giant/female/leg_r.vox index 35057ff177..c2ac8a3699 100644 --- a/assets/voxygen/voxel/npc/giant/female/leg_r.vox +++ b/assets/voxygen/voxel/npc/giant/female/leg_r.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d2f75264495aaf6ad3568758bd6fdf53a2f9cf05d2d1a7dcacc0735253ed6564 -size 1576 +oid sha256:817b57a4b69c99e8c34c908a33d8436867a1563e25d2b632b6519b15c3b72512 +size 1704 diff --git a/assets/voxygen/voxel/npc/giant/female/torso_lower.vox b/assets/voxygen/voxel/npc/giant/female/torso_lower.vox index 14e262784c..bfbc82b50b 100644 --- a/assets/voxygen/voxel/npc/giant/female/torso_lower.vox +++ b/assets/voxygen/voxel/npc/giant/female/torso_lower.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8e9c3d59e74bb7c1c3fee2bd0a63b78e2e29ddd34322a7260bf575d743c0434e +oid sha256:6b4b9201910750c3d04924faaaaf34e6bb7283d33f6fb6208a9696d767f01ed7 size 2172 diff --git a/assets/voxygen/voxel/npc/giant/female/torso_upper.vox b/assets/voxygen/voxel/npc/giant/female/torso_upper.vox index 56b1bffafe..62c7c557a9 100644 --- a/assets/voxygen/voxel/npc/giant/female/torso_upper.vox +++ b/assets/voxygen/voxel/npc/giant/female/torso_upper.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62ef18d8a1d599b5de13a6d383af92562aa982dde6fca3356400138614d22657 -size 4932 +oid sha256:2f92b25d03056e0097910f0898e8f2cbaedf062aaeab60d5ba68a524976ec5ec +size 4980 diff --git a/assets/voxygen/voxel/npc/giant/male/foot_l.vox b/assets/voxygen/voxel/npc/giant/male/foot_l.vox index a6e7f95b14..6a6f33e1e8 100644 --- a/assets/voxygen/voxel/npc/giant/male/foot_l.vox +++ b/assets/voxygen/voxel/npc/giant/male/foot_l.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0381fb00349446b2b7bb9b3569037bc745699bfaf2ade019efde70e233a5c4c3 -size 1964 +oid sha256:28d297cc21308270571f67ffd2d89d9f2019e24577fea3f17bbe5f18d72f6dca +size 1920 diff --git a/assets/voxygen/voxel/npc/giant/male/foot_r.vox b/assets/voxygen/voxel/npc/giant/male/foot_r.vox index a6e7f95b14..08efe9ec67 100644 --- a/assets/voxygen/voxel/npc/giant/male/foot_r.vox +++ b/assets/voxygen/voxel/npc/giant/male/foot_r.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0381fb00349446b2b7bb9b3569037bc745699bfaf2ade019efde70e233a5c4c3 -size 1964 +oid sha256:f399f33fd5f0f3abdb6b6466ee28fed54e323fe4dee2f2c5accc4fac375fa00f +size 1920 diff --git a/assets/voxygen/voxel/npc/giant/male/hand_l.vox b/assets/voxygen/voxel/npc/giant/male/hand_l.vox index 25decb62bc..3209a770c9 100644 --- a/assets/voxygen/voxel/npc/giant/male/hand_l.vox +++ b/assets/voxygen/voxel/npc/giant/male/hand_l.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:944eddaefc49572e18222028419cc2ecf492bc90a665c43ab7ecb1d8c8bd8044 -size 1392 +oid sha256:e4d18bfbe4f6fd079d295238bae40a491c9b2c13c7a6d538b4c3adf94b5ce88c +size 1576 diff --git a/assets/voxygen/voxel/npc/giant/male/hand_r.vox b/assets/voxygen/voxel/npc/giant/male/hand_r.vox index 958ee44410..7447d81403 100644 --- a/assets/voxygen/voxel/npc/giant/male/hand_r.vox +++ b/assets/voxygen/voxel/npc/giant/male/hand_r.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7f35050d1534a1add47893c9651ec220aac67e6190747a4b3c947744c9a9a36 -size 1392 +oid sha256:5492321b3af03ffc78f162a0f40e7fa1e1fb6f7285327ca47af043b3c43d63ac +size 1576 diff --git a/assets/voxygen/voxel/npc/giant/male/head.vox b/assets/voxygen/voxel/npc/giant/male/head.vox index c8788e4449..0ab6f106de 100644 --- a/assets/voxygen/voxel/npc/giant/male/head.vox +++ b/assets/voxygen/voxel/npc/giant/male/head.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bbeea43aec6edc007c8d271271837664edbd46400200bf5f0bbfe2332f5c6675 -size 5756 +oid sha256:8ccc48349b2beb4a9c5a416e460bfe31ec0246c7aab0ad65b3c176eb27380c1d +size 5860 diff --git a/assets/voxygen/voxel/npc/giant/male/leg_l.vox b/assets/voxygen/voxel/npc/giant/male/leg_l.vox index 8172ab59a3..250ae798fb 100644 --- a/assets/voxygen/voxel/npc/giant/male/leg_l.vox +++ b/assets/voxygen/voxel/npc/giant/male/leg_l.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c3565904f0822154d0474ea6498a02a9ee3604fa919f6417af0fdaf49014ed25 -size 1576 +oid sha256:dfb9da63ede02f3d4cac323d7152d7764b3144e517cfc46fe1ceb1f9accf0378 +size 1704 diff --git a/assets/voxygen/voxel/npc/giant/male/leg_r.vox b/assets/voxygen/voxel/npc/giant/male/leg_r.vox index 35057ff177..c2ac8a3699 100644 --- a/assets/voxygen/voxel/npc/giant/male/leg_r.vox +++ b/assets/voxygen/voxel/npc/giant/male/leg_r.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d2f75264495aaf6ad3568758bd6fdf53a2f9cf05d2d1a7dcacc0735253ed6564 -size 1576 +oid sha256:817b57a4b69c99e8c34c908a33d8436867a1563e25d2b632b6519b15c3b72512 +size 1704 diff --git a/assets/voxygen/voxel/npc/giant/male/torso_lower.vox b/assets/voxygen/voxel/npc/giant/male/torso_lower.vox index 14e262784c..bfbc82b50b 100644 --- a/assets/voxygen/voxel/npc/giant/male/torso_lower.vox +++ b/assets/voxygen/voxel/npc/giant/male/torso_lower.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8e9c3d59e74bb7c1c3fee2bd0a63b78e2e29ddd34322a7260bf575d743c0434e +oid sha256:6b4b9201910750c3d04924faaaaf34e6bb7283d33f6fb6208a9696d767f01ed7 size 2172 diff --git a/assets/voxygen/voxel/npc/giant/male/torso_upper.vox b/assets/voxygen/voxel/npc/giant/male/torso_upper.vox index 56b1bffafe..62c7c557a9 100644 --- a/assets/voxygen/voxel/npc/giant/male/torso_upper.vox +++ b/assets/voxygen/voxel/npc/giant/male/torso_upper.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62ef18d8a1d599b5de13a6d383af92562aa982dde6fca3356400138614d22657 -size 4932 +oid sha256:2f92b25d03056e0097910f0898e8f2cbaedf062aaeab60d5ba68a524976ec5ec +size 4980 diff --git a/assets/voxygen/voxel/object/lantern0.vox b/assets/voxygen/voxel/object/lantern0.vox new file mode 100644 index 0000000000..2251bf2726 --- /dev/null +++ b/assets/voxygen/voxel/object/lantern0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3fa40e891c67261cae80c21223f6ec1ef7d02b0e2546396e07547c2e19079857 +size 56375 diff --git a/assets/voxygen/voxel/sprite/cabbage/cabbage-0.vox b/assets/voxygen/voxel/sprite/cabbage/cabbage-0.vox new file mode 100644 index 0000000000..44e668cd82 --- /dev/null +++ b/assets/voxygen/voxel/sprite/cabbage/cabbage-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d791e42efa4d87cffa51e09bfe308576390ae1ce392e2c2ec971ce41a0ee35b5 +size 2064 diff --git a/assets/voxygen/voxel/sprite/cabbage/cabbage-1.vox b/assets/voxygen/voxel/sprite/cabbage/cabbage-1.vox new file mode 100644 index 0000000000..34e4d5cb5e --- /dev/null +++ b/assets/voxygen/voxel/sprite/cabbage/cabbage-1.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eaa8cf37b170c93a1c6ecf5a71376ed041d5e1a58faedcb96ee3f128aa5fa2d4 +size 2064 diff --git a/assets/voxygen/voxel/sprite/cabbage/cabbage-2.vox b/assets/voxygen/voxel/sprite/cabbage/cabbage-2.vox new file mode 100644 index 0000000000..da5cdbed44 --- /dev/null +++ b/assets/voxygen/voxel/sprite/cabbage/cabbage-2.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9bc7359384c83b0e71f67df185e0c86594375335cf24845469f3f873985c9147 +size 2064 diff --git a/assets/voxygen/voxel/sprite/carrot/0.vox b/assets/voxygen/voxel/sprite/carrot/0.vox new file mode 100644 index 0000000000..0a31db6a43 --- /dev/null +++ b/assets/voxygen/voxel/sprite/carrot/0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bdccd8d4e34a2bc43ef75d406b01806b757a2602ee300d58c82ea2818ebb45b8 +size 1716 diff --git a/assets/voxygen/voxel/sprite/carrot/1.vox b/assets/voxygen/voxel/sprite/carrot/1.vox new file mode 100644 index 0000000000..a34498bf76 --- /dev/null +++ b/assets/voxygen/voxel/sprite/carrot/1.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbe059eaf30ea72eb10981c8f6f52b9604c2fbf161240e298240c580d5c6cf81 +size 1560 diff --git a/assets/voxygen/voxel/sprite/carrot/2.vox b/assets/voxygen/voxel/sprite/carrot/2.vox new file mode 100644 index 0000000000..fa50b10e15 --- /dev/null +++ b/assets/voxygen/voxel/sprite/carrot/2.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b29b616c5557510e857735d694e05af85668f0e8b44b9096ad0a1d1a1d7d03f1 +size 1664 diff --git a/assets/voxygen/voxel/sprite/carrot/3.vox b/assets/voxygen/voxel/sprite/carrot/3.vox new file mode 100644 index 0000000000..91f533832a --- /dev/null +++ b/assets/voxygen/voxel/sprite/carrot/3.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14e7624d5fc4d068f3886a00a389e6fd25f9b2c027bb3b51a9d577f404dc51b0 +size 1656 diff --git a/assets/voxygen/voxel/sprite/carrot/4.vox b/assets/voxygen/voxel/sprite/carrot/4.vox new file mode 100644 index 0000000000..f7de3722ac --- /dev/null +++ b/assets/voxygen/voxel/sprite/carrot/4.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0d9442ed362367c675d33552b5fbe0157c69ce900860ce5667414ad05de19b8 +size 1672 diff --git a/assets/voxygen/voxel/sprite/carrot/5.vox b/assets/voxygen/voxel/sprite/carrot/5.vox new file mode 100644 index 0000000000..ceb03ecdbc --- /dev/null +++ b/assets/voxygen/voxel/sprite/carrot/5.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f97c8894e1c607dd9d71967e33fa5221be743e21aef0fae5587c0011de290035 +size 1632 diff --git a/assets/voxygen/voxel/sprite/corn/corn-0.vox b/assets/voxygen/voxel/sprite/corn/corn-0.vox new file mode 100644 index 0000000000..e1eba1c9cd --- /dev/null +++ b/assets/voxygen/voxel/sprite/corn/corn-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8d6525d00da9357c5a732b32dfcced3761888a72709186498d22614d1c90c8c +size 2252 diff --git a/assets/voxygen/voxel/sprite/corn/corn-1.vox b/assets/voxygen/voxel/sprite/corn/corn-1.vox new file mode 100644 index 0000000000..2368757a4a --- /dev/null +++ b/assets/voxygen/voxel/sprite/corn/corn-1.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a933f0ba129c777c5fd1bae7db55afe1caf7680a7b91467073aee2f7d134fc2 +size 2420 diff --git a/assets/voxygen/voxel/sprite/corn/corn-2.vox b/assets/voxygen/voxel/sprite/corn/corn-2.vox new file mode 100644 index 0000000000..9296f2dfc2 --- /dev/null +++ b/assets/voxygen/voxel/sprite/corn/corn-2.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b41032bc335fcae7f526873b0e54bd0bfb3ce182811af03b747c92d38485173 +size 2204 diff --git a/assets/voxygen/voxel/sprite/corn/corn-3.vox b/assets/voxygen/voxel/sprite/corn/corn-3.vox new file mode 100644 index 0000000000..6315ee2469 --- /dev/null +++ b/assets/voxygen/voxel/sprite/corn/corn-3.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4cecfceca8280aa54b30382e3913d041d3d30658d9f38edd94b84365cd72a8bd +size 1832 diff --git a/assets/voxygen/voxel/sprite/corn/corn-4.vox b/assets/voxygen/voxel/sprite/corn/corn-4.vox new file mode 100644 index 0000000000..7723d17f82 --- /dev/null +++ b/assets/voxygen/voxel/sprite/corn/corn-4.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eaee492532b274e1f2e405c64fcf6a01f30e5a799681b4ef0d3ac9bdc37a06be +size 2096 diff --git a/assets/voxygen/voxel/sprite/corn/corn-5.vox b/assets/voxygen/voxel/sprite/corn/corn-5.vox new file mode 100644 index 0000000000..e7c78406bd --- /dev/null +++ b/assets/voxygen/voxel/sprite/corn/corn-5.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ffefe83d5d1894158672664287b88593a593790a7dc56a861297bfea095971e0 +size 2252 diff --git a/assets/voxygen/voxel/sprite/door/door-0.vox b/assets/voxygen/voxel/sprite/door/door-0.vox new file mode 100644 index 0000000000..87960f0ef8 --- /dev/null +++ b/assets/voxygen/voxel/sprite/door/door-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4f201e1f3968ed005eb8080cf3ff181f3ec34f42ddba17dc09e30adb180c36c +size 6156 diff --git a/assets/voxygen/voxel/sprite/ember/1.vox b/assets/voxygen/voxel/sprite/ember/1.vox new file mode 100644 index 0000000000..f7b6f438ee --- /dev/null +++ b/assets/voxygen/voxel/sprite/ember/1.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4fb63355560eb8f6d4661f8e3eb41ea9a5b3ede8628a84c3d36d5c1063e7154c +size 3124 diff --git a/assets/voxygen/voxel/sprite/flax/flax-0.vox b/assets/voxygen/voxel/sprite/flax/flax-0.vox new file mode 100644 index 0000000000..b98bde1434 --- /dev/null +++ b/assets/voxygen/voxel/sprite/flax/flax-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb094f06bb530d44dab7581539de04bb011208f4883e40999be6445538cb6e5a +size 1664 diff --git a/assets/voxygen/voxel/sprite/flax/flax-1.vox b/assets/voxygen/voxel/sprite/flax/flax-1.vox new file mode 100644 index 0000000000..d0c80d4766 --- /dev/null +++ b/assets/voxygen/voxel/sprite/flax/flax-1.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d7ee17a1f40778a071929be44f7d63e449fcb7dc35ac3fa5aa20bf38e9252c3 +size 1768 diff --git a/assets/voxygen/voxel/sprite/flax/flax-2.vox b/assets/voxygen/voxel/sprite/flax/flax-2.vox new file mode 100644 index 0000000000..af5b27d1a7 --- /dev/null +++ b/assets/voxygen/voxel/sprite/flax/flax-2.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:632b581575bae82cf5b9a106bd11a98fbed8c7b85b85fc652a689a890a2d985b +size 1800 diff --git a/assets/voxygen/voxel/sprite/flax/flax-3.vox b/assets/voxygen/voxel/sprite/flax/flax-3.vox new file mode 100644 index 0000000000..9db44fb2cd --- /dev/null +++ b/assets/voxygen/voxel/sprite/flax/flax-3.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a979f7d9b0d8047a6720ab17bf35b005c14190ccc4a35435d56e77a3e5ebb8f +size 1472 diff --git a/assets/voxygen/voxel/sprite/flax/flax-4.vox b/assets/voxygen/voxel/sprite/flax/flax-4.vox new file mode 100644 index 0000000000..93e28fb98a --- /dev/null +++ b/assets/voxygen/voxel/sprite/flax/flax-4.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f33665ea85935b94de2db220c2921ad7525b681ac5513daa5030e2b3b96930e +size 1632 diff --git a/assets/voxygen/voxel/sprite/flax/flax-5.vox b/assets/voxygen/voxel/sprite/flax/flax-5.vox new file mode 100644 index 0000000000..9396ef40d1 --- /dev/null +++ b/assets/voxygen/voxel/sprite/flax/flax-5.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ceab01a1f453292838c38d4c1cd26c0ec94842edf01461cd6fe4f080d788688 +size 1380 diff --git a/assets/voxygen/voxel/sprite/fruit/coconut.vox b/assets/voxygen/voxel/sprite/fruit/coconut.vox new file mode 100644 index 0000000000..56545168fa --- /dev/null +++ b/assets/voxygen/voxel/sprite/fruit/coconut.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:24821d19153f380c18b8e255edcd1d9a5473485742e8b4fd6d9eaa9921d96c5c +size 3100 diff --git a/assets/voxygen/voxel/sprite/misc/scarecrow.vox b/assets/voxygen/voxel/sprite/misc/scarecrow.vox new file mode 100644 index 0000000000..85d65d4841 --- /dev/null +++ b/assets/voxygen/voxel/sprite/misc/scarecrow.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ce75f40786d154186e6da42ed99117c19e53c02ec8a61adc9216de4935739cf +size 4468 diff --git a/assets/voxygen/voxel/sprite/misc/street_lamp.vox b/assets/voxygen/voxel/sprite/misc/street_lamp.vox new file mode 100644 index 0000000000..6f738e8bb5 --- /dev/null +++ b/assets/voxygen/voxel/sprite/misc/street_lamp.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5909211edd73fe743f81ae280aaf2e6022a3e5d0430a7c79509479401a01dac0 +size 4776 diff --git a/assets/voxygen/voxel/sprite/pumpkin/1.vox b/assets/voxygen/voxel/sprite/pumpkin/1.vox index 731d773e04..e04c596c6d 100644 --- a/assets/voxygen/voxel/sprite/pumpkin/1.vox +++ b/assets/voxygen/voxel/sprite/pumpkin/1.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:09aedb0c998e662d2d9cb8a38d31094316804442b6aa68cb5533c043c07b87b8 +oid sha256:f0580c2e316710f88f1bb7a20462e64ad44aa6a47b3b58069d1d6b1015bdec06 size 2568 diff --git a/assets/voxygen/voxel/sprite/pumpkin/2.vox b/assets/voxygen/voxel/sprite/pumpkin/2.vox index a26eae8383..2cb8808644 100644 --- a/assets/voxygen/voxel/sprite/pumpkin/2.vox +++ b/assets/voxygen/voxel/sprite/pumpkin/2.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cdf17c9e4feac356bf4941df983bfdb1c25bbe151d124dc24e177b793553bc99 +oid sha256:2616a9c1e18ec29006d0fce787957cb9c19aab3a25b2b360f0dd490515ad57d3 size 2612 diff --git a/assets/voxygen/voxel/sprite/pumpkin/3.vox b/assets/voxygen/voxel/sprite/pumpkin/3.vox index bbbace60ec..ce49f8bbed 100644 --- a/assets/voxygen/voxel/sprite/pumpkin/3.vox +++ b/assets/voxygen/voxel/sprite/pumpkin/3.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c42591c770fe910efb091b607e8f9c86f39ac687d70766251ee2a7f0e3da303e +oid sha256:f338dd3b94bb36a7f36e5272e847e83bfa9317daf3f7b8535e73ea39842ef1b6 size 2580 diff --git a/assets/voxygen/voxel/sprite/pumpkin/4.vox b/assets/voxygen/voxel/sprite/pumpkin/4.vox index 8aea98cb56..ebe4c384a3 100644 --- a/assets/voxygen/voxel/sprite/pumpkin/4.vox +++ b/assets/voxygen/voxel/sprite/pumpkin/4.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:735a9f6b32f207bf1ae0e1551c8789f08116991d97743e93620406b1d2850d2d +oid sha256:6e65e3d6c3bda693b7ef21a86aac13f7a4fa87a09a1b731e58b13369e75751d7 size 2592 diff --git a/assets/voxygen/voxel/sprite/pumpkin/5.vox b/assets/voxygen/voxel/sprite/pumpkin/5.vox index 2bdf3cef8c..1ebaedd451 100644 --- a/assets/voxygen/voxel/sprite/pumpkin/5.vox +++ b/assets/voxygen/voxel/sprite/pumpkin/5.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:260a5e45c90cbc153ecf1846a3461980710c648f682f62cf416d25c4c078db04 +oid sha256:d96e0c99bd97e8a16d74f6419d6c5ea4740b5fbf82f7d61835f68dfb75debc87 size 2616 diff --git a/assets/voxygen/voxel/sprite/pumpkin/6.vox b/assets/voxygen/voxel/sprite/pumpkin/6.vox new file mode 100644 index 0000000000..6e2e3bf480 --- /dev/null +++ b/assets/voxygen/voxel/sprite/pumpkin/6.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0abc7fd43fbb5cfbe6ff4a341a94d40f09693164a0d35c480f297ff1bae5eae +size 4668 diff --git a/assets/voxygen/voxel/sprite/pumpkin/7.vox b/assets/voxygen/voxel/sprite/pumpkin/7.vox new file mode 100644 index 0000000000..3842695cce --- /dev/null +++ b/assets/voxygen/voxel/sprite/pumpkin/7.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f965826e3c5d890d32cdc1de331ae60e6d35e672cc6f44a4dd407268f9e66ab +size 4740 diff --git a/assets/voxygen/voxel/sprite/radish/0.vox b/assets/voxygen/voxel/sprite/radish/0.vox new file mode 100644 index 0000000000..2dbf98d2d8 --- /dev/null +++ b/assets/voxygen/voxel/sprite/radish/0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:acf2166f24ef4931837e10ca8b8ff71b1963480db475f98227cf13053b08d8e6 +size 1640 diff --git a/assets/voxygen/voxel/sprite/radish/1.vox b/assets/voxygen/voxel/sprite/radish/1.vox new file mode 100644 index 0000000000..745b8f6df4 --- /dev/null +++ b/assets/voxygen/voxel/sprite/radish/1.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25f1d72e72380947c51610d83c693a0fcbd985ff0f65dd11065d95d01f0b66e1 +size 1604 diff --git a/assets/voxygen/voxel/sprite/radish/2.vox b/assets/voxygen/voxel/sprite/radish/2.vox new file mode 100644 index 0000000000..4560be2fdb --- /dev/null +++ b/assets/voxygen/voxel/sprite/radish/2.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b872912c813034d2d398ec2aa2d9f9a410a8f28766b2070a6307d6bef495d8f5 +size 1452 diff --git a/assets/voxygen/voxel/sprite/radish/3.vox b/assets/voxygen/voxel/sprite/radish/3.vox new file mode 100644 index 0000000000..01957a6b9c --- /dev/null +++ b/assets/voxygen/voxel/sprite/radish/3.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e6d4fbed96dc161c5fbd3de4cb8e62e0b6b79b8430f0c01b2d6bd48ba77b213 +size 1488 diff --git a/assets/voxygen/voxel/sprite/radish/4.vox b/assets/voxygen/voxel/sprite/radish/4.vox new file mode 100644 index 0000000000..d5d4efb5aa --- /dev/null +++ b/assets/voxygen/voxel/sprite/radish/4.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21d9b7c04d63493858ebdc0162a00633927c71f79fce5df0aa06fffbd5f7a477 +size 1520 diff --git a/assets/voxygen/voxel/sprite/tomato/0.vox b/assets/voxygen/voxel/sprite/tomato/0.vox new file mode 100644 index 0000000000..c018365cbb --- /dev/null +++ b/assets/voxygen/voxel/sprite/tomato/0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0d0296db4368f79f0231f5cfd5d2b89cf06c4820196bf5a5a3b2a4f2430bb23 +size 2520 diff --git a/assets/voxygen/voxel/sprite/tomato/1.vox b/assets/voxygen/voxel/sprite/tomato/1.vox new file mode 100644 index 0000000000..f44563c1d3 --- /dev/null +++ b/assets/voxygen/voxel/sprite/tomato/1.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0b588c45d77f3712195f992f7e979724aa196ea0648a07c002998d2f1b8435b +size 2616 diff --git a/assets/voxygen/voxel/sprite/tomato/2.vox b/assets/voxygen/voxel/sprite/tomato/2.vox new file mode 100644 index 0000000000..99127bfa46 --- /dev/null +++ b/assets/voxygen/voxel/sprite/tomato/2.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f1c035ba21740573df46c3fa0a49aaf00b82758279f716c32ea5763df0a3a8b3 +size 2500 diff --git a/assets/voxygen/voxel/sprite/tomato/3.vox b/assets/voxygen/voxel/sprite/tomato/3.vox new file mode 100644 index 0000000000..149bce29f6 --- /dev/null +++ b/assets/voxygen/voxel/sprite/tomato/3.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:019abd752ceba6bb30cefebbb55a9c4742f114cb7c155f93642253cb71ee2532 +size 2444 diff --git a/assets/voxygen/voxel/sprite/tomato/4.vox b/assets/voxygen/voxel/sprite/tomato/4.vox new file mode 100644 index 0000000000..f909913d61 --- /dev/null +++ b/assets/voxygen/voxel/sprite/tomato/4.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4fc2ca75eb934c5684e8911b2b4fb4ee3f22b9ae2cdfec780f5f2e4025ceaa3 +size 2456 diff --git a/assets/voxygen/voxel/sprite/turnip/turnip-0.vox b/assets/voxygen/voxel/sprite/turnip/turnip-0.vox new file mode 100644 index 0000000000..fc55170674 --- /dev/null +++ b/assets/voxygen/voxel/sprite/turnip/turnip-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08fb3876bbc499ab64f62776fcfbdab9709ed5ac90c5c94d283a5c6defe7a0fd +size 1360 diff --git a/assets/voxygen/voxel/sprite/turnip/turnip-1.vox b/assets/voxygen/voxel/sprite/turnip/turnip-1.vox new file mode 100644 index 0000000000..9ab7b388d6 --- /dev/null +++ b/assets/voxygen/voxel/sprite/turnip/turnip-1.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34d59017041f4f340b57d67b0929482bf170d996404f15dbb323f65e72c6b3f6 +size 1528 diff --git a/assets/voxygen/voxel/sprite/turnip/turnip-2.vox b/assets/voxygen/voxel/sprite/turnip/turnip-2.vox new file mode 100644 index 0000000000..d3b58dbed2 --- /dev/null +++ b/assets/voxygen/voxel/sprite/turnip/turnip-2.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c86e20979271f63338dc4ac7afecb79f9461297d460731f145f6b35d149eefa +size 1328 diff --git a/assets/voxygen/voxel/sprite/turnip/turnip-3.vox b/assets/voxygen/voxel/sprite/turnip/turnip-3.vox new file mode 100644 index 0000000000..e34eb2b716 --- /dev/null +++ b/assets/voxygen/voxel/sprite/turnip/turnip-3.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea31d0b8739c93d07566f6c01f782aacd7873ff41407f2bd9880776dca7bcb48 +size 1480 diff --git a/assets/voxygen/voxel/sprite/turnip/turnip-4.vox b/assets/voxygen/voxel/sprite/turnip/turnip-4.vox new file mode 100644 index 0000000000..a7b8e8ae2d --- /dev/null +++ b/assets/voxygen/voxel/sprite/turnip/turnip-4.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1844a7c3c35b1e0b3ad6914eee0193b2f7aca0a1ff745b408b9ea89809bc9c09 +size 1336 diff --git a/assets/voxygen/voxel/sprite/turnip/turnip-5.vox b/assets/voxygen/voxel/sprite/turnip/turnip-5.vox new file mode 100644 index 0000000000..bc49b4c6e3 --- /dev/null +++ b/assets/voxygen/voxel/sprite/turnip/turnip-5.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b18479edcd9b10aa4ba7c7bdc1f1456ea09236aee2f499a5f86cf7adb72214d8 +size 1312 diff --git a/assets/voxygen/voxel/sprite/wheat_green/wheat-0.vox b/assets/voxygen/voxel/sprite/wheat_green/wheat-0.vox new file mode 100644 index 0000000000..1e2cd0f8f8 --- /dev/null +++ b/assets/voxygen/voxel/sprite/wheat_green/wheat-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ea27a3e7a8be87331cbad2f32ed4ae437243faa780772d491597a033de6eddf +size 1424 diff --git a/assets/voxygen/voxel/sprite/wheat_green/wheat-1.vox b/assets/voxygen/voxel/sprite/wheat_green/wheat-1.vox new file mode 100644 index 0000000000..bdd9e2a198 --- /dev/null +++ b/assets/voxygen/voxel/sprite/wheat_green/wheat-1.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a80334b4be59c5a64a5246dff836ea448770e98a6f2e3a3a60a13d2174507f84 +size 1464 diff --git a/assets/voxygen/voxel/sprite/wheat_green/wheat-2.vox b/assets/voxygen/voxel/sprite/wheat_green/wheat-2.vox new file mode 100644 index 0000000000..5bd2acf9e7 --- /dev/null +++ b/assets/voxygen/voxel/sprite/wheat_green/wheat-2.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de10ae4506e55d1b3b2ee8b5f2cc3eb0262778ef3570c5d3af40aef8e6a9cc78 +size 1364 diff --git a/assets/voxygen/voxel/sprite/wheat_green/wheat-3.vox b/assets/voxygen/voxel/sprite/wheat_green/wheat-3.vox new file mode 100644 index 0000000000..0cd7197aae --- /dev/null +++ b/assets/voxygen/voxel/sprite/wheat_green/wheat-3.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6e2ad13a9b22484327aff8084f43d93867c5ef009f0e20334f1f9ed605fdb2a +size 1452 diff --git a/assets/voxygen/voxel/sprite/wheat_green/wheat-4.vox b/assets/voxygen/voxel/sprite/wheat_green/wheat-4.vox new file mode 100644 index 0000000000..5128876e60 --- /dev/null +++ b/assets/voxygen/voxel/sprite/wheat_green/wheat-4.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eab723b6b0d81fba634beb363d1363ba155f3964876f1c71b43ad70d00efdf32 +size 1296 diff --git a/assets/voxygen/voxel/sprite/wheat_green/wheat-5.vox b/assets/voxygen/voxel/sprite/wheat_green/wheat-5.vox new file mode 100644 index 0000000000..80ad6a3f22 --- /dev/null +++ b/assets/voxygen/voxel/sprite/wheat_green/wheat-5.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b53d94bffbdf2581a9c399d4052baffead2156b79bc9c2018de6c714bdcdc287 +size 1576 diff --git a/assets/voxygen/voxel/sprite/wheat_green/wheat-6.vox b/assets/voxygen/voxel/sprite/wheat_green/wheat-6.vox new file mode 100644 index 0000000000..1a794d41a2 --- /dev/null +++ b/assets/voxygen/voxel/sprite/wheat_green/wheat-6.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:990b221356fdcac1be68830dcf81552b3878f428eef83f21dee7352c693e70a4 +size 1432 diff --git a/assets/voxygen/voxel/sprite/wheat_green/wheat-7.vox b/assets/voxygen/voxel/sprite/wheat_green/wheat-7.vox new file mode 100644 index 0000000000..602e2ebf04 --- /dev/null +++ b/assets/voxygen/voxel/sprite/wheat_green/wheat-7.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9fde089733a0f63fe192aa156703630d17ac859a46ad69f88dd0873dbb3edc82 +size 1504 diff --git a/assets/voxygen/voxel/sprite/wheat_green/wheat-8.vox b/assets/voxygen/voxel/sprite/wheat_green/wheat-8.vox new file mode 100644 index 0000000000..82310499a8 --- /dev/null +++ b/assets/voxygen/voxel/sprite/wheat_green/wheat-8.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f4f96a6df2c70159c929f9b8354502855f7b4ca120da0a1d66c663a2863f558 +size 1548 diff --git a/assets/voxygen/voxel/sprite/wheat_green/wheat-9.vox b/assets/voxygen/voxel/sprite/wheat_green/wheat-9.vox new file mode 100644 index 0000000000..149bb832eb --- /dev/null +++ b/assets/voxygen/voxel/sprite/wheat_green/wheat-9.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:754ca2705d1a0ea93096475ac976bd48b0f03fb5b8327e13a5c767e7204b188a +size 1580 diff --git a/assets/voxygen/voxel/sprite/wheat_yellow/wheat-0.vox b/assets/voxygen/voxel/sprite/wheat_yellow/wheat-0.vox new file mode 100644 index 0000000000..f30de9cb1f --- /dev/null +++ b/assets/voxygen/voxel/sprite/wheat_yellow/wheat-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fdd5bf80f996bae9f28f48d2a5fb618336b3de11ee4a8ee681083e30c02b2d8a +size 1424 diff --git a/assets/voxygen/voxel/sprite/wheat_yellow/wheat-1.vox b/assets/voxygen/voxel/sprite/wheat_yellow/wheat-1.vox new file mode 100644 index 0000000000..3ff4fa2c6d --- /dev/null +++ b/assets/voxygen/voxel/sprite/wheat_yellow/wheat-1.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:83eb117be030060821ccf7a03c53135c5508cd4adec7f3164b912323c2580958 +size 1464 diff --git a/assets/voxygen/voxel/sprite/wheat_yellow/wheat-2.vox b/assets/voxygen/voxel/sprite/wheat_yellow/wheat-2.vox new file mode 100644 index 0000000000..bd1b67472b --- /dev/null +++ b/assets/voxygen/voxel/sprite/wheat_yellow/wheat-2.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:731230c97d1b80ec0d5457f8f8d121084da2f516888f3ea3f45c37e0474415fd +size 1364 diff --git a/assets/voxygen/voxel/sprite/wheat_yellow/wheat-3.vox b/assets/voxygen/voxel/sprite/wheat_yellow/wheat-3.vox new file mode 100644 index 0000000000..28772db191 --- /dev/null +++ b/assets/voxygen/voxel/sprite/wheat_yellow/wheat-3.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ee83d5b33e7c8f8f51272462ae874dd1c62e600a095130046d49f62bbfe2a04 +size 1452 diff --git a/assets/voxygen/voxel/sprite/wheat_yellow/wheat-4.vox b/assets/voxygen/voxel/sprite/wheat_yellow/wheat-4.vox new file mode 100644 index 0000000000..0c1755a4b6 --- /dev/null +++ b/assets/voxygen/voxel/sprite/wheat_yellow/wheat-4.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fbbada83a34bf25c12965ba758c1c88250cf2712c75149342fcebca4550d019f +size 1296 diff --git a/assets/voxygen/voxel/sprite/wheat_yellow/wheat-5.vox b/assets/voxygen/voxel/sprite/wheat_yellow/wheat-5.vox new file mode 100644 index 0000000000..42f2aa6ebc --- /dev/null +++ b/assets/voxygen/voxel/sprite/wheat_yellow/wheat-5.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:35c9a77bef70904013325fe92692e60e10b13efc0a710ccf59c23ef4185879d9 +size 1576 diff --git a/assets/voxygen/voxel/sprite/wheat_yellow/wheat-6.vox b/assets/voxygen/voxel/sprite/wheat_yellow/wheat-6.vox new file mode 100644 index 0000000000..ee7fef75f9 --- /dev/null +++ b/assets/voxygen/voxel/sprite/wheat_yellow/wheat-6.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9fc5ea30ec387fd54aebad5cca3207c310d23a7ce4cb2c6fc2e6290b5e520b40 +size 1432 diff --git a/assets/voxygen/voxel/sprite/wheat_yellow/wheat-7.vox b/assets/voxygen/voxel/sprite/wheat_yellow/wheat-7.vox new file mode 100644 index 0000000000..ef3f36738f --- /dev/null +++ b/assets/voxygen/voxel/sprite/wheat_yellow/wheat-7.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:127a88529c25346b51fab378af6d2d19ea5d427b3dd8cfbd6270b675eeb578c6 +size 1504 diff --git a/assets/voxygen/voxel/sprite/wheat_yellow/wheat-8.vox b/assets/voxygen/voxel/sprite/wheat_yellow/wheat-8.vox new file mode 100644 index 0000000000..fffe9c7010 --- /dev/null +++ b/assets/voxygen/voxel/sprite/wheat_yellow/wheat-8.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c1e58d31d75947c2e18e5b207e7df7cf266949b95e678ef2d93491f3c81ea508 +size 1548 diff --git a/assets/voxygen/voxel/sprite/wheat_yellow/wheat-9.vox b/assets/voxygen/voxel/sprite/wheat_yellow/wheat-9.vox new file mode 100644 index 0000000000..213c625a46 --- /dev/null +++ b/assets/voxygen/voxel/sprite/wheat_yellow/wheat-9.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9784152567b21d75d6266338d13fc05179debb01a6da2490f2b99c38935a9e1f +size 1580 diff --git a/assets/voxygen/voxel/sprite/window/window-0.vox b/assets/voxygen/voxel/sprite/window/window-0.vox new file mode 100644 index 0000000000..1bd7d003d9 --- /dev/null +++ b/assets/voxygen/voxel/sprite/window/window-0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe857eae2b685cd9bf7abdf68e6d5c0e108886f61e6a4f0004eee30ff441e500 +size 1560 diff --git a/assets/voxygen/voxel/sprite/window/window-1.vox b/assets/voxygen/voxel/sprite/window/window-1.vox new file mode 100644 index 0000000000..24e3d6877f --- /dev/null +++ b/assets/voxygen/voxel/sprite/window/window-1.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e4beef6bee7cfdef4f41336775cdaac73376a8edc40a0e22bfb3e8d620fb9f49 +size 1544 diff --git a/assets/voxygen/voxel/sprite/window/window-2.vox b/assets/voxygen/voxel/sprite/window/window-2.vox new file mode 100644 index 0000000000..07bfd7f345 --- /dev/null +++ b/assets/voxygen/voxel/sprite/window/window-2.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:430ebb35fc140cb1203a3e0b4be4f52205db628cc9071f05b35a27f16e4f9a88 +size 1528 diff --git a/assets/voxygen/voxel/sprite/window/window-3.vox b/assets/voxygen/voxel/sprite/window/window-3.vox new file mode 100644 index 0000000000..f0d8901ae4 --- /dev/null +++ b/assets/voxygen/voxel/sprite/window/window-3.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ae2e9c20bb7a32202f3c72abc3793725be8059f5b877e161762c28cd6b2cdbb +size 1540 diff --git a/assets/world/manifests/fruit_trees.ron b/assets/world/manifests/fruit_trees.ron index 0e8d4262bf..85beb0896f 100644 --- a/assets/world/manifests/fruit_trees.ron +++ b/assets/world/manifests/fruit_trees.ron @@ -2,7 +2,7 @@ [ ( specifier: "world.tree.fruit.1", - center: (5, 5, 7) + center: (6, 6, 7) ), ( specifier: "world.tree.fruit.2", diff --git a/assets/world/map/veloren_0_5_0_0.bin b/assets/world/map/veloren_0_5_0_0.bin index f121098936..3c52bd8161 100644 --- a/assets/world/map/veloren_0_5_0_0.bin +++ b/assets/world/map/veloren_0_5_0_0.bin @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4fd18a0b50764564cfc7ffc6c2551100ca564c951fe531f508469e029d02482c +oid sha256:267884532ae482c58f8cc2308c36824b4f4216b124b0310104364c7619e20130 size 16777236 diff --git a/assets/world/tree/desert_palm/1.vox b/assets/world/tree/desert_palm/1.vox index bec36a7ddf..3fa08ff0ff 100644 --- a/assets/world/tree/desert_palm/1.vox +++ b/assets/world/tree/desert_palm/1.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:abe347ac442a2afc287869f9d2256c5af2b6507f4d1fa370f39769a95119713c -size 2568 +oid sha256:6cfa51cd9bedf24d727aeffb809b0c673dc8cb15aeb0a7b6a23e05aac2121436 +size 2580 diff --git a/assets/world/tree/desert_palm/10.vox b/assets/world/tree/desert_palm/10.vox index c0daf1cff2..4f5a6d283d 100644 --- a/assets/world/tree/desert_palm/10.vox +++ b/assets/world/tree/desert_palm/10.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:06a39754b0d316639d487275ea85534eb6a325a4d70b4e987450c1c702f12e01 +oid sha256:51c8a5c03190aaf0752b0c2dcc1f2d3aa4744afadd3b45774b157498ac0ad717 size 3356 diff --git a/assets/world/tree/desert_palm/2.vox b/assets/world/tree/desert_palm/2.vox index 7c582c5ae1..644b57f599 100644 --- a/assets/world/tree/desert_palm/2.vox +++ b/assets/world/tree/desert_palm/2.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:807b9c55da8106d93d538d2e55b9cb3056cd01c35d98eb6cead69a9814e71225 -size 2536 +oid sha256:3980edde33843925e017e7986c3722ce03202119f95b177f3dbde45dd47084ef +size 2544 diff --git a/assets/world/tree/desert_palm/3.vox b/assets/world/tree/desert_palm/3.vox index c8862eaee3..681c7602ef 100644 --- a/assets/world/tree/desert_palm/3.vox +++ b/assets/world/tree/desert_palm/3.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a8869e83bc5fd1263d512153ea1e30296ffcce3d87f319edd488ee4d1c9fb4a7 -size 2728 +oid sha256:65973116a2c4e2166b06c93c40bbdf2cb0cd055186e997eb13a9d4665a360407 +size 2740 diff --git a/assets/world/tree/desert_palm/4.vox b/assets/world/tree/desert_palm/4.vox index 83ee9bcdbb..486f8ce1ae 100644 --- a/assets/world/tree/desert_palm/4.vox +++ b/assets/world/tree/desert_palm/4.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d9d7b5343efd602dfbb5f2fa61ad2882a67a1554a977f14d68b916229e284409 -size 2664 +oid sha256:c739af1da0a6502dc333e8f26e6aa3c5b43dd1bf9a739fa1183a6925b06c3d95 +size 2676 diff --git a/assets/world/tree/desert_palm/5.vox b/assets/world/tree/desert_palm/5.vox index 3ca1dc1d68..82f66b0390 100644 --- a/assets/world/tree/desert_palm/5.vox +++ b/assets/world/tree/desert_palm/5.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d37842b815dcb28c0c2004d150ef2f0cbb466a1fc64934beb469e6f8312502c8 -size 2668 +oid sha256:839d59a6899359d75ae40ede6eab197e4438753afb36b59dac03217a1867cdf1 +size 2680 diff --git a/assets/world/tree/desert_palm/6.vox b/assets/world/tree/desert_palm/6.vox index 93e02f1ba3..2978b19852 100644 --- a/assets/world/tree/desert_palm/6.vox +++ b/assets/world/tree/desert_palm/6.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:06876ab785668631c73147d4cea201e40579746055cab5f505f0d3017e21fe88 -size 2768 +oid sha256:fa64cff8589f64f1857975b3acba8fa16c24b1d19d6482f9ff08fdd95d8705e7 +size 2780 diff --git a/assets/world/tree/desert_palm/7.vox b/assets/world/tree/desert_palm/7.vox index 4445dbf3b3..f3095c9076 100644 --- a/assets/world/tree/desert_palm/7.vox +++ b/assets/world/tree/desert_palm/7.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7ae32444793fe113333e6969597eb8864b5212b0326eb0506c68cbde224d810 -size 3040 +oid sha256:2bf99aeaf0936a67fea13d715095ed53506859723e2543838de9d561c8b9e2cb +size 3048 diff --git a/assets/world/tree/desert_palm/8.vox b/assets/world/tree/desert_palm/8.vox index 579d53bf10..fbd63a59d0 100644 --- a/assets/world/tree/desert_palm/8.vox +++ b/assets/world/tree/desert_palm/8.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c33f5c43adf96b9a405f50b29bdb226b2775b3cca69692f3629afcc558f54cab -size 2704 +oid sha256:2581ec5257928a5d5fd64e533bef1ba7267438ce6fae5849a39bccdd547b7ba3 +size 2716 diff --git a/assets/world/tree/desert_palm/9.vox b/assets/world/tree/desert_palm/9.vox index 58315225c9..6af18dd413 100644 --- a/assets/world/tree/desert_palm/9.vox +++ b/assets/world/tree/desert_palm/9.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3005d011c0a2cb0117adafffbb884c97ee8b6491e104cbcb97410be325acc096 -size 3152 +oid sha256:b787e74402728a73be2963533ae5f2e1438fdb90345b0e3d0e6f0452ae314b60 +size 3164 diff --git a/assets/world/tree/fruit/1.vox b/assets/world/tree/fruit/1.vox index 9d3539eeb5..71dcc0526e 100644 --- a/assets/world/tree/fruit/1.vox +++ b/assets/world/tree/fruit/1.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:525d55c479c0aebe717dc8eba0cde69c1177b1feaa85e4fb83991d68460cc682 -size 3360 +oid sha256:d8a1c3221de26302e2e639ccf0c616b696f794e8924bede9707dc2c97b0c494e +size 4836 diff --git a/client/src/lib.rs b/client/src/lib.rs index 10e293cd6a..62143885bc 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -383,21 +383,21 @@ impl Client { // Can't fail } - pub fn use_inventory_slot(&mut self, slot: usize) { + pub fn use_slot(&mut self, slot: comp::slot::Slot) { self.postbox .send_message(ClientMsg::ControlEvent(ControlEvent::InventoryManip( InventoryManip::Use(slot), ))); } - pub fn swap_inventory_slots(&mut self, a: usize, b: usize) { + pub fn swap_slots(&mut self, a: comp::slot::Slot, b: comp::slot::Slot) { self.postbox .send_message(ClientMsg::ControlEvent(ControlEvent::InventoryManip( InventoryManip::Swap(a, b), ))); } - pub fn drop_inventory_slot(&mut self, slot: usize) { + pub fn drop_slot(&mut self, slot: comp::slot::Slot) { self.postbox .send_message(ClientMsg::ControlEvent(ControlEvent::InventoryManip( InventoryManip::Drop(slot), @@ -526,6 +526,8 @@ impl Client { pub fn inventories(&self) -> ReadStorage { self.state.read_storage() } + pub fn loadouts(&self) -> ReadStorage { self.state.read_storage() } + /// Send a chat message to the server. pub fn send_chat(&mut self, message: String) { match validate_chat_msg(&message) { diff --git a/common/src/assets/mod.rs b/common/src/assets/mod.rs index cdfa7fa1f2..62f255895e 100644 --- a/common/src/assets/mod.rs +++ b/common/src/assets/mod.rs @@ -297,7 +297,23 @@ lazy_static! { // System paths #[cfg(target_os = "linux")] - paths.push("/usr/share/veloren/assets".into()); + { + if let Ok(result) = std::env::var("XDG_DATA_HOME") { + paths.push(format!("{}/veloren/assets", result).into()); + } else if let Ok(result) = std::env::var("HOME") { + paths.push(format!("{}/.local/share/veloren/assets", result).into()); + } + + if let Ok(result) = std::env::var("XDG_DATA_DIRS") { + result.split(":").for_each(|x| paths.push(format!("{}/veloren/assets", x).into())); + } else { + // Fallback + let fallback_paths = vec!["/usr/local/share", "/usr/share"]; + for fallback_path in fallback_paths { + paths.push(format!("{}/veloren/assets", fallback_path).into()); + } + } + } for path in paths.clone() { match find_folder::Search::ParentsThenKids(3, 1) diff --git a/common/src/astar.rs b/common/src/astar.rs index 4c153c9de9..3ad4912733 100644 --- a/common/src/astar.rs +++ b/common/src/astar.rs @@ -34,6 +34,15 @@ pub enum PathResult { Pending, } +impl PathResult { + pub fn into_path(self) -> Option> { + match self { + PathResult::Path(path) => Some(path), + _ => None, + } + } +} + #[derive(Clone, Debug)] pub struct Astar { iter: usize, @@ -43,7 +52,8 @@ pub struct Astar { cheapest_scores: HashMap, final_scores: HashMap, visited: HashSet, - lowest_cost: Option, + cheapest_node: Option, + cheapest_cost: Option, } impl Astar { @@ -60,7 +70,8 @@ impl Astar { cheapest_scores: std::iter::once((start.clone(), 0.0)).collect(), final_scores: std::iter::once((start.clone(), heuristic(&start))).collect(), visited: std::iter::once(start).collect(), - lowest_cost: None, + cheapest_node: None, + cheapest_cost: None, } } @@ -77,11 +88,12 @@ impl Astar { { let iter_limit = self.max_iters.min(self.iter + iters); while self.iter < iter_limit { - if let Some(PathEntry { node, .. }) = self.potential_nodes.pop() { + if let Some(PathEntry { node, cost }) = self.potential_nodes.pop() { + self.cheapest_cost = Some(cost); if satisfied(&node) { return PathResult::Path(self.reconstruct_path_to(node)); } else { - self.lowest_cost = Some(node.clone()); + self.cheapest_node = Some(node.clone()); for neighbor in neighbors(&node) { let node_cheapest = self.cheapest_scores.get(&node).unwrap_or(&f32::MAX); let neighbor_cheapest = @@ -105,7 +117,7 @@ impl Astar { } } else { return PathResult::None( - self.lowest_cost + self.cheapest_node .clone() .map(|lc| self.reconstruct_path_to(lc)) .unwrap_or_default(), @@ -117,7 +129,7 @@ impl Astar { if self.iter >= self.max_iters { PathResult::Exhausted( - self.lowest_cost + self.cheapest_node .clone() .map(|lc| self.reconstruct_path_to(lc)) .unwrap_or_default(), @@ -127,6 +139,8 @@ impl Astar { } } + pub fn get_cheapest_cost(&self) -> Option { self.cheapest_cost } + fn reconstruct_path_to(&mut self, end: S) -> Path { let mut path = vec![end.clone()]; let mut cnode = &end; diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 0b009b270f..e1f0af4d2b 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -104,6 +104,12 @@ pub struct Loadout { pub hand: Option, pub pants: Option, pub foot: Option, + pub back: Option, + pub ring: Option, + pub neck: Option, + pub lantern: Option, + pub head: Option, + pub tabard: Option, } impl From<&CharacterAbility> for CharacterState { diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 80965cf2cc..be33b4bb49 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -25,6 +25,7 @@ impl Alignment { // Never attacks pub fn passive_towards(self, other: Alignment) -> bool { match (self, other) { + (Alignment::Enemy, Alignment::Enemy) => true, (Alignment::Owned(a), Alignment::Owned(b)) if a == b => true, _ => false, } @@ -61,6 +62,7 @@ pub enum Activity { chaser: Chaser, time: f64, been_close: bool, + powerup: f32, }, } diff --git a/common/src/comp/body/humanoid.rs b/common/src/comp/body/humanoid.rs index f68b323a5a..4c243bc599 100644 --- a/common/src/comp/body/humanoid.rs +++ b/common/src/comp/body/humanoid.rs @@ -432,8 +432,8 @@ impl Race { match (self, body_type) { (Race::Danari, BodyType::Female) => 1, (Race::Danari, BodyType::Male) => 1, - (Race::Dwarf, BodyType::Female) => 1, - (Race::Dwarf, BodyType::Male) => 1, + (Race::Dwarf, BodyType::Female) => 7, + (Race::Dwarf, BodyType::Male) => 7, (Race::Elf, BodyType::Female) => 2, (Race::Elf, BodyType::Male) => 1, (Race::Human, BodyType::Female) => 1, diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index 5852ad56ec..6daf98e1cf 100644 --- a/common/src/comp/controller.rs +++ b/common/src/comp/controller.rs @@ -1,4 +1,4 @@ -use crate::{sync::Uid, util::Dir}; +use crate::{comp::inventory::slot::Slot, sync::Uid, util::Dir}; use specs::{Component, FlaggedStorage}; use specs_idvs::IDVStorage; use std::time::Duration; @@ -7,6 +7,15 @@ use vek::*; /// Default duration before an input is considered 'held'. pub const DEFAULT_HOLD_DURATION: Duration = Duration::from_millis(200); +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum InventoryManip { + Pickup(Uid), + Collect(Vec3), + Use(Slot), + Swap(Slot, Slot), + Drop(Slot), +} + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum ControlEvent { Mount(Uid), @@ -230,12 +239,3 @@ pub struct Mounting(pub Uid); impl Component for Mounting { type Storage = FlaggedStorage>; } - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub enum InventoryManip { - Pickup(Uid), - Collect(Vec3), - Use(usize), - Swap(usize, usize), - Drop(usize), -} diff --git a/common/src/comp/inventory/item/armor.rs b/common/src/comp/inventory/item/armor.rs index 88cd537203..74885598be 100644 --- a/common/src/comp/inventory/item/armor.rs +++ b/common/src/comp/inventory/item/armor.rs @@ -14,8 +14,9 @@ pub enum Chest { ClothPurple0 = 11, ClothBlue0 = 12, ClothGreen0 = 13, + Rugged0 = 14, } -pub const ALL_CHESTS: [Chest; 13] = [ +pub const ALL_CHESTS: [Chest; 14] = [ Chest::Blue, Chest::Brown, Chest::Dark, @@ -29,6 +30,7 @@ pub const ALL_CHESTS: [Chest; 13] = [ Chest::ClothPurple0, Chest::ClothBlue0, Chest::ClothGreen0, + Chest::Rugged0, ]; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -76,8 +78,9 @@ pub enum Pants { ClothPurple0 = 10, ClothBlue0 = 11, ClothGreen0 = 12, + Rugged0 = 13, } -pub const ALL_PANTS: [Pants; 13] = [ +pub const ALL_PANTS: [Pants; 14] = [ Pants::None, Pants::Blue, Pants::Brown, @@ -91,6 +94,7 @@ pub const ALL_PANTS: [Pants; 13] = [ Pants::ClothPurple0, Pants::ClothBlue0, Pants::ClothGreen0, + Pants::Rugged0, ]; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -118,7 +122,7 @@ pub const ALL_HANDS: [Hand; 7] = [ #[repr(u32)] pub enum Foot { Dark = 1, - Sandal = 2, + Sandal0 = 2, Jester = 3, Assassin = 4, Plate0 = 5, @@ -129,7 +133,7 @@ pub enum Foot { } pub const ALL_FEET: [Foot; 9] = [ Foot::Dark, - Foot::Sandal, + Foot::Sandal0, Foot::Jester, Foot::Assassin, Foot::Plate0, @@ -163,6 +167,38 @@ pub const ALL_SHOULDERS: [Shoulder; 9] = [ Shoulder::ClothBlue0, Shoulder::ClothGreen0, ]; +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[repr(u32)] +pub enum Back { + Short0 = 1, + Admin = 2, +} +pub const ALL_BACKS: [Back; 2] = [Back::Short0, Back::Admin]; +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[repr(u32)] +pub enum Ring { + Ring0 = 1, +} +pub const ALL_RINGS: [Ring; 1] = [Ring::Ring0]; +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[repr(u32)] +pub enum Neck { + Neck0 = 1, +} +pub const ALL_NECKS: [Neck; 1] = [Neck::Neck0]; +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[repr(u32)] +pub enum Head { + Leather0 = 1, + AssaMask0 = 2, +} +pub const ALL_HEADS: [Head; 2] = [Head::Leather0, Head::AssaMask0]; +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[repr(u32)] +pub enum Tabard { + Admin = 1, +} +pub const ALL_TABARDS: [Tabard; 1] = [Tabard::Admin]; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum Armor { @@ -172,6 +208,11 @@ pub enum Armor { Hand(Hand), Pants(Pants), Foot(Foot), + Back(Back), + Ring(Ring), + Neck(Neck), + Head(Head), + Tabard(Tabard), } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] diff --git a/common/src/comp/inventory/item/mod.rs b/common/src/comp/inventory/item/mod.rs index 5cc42d5b8e..3e818a0393 100644 --- a/common/src/comp/inventory/item/mod.rs +++ b/common/src/comp/inventory/item/mod.rs @@ -16,6 +16,7 @@ use std::{fs::File, io::BufReader}; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum Consumable { + Coconut, Apple, Cheese, Potion, @@ -36,12 +37,21 @@ pub enum Ingredient { Grass, } +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[repr(u32)] +pub enum Lantern { + Black0 = 1, + Green0 = 2, +} +pub const ALL_LANTERNS: [Lantern; 2] = [Lantern::Black0, Lantern::Green0]; + fn default_amount() -> u32 { 1 } #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum ItemKind { /// Something wieldable Tool(tool::Tool), + Lantern(Lantern), Armor { kind: armor::Armor, stats: armor::Stats, @@ -117,6 +127,7 @@ impl Item { Some(assets::load_expect_cloned("common.items.grasses.medium")) }, BlockKind::ShortGrass => Some(assets::load_expect_cloned("common.items.grasses.short")), + BlockKind::Coconut => Some(assets::load_expect_cloned("common.items.coconut")), BlockKind::Chest => Some(assets::load_expect_cloned( [ "common.items.apple", @@ -164,6 +175,9 @@ impl Item { "common.items.armor.pants.cloth_purple_0", "common.items.armor.shoulder.cloth_purple_0", "common.items.armor.hand.cloth_purple_0", + "common.items.armor.ring.ring_0", + "common.items.armor.back.short_0", + "common.items.armor.neck.neck_0", ] .choose(&mut rand::thread_rng()) .unwrap(), // Can't fail diff --git a/common/src/comp/inventory/mod.rs b/common/src/comp/inventory/mod.rs index c5ee442e96..e7e126ed96 100644 --- a/common/src/comp/inventory/mod.rs +++ b/common/src/comp/inventory/mod.rs @@ -1,4 +1,5 @@ pub mod item; +pub mod slot; use crate::assets; use item::{Consumable, Item, ItemKind}; @@ -36,7 +37,9 @@ impl Inventory { /// new group. Returns the item again if no space was found. pub fn push(&mut self, item: Item) -> Option { let item = match item.kind { - ItemKind::Tool(_) | ItemKind::Armor { .. } => self.add_to_first_empty(item), + ItemKind::Tool(_) | ItemKind::Armor { .. } | ItemKind::Lantern(_) => { + self.add_to_first_empty(item) + }, ItemKind::Utility { kind: item_kind, amount: new_amount, @@ -239,7 +242,9 @@ impl Inventory { if let Some(Some(item)) = self.slots.get_mut(cell) { let mut return_item = item.clone(); match &mut item.kind { - ItemKind::Tool(_) | ItemKind::Armor { .. } => self.remove(cell), + ItemKind::Tool(_) | ItemKind::Armor { .. } | ItemKind::Lantern(_) => { + self.remove(cell) + }, ItemKind::Utility { kind, amount } => { if *amount <= 1 { self.remove(cell) diff --git a/common/src/comp/inventory/slot.rs b/common/src/comp/inventory/slot.rs new file mode 100644 index 0000000000..b7d6f27842 --- /dev/null +++ b/common/src/comp/inventory/slot.rs @@ -0,0 +1,252 @@ +use crate::{comp, comp::item}; +use comp::{Inventory, Loadout}; + +#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)] +pub enum Slot { + Inventory(usize), + Equip(EquipSlot), +} + +#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)] +pub enum EquipSlot { + Armor(ArmorSlot), + Mainhand, + Offhand, + Lantern, +} + +#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)] +pub enum ArmorSlot { + Head, + Neck, + Shoulders, + Chest, + Hands, + Ring, + Back, + Belt, + Legs, + Feet, + Tabard, +} + +//const ALL_ARMOR_SLOTS: [ArmorSlot; 11] = [ +// Head, Neck, Shoulders, Chest, Hands, Ring, Back, Belt, Legs, Feet, Tabard, +//]; + +impl Slot { + pub fn can_hold(self, item_kind: &item::ItemKind) -> bool { + match (self, item_kind) { + (Self::Inventory(_), _) => true, + (Self::Equip(slot), item_kind) => slot.can_hold(item_kind), + } + } +} + +impl EquipSlot { + fn can_hold(self, item_kind: &item::ItemKind) -> bool { + use item::ItemKind; + match (self, item_kind) { + (Self::Armor(slot), ItemKind::Armor { kind, .. }) => slot.can_hold(kind), + (Self::Mainhand, ItemKind::Tool(_)) => true, + (Self::Offhand, ItemKind::Tool(_)) => true, + (Self::Lantern, ItemKind::Lantern(_)) => true, + _ => false, + } + } +} + +impl ArmorSlot { + fn can_hold(self, armor: &item::armor::Armor) -> bool { + use item::armor::Armor; + match (self, armor) { + (Self::Head, Armor::Head(_)) => true, + (Self::Neck, Armor::Neck(_)) => true, + (Self::Shoulders, Armor::Shoulder(_)) => true, + (Self::Chest, Armor::Chest(_)) => true, + (Self::Hands, Armor::Hand(_)) => true, + (Self::Ring, Armor::Ring(_)) => true, + (Self::Back, Armor::Back(_)) => true, + (Self::Belt, Armor::Belt(_)) => true, + (Self::Legs, Armor::Pants(_)) => true, + (Self::Feet, Armor::Foot(_)) => true, + (Self::Tabard, Armor::Tabard(_)) => true, + _ => false, + } + } +} + +// TODO: There are plans to save the selected abilities for each tool even +// when they are not equipped, when that is implemented this helper function +// should no longer be needed +fn item_config(item: item::Item) -> comp::ItemConfig { + let mut abilities = if let item::ItemKind::Tool(tool) = &item.kind { + tool.get_abilities() + } else { + Vec::new() + } + .into_iter(); + + comp::ItemConfig { + item, + ability1: abilities.next(), + ability2: abilities.next(), + ability3: abilities.next(), + block_ability: Some(comp::CharacterAbility::BasicBlock), + dodge_ability: Some(comp::CharacterAbility::Roll), + } +} + +fn loadout_replace( + equip_slot: EquipSlot, + item: Option, + loadout: &mut Loadout, +) -> Option { + use std::mem::replace; + match equip_slot { + EquipSlot::Armor(ArmorSlot::Head) => replace(&mut loadout.head, item), + EquipSlot::Armor(ArmorSlot::Neck) => replace(&mut loadout.neck, item), + EquipSlot::Armor(ArmorSlot::Shoulders) => replace(&mut loadout.shoulder, item), + EquipSlot::Armor(ArmorSlot::Chest) => replace(&mut loadout.chest, item), + EquipSlot::Armor(ArmorSlot::Hands) => replace(&mut loadout.hand, item), + EquipSlot::Armor(ArmorSlot::Ring) => replace(&mut loadout.ring, item), + EquipSlot::Armor(ArmorSlot::Back) => replace(&mut loadout.back, item), + EquipSlot::Armor(ArmorSlot::Belt) => replace(&mut loadout.belt, item), + EquipSlot::Armor(ArmorSlot::Legs) => replace(&mut loadout.pants, item), + EquipSlot::Armor(ArmorSlot::Feet) => replace(&mut loadout.foot, item), + EquipSlot::Armor(ArmorSlot::Tabard) => replace(&mut loadout.tabard, item), + EquipSlot::Lantern => replace(&mut loadout.lantern, item), + EquipSlot::Mainhand => { + replace(&mut loadout.active_item, item.map(item_config)).map(|i| i.item) + }, + EquipSlot::Offhand => { + replace(&mut loadout.second_item, item.map(item_config)).map(|i| i.item) + }, + } +} + +#[must_use] +fn loadout_insert( + equip_slot: EquipSlot, + item: item::Item, + loadout: &mut Loadout, +) -> Option { + loadout_replace(equip_slot, Some(item), loadout) +} + +pub fn loadout_remove(equip_slot: EquipSlot, loadout: &mut Loadout) -> Option { + loadout_replace(equip_slot, None, loadout) +} + +fn swap_inventory_loadout( + inventory_slot: usize, + equip_slot: EquipSlot, + inventory: &mut Inventory, + loadout: &mut Loadout, +) { + // Check if loadout slot can hold item + if inventory + .get(inventory_slot) + .map_or(true, |item| equip_slot.can_hold(&item.kind)) + { + // Take item from loadout + let from_equip = loadout_remove(equip_slot, loadout); + // Swap with item in the inventory + let from_inv = if let Some(item) = from_equip { + // If this fails and we get item back as an err it will just be put back in the + // loadout + inventory + .insert(inventory_slot, item) + .unwrap_or_else(|i| Some(i)) + } else { + inventory.remove(inventory_slot) + }; + // Put item from the inventory in loadout + if let Some(item) = from_inv { + loadout_insert(equip_slot, item, loadout).unwrap_none(); // Can never fail + } + } +} + +fn swap_loadout(slot_a: EquipSlot, slot_b: EquipSlot, loadout: &mut Loadout) { + // Get items from the slots + let item_a = loadout_remove(slot_a, loadout); + let item_b = loadout_remove(slot_b, loadout); + // Check if items can go in the other slots + if item_a.as_ref().map_or(true, |i| slot_b.can_hold(&i.kind)) + && item_b.as_ref().map_or(true, |i| slot_a.can_hold(&i.kind)) + { + // Swap + loadout_replace(slot_b, item_a, loadout).unwrap_none(); + loadout_replace(slot_a, item_b, loadout).unwrap_none(); + } else { + // Otherwise put the items back + loadout_replace(slot_a, item_a, loadout).unwrap_none(); + loadout_replace(slot_b, item_b, loadout).unwrap_none(); + } +} + +// Should this report if a change actually occurred? (might be useful when +// minimizing network use) +pub fn swap( + slot_a: Slot, + slot_b: Slot, + inventory: Option<&mut Inventory>, + loadout: Option<&mut Loadout>, +) { + match (slot_a, slot_b) { + (Slot::Inventory(slot_a), Slot::Inventory(slot_b)) => { + inventory.map(|i| i.swap_slots(slot_a, slot_b)); + }, + (Slot::Inventory(inv_slot), Slot::Equip(equip_slot)) + | (Slot::Equip(equip_slot), Slot::Inventory(inv_slot)) => { + if let Some((inventory, loadout)) = loadout.and_then(|l| inventory.map(|i| (i, l))) { + swap_inventory_loadout(inv_slot, equip_slot, inventory, loadout); + } + }, + + (Slot::Equip(slot_a), Slot::Equip(slot_b)) => { + loadout.map(|l| swap_loadout(slot_a, slot_b, l)); + }, + } +} + +pub fn equip(slot: usize, inventory: &mut Inventory, loadout: &mut Loadout) { + use item::{armor::Armor, ItemKind}; + + let equip_slot = inventory.get(slot).and_then(|i| match &i.kind { + ItemKind::Tool(_) => Some(EquipSlot::Mainhand), + ItemKind::Armor { kind, .. } => Some(EquipSlot::Armor(match kind { + Armor::Head(_) => ArmorSlot::Head, + Armor::Neck(_) => ArmorSlot::Neck, + Armor::Shoulder(_) => ArmorSlot::Shoulders, + Armor::Chest(_) => ArmorSlot::Chest, + Armor::Hand(_) => ArmorSlot::Hands, + Armor::Ring(_) => ArmorSlot::Ring, + Armor::Back(_) => ArmorSlot::Back, + Armor::Belt(_) => ArmorSlot::Belt, + Armor::Pants(_) => ArmorSlot::Legs, + Armor::Foot(_) => ArmorSlot::Feet, + Armor::Tabard(_) => ArmorSlot::Tabard, + })), + ItemKind::Lantern(_) => Some(EquipSlot::Lantern), + _ => None, + }); + + if let Some(equip_slot) = equip_slot { + // If item is going to mainhand, put mainhand in offhand and place offhand in + // inventory + if let EquipSlot::Mainhand = equip_slot { + swap_loadout(EquipSlot::Mainhand, EquipSlot::Offhand, loadout); + } + + swap_inventory_loadout(slot, equip_slot, inventory, loadout); + } +} + +pub fn unequip(slot: EquipSlot, inventory: &mut Inventory, loadout: &mut Loadout) { + loadout_remove(slot, loadout) // Remove item from loadout + .and_then(|i| inventory.push(i)) // Insert into inventory + .and_then(|i| loadout_insert(slot, i, loadout)) // If that fails put back in loadout + .unwrap_none(); // Never fails +} diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 00079da365..09f88a6a08 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -31,7 +31,7 @@ pub use controller::{ pub use energy::{Energy, EnergySource}; pub use inputs::CanBuild; pub use inventory::{ - item, item::Item, Inventory, InventoryUpdate, InventoryUpdateEvent, MAX_PICKUP_RANGE_SQR, + item, item::Item, slot, Inventory, InventoryUpdate, InventoryUpdateEvent, MAX_PICKUP_RANGE_SQR, }; pub use last::Last; pub use location::{Waypoint, WaypointArea}; diff --git a/common/src/generation.rs b/common/src/generation.rs index c88ddc9c5c..22cf420021 100644 --- a/common/src/generation.rs +++ b/common/src/generation.rs @@ -1,14 +1,95 @@ +use crate::{ + comp::{self, humanoid, Alignment, Body, Item}, + npc::{self, NPC_NAMES}, +}; use vek::*; -pub enum EntityKind { - Enemy, - Boss, - Waypoint, +pub enum EntityTemplate { + Traveller, } pub struct EntityInfo { pub pos: Vec3, - pub kind: EntityKind, + pub is_waypoint: bool, // Edge case, overrides everything else + pub is_giant: bool, + pub alignment: Alignment, + pub body: Body, + pub name: Option, + pub main_tool: Option, +} + +impl EntityInfo { + pub fn at(pos: Vec3) -> Self { + Self { + pos, + is_waypoint: false, + is_giant: false, + alignment: Alignment::Wild, + body: Body::Humanoid(humanoid::Body::random()), + name: None, + main_tool: Some(Item::empty()), + } + } + + pub fn do_if(mut self, cond: bool, f: impl FnOnce(Self) -> Self) -> Self { + if cond { + self = f(self); + } + self + } + + pub fn into_waypoint(mut self) -> Self { + self.is_waypoint = true; + self + } + + pub fn into_giant(mut self) -> Self { + self.is_giant = true; + self + } + + pub fn with_alignment(mut self, alignment: Alignment) -> Self { + self.alignment = alignment; + self + } + + pub fn with_body(mut self, body: Body) -> Self { + self.body = body; + self + } + + pub fn with_name(mut self, name: String) -> Self { + self.name = Some(name); + self + } + + pub fn with_main_tool(mut self, main_tool: Item) -> Self { + self.main_tool = Some(main_tool); + self + } + + pub fn with_automatic_name(mut self) -> Self { + self.name = match &self.body { + Body::Humanoid(body) => Some(get_npc_name(&NPC_NAMES.humanoid, body.race)), + Body::QuadrupedMedium(body) => { + Some(get_npc_name(&NPC_NAMES.quadruped_medium, body.species)) + }, + Body::BirdMedium(body) => Some(get_npc_name(&NPC_NAMES.bird_medium, body.species)), + Body::Critter(body) => Some(get_npc_name(&NPC_NAMES.critter, body.species)), + Body::QuadrupedSmall(body) => { + Some(get_npc_name(&NPC_NAMES.quadruped_small, body.species)) + }, + _ => None, + } + .map(|s| { + if self.is_giant { + format!("Giant {}", s) + } else { + s.to_string() + } + }); + self + } } #[derive(Default)] @@ -17,8 +98,16 @@ pub struct ChunkSupplement { } impl ChunkSupplement { - pub fn with_entity(mut self, entity: EntityInfo) -> Self { - self.entities.push(entity); - self - } + pub fn add_entity(&mut self, entity: EntityInfo) { self.entities.push(entity); } +} + +pub fn get_npc_name< + 'a, + Species, + SpeciesData: for<'b> core::ops::Index<&'b Species, Output = npc::SpeciesNames>, +>( + body_data: &'a comp::BodyData, + species: Species, +) -> &'a str { + &body_data.species[&species].generic } diff --git a/common/src/lib.rs b/common/src/lib.rs index d0aaf2b8ad..899578a760 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -2,6 +2,7 @@ #![type_length_limit = "1664759"] #![feature( arbitrary_enum_discriminant, + option_unwrap_none, bool_to_option, label_break_value, trait_alias, @@ -27,6 +28,7 @@ pub mod region; pub mod spiral; pub mod state; pub mod states; +pub mod store; pub mod sync; pub mod sys; pub mod terrain; diff --git a/common/src/path.rs b/common/src/path.rs index 85e8f174af..a3c649fc57 100644 --- a/common/src/path.rs +++ b/common/src/path.rs @@ -33,9 +33,13 @@ impl FromIterator for Path { impl Path { pub fn len(&self) -> usize { self.nodes.len() } + pub fn iter(&self) -> impl Iterator { self.nodes.iter() } + pub fn start(&self) -> Option<&T> { self.nodes.first() } pub fn end(&self) -> Option<&T> { self.nodes.last() } + + pub fn nodes(&self) -> &[T] { &self.nodes } } // Route: A path that can be progressed along @@ -57,7 +61,12 @@ impl Route { pub fn is_finished(&self) -> bool { self.next().is_none() } - pub fn traverse(&mut self, vol: &V, pos: Vec3) -> Option> + pub fn traverse( + &mut self, + vol: &V, + pos: Vec3, + traversal_tolerance: f32, + ) -> Option> where V: BaseVol + ReadVol, { @@ -66,7 +75,9 @@ impl Route { None } else { let next_tgt = next.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0); - if ((pos - next_tgt) * Vec3::new(1.0, 1.0, 0.3)).magnitude_squared() < 1.0f32.powf(2.0) + if ((pos - (next_tgt + Vec3::unit_z() * 0.5)) * Vec3::new(1.0, 1.0, 0.3)) + .magnitude_squared() + < (traversal_tolerance * 2.0).powf(2.0) { self.next_idx += 1; } @@ -91,13 +102,14 @@ impl Chaser { pos: Vec3, tgt: Vec3, min_dist: f32, + traversal_tolerance: f32, ) -> Option> where V: BaseVol + ReadVol, { let pos_to_tgt = pos.distance(tgt); - if ((pos - tgt) * Vec3::new(1.0, 1.0, 0.3)).magnitude_squared() < min_dist.powf(2.0) { + if ((pos - tgt) * Vec3::new(1.0, 1.0, 0.15)).magnitude_squared() < min_dist.powf(2.0) { return None; } @@ -111,7 +123,7 @@ impl Chaser { self.route = Route::default(); } - self.route.traverse(vol, pos) + self.route.traverse(vol, pos, traversal_tolerance) } } else { None @@ -145,7 +157,7 @@ where { let is_walkable = |pos: &Vec3| { vol.get(*pos - Vec3::new(0, 0, 1)) - .map(|b| b.is_solid()) + .map(|b| b.is_solid() && b.get_height() == 1.0) .unwrap_or(false) && vol .get(*pos + Vec3::new(0, 0, 0)) diff --git a/common/src/spiral.rs b/common/src/spiral.rs index c08dee7f18..f0044763ca 100644 --- a/common/src/spiral.rs +++ b/common/src/spiral.rs @@ -1,5 +1,7 @@ use vek::*; +/// An iterator of coordinates that create a rectangular spiral out from the +/// origin #[derive(Clone)] pub struct Spiral2d { layer: i32, diff --git a/common/src/store.rs b/common/src/store.rs new file mode 100644 index 0000000000..983d6a156b --- /dev/null +++ b/common/src/store.rs @@ -0,0 +1,74 @@ +use std::{ + cmp::{Eq, PartialEq}, + fmt, hash, + marker::PhantomData, + ops::{Index, IndexMut}, +}; + +pub struct Id(usize, PhantomData); + +impl Id { + pub fn id(&self) -> usize { self.0 } +} + +impl Copy for Id {} +impl Clone for Id { + fn clone(&self) -> Self { Self(self.0, PhantomData) } +} +impl Eq for Id {} +impl PartialEq for Id { + fn eq(&self, other: &Self) -> bool { self.0 == other.0 } +} +impl fmt::Debug for Id { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Id<{}>({})", std::any::type_name::(), self.0) + } +} +impl hash::Hash for Id { + fn hash(&self, h: &mut H) { self.0.hash(h); } +} + +pub struct Store { + items: Vec, +} + +impl Default for Store { + fn default() -> Self { Self { items: Vec::new() } } +} + +impl Store { + pub fn get(&self, id: Id) -> &T { self.items.get(id.0).unwrap() } + + pub fn get_mut(&mut self, id: Id) -> &mut T { self.items.get_mut(id.0).unwrap() } + + pub fn ids(&self) -> impl Iterator> { + (0..self.items.len()).map(|i| Id(i, PhantomData)) + } + + pub fn iter(&self) -> impl Iterator { self.items.iter() } + + pub fn iter_mut(&mut self) -> impl Iterator { self.items.iter_mut() } + + pub fn iter_ids(&self) -> impl Iterator, &T)> { + self.items + .iter() + .enumerate() + .map(|(i, item)| (Id(i, PhantomData), item)) + } + + pub fn insert(&mut self, item: T) -> Id { + let id = Id(self.items.len(), PhantomData); + self.items.push(item); + id + } +} + +impl Index> for Store { + type Output = T; + + fn index(&self, id: Id) -> &Self::Output { self.get(id) } +} + +impl IndexMut> for Store { + fn index_mut(&mut self, id: Id) -> &mut Self::Output { self.get_mut(id) } +} diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index b11dcf22bb..61ce280579 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -1,7 +1,13 @@ use crate::{ - comp::{self, agent::Activity, Agent, Alignment, Controller, MountState, Ori, Pos, Stats}, + comp::{ + self, + agent::Activity, + item::{tool::ToolKind, ItemKind}, + Agent, Alignment, CharacterState, ControlAction, Controller, Loadout, MountState, Ori, Pos, + Scale, Stats, + }, path::Chaser, - state::Time, + state::{DeltaTime, Time}, sync::UidAllocator, terrain::TerrainGrid, util::Dir, @@ -20,10 +26,14 @@ impl<'a> System<'a> for Sys { type SystemData = ( Read<'a, UidAllocator>, Read<'a, Time>, + Read<'a, DeltaTime>, Entities<'a>, ReadStorage<'a, Pos>, ReadStorage<'a, Ori>, + ReadStorage<'a, Scale>, ReadStorage<'a, Stats>, + ReadStorage<'a, Loadout>, + ReadStorage<'a, CharacterState>, ReadExpect<'a, TerrainGrid>, ReadStorage<'a, Alignment>, WriteStorage<'a, Agent>, @@ -36,10 +46,14 @@ impl<'a> System<'a> for Sys { ( uid_allocator, time, + dt, entities, positions, orientations, + scales, stats, + loadouts, + character_states, terrain, alignments, mut agents, @@ -47,11 +61,23 @@ impl<'a> System<'a> for Sys { mount_states, ): Self::SystemData, ) { - for (entity, pos, ori, alignment, agent, controller, mount_state) in ( + for ( + entity, + pos, + ori, + alignment, + loadout, + character_state, + agent, + controller, + mount_state, + ) in ( &entities, &positions, &orientations, alignments.maybe(), + &loadouts, + &character_states, &mut agents, &mut controllers, mount_states.maybe(), @@ -82,10 +108,19 @@ impl<'a> System<'a> for Sys { const AVG_FOLLOW_DIST: f32 = 6.0; const MAX_FOLLOW_DIST: f32 = 12.0; const MAX_CHASE_DIST: f32 = 24.0; - const SEARCH_DIST: f32 = 30.0; - const SIGHT_DIST: f32 = 64.0; + const LISTEN_DIST: f32 = 16.0; + const SEARCH_DIST: f32 = 48.0; + const SIGHT_DIST: f32 = 128.0; const MIN_ATTACK_DIST: f32 = 3.25; + let scale = scales.get(entity).map(|s| s.0).unwrap_or(1.0); + + // This controls how picky NPCs are about their pathfinding. Giants are larger + // and so can afford to be less precise when trying to move around + // the world (especially since they would otherwise get stuck on + // obstacles that smaller entities would not). + let traversal_tolerance = scale; + let mut do_idle = false; let mut choose_target = false; @@ -96,19 +131,23 @@ impl<'a> System<'a> for Sys { thread_rng().gen::() - 0.5, thread_rng().gen::() - 0.5, ) * 0.1 - - *bearing * 0.01 + - *bearing * 0.003 - if let Some(patrol_origin) = agent.patrol_origin { Vec2::::from(pos.0 - patrol_origin) * 0.0002 } else { Vec2::zero() }; + // Stop if we're too close to a wall *bearing *= 0.1 + if terrain .ray( pos.0 + Vec3::unit_z(), pos.0 - + Vec3::from(*bearing).normalized() * 1.5 + + Vec3::from(*bearing) + .try_normalized() + .unwrap_or(Vec3::unit_y()) + * 5.0 + Vec3::unit_z(), ) .until(|block| block.is_solid()) @@ -122,9 +161,18 @@ impl<'a> System<'a> for Sys { 0.0 }; - if bearing.magnitude_squared() > 0.25f32.powf(2.0) { - inputs.move_dir = - bearing.try_normalized().unwrap_or(Vec2::zero()) * 0.65; + if bearing.magnitude_squared() > 0.5f32.powf(2.0) { + inputs.move_dir = *bearing * 0.65; + } + + // Put away weapon + if thread_rng().gen::() < 0.005 { + controller.actions.push(ControlAction::Unwield); + } + + // Sit + if thread_rng().gen::() < 0.0035 { + controller.actions.push(ControlAction::Sit); } // Sometimes try searching for new targets @@ -139,9 +187,13 @@ impl<'a> System<'a> for Sys { let dist_sqrd = pos.0.distance_squared(tgt_pos.0); // Follow, or return to idle if dist_sqrd > AVG_FOLLOW_DIST.powf(2.0) { - if let Some(bearing) = - chaser.chase(&*terrain, pos.0, tgt_pos.0, AVG_FOLLOW_DIST) - { + if let Some(bearing) = chaser.chase( + &*terrain, + pos.0, + tgt_pos.0, + AVG_FOLLOW_DIST, + traversal_tolerance, + ) { inputs.move_dir = Vec2::from(bearing) .try_normalized() .unwrap_or(Vec2::zero()); @@ -158,8 +210,27 @@ impl<'a> System<'a> for Sys { target, chaser, been_close, + powerup, .. } => { + enum Tactic { + Melee, + RangedPowerup, + Staff, + } + + let tactic = match loadout.active_item.as_ref().and_then(|ic| { + if let ItemKind::Tool(tool) = &ic.item.kind { + Some(&tool.kind) + } else { + None + } + }) { + Some(ToolKind::Bow(_)) => Tactic::RangedPowerup, + Some(ToolKind::Staff(_)) => Tactic::Staff, + _ => Tactic::Melee, + }; + if let (Some(tgt_pos), Some(tgt_stats), tgt_alignment) = ( positions.get(*target), stats.get(*target), @@ -183,32 +254,68 @@ impl<'a> System<'a> for Sys { } let dist_sqrd = pos.0.distance_squared(tgt_pos.0); - if dist_sqrd < MIN_ATTACK_DIST.powf(2.0) { + if dist_sqrd < (MIN_ATTACK_DIST * scale).powf(2.0) { // Close-range attack inputs.move_dir = Vec2::from(tgt_pos.0 - pos.0) .try_normalized() .unwrap_or(Vec2::unit_y()) * 0.7; - inputs.primary.set_state(true); + + match tactic { + Tactic::Melee | Tactic::Staff => inputs.primary.set_state(true), + Tactic::RangedPowerup => inputs.roll.set_state(true), + } } else if dist_sqrd < MAX_CHASE_DIST.powf(2.0) - || (dist_sqrd < SIGHT_DIST.powf(2.0) && !*been_close) + || (dist_sqrd < SIGHT_DIST.powf(2.0) + && (!*been_close || !matches!(tactic, Tactic::Melee))) { + let can_see_tgt = terrain + .ray(pos.0 + Vec3::unit_z(), tgt_pos.0 + Vec3::unit_z()) + .until(|block| !block.is_air()) + .cast() + .0 + .powf(2.0) + >= dist_sqrd; + + if can_see_tgt { + if let Tactic::RangedPowerup = tactic { + if *powerup > 2.0 { + inputs.primary.set_state(false); + *powerup = 0.0; + } else { + inputs.primary.set_state(true); + *powerup += dt.0; + } + } else if let Tactic::Staff = tactic { + if !character_state.is_wield() { + inputs.primary.set_state(true); + } + + inputs.secondary.set_state(true); + } + } + if dist_sqrd < MAX_CHASE_DIST.powf(2.0) { *been_close = true; } // Long-range chase - if let Some(bearing) = - chaser.chase(&*terrain, pos.0, tgt_pos.0, 1.25) - { + if let Some(bearing) = chaser.chase( + &*terrain, + pos.0, + tgt_pos.0, + 1.25, + traversal_tolerance, + ) { inputs.move_dir = Vec2::from(bearing) .try_normalized() .unwrap_or(Vec2::zero()); inputs.jump.set_state(bearing.z > 1.0); } - if dist_sqrd < (MAX_CHASE_DIST * 0.65).powf(2.0) - && thread_rng().gen::() < 0.01 + if dist_sqrd < 16.0f32.powf(2.0) + && matches!(tactic, Tactic::Melee) + && thread_rng().gen::() < 0.02 { inputs.roll.set_state(true); } @@ -234,13 +341,23 @@ impl<'a> System<'a> for Sys { let closest_entity = (&entities, &positions, &stats, alignments.maybe()) .join() .filter(|(e, e_pos, e_stats, e_alignment)| { - e_pos.0.distance_squared(pos.0) < SEARCH_DIST.powf(2.0) + ((e_pos.0.distance_squared(pos.0) < SEARCH_DIST.powf(2.0) && + // Within our view + (e_pos.0 - pos.0).try_normalized().map(|v| v.dot(*inputs.look_dir) > 0.15).unwrap_or(true)) + // Within listen distance + || e_pos.0.distance_squared(pos.0) < LISTEN_DIST.powf(2.0)) && *e != entity && !e_stats.is_dead && alignment .and_then(|a| e_alignment.map(|b| a.hostile_towards(*b))) .unwrap_or(false) }) + // Can we even see them? + .filter(|(_, e_pos, _, _)| terrain + .ray(pos.0 + Vec3::unit_z(), e_pos.0 + Vec3::unit_z()) + .until(|block| !block.is_air()) + .cast() + .0 >= e_pos.0.distance(pos.0)) .min_by_key(|(_, e_pos, _, _)| (e_pos.0.distance_squared(pos.0) * 100.0) as i32) .map(|(e, _, _, _)| e); @@ -250,6 +367,7 @@ impl<'a> System<'a> for Sys { chaser: Chaser::default(), time: time.0, been_close: false, + powerup: 0.0, }; } } @@ -273,6 +391,7 @@ impl<'a> System<'a> for Sys { chaser: Chaser::default(), time: time.0, been_close: false, + powerup: 0.0, }; } } @@ -303,6 +422,7 @@ impl<'a> System<'a> for Sys { chaser: Chaser::default(), time: time.0, been_close: false, + powerup: 0.0, }; } } diff --git a/common/src/sys/phys.rs b/common/src/sys/phys.rs index 2546af51a9..45cf64efdc 100644 --- a/common/src/sys/phys.rs +++ b/common/src/sys/phys.rs @@ -3,7 +3,7 @@ use crate::{ event::{EventBus, ServerEvent}, state::DeltaTime, sync::Uid, - terrain::{Block, TerrainGrid}, + terrain::{Block, BlockKind, TerrainGrid}, vol::ReadVol, }; use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage}; @@ -110,7 +110,9 @@ impl<'a> System<'a> for Sys { // Neighbouring blocks iterator let near_iter = (-hdist..hdist + 1) .map(move |i| { - (-hdist..hdist + 1).map(move |j| (0..vdist + 1).map(move |k| (i, j, k))) + (-hdist..hdist + 1).map(move |j| { + (1 - BlockKind::MAX_HEIGHT.ceil() as i32..vdist + 1).map(move |k| (i, j, k)) + }) }) .flatten() .flatten(); @@ -150,18 +152,19 @@ impl<'a> System<'a> for Sys { // Function for determining whether the player at a specific position collides // with the ground - let collision_with = |pos: Vec3, hit: fn(&Block) -> bool, near_iter| { + let collision_with = |pos: Vec3, hit: &dyn Fn(&Block) -> bool, near_iter| { for (i, j, k) in near_iter { let block_pos = pos.map(|e| e.floor() as i32) + Vec3::new(i, j, k); - if terrain.get(block_pos).map(hit).unwrap_or(false) { + if let Some(block) = terrain.get(block_pos).ok().copied().filter(hit) { let player_aabb = Aabb { min: pos + Vec3::new(-player_rad, -player_rad, 0.0), max: pos + Vec3::new(player_rad, player_rad, player_height), }; let block_aabb = Aabb { min: block_pos.map(|e| e as f32), - max: block_pos.map(|e| e as f32) + 1.0, + max: block_pos.map(|e| e as f32) + + Vec3::new(1.0, 1.0, block.get_height()), }; if player_aabb.collides_with_aabb(block_aabb) { @@ -189,7 +192,7 @@ impl<'a> System<'a> for Sys { const MAX_ATTEMPTS: usize = 16; // While the player is colliding with the terrain... - while collision_with(pos.0, |vox| vox.is_solid(), near_iter.clone()) + while collision_with(pos.0, &|block| block.is_solid(), near_iter.clone()) && attempts < MAX_ATTEMPTS { // Calculate the player's AABB @@ -200,31 +203,34 @@ impl<'a> System<'a> for Sys { // Determine the block that we are colliding with most (based on minimum // collision axis) - let (_block_pos, block_aabb) = near_iter + let (_block_pos, block_aabb, block_height) = near_iter .clone() // Calculate the block's position in world space .map(|(i, j, k)| pos.0.map(|e| e.floor() as i32) + Vec3::new(i, j, k)) - // Calculate the AABB of the block - .map(|block_pos| { - ( - block_pos, - Aabb { - min: block_pos.map(|e| e as f32), - max: block_pos.map(|e| e as f32) + 1.0, - }, - ) - }) // Make sure the block is actually solid - .filter(|(block_pos, _)| { - terrain - .get(*block_pos) - .map(|vox| vox.is_solid()) - .unwrap_or(false) + .filter_map(|block_pos| { + if let Some(block) = terrain + .get(block_pos) + .ok() + .filter(|block| block.is_solid()) + { + // Calculate block AABB + Some(( + block_pos, + Aabb { + min: block_pos.map(|e| e as f32), + max: block_pos.map(|e| e as f32) + Vec3::new(1.0, 1.0, block.get_height()), + }, + block.get_height(), + )) + } else { + None + } }) // Determine whether the block's AABB collides with the player's AABB - .filter(|(_, block_aabb)| block_aabb.collides_with_aabb(player_aabb)) + .filter(|(_, block_aabb, _)| block_aabb.collides_with_aabb(player_aabb)) // Find the maximum of the minimum collision axes (this bit is weird, trust me that it works) - .min_by_key(|(_, block_aabb)| { + .min_by_key(|(_, block_aabb, _)| { ((block_aabb.center() - player_aabb.center() - Vec3::unit_z() * 0.5) .map(|e| e.abs()) .sum() @@ -257,7 +263,7 @@ impl<'a> System<'a> for Sys { // When the resolution direction is non-vertical, we must be colliding with a // wall If the space above is free... - if !collision_with(Vec3::new(pos.0.x, pos.0.y, (pos.0.z + 0.1).ceil()), |vox| vox.is_solid(), near_iter.clone()) + if !collision_with(Vec3::new(pos.0.x, pos.0.y, (pos.0.z + 0.1).ceil()), &|block| block.is_solid(), near_iter.clone()) // ...and we're being pushed out horizontally... && resolve_dir.z == 0.0 // ...and the vertical resolution direction is sufficiently great... @@ -265,17 +271,17 @@ impl<'a> System<'a> for Sys { // ...and we're falling/standing OR there is a block *directly* beneath our current origin (note: not hitbox)... && (vel.0.z <= 0.0 || terrain .get((pos.0 - Vec3::unit_z() * 0.1).map(|e| e.floor() as i32)) - .map(|vox| vox.is_solid()) + .map(|block| block.is_solid()) .unwrap_or(false)) // ...and there is a collision with a block beneath our current hitbox... && collision_with( old_pos + resolve_dir - Vec3::unit_z() * 1.05, - |vox| vox.is_solid(), + &|block| block.is_solid(), near_iter.clone(), ) { // ...block-hop! - pos.0.z = (pos.0.z + 0.1).ceil(); + pos.0.z = (pos.0.z + 0.1).floor() + block_height; vel.0.z = 0.0; on_ground = true; break; @@ -304,20 +310,26 @@ impl<'a> System<'a> for Sys { // If the space below us is free, then "snap" to the ground } else if collision_with( pos.0 - Vec3::unit_z() * 1.05, - |vox| vox.is_solid(), + &|block| block.is_solid(), near_iter.clone(), ) && vel.0.z < 0.0 && vel.0.z > -1.5 && was_on_ground - && !terrain - .get( - Vec3::new(pos.0.x, pos.0.y, (pos.0.z - 0.05).floor()) - .map(|e| e.floor() as i32), - ) - .map(|vox| vox.is_solid()) - .unwrap_or(false) + && !collision_with( + pos.0 - Vec3::unit_z() * 0.05, + &|block| { + block.is_solid() && block.get_height() >= (pos.0.z - 0.05).rem_euclid(1.0) + }, + near_iter.clone(), + ) { - pos.0.z = (pos.0.z - 0.05).floor(); + let snap_height = terrain + .get(Vec3::new(pos.0.x, pos.0.y, pos.0.z - 0.05).map(|e| e.floor() as i32)) + .ok() + .filter(|block| block.is_solid()) + .map(|block| block.get_height()) + .unwrap_or(0.0); + pos.0.z = (pos.0.z - 0.05).floor() + snap_height; physics_state.on_ground = true; } @@ -329,7 +341,11 @@ impl<'a> System<'a> for Sys { ]; if let (wall_dir, true) = dirs.iter().fold((Vec3::zero(), false), |(a, hit), dir| { - if collision_with(pos.0 + *dir * 0.01, |vox| vox.is_solid(), near_iter.clone()) { + if collision_with( + pos.0 + *dir * 0.01, + &|block| block.is_solid(), + near_iter.clone(), + ) { (a + dir, true) } else { (a, hit) @@ -341,7 +357,8 @@ impl<'a> System<'a> for Sys { } // Figure out if we're in water - physics_state.in_fluid = collision_with(pos.0, |vox| vox.is_fluid(), near_iter.clone()); + physics_state.in_fluid = + collision_with(pos.0, &|block| block.is_fluid(), near_iter.clone()); let _ = physics_states.insert(entity, physics_state); } diff --git a/common/src/sys/projectile.rs b/common/src/sys/projectile.rs index 0174f4299f..bf39c3ed02 100644 --- a/common/src/sys/projectile.rs +++ b/common/src/sys/projectile.rs @@ -100,7 +100,9 @@ impl<'a> System<'a> for Sys { for effect in projectile.hit_entity.drain(..) { match effect { projectile::Effect::Damage(change) => { - server_emitter.emit(ServerEvent::Damage { uid: other, change }) + if other != projectile.owner.unwrap() { + server_emitter.emit(ServerEvent::Damage { uid: other, change }); + } }, projectile::Effect::Knockback(knockback) => { if let Some(entity) = @@ -134,8 +136,10 @@ impl<'a> System<'a> for Sys { cause: HealthSource::World, }), projectile::Effect::Possess => { - if let Some(owner) = projectile.owner { - server_emitter.emit(ServerEvent::Possess(owner.into(), other)); + if other != projectile.owner.unwrap() { + if let Some(owner) = projectile.owner { + server_emitter.emit(ServerEvent::Possess(owner.into(), other)); + } } }, _ => {}, diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index 0c5c97155a..056b8715eb 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -40,9 +40,29 @@ pub enum BlockKind { Fern, DeadBush, Blueberry, + Ember, + Corn, + WheatYellow, + WheatGreen, + Cabbage, + Flax, + Carrot, + Tomato, + Radish, + Coconut, + Turnip, + Window1, + Window2, + Window3, + Window4, + Scarecrow, + StreetLamp, + Door, } impl BlockKind { + pub const MAX_HEIGHT: f32 = 3.0; + pub fn is_tangible(&self) -> bool { match self { BlockKind::Air => false, @@ -81,6 +101,25 @@ impl BlockKind { BlockKind::Fern => true, BlockKind::DeadBush => true, BlockKind::Blueberry => true, + BlockKind::Ember => true, + BlockKind::Corn => true, + BlockKind::WheatYellow => true, + BlockKind::WheatGreen => true, + BlockKind::Cabbage => false, + BlockKind::Pumpkin => false, + BlockKind::Flax => true, + BlockKind::Carrot => true, + BlockKind::Tomato => false, + BlockKind::Radish => true, + BlockKind::Turnip => true, + BlockKind::Coconut => true, + BlockKind::Window1 => true, + BlockKind::Window2 => true, + BlockKind::Window3 => true, + BlockKind::Window4 => true, + BlockKind::Scarecrow => true, + BlockKind::StreetLamp => true, + BlockKind::Door => false, _ => false, } } @@ -118,12 +157,31 @@ impl BlockKind { BlockKind::Velorite => false, BlockKind::VeloriteFrag => false, BlockKind::Chest => false, + BlockKind::Pumpkin => false, BlockKind::Welwitch => false, BlockKind::LingonBerry => false, BlockKind::LeafyPlant => false, BlockKind::Fern => false, BlockKind::DeadBush => false, BlockKind::Blueberry => false, + BlockKind::Ember => false, + BlockKind::Corn => false, + BlockKind::WheatYellow => false, + BlockKind::WheatGreen => false, + BlockKind::Cabbage => false, + BlockKind::Flax => false, + BlockKind::Carrot => false, + BlockKind::Tomato => false, + BlockKind::Radish => false, + BlockKind::Turnip => false, + BlockKind::Coconut => false, + BlockKind::Window1 => false, + BlockKind::Window2 => false, + BlockKind::Window3 => false, + BlockKind::Window4 => false, + BlockKind::Scarecrow => false, + BlockKind::StreetLamp => false, + BlockKind::Door => false, _ => true, } } @@ -159,10 +217,44 @@ impl BlockKind { BlockKind::Fern => false, BlockKind::DeadBush => false, BlockKind::Blueberry => false, + BlockKind::Ember => false, + BlockKind::Corn => false, + BlockKind::WheatYellow => false, + BlockKind::WheatGreen => false, + BlockKind::Cabbage => true, + BlockKind::Flax => false, + BlockKind::Carrot => true, + BlockKind::Tomato => true, + BlockKind::Radish => true, + BlockKind::Turnip => true, + BlockKind::Coconut => true, + BlockKind::Scarecrow => true, + BlockKind::StreetLamp => true, + BlockKind::Door => false, _ => true, } } + // TODO: Integrate this into `is_solid` by returning an `Option` + pub fn get_height(&self) -> f32 { + // Beware: the height *must* be <= `MAX_HEIGHT` or the collision system will not + // properly detect it! + match self { + BlockKind::Tomato => 1.65, + BlockKind::LargeCactus => 2.5, + BlockKind::Scarecrow => 3.0, + BlockKind::Turnip => 0.36, + BlockKind::Pumpkin => 0.81, + BlockKind::Cabbage => 0.45, + BlockKind::Chest => 1.09, + BlockKind::StreetLamp => 3.0, + BlockKind::Carrot => 0.18, + BlockKind::Radish => 0.18, + BlockKind::Door => 3.0, + _ => 1.0, + } + } + pub fn is_collectible(&self) -> bool { match self { BlockKind::BlueFlower => false, @@ -180,7 +272,7 @@ impl BlockKind { BlockKind::Velorite => true, BlockKind::VeloriteFrag => true, BlockKind::Chest => true, - BlockKind::Pumpkin => true, + BlockKind::Coconut => true, _ => false, } } @@ -209,6 +301,17 @@ impl Block { } } + pub fn get_ori(&self) -> Option { + match self.kind { + BlockKind::Window1 + | BlockKind::Window2 + | BlockKind::Window3 + | BlockKind::Window4 + | BlockKind::Door => Some(self.color[0] & 0b111), + _ => None, + } + } + pub fn kind(&self) -> BlockKind { self.kind } } diff --git a/common/src/terrain/structure.rs b/common/src/terrain/structure.rs index 06637ecdfe..8bdf71586e 100644 --- a/common/src/terrain/structure.rs +++ b/common/src/terrain/structure.rs @@ -20,6 +20,7 @@ pub enum StructureBlock { Water, GreenSludge, Fruit, + Coconut, Chest, Hollow, Liana, @@ -114,6 +115,7 @@ impl Asset for Structure { 7 => StructureBlock::Fruit, 9 => StructureBlock::Liana, 10 => StructureBlock::Chest, + 11 => StructureBlock::Coconut, 13 => StructureBlock::PalmLeavesOuter, 14 => StructureBlock::PalmLeavesInner, 15 => StructureBlock::Hollow, diff --git a/rust-toolchain b/rust-toolchain index be5bcdaf12..1ff3407a54 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2020-03-05 +nightly-2020-04-17 \ No newline at end of file diff --git a/server-cli/Dockerfile b/server-cli/Dockerfile index ee9c4009a5..319362c180 100644 --- a/server-cli/Dockerfile +++ b/server-cli/Dockerfile @@ -2,6 +2,12 @@ FROM debian:stable-slim ARG PROJECTNAME=server-cli +# librust-backtrace+libbacktrace-dev = backtrace functionality +RUN apt-get update; export DEBIAN_FRONTEND=noninteractive; \ + apt-get install -y --no-install-recommends --assume-yes \ + librust-backtrace+libbacktrace-dev; \ + rm -rf /var/lib/apt/lists/*; + COPY ./veloren-server-cli /opt/veloren-server-cli COPY ./assets/common /opt/assets/common COPY ./assets/world /opt/assets/world diff --git a/server-cli/docker-compose.yml b/server-cli/docker-compose.yml index d4e52b364f..6999700d37 100644 --- a/server-cli/docker-compose.yml +++ b/server-cli/docker-compose.yml @@ -3,22 +3,13 @@ version: "3.5" services: game-server: image: registry.gitlab.com/veloren/veloren:master-server + container_name: veloren-game-server-master ports: - "14004:14004" - "14005:14005" - deploy: - replicas: 1 - update_config: - parallelism: 2 - delay: 10s - order: stop-first - failure_action: rollback - restart_policy: - condition: on-failure + restart: on-failure:0 watchtower: image: containrrr/watchtower volumes: - /var/run/docker.sock:/var/run/docker.sock - - /root/.docker/config.json:/config.json - command: --interval 30 --cleanup - + command: --interval 30 --cleanup veloren-game-server-master \ No newline at end of file diff --git a/server/src/cmd.rs b/server/src/cmd.rs index b9a107d4d8..7bc96dde8f 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -216,7 +216,7 @@ lazy_static! { "waypoint", "{}", "/waypoint : Set your waypoint to your current position", - false, + true, handle_waypoint, ), ChatCommand::new( diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 59eb020f02..8523af441d 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -138,15 +138,13 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3) { let state = &server.state; - if vel.z <= -37.0 { + if vel.z <= -30.0 { if let Some(stats) = state.ecs().write_storage::().get_mut(entity) { - let falldmg = (vel.z / 2.5) as i32; - if falldmg < 0 { - stats.health.change_by(comp::HealthChange { - amount: falldmg, - cause: comp::HealthSource::World, - }); - } + let falldmg = vel.z.powi(2) as i32 / 20 - 40; + stats.health.change_by(comp::HealthChange { + amount: -falldmg, + cause: comp::HealthSource::World, + }); } } } diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index f5929ec4f5..2228af5424 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -1,6 +1,10 @@ use crate::{Server, StateExt}; use common::{ - comp::{self, item, Pos, MAX_PICKUP_RANGE_SQR}, + comp::{ + self, item, + slot::{self, Slot}, + Pos, MAX_PICKUP_RANGE_SQR, + }, sync::WorldSyncExt, terrain::block::Block, vol::{ReadVol, Vox}, @@ -83,156 +87,141 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv } }, - comp::InventoryManip::Use(slot_idx) => { - let item_opt = state - .ecs() - .write_storage::() - .get_mut(entity) - .and_then(|inv| inv.take(slot_idx)); + comp::InventoryManip::Use(slot) => { + let mut inventories = state.ecs().write_storage::(); + let inventory = if let Some(inventory) = inventories.get_mut(entity) { + inventory + } else { + error!("Can't manipulate inventory, entity doesn't have one"); + return; + }; - let mut event = comp::InventoryUpdateEvent::Used; + let mut maybe_effect = None; - if let Some(item) = item_opt { - match &item.kind { - item::ItemKind::Tool(tool) => { - if let Some(loadout) = - state.ecs().write_storage::().get_mut(entity) - { - // Insert old item into inventory - if let Some(old_item) = loadout.active_item.take() { - state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|inv| inv.insert(slot_idx, old_item.item)); - } - - let mut abilities = tool.get_abilities(); - let mut ability_drain = abilities.drain(..); - let active_item = comp::ItemConfig { - item, - ability1: ability_drain.next(), - ability2: ability_drain.next(), - ability3: ability_drain.next(), - block_ability: Some(comp::CharacterAbility::BasicBlock), - dodge_ability: Some(comp::CharacterAbility::Roll), - }; - loadout.active_item = Some(active_item); + let event = match slot { + Slot::Inventory(slot) => { + use item::ItemKind; + // Check if item is equipable + if inventory.get(slot).map_or(false, |i| match &i.kind { + ItemKind::Tool(_) | ItemKind::Armor { .. } | ItemKind::Lantern(_) => true, + _ => false, + }) { + if let Some(loadout) = state.ecs().write_storage().get_mut(entity) { + slot::equip(slot, inventory, loadout); + Some(comp::InventoryUpdateEvent::Used) + } else { + None } - }, - - item::ItemKind::Consumable { kind, effect, .. } => { - event = comp::InventoryUpdateEvent::Consumed(*kind); - state.apply_effect(entity, *effect); - }, - - item::ItemKind::Armor { kind, .. } => { - if let Some(loadout) = - state.ecs().write_storage::().get_mut(entity) - { - use comp::item::armor::Armor::*; - let slot = match kind.clone() { - Shoulder(_) => &mut loadout.shoulder, - Chest(_) => &mut loadout.chest, - Belt(_) => &mut loadout.belt, - Hand(_) => &mut loadout.hand, - Pants(_) => &mut loadout.pants, - Foot(_) => &mut loadout.foot, - }; - - // Insert old item into inventory - if let Some(old_item) = slot.take() { - state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|inv| inv.insert(slot_idx, old_item)); - } - - *slot = Some(item); - } - }, - - item::ItemKind::Utility { kind, .. } => match kind { - comp::item::Utility::Collar => { - let reinsert = if let Some(pos) = - state.read_storage::().get(entity) - { - if ( - &state.read_storage::(), - &state.read_storage::(), - ) - .join() - .filter(|(alignment, _)| { - alignment == &&comp::Alignment::Owned(entity) - }) - .count() - >= 3 + } else if let Some(item) = inventory.take(slot) { + match &item.kind { + ItemKind::Consumable { kind, effect, .. } => { + maybe_effect = Some(*effect); + Some(comp::InventoryUpdateEvent::Consumed(*kind)) + }, + ItemKind::Utility { + kind: comp::item::Utility::Collar, + .. + } => { + let reinsert = if let Some(pos) = + state.read_storage::().get(entity) { - true - } else if let Some(tameable_entity) = { - let nearest_tameable = ( - &state.ecs().entities(), - &state.ecs().read_storage::(), - &state.ecs().read_storage::(), + if ( + &state.read_storage::(), + &state.read_storage::(), ) .join() - .filter(|(_, wild_pos, _)| { - wild_pos.0.distance_squared(pos.0) < 5.0f32.powf(2.0) + .filter(|(alignment, _)| { + alignment == &&comp::Alignment::Owned(entity) }) - .filter(|(_, _, alignment)| { - alignment == &&comp::Alignment::Wild - }) - .min_by_key(|(_, wild_pos, _)| { - (wild_pos.0.distance_squared(pos.0) * 100.0) as i32 - }) - .map(|(entity, _, _)| entity); - nearest_tameable - } { - let _ = state - .ecs() - .write_storage() - .insert(tameable_entity, comp::Alignment::Owned(entity)); - let _ = state - .ecs() - .write_storage() - .insert(tameable_entity, comp::Agent::default()); - false + .count() + >= 3 + { + true + } else if let Some(tameable_entity) = { + let nearest_tameable = ( + &state.ecs().entities(), + &state.ecs().read_storage::(), + &state.ecs().read_storage::(), + ) + .join() + .filter(|(_, wild_pos, _)| { + wild_pos.0.distance_squared(pos.0) + < 5.0f32.powf(2.0) + }) + .filter(|(_, _, alignment)| { + alignment == &&comp::Alignment::Wild + }) + .min_by_key(|(_, wild_pos, _)| { + (wild_pos.0.distance_squared(pos.0) * 100.0) as i32 + }) + .map(|(entity, _, _)| entity); + nearest_tameable + } { + let _ = state.ecs().write_storage().insert( + tameable_entity, + comp::Alignment::Owned(entity), + ); + let _ = state + .ecs() + .write_storage() + .insert(tameable_entity, comp::Agent::default()); + false + } else { + true + } } else { true + }; + + if reinsert { + let _ = inventory.insert(slot, item); } - } else { - true - }; - if reinsert { - let _ = state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|inv| inv.insert(slot_idx, item)); - } - }, - }, - _ => { - let _ = state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|inv| inv.insert(slot_idx, item)); - }, - } + Some(comp::InventoryUpdateEvent::Used) + }, + _ => { + // TODO: this doesn't work for stackable items + inventory.insert(slot, item).unwrap(); + None + }, + } + } else { + None + } + }, + Slot::Equip(slot) => { + if let Some(loadout) = state.ecs().write_storage().get_mut(entity) { + slot::unequip(slot, inventory, loadout); + Some(comp::InventoryUpdateEvent::Used) + } else { + error!("Entity doesn't have a loadout, can't unequip..."); + None + } + }, + }; + + drop(inventories); + if let Some(effect) = maybe_effect { + state.apply_effect(entity, effect); + } + if let Some(event) = event { + state.write_component(entity, comp::InventoryUpdate::new(event)); } - - state.write_component(entity, comp::InventoryUpdate::new(event)); }, comp::InventoryManip::Swap(a, b) => { - state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|inv| inv.swap_slots(a, b)); + let ecs = state.ecs(); + let mut inventories = ecs.write_storage(); + let mut loadouts = ecs.write_storage(); + let inventory = inventories.get_mut(entity); + let loadout = loadouts.get_mut(entity); + + slot::swap(a, b, inventory, loadout); + + // :/ + drop(loadouts); + drop(inventories); + state.write_component( entity, comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Swapped), @@ -240,11 +229,18 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv }, comp::InventoryManip::Drop(slot) => { - let item = state - .ecs() - .write_storage::() - .get_mut(entity) - .and_then(|inv| inv.remove(slot)); + let item = match slot { + Slot::Inventory(slot) => state + .ecs() + .write_storage::() + .get_mut(entity) + .and_then(|inv| inv.remove(slot)), + Slot::Equip(slot) => state + .ecs() + .write_storage() + .get_mut(entity) + .and_then(|ldt| slot::loadout_remove(slot, ldt)), + }; if let (Some(item), Some(pos)) = (item, state.ecs().read_storage::().get(entity)) diff --git a/server/src/lib.rs b/server/src/lib.rs index 5c83b273a7..61dad7c767 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -49,6 +49,7 @@ use uvth::{ThreadPool, ThreadPoolBuilder}; use vek::*; #[cfg(feature = "worldgen")] use world::{ + civ::SiteKind, sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP, WORLD_SIZE}, World, }; @@ -131,11 +132,23 @@ impl Server { // complaining) // spawn in the chunk, that is in the middle of the world - let spawn_chunk: Vec2 = WORLD_SIZE.map(|e| e as i32) / 2; + let center_chunk: Vec2 = WORLD_SIZE.map(|e| e as i32) / 2; + + // Find a town to spawn in that's close to the centre of the world + let spawn_chunk = world + .civs() + .sites() + .filter(|site| matches!(site.kind, SiteKind::Settlement)) + .map(|site| site.center) + .min_by_key(|site_pos| site_pos.distance_squared(center_chunk)) + .unwrap_or(center_chunk); + // calculate the absolute position of the chunk in the world // (we could add TerrainChunkSize::RECT_SIZE / 2 here, to spawn in the midde of // the chunk) - let spawn_location = spawn_chunk * TerrainChunkSize::RECT_SIZE.map(|e| e as i32); + let spawn_location = spawn_chunk.map2(TerrainChunkSize::RECT_SIZE, |e, sz| { + e as i32 * sz as i32 + sz as i32 / 2 + }); // get a z cache for the collumn in which we want to spawn let mut block_sampler = world.sample_blocks(); diff --git a/server/src/settings.rs b/server/src/settings.rs index 12c6fc157f..0a04f8225a 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -121,9 +121,9 @@ impl ServerSettings { start_time: 9.0 * 3600.0, admins: vec!["singleplayer".to_string()], /* TODO: Let the player choose if they want * to use admin commands or not */ - ..load // Fill in remaining fields from settings.ron. + ..load // Fill in remaining fields from server_settings.ron. } } - fn get_settings_path() -> PathBuf { PathBuf::from(r"settings.ron") } + fn get_settings_path() -> PathBuf { PathBuf::from(r"server_settings.ron") } } diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 458339b690..b5c178a23a 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -179,11 +179,23 @@ impl StateExt for State { }), second_item: None, shoulder: None, - chest: None, + chest: Some(assets::load_expect_cloned( + "common.items.armor.starter.rugged_chest", + )), belt: None, hand: None, - pants: None, - foot: None, + pants: Some(assets::load_expect_cloned( + "common.items.armor.starter.rugged_pants", + )), + foot: Some(assets::load_expect_cloned( + "common.items.armor.starter.sandals_0", + )), + back: None, + ring: None, + neck: None, + lantern: None, + head: None, + tabard: None, } } else { comp::Loadout::default() diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index dbc6bcd513..dab026bf29 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -4,13 +4,13 @@ use common::{ assets, comp::{self, item, CharacterAbility, Item, ItemConfig, Player, Pos}, event::{EventBus, ServerEvent}, - generation::EntityKind, + generation::get_npc_name, msg::ServerMsg, - npc::{self, NPC_NAMES}, + npc::NPC_NAMES, state::TerrainChanges, terrain::TerrainGrid, }; -use rand::{seq::SliceRandom, Rng}; +use rand::Rng; use specs::{Join, Read, ReadStorage, System, Write, WriteExpect, WriteStorage}; use std::{sync::Arc, time::Duration}; use vek::*; @@ -101,271 +101,217 @@ impl<'a> System<'a> for Sys { // Handle chunk supplement for entity in supplement.entities { - if let EntityKind::Waypoint = entity.kind { + if entity.is_waypoint { server_emitter.emit(ServerEvent::CreateWaypoint(entity.pos)); - } else { - fn get_npc_name< - 'a, - Species, - SpeciesData: for<'b> core::ops::Index<&'b Species, Output = npc::SpeciesNames>, - >( - body_data: &'a comp::BodyData, - species: Species, - ) -> &'a str { - &body_data.species[&species].generic - } + continue; + } - const SPAWN_NPCS: &'static [fn() -> ( - String, - comp::Body, - Option, - comp::Alignment, - )] = &[ - (|| { - let body = comp::humanoid::Body::random(); - ( - format!( - "{} Traveler", - get_npc_name(&NPC_NAMES.humanoid, body.race) - ), - comp::Body::Humanoid(body), - Some(assets::load_expect_cloned( - "common.items.weapons.starter_axe", - )), - comp::Alignment::Npc, - ) - }) as _, - (|| { - let body = comp::humanoid::Body::random(); - ( - format!("{} Bandit", get_npc_name(&NPC_NAMES.humanoid, body.race)), - comp::Body::Humanoid(body), - Some(assets::load_expect_cloned( - "common.items.weapons.short_sword_0", - )), - comp::Alignment::Enemy, - ) - }) as _, - (|| { - let body = comp::quadruped_medium::Body::random(); - ( - get_npc_name(&NPC_NAMES.quadruped_medium, body.species).into(), - comp::Body::QuadrupedMedium(body), - None, - comp::Alignment::Enemy, - ) - }) as _, - (|| { - let body = comp::bird_medium::Body::random(); - ( - get_npc_name(&NPC_NAMES.bird_medium, body.species).into(), - comp::Body::BirdMedium(body), - None, - comp::Alignment::Wild, - ) - }) as _, - (|| { - let body = comp::critter::Body::random(); - ( - get_npc_name(&NPC_NAMES.critter, body.species).into(), - comp::Body::Critter(body), - None, - comp::Alignment::Wild, - ) - }) as _, - (|| { - let body = comp::quadruped_small::Body::random(); - ( - get_npc_name(&NPC_NAMES.quadruped_small, body.species).into(), - comp::Body::QuadrupedSmall(body), - None, - comp::Alignment::Wild, - ) - }), - ]; - let (name, mut body, main, mut alignment) = SPAWN_NPCS - .choose(&mut rand::thread_rng()) - .expect("SPAWN_NPCS is nonempty")( - ); - let mut stats = comp::Stats::new(name, body); + let mut body = entity.body; + let name = entity.name.unwrap_or("Unnamed".to_string()); + let alignment = entity.alignment; + let main_tool = entity.main_tool; - let active_item = - if let Some(item::ItemKind::Tool(tool)) = main.as_ref().map(|i| &i.kind) { - let mut abilities = tool.get_abilities(); - let mut ability_drain = abilities.drain(..); + let mut stats = comp::Stats::new(name, body); - main.map(|item| comp::ItemConfig { - item, - ability1: ability_drain.next(), - ability2: ability_drain.next(), - ability3: ability_drain.next(), - block_ability: None, - dodge_ability: Some(comp::CharacterAbility::Roll), - }) - } else { - Some(ItemConfig { - // We need the empty item so npcs can attack - item: Item::empty(), - ability1: Some(CharacterAbility::BasicMelee { - energy_cost: 0, - buildup_duration: Duration::from_millis(0), - recover_duration: Duration::from_millis(400), - base_healthchange: -4, - range: 3.5, - max_angle: 60.0, - }), - ability2: None, - ability3: None, - block_ability: None, - dodge_ability: None, - }) - }; + let active_item = + if let Some(item::ItemKind::Tool(tool)) = main_tool.as_ref().map(|i| &i.kind) { + let mut abilities = tool.get_abilities(); + let mut ability_drain = abilities.drain(..); - let mut loadout = match alignment { - comp::Alignment::Npc => comp::Loadout { - active_item, - second_item: None, - shoulder: Some(assets::load_expect_cloned( - "common.items.armor.shoulder.leather_0", - )), - chest: Some(assets::load_expect_cloned( - "common.items.armor.chest.leather_0", - )), - belt: Some(assets::load_expect_cloned( - "common.items.armor.belt.plate_0", - )), - hand: Some(assets::load_expect_cloned( - "common.items.armor.hand.plate_0", - )), - pants: Some(assets::load_expect_cloned( - "common.items.armor.pants.plate_green_0", - )), - foot: Some(assets::load_expect_cloned( - "common.items.armor.foot.leather_0", - )), - }, - comp::Alignment::Enemy => comp::Loadout { - active_item, - second_item: None, - shoulder: Some(assets::load_expect_cloned( - "common.items.armor.shoulder.leather_0", - )), - chest: Some(assets::load_expect_cloned( - "common.items.armor.chest.plate_green_0", - )), - belt: Some(assets::load_expect_cloned( - "common.items.armor.belt.plate_0", - )), - hand: Some(assets::load_expect_cloned( - "common.items.armor.hand.plate_0", - )), - pants: Some(assets::load_expect_cloned( - "common.items.armor.pants.plate_green_0", - )), - foot: Some(assets::load_expect_cloned( - "common.items.armor.foot.plate_0", - )), - }, - _ => comp::Loadout { - active_item, - second_item: None, - shoulder: None, - chest: None, - belt: None, - hand: None, - pants: None, - foot: None, - }, + main_tool.map(|item| comp::ItemConfig { + item, + ability1: ability_drain.next(), + ability2: ability_drain.next(), + ability3: ability_drain.next(), + block_ability: None, + dodge_ability: Some(comp::CharacterAbility::Roll), + }) + } else { + Some(ItemConfig { + // We need the empty item so npcs can attack + item: Item::empty(), + ability1: Some(CharacterAbility::BasicMelee { + energy_cost: 0, + buildup_duration: Duration::from_millis(0), + recover_duration: Duration::from_millis(400), + base_healthchange: -4, + range: 3.5, + max_angle: 60.0, + }), + ability2: None, + ability3: None, + block_ability: None, + dodge_ability: None, + }) }; - let mut scale = 1.0; + let mut loadout = match alignment { + comp::Alignment::Npc => comp::Loadout { + active_item, + second_item: None, + shoulder: Some(assets::load_expect_cloned( + "common.items.armor.shoulder.leather_0", + )), + chest: Some(assets::load_expect_cloned( + "common.items.armor.chest.leather_0", + )), + belt: Some(assets::load_expect_cloned( + "common.items.armor.belt.plate_0", + )), + hand: Some(assets::load_expect_cloned( + "common.items.armor.hand.plate_0", + )), + pants: Some(assets::load_expect_cloned( + "common.items.armor.pants.plate_green_0", + )), + foot: Some(assets::load_expect_cloned( + "common.items.armor.foot.leather_0", + )), + back: None, + ring: None, + neck: None, + lantern: None, + head: None, + tabard: None, + }, + comp::Alignment::Enemy => comp::Loadout { + active_item, + second_item: None, + shoulder: Some(assets::load_expect_cloned( + "common.items.armor.shoulder.leather_0", + )), + chest: Some(assets::load_expect_cloned( + "common.items.armor.chest.plate_green_0", + )), + belt: Some(assets::load_expect_cloned( + "common.items.armor.belt.plate_0", + )), + hand: Some(assets::load_expect_cloned( + "common.items.armor.hand.plate_0", + )), + pants: Some(assets::load_expect_cloned( + "common.items.armor.pants.plate_green_0", + )), + foot: Some(assets::load_expect_cloned( + "common.items.armor.foot.plate_0", + )), + back: None, + ring: None, + neck: None, + lantern: None, + head: None, + tabard: None, + }, + _ => comp::Loadout { + active_item, + second_item: None, + shoulder: None, + chest: None, + belt: None, + hand: None, + pants: None, + foot: None, + back: None, + ring: None, + neck: None, + lantern: None, + head: None, + tabard: None, + }, + }; - // TODO: Remove this and implement scaling or level depending on stuff like - // species instead - stats.level.set_level(rand::thread_rng().gen_range(1, 9)); + let mut scale = 1.0; - // Replace stuff if it's a boss - if let EntityKind::Boss = entity.kind { - if rand::random::() < 0.65 { - let body_new = comp::humanoid::Body::random(); - body = comp::Body::Humanoid(body_new); - alignment = comp::Alignment::Npc; - stats = comp::Stats::new( - format!( - "Fearless Giant {}", - get_npc_name(&NPC_NAMES.humanoid, body_new.race) - ), - body, - ); - } - loadout = comp::Loadout { - active_item: Some(comp::ItemConfig { - item: assets::load_expect_cloned( - "common.items.weapons.zweihander_sword_0", - ), - ability1: Some(CharacterAbility::BasicMelee { - energy_cost: 0, - buildup_duration: Duration::from_millis(800), - recover_duration: Duration::from_millis(200), - base_healthchange: -13, - range: 3.5, - max_angle: 60.0, - }), - ability2: None, - ability3: None, - block_ability: None, - dodge_ability: None, + // TODO: Remove this and implement scaling or level depending on stuff like + // species instead + stats.level.set_level(rand::thread_rng().gen_range(1, 9)); + + // Replace stuff if it's a boss + if entity.is_giant { + if rand::random::() < 0.65 { + let body_new = comp::humanoid::Body::random(); + body = comp::Body::Humanoid(body_new); + stats = comp::Stats::new( + format!( + "Fearless Giant {}", + get_npc_name(&NPC_NAMES.humanoid, body_new.race) + ), + body, + ); + } + loadout = comp::Loadout { + active_item: Some(comp::ItemConfig { + item: assets::load_expect_cloned( + "common.items.weapons.zweihander_sword_0", + ), + ability1: Some(CharacterAbility::BasicMelee { + energy_cost: 0, + buildup_duration: Duration::from_millis(800), + recover_duration: Duration::from_millis(200), + base_healthchange: -13, + range: 3.5, + max_angle: 60.0, }), - second_item: None, - shoulder: Some(assets::load_expect_cloned( - "common.items.armor.shoulder.plate_0", - )), - chest: Some(assets::load_expect_cloned( - "common.items.armor.chest.plate_green_0", - )), - belt: Some(assets::load_expect_cloned( - "common.items.armor.belt.plate_0", - )), - hand: Some(assets::load_expect_cloned( - "common.items.armor.hand.plate_0", - )), - pants: Some(assets::load_expect_cloned( - "common.items.armor.pants.plate_green_0", - )), - foot: Some(assets::load_expect_cloned( - "common.items.armor.foot.plate_0", - )), - }; + ability2: None, + ability3: None, + block_ability: None, + dodge_ability: None, + }), + second_item: None, + shoulder: Some(assets::load_expect_cloned( + "common.items.armor.shoulder.plate_0", + )), + chest: Some(assets::load_expect_cloned( + "common.items.armor.chest.plate_green_0", + )), + belt: Some(assets::load_expect_cloned( + "common.items.armor.belt.plate_0", + )), + hand: Some(assets::load_expect_cloned( + "common.items.armor.hand.plate_0", + )), + pants: Some(assets::load_expect_cloned( + "common.items.armor.pants.plate_green_0", + )), + foot: Some(assets::load_expect_cloned( + "common.items.armor.foot.plate_0", + )), + back: None, + ring: None, + neck: None, + lantern: None, + head: None, + tabard: None, + }; - stats.level.set_level(rand::thread_rng().gen_range(30, 35)); - scale = 2.0 + rand::random::(); - } - - stats.update_max_hp(); - - stats - .health - .set_to(stats.health.maximum(), comp::HealthSource::Revive); - - // TODO: This code sets an appropriate base_damage for the enemy. This doesn't - // work because the damage is now saved in an ability - /* - if let Some(item::ItemKind::Tool(item::ToolData { base_damage, .. })) = - &mut loadout.active_item.map(|i| i.item.kind) - { - *base_damage = stats.level.level() as u32 * 3; - } - */ - server_emitter.emit(ServerEvent::CreateNpc { - pos: Pos(entity.pos), - stats, - loadout, - body, - alignment, - agent: comp::Agent::default().with_patrol_origin(entity.pos), - scale: comp::Scale(scale), - }) + stats.level.set_level(rand::thread_rng().gen_range(30, 35)); + scale = 2.0 + rand::random::(); } + + stats.update_max_hp(); + + stats + .health + .set_to(stats.health.maximum(), comp::HealthSource::Revive); + + // TODO: This code sets an appropriate base_damage for the enemy. This doesn't + // work because the damage is now saved in an ability + /* + if let Some(item::ItemKind::Tool(item::ToolData { base_damage, .. })) = + &mut loadout.active_item.map(|i| i.item.kind) + { + *base_damage = stats.level.level() as u32 * 3; + } + */ + server_emitter.emit(ServerEvent::CreateNpc { + pos: Pos(entity.pos), + stats, + loadout, + body, + alignment, + agent: comp::Agent::default().with_patrol_origin(entity.pos), + scale: comp::Scale(scale), + }) } } diff --git a/voxygen/examples/character_renderer.rs b/voxygen/examples/character_renderer.rs index c19515da68..a26255d38d 100644 --- a/voxygen/examples/character_renderer.rs +++ b/voxygen/examples/character_renderer.rs @@ -37,6 +37,12 @@ fn main() { hand: None, pants: None, foot: None, + back: None, + ring: None, + neck: None, + lantern: None, + head: None, + tabard: None, }; // Setup scene (using the character selection screen `Scene`) @@ -47,10 +53,13 @@ fn main() { tick: 0, body: Some(body.clone()), gamma: 1.0, + mouse_smoothing: true, }; scene.camera_mut().set_focus_pos(Vec3::unit_z() * 0.8); scene.camera_mut().set_distance(1.5); - scene.camera_mut().update(0.0); + scene + .camera_mut() + .update(0.0, 1.0 / 60.0, scene_data.mouse_smoothing); scene.maintain(&mut renderer, scene_data); // Render diff --git a/voxygen/src/anim/biped_large/idle.rs b/voxygen/src/anim/biped_large/idle.rs index 6a463c5bd3..79f778c92b 100644 --- a/voxygen/src/anim/biped_large/idle.rs +++ b/voxygen/src/anim/biped_large/idle.rs @@ -36,10 +36,10 @@ impl Animation for IdleAnimation { next.head.offset = Vec3::new( 0.0, skeleton_attr.head.0, - skeleton_attr.head.1 + torso * 0.6, - ) / 8.0; + skeleton_attr.head.1 + torso * 0.2, + ) * 1.02; next.head.ori = Quaternion::rotation_z(look.x * 0.6) * Quaternion::rotation_x(look.y * 0.6); - next.head.scale = Vec3::one() / 8.0; + next.head.scale = Vec3::one() * 1.02; next.upper_torso.offset = Vec3::new( 0.0, @@ -52,7 +52,7 @@ impl Animation for IdleAnimation { next.lower_torso.offset = Vec3::new( 0.0, skeleton_attr.lower_torso.0, - skeleton_attr.lower_torso.1 + torso * 0.35, + skeleton_attr.lower_torso.1 + torso * 0.15, ); next.lower_torso.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(0.0); next.lower_torso.scale = Vec3::one() * 1.02; @@ -79,7 +79,7 @@ impl Animation for IdleAnimation { skeleton_attr.hand.2 + torso * 0.6, ); next.hand_l.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(0.0); - next.hand_l.scale = Vec3::one(); + next.hand_l.scale = Vec3::one() * 1.02; next.hand_r.offset = Vec3::new( skeleton_attr.hand.0, @@ -87,23 +87,23 @@ impl Animation for IdleAnimation { skeleton_attr.hand.2 + torso * 0.6, ); next.hand_r.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(0.0); - next.hand_r.scale = Vec3::one(); + next.hand_r.scale = Vec3::one() * 1.02; next.leg_l.offset = Vec3::new( -skeleton_attr.leg.0, skeleton_attr.leg.1, skeleton_attr.leg.2 + torso * 0.2, - ) / 8.0; + ) * 1.02; next.leg_l.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(0.0); - next.leg_l.scale = Vec3::one() / 8.0; + next.leg_l.scale = Vec3::one() * 1.02; next.leg_r.offset = Vec3::new( skeleton_attr.leg.0, skeleton_attr.leg.1, skeleton_attr.leg.2 + torso * 0.2, - ) / 8.0; + ) * 1.02; next.leg_r.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(0.0); - next.leg_r.scale = Vec3::one() / 8.0; + next.leg_r.scale = Vec3::one() * 1.02; next.foot_l.offset = Vec3::new( -skeleton_attr.foot.0, diff --git a/voxygen/src/anim/biped_large/mod.rs b/voxygen/src/anim/biped_large/mod.rs index 282c094876..ba73161fb7 100644 --- a/voxygen/src/anim/biped_large/mod.rs +++ b/voxygen/src/anim/biped_large/mod.rs @@ -47,7 +47,7 @@ impl BipedLargeSkeleton { impl Skeleton for BipedLargeSkeleton { type Attr = SkeletonAttr; - fn compute_matrices(&self) -> [FigureBoneData; 18] { + fn compute_matrices(&self) -> [FigureBoneData; 16] { let upper_torso_mat = self.upper_torso.compute_base_matrix(); let shoulder_l_mat = self.shoulder_l.compute_base_matrix(); let shoulder_r_mat = self.shoulder_r.compute_base_matrix(); @@ -56,25 +56,19 @@ impl Skeleton for BipedLargeSkeleton { let torso_mat = self.torso.compute_base_matrix(); [ - FigureBoneData::new(torso_mat * self.head.compute_base_matrix()), + FigureBoneData::new(torso_mat * upper_torso_mat * self.head.compute_base_matrix()), FigureBoneData::new(torso_mat * upper_torso_mat), FigureBoneData::new( torso_mat * upper_torso_mat * self.lower_torso.compute_base_matrix(), ), FigureBoneData::new(torso_mat * upper_torso_mat * shoulder_l_mat), FigureBoneData::new(torso_mat * upper_torso_mat * shoulder_r_mat), - FigureBoneData::new( - torso_mat * upper_torso_mat * shoulder_l_mat * self.hand_l.compute_base_matrix(), - ), - FigureBoneData::new( - torso_mat * upper_torso_mat * shoulder_r_mat * self.hand_r.compute_base_matrix(), - ), - FigureBoneData::new(torso_mat * leg_l_mat), - FigureBoneData::new(torso_mat * leg_r_mat), - FigureBoneData::new(torso_mat * self.foot_l.compute_base_matrix()), - FigureBoneData::new(torso_mat * self.foot_r.compute_base_matrix()), - FigureBoneData::default(), - FigureBoneData::default(), + FigureBoneData::new(torso_mat * upper_torso_mat * self.hand_l.compute_base_matrix()), + FigureBoneData::new(torso_mat * upper_torso_mat * self.hand_r.compute_base_matrix()), + FigureBoneData::new(torso_mat * upper_torso_mat * leg_l_mat), + FigureBoneData::new(torso_mat * upper_torso_mat * leg_r_mat), + FigureBoneData::new(self.foot_l.compute_base_matrix()), + FigureBoneData::new(self.foot_r.compute_base_matrix()), FigureBoneData::default(), FigureBoneData::default(), FigureBoneData::default(), @@ -139,10 +133,10 @@ impl<'a> From<&'a comp::biped_large::Body> for SkeletonAttr { use comp::biped_large::Species::*; Self { head: match (body.species, body.body_type) { - (Giant, _) => (0.0, 28.5), + (Giant, _) => (0.0, 10.0), }, upper_torso: match (body.species, body.body_type) { - (Giant, _) => (0.0, 18.5), + (Giant, _) => (0.0, 20.0), }, lower_torso: match (body.species, body.body_type) { (Giant, _) => (1.0, -9.5), @@ -151,13 +145,13 @@ impl<'a> From<&'a comp::biped_large::Body> for SkeletonAttr { (Giant, _) => (6.0, 0.5, 2.5), }, hand: match (body.species, body.body_type) { - (Giant, _) => (3.5, -1.0, 3.0), + (Giant, _) => (10.5, -1.0, 3.5), }, leg: match (body.species, body.body_type) { - (Giant, _) => (3.5, 0.0, 14.0), + (Giant, _) => (0.0, 0.0, -6.0), }, foot: match (body.species, body.body_type) { - (Giant, _) => (4.0, 0.5, 11.0), + (Giant, _) => (4.0, 0.5, 2.5), }, } } diff --git a/voxygen/src/anim/biped_large/run.rs b/voxygen/src/anim/biped_large/run.rs index 2165834fb4..e41f67a41e 100644 --- a/voxygen/src/anim/biped_large/run.rs +++ b/voxygen/src/anim/biped_large/run.rs @@ -17,29 +17,36 @@ impl Animation for RunAnimation { ) -> Self::Skeleton { let mut next = (*skeleton).clone(); - let lab = 14.0; + let lab = 10.0; - let legl = (anim_time as f32 * lab as f32).sin(); - let legr = (anim_time as f32 * lab as f32 + PI).sin(); let belt = (anim_time as f32 * lab as f32 + 1.5 * PI).sin(); - let foothoril = (anim_time as f32 * lab as f32).sin(); - let foothorir = (anim_time as f32 * lab as f32 + PI).sin(); + let foothoril = (anim_time as f32 * lab as f32 + PI * 1.4).sin(); + let foothorir = (anim_time as f32 * lab as f32 + PI * 0.4).sin(); - let footvertl = (anim_time as f32 * lab as f32 + PI * 1.4).sin().max(0.0); - let footvertr = (anim_time as f32 * lab as f32 + PI * 0.4).sin().max(0.0); + let footvertl = (anim_time as f32 * lab as f32).sin().max(0.1); + let footvertr = (anim_time as f32 * lab as f32 + PI).sin().max(0.1); - next.head.offset = - Vec3::new(0.0, skeleton_attr.head.0, skeleton_attr.head.1 + belt * 1.0) / 8.0; - next.head.ori = Quaternion::rotation_z(belt * 0.1) * Quaternion::rotation_x(0.3); - next.head.scale = Vec3::one() / 8.0; + let footrotl = (((5.0) + / (1.0 + (4.0) * ((anim_time as f32 * lab as f32 + PI * 1.4).sin()).powf(2.0 as f32))) + .sqrt()) + * ((anim_time as f32 * lab as f32 + PI * 1.4).sin()); + + let footrotr = (((5.0) + / (1.0 + (4.0) * ((anim_time as f32 * lab as f32 + PI * 0.4).sin()).powf(2.0 as f32))) + .sqrt()) + * ((anim_time as f32 * lab as f32 + PI * 0.4).sin()); + + next.head.offset = Vec3::new(0.0, skeleton_attr.head.0, skeleton_attr.head.1) * 1.02; + next.head.ori = Quaternion::rotation_z(belt * -0.3) * Quaternion::rotation_x(0.3); + next.head.scale = Vec3::one() * 1.02; next.upper_torso.offset = Vec3::new( 0.0, skeleton_attr.upper_torso.0, skeleton_attr.upper_torso.1 + belt * 1.0, ) / 8.0; - next.upper_torso.ori = Quaternion::rotation_z(belt * 0.3) * Quaternion::rotation_x(0.0); + next.upper_torso.ori = Quaternion::rotation_z(belt * 0.40) * Quaternion::rotation_x(0.0); next.upper_torso.scale = Vec3::one() / 8.0; next.lower_torso.offset = Vec3::new( @@ -47,7 +54,7 @@ impl Animation for RunAnimation { skeleton_attr.lower_torso.0, skeleton_attr.lower_torso.1, ); - next.lower_torso.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(0.0); + next.lower_torso.ori = Quaternion::rotation_z(belt * -0.55) * Quaternion::rotation_x(0.0); next.lower_torso.scale = Vec3::one() * 1.02; next.shoulder_l.offset = Vec3::new( @@ -55,7 +62,8 @@ impl Animation for RunAnimation { skeleton_attr.shoulder.1, skeleton_attr.shoulder.2, ); - next.shoulder_l.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(legr * 0.06); + next.shoulder_l.ori = + Quaternion::rotation_z(0.0) * Quaternion::rotation_x(footrotl * -0.15); next.shoulder_l.scale = Vec3::one(); next.shoulder_r.offset = Vec3::new( @@ -63,7 +71,8 @@ impl Animation for RunAnimation { skeleton_attr.shoulder.1, skeleton_attr.shoulder.2, ); - next.shoulder_r.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(legl * 0.1); + next.shoulder_r.ori = + Quaternion::rotation_z(0.0) * Quaternion::rotation_x(footrotr * -0.15); next.shoulder_r.scale = Vec3::one(); next.hand_l.offset = Vec3::new( @@ -71,51 +80,56 @@ impl Animation for RunAnimation { skeleton_attr.hand.1, skeleton_attr.hand.2, ); - next.hand_l.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(0.3 + legr * 0.5); - next.hand_l.scale = Vec3::one(); + next.hand_l.ori = + Quaternion::rotation_z(0.0) * Quaternion::rotation_x(0.3 + footrotl * -0.8); + next.hand_l.scale = Vec3::one() * 1.02; next.hand_r.offset = Vec3::new( skeleton_attr.hand.0, skeleton_attr.hand.1, skeleton_attr.hand.2, ); - next.hand_r.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(0.3 - legr * 0.5); - next.hand_r.scale = Vec3::one(); + next.hand_r.ori = + Quaternion::rotation_z(0.0) * Quaternion::rotation_x(0.3 + footrotr * -0.8); + next.hand_r.scale = Vec3::one() * 1.02; next.leg_l.offset = Vec3::new( -skeleton_attr.leg.0, skeleton_attr.leg.1, - skeleton_attr.leg.2 + belt * 0.4, - ) / 8.0; - next.leg_l.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(-legl * 0.3); - next.leg_l.scale = Vec3::one() / 8.0; + skeleton_attr.leg.2, + ) * 1.02; + next.leg_l.ori = + Quaternion::rotation_z(belt * -0.8) * Quaternion::rotation_x(foothoril * 0.2); + next.leg_l.scale = Vec3::one() * 1.02; next.leg_r.offset = Vec3::new( skeleton_attr.leg.0, skeleton_attr.leg.1, - skeleton_attr.leg.2 + belt * 0.4, - ) / 8.0; - next.leg_r.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(-legr * 0.3); - next.leg_r.scale = Vec3::one() / 8.0; + skeleton_attr.leg.2, + ) * 1.02; + + next.leg_r.ori = + Quaternion::rotation_z(belt * -0.8) * Quaternion::rotation_x(foothorir * 0.2); + next.leg_r.scale = Vec3::one() * 1.02; next.foot_l.offset = Vec3::new( -skeleton_attr.foot.0, - skeleton_attr.foot.1 - foothoril * 4.5 - 0.5, - skeleton_attr.foot.2 + footvertl * 6.0, + skeleton_attr.foot.1 + foothoril * 8.0 + 3.0, + skeleton_attr.foot.2 + footvertl * 4.0, ) / 8.0; - next.foot_l.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(0.0 - legl * 0.5); + next.foot_l.ori = Quaternion::rotation_x(footrotl * 0.5); next.foot_l.scale = Vec3::one() / 8.0 * 0.98; next.foot_r.offset = Vec3::new( skeleton_attr.foot.0, - skeleton_attr.foot.1 - foothorir * 4.5 - 0.5, - skeleton_attr.foot.2 + footvertr * 6.0, + skeleton_attr.foot.1 + foothorir * 8.0 + 3.0, + skeleton_attr.foot.2 + footvertr * 4.0, ) / 8.0; - next.foot_r.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(0.0 - legr * 0.5); + next.foot_r.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(footrotr * 0.5); next.foot_r.scale = Vec3::one() / 8.0 * 0.98; - next.torso.offset = Vec3::new(0.0, 0.0, 0.0); - next.torso.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(-0.3); + next.torso.offset = Vec3::new(0.0, 0.0, belt * 0.15); + next.torso.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(-0.2); next.torso.scale = Vec3::one(); next } diff --git a/voxygen/src/anim/bird_medium/mod.rs b/voxygen/src/anim/bird_medium/mod.rs index 8e15bdaef2..64e2d400e3 100644 --- a/voxygen/src/anim/bird_medium/mod.rs +++ b/voxygen/src/anim/bird_medium/mod.rs @@ -27,7 +27,7 @@ impl BirdMediumSkeleton { impl Skeleton for BirdMediumSkeleton { type Attr = SkeletonAttr; - fn compute_matrices(&self) -> [FigureBoneData; 18] { + fn compute_matrices(&self) -> [FigureBoneData; 16] { let torso_mat = self.torso.compute_base_matrix(); [ @@ -47,8 +47,6 @@ impl Skeleton for BirdMediumSkeleton { FigureBoneData::default(), FigureBoneData::default(), FigureBoneData::default(), - FigureBoneData::default(), - FigureBoneData::default(), ] } diff --git a/voxygen/src/anim/bird_small/mod.rs b/voxygen/src/anim/bird_small/mod.rs index 763bb2fac8..610b63122a 100644 --- a/voxygen/src/anim/bird_small/mod.rs +++ b/voxygen/src/anim/bird_small/mod.rs @@ -31,7 +31,7 @@ impl BirdSmallSkeleton { impl Skeleton for BirdSmallSkeleton { type Attr = SkeletonAttr; - fn compute_matrices(&self) -> [FigureBoneData; 18] { + fn compute_matrices(&self) -> [FigureBoneData; 16] { let torso_mat = self.torso.compute_base_matrix(); [ @@ -51,8 +51,6 @@ impl Skeleton for BirdSmallSkeleton { FigureBoneData::default(), FigureBoneData::default(), FigureBoneData::default(), - FigureBoneData::default(), - FigureBoneData::default(), ] } diff --git a/voxygen/src/anim/character/alpha.rs b/voxygen/src/anim/character/alpha.rs index 74573fc146..fc09deca0c 100644 --- a/voxygen/src/anim/character/alpha.rs +++ b/voxygen/src/anim/character/alpha.rs @@ -104,6 +104,10 @@ impl Animation for AlphaAnimation { next.r_foot.ori = Quaternion::rotation_x(slow * -0.6) * Quaternion::rotation_y((slow * 0.2).min(0.0)); next.r_foot.scale = Vec3::one(); + + next.lantern.ori = + Quaternion::rotation_x(slow * -0.7 + 0.4) * Quaternion::rotation_y(slow * 0.4); + next.torso.offset = Vec3::new(0.0, 0.0, 0.1) * skeleton_attr.scaler; next.torso.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(0.0) @@ -151,6 +155,9 @@ impl Animation for AlphaAnimation { * Quaternion::rotation_z(-0.8); next.main.scale = Vec3::one(); + next.lantern.ori = Quaternion::rotation_x(slowax * -0.7 + 0.4) + * Quaternion::rotation_y(slowax * 0.4); + next.control.offset = Vec3::new(0.0, 0.0 + slowax * 8.2, 6.0); next.control.ori = Quaternion::rotation_x(0.8) * Quaternion::rotation_y(-0.3) @@ -197,6 +204,10 @@ impl Animation for AlphaAnimation { next.shorts.offset = Vec3::new(0.0, 0.0, -5.0); next.shorts.ori = next.chest.ori * -0.15; next.shorts.scale = Vec3::one(); + + next.lantern.ori = Quaternion::rotation_x(slower * -0.7 + 0.4) + * Quaternion::rotation_y(slower * 0.4); + next.torso.offset = Vec3::new(0.0, 0.0, 0.1) * skeleton_attr.scaler; next.torso.ori = Quaternion::rotation_z(0.0) * Quaternion::rotation_x(0.0) @@ -519,6 +530,8 @@ impl Animation for AlphaAnimation { }, _ => {}, } + next.lantern.offset = Vec3::new(-5.0, 2.5, 5.5); + next.lantern.scale = Vec3::one() * 0.65; next.l_shoulder.offset = Vec3::new(-5.0, 0.0, 4.7); next.l_shoulder.ori = Quaternion::rotation_x(0.0); @@ -532,10 +545,6 @@ impl Animation for AlphaAnimation { next.glider.ori = Quaternion::rotation_y(0.0); next.glider.scale = Vec3::one() * 0.0; - next.lantern.offset = Vec3::new(0.0, 0.0, 0.0); - next.lantern.ori = Quaternion::rotation_x(0.0); - next.lantern.scale = Vec3::one() * 0.0; - next.l_control.offset = Vec3::new(0.0, 0.0, 0.0); next.l_control.ori = Quaternion::rotation_x(0.0); next.l_control.scale = Vec3::one(); diff --git a/voxygen/src/anim/character/beta.rs b/voxygen/src/anim/character/beta.rs index a06a876332..5b0b87e28b 100644 --- a/voxygen/src/anim/character/beta.rs +++ b/voxygen/src/anim/character/beta.rs @@ -116,9 +116,10 @@ impl Animation for BetaAnimation { next.glider.ori = Quaternion::rotation_y(0.0); next.glider.scale = Vec3::one() * 0.0; - next.lantern.offset = Vec3::new(0.0, 0.0, 0.0); - next.lantern.ori = Quaternion::rotation_x(0.0); - next.lantern.scale = Vec3::one() * 0.0; + next.lantern.offset = Vec3::new(-5.0, 2.5, 5.5); + next.lantern.ori = + Quaternion::rotation_x(slow * -0.7 + 0.4) * Quaternion::rotation_y(slow * 0.4); + next.lantern.scale = Vec3::one() * 0.65; next.l_control.offset = Vec3::new(0.0, 0.0, 0.0); next.l_control.ori = Quaternion::rotation_x(0.0); diff --git a/voxygen/src/anim/character/charge.rs b/voxygen/src/anim/character/charge.rs index bb6e3b168b..ea016471d8 100644 --- a/voxygen/src/anim/character/charge.rs +++ b/voxygen/src/anim/character/charge.rs @@ -135,6 +135,7 @@ impl Animation for ChargeAnimation { * Quaternion::rotation_z(0.4) * Quaternion::rotation_y(0.0); next.r_foot.scale = Vec3::one(); + next.torso.offset = Vec3::new(0.0 + foot * 0.03, foote * 0.05, 0.1) * skeleton_attr.scaler; next.torso.ori = Quaternion::rotation_z(0.0) @@ -158,6 +159,9 @@ impl Animation for ChargeAnimation { * Quaternion::rotation_y(0.0); next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler; } + next.back.offset = Vec3::new(0.0, -2.8, 7.25); + next.back.ori = Quaternion::rotation_x(-0.3); + next.back.scale = Vec3::one() * 1.02; next.l_shoulder.offset = Vec3::new(-5.0, 0.0, 4.7); next.l_shoulder.ori = Quaternion::rotation_x(0.0); @@ -171,9 +175,9 @@ impl Animation for ChargeAnimation { next.glider.ori = Quaternion::rotation_y(0.0); next.glider.scale = Vec3::one() * 0.0; - next.lantern.offset = Vec3::new(0.0, 0.0, 0.0); - next.lantern.ori = Quaternion::rotation_x(0.0); - next.lantern.scale = Vec3::one() * 0.0; + next.lantern.offset = Vec3::new(-5.0, 2.5, 5.5); + next.lantern.ori = Quaternion::rotation_x(0.1) * Quaternion::rotation_y(0.1); + next.lantern.scale = Vec3::one() * 0.65; next.l_control.offset = Vec3::new(0.0, 0.0, 0.0); next.l_control.ori = Quaternion::rotation_x(0.0); diff --git a/voxygen/src/anim/character/climb.rs b/voxygen/src/anim/character/climb.rs index 463a4fdd0e..5787dd4fc3 100644 --- a/voxygen/src/anim/character/climb.rs +++ b/voxygen/src/anim/character/climb.rs @@ -57,6 +57,10 @@ impl Animation for ClimbAnimation { next.belt.ori = Quaternion::rotation_z(quick * 0.0) * Quaternion::rotation_x(0.0); next.belt.scale = Vec3::one(); + next.back.offset = Vec3::new(0.0, -2.8, 7.25); + next.back.ori = Quaternion::rotation_x(-0.2); + next.back.scale = Vec3::one() * 1.02; + next.shorts.offset = Vec3::new(0.0, 1.0, -5.0); next.shorts.ori = Quaternion::rotation_z(quick * 0.0) * Quaternion::rotation_x(0.1) @@ -107,9 +111,10 @@ impl Animation for ClimbAnimation { next.second.ori = Quaternion::rotation_y(0.0); next.second.scale = Vec3::one() * 0.0; - next.lantern.offset = Vec3::new(0.0, 0.0, 0.0); - next.lantern.ori = Quaternion::rotation_x(0.0); - next.lantern.scale = Vec3::one() * 0.0; + next.lantern.offset = Vec3::new(-5.0, 2.5, 5.5); + next.lantern.ori = + Quaternion::rotation_x(smooth * -0.3) * Quaternion::rotation_y(smooth * -0.3); + next.lantern.scale = Vec3::one() * 0.65; next.torso.offset = Vec3::new(0.0, -0.2 + smooth * -0.08, 0.4) * skeleton_attr.scaler; next.torso.ori = Quaternion::rotation_x(0.0) * Quaternion::rotation_y(0.0); diff --git a/voxygen/src/anim/character/dash.rs b/voxygen/src/anim/character/dash.rs index 05f64819c7..9be8006f0b 100644 --- a/voxygen/src/anim/character/dash.rs +++ b/voxygen/src/anim/character/dash.rs @@ -103,9 +103,10 @@ impl Animation for DashAnimation { next.glider.ori = Quaternion::rotation_y(0.0); next.glider.scale = Vec3::one() * 0.0; - next.lantern.offset = Vec3::new(0.0, 0.0, 0.0); - next.lantern.ori = Quaternion::rotation_x(0.0); - next.lantern.scale = Vec3::one() * 0.0; + next.lantern.offset = Vec3::new(-5.0, 2.5, 5.5); + next.lantern.ori = + Quaternion::rotation_x(slow * -0.7 + 0.4) * Quaternion::rotation_y(slow * 0.4); + next.lantern.scale = Vec3::one() * 0.65; next.torso.offset = Vec3::new(0.0, 0.0, 0.1) * skeleton_attr.scaler; next.torso.ori = diff --git a/voxygen/src/anim/character/jump.rs b/voxygen/src/anim/character/jump.rs index ba90251487..9961a6e356 100644 --- a/voxygen/src/anim/character/jump.rs +++ b/voxygen/src/anim/character/jump.rs @@ -36,6 +36,10 @@ impl Animation for JumpAnimation { next.belt.ori = Quaternion::rotation_z(0.0); next.belt.scale = Vec3::one(); + next.back.offset = Vec3::new(0.0, -2.8, 7.25); + next.back.ori = Quaternion::rotation_z(0.0); + next.back.scale = Vec3::one() * 1.02; + next.shorts.offset = Vec3::new(0.0, 0.0, -5.0); next.shorts.ori = Quaternion::rotation_z(0.0); next.shorts.scale = Vec3::one(); @@ -94,9 +98,10 @@ impl Animation for JumpAnimation { next.second.ori = Quaternion::rotation_y(0.0); next.second.scale = Vec3::one() * 0.0; - next.lantern.offset = Vec3::new(0.0, 0.0, 0.0); - next.lantern.ori = Quaternion::rotation_x(0.0); - next.lantern.scale = Vec3::one() * 0.0; + next.lantern.offset = Vec3::new(-5.0, 2.5, 5.5); + next.lantern.ori = Quaternion::rotation_x(stop * 1.2 + slow * 0.3) + * Quaternion::rotation_y(stop * 0.4 + slow * 0.3); + next.lantern.scale = Vec3::one() * 0.65; next.torso.offset = Vec3::new(0.0, 0.0, 0.0) * skeleton_attr.scaler; next.torso.ori = Quaternion::rotation_x(-0.2); diff --git a/voxygen/src/anim/character/mod.rs b/voxygen/src/anim/character/mod.rs index 26cf805191..9f50a3bd06 100644 --- a/voxygen/src/anim/character/mod.rs +++ b/voxygen/src/anim/character/mod.rs @@ -37,6 +37,7 @@ pub struct CharacterSkeleton { head: Bone, chest: Bone, belt: Bone, + back: Bone, shorts: Bone, l_hand: Bone, r_hand: Bone, @@ -61,7 +62,7 @@ impl CharacterSkeleton { impl Skeleton for CharacterSkeleton { type Attr = SkeletonAttr; - fn compute_matrices(&self) -> [FigureBoneData; 18] { + fn compute_matrices(&self) -> [FigureBoneData; 16] { let chest_mat = self.chest.compute_base_matrix(); let torso_mat = self.torso.compute_base_matrix(); let l_hand_mat = self.l_hand.compute_base_matrix(); @@ -71,13 +72,14 @@ impl Skeleton for CharacterSkeleton { let r_control_mat = self.r_control.compute_base_matrix(); let main_mat = self.main.compute_base_matrix(); let second_mat = self.second.compute_base_matrix(); - + let shorts_mat = self.shorts.compute_base_matrix(); let head_mat = self.head.compute_base_matrix(); [ FigureBoneData::new(torso_mat * chest_mat * head_mat), FigureBoneData::new(torso_mat * chest_mat), FigureBoneData::new(torso_mat * chest_mat * self.belt.compute_base_matrix()), - FigureBoneData::new(torso_mat * chest_mat * self.shorts.compute_base_matrix()), + FigureBoneData::new(torso_mat * chest_mat * self.back.compute_base_matrix()), + FigureBoneData::new(torso_mat * chest_mat * shorts_mat), FigureBoneData::new(torso_mat * chest_mat * control_mat * l_control_mat * l_hand_mat), FigureBoneData::new(torso_mat * chest_mat * control_mat * r_control_mat * r_hand_mat), FigureBoneData::new(torso_mat * self.l_foot.compute_base_matrix()), @@ -87,11 +89,10 @@ impl Skeleton for CharacterSkeleton { FigureBoneData::new(torso_mat * self.glider.compute_base_matrix()), FigureBoneData::new(torso_mat * chest_mat * control_mat * l_control_mat * main_mat), FigureBoneData::new(torso_mat * chest_mat * control_mat * r_control_mat * second_mat), - FigureBoneData::new(torso_mat * chest_mat * self.lantern.compute_base_matrix()), - FigureBoneData::new(torso_mat), - FigureBoneData::new(control_mat), - FigureBoneData::new(l_control_mat), - FigureBoneData::new(r_control_mat), + FigureBoneData::new( + torso_mat * chest_mat * shorts_mat * self.lantern.compute_base_matrix(), + ), + FigureBoneData::default(), ] } @@ -99,6 +100,7 @@ impl Skeleton for CharacterSkeleton { self.head.interpolate(&target.head, dt); self.chest.interpolate(&target.chest, dt); self.belt.interpolate(&target.belt, dt); + self.back.interpolate(&target.back, dt); self.shorts.interpolate(&target.shorts, dt); self.l_hand.interpolate(&target.l_hand, dt); self.r_hand.interpolate(&target.r_hand, dt); diff --git a/voxygen/src/anim/character/roll.rs b/voxygen/src/anim/character/roll.rs index 2357bfd180..e7fde1f91c 100644 --- a/voxygen/src/anim/character/roll.rs +++ b/voxygen/src/anim/character/roll.rs @@ -19,11 +19,11 @@ impl Animation for RollAnimation { *rate = 1.0; let mut next = (*skeleton).clone(); - let wave = (anim_time as f32 * 5.5).sin(); - let wave_quick = (anim_time as f32 * 9.5).sin(); - let wave_quick_cos = (anim_time as f32 * 9.5).cos(); - let wave_slow = (anim_time as f32 * 2.8 + PI).sin(); - let wave_dub = (anim_time as f32 * 5.5).sin(); + let wave = (anim_time as f32 * 4.5).sin(); + let wave_quick = (anim_time as f32 * 7.5).sin(); + let wave_quick_cos = (anim_time as f32 * 7.5).cos(); + let wave_slow = (anim_time as f32 * 2.3 + PI).sin(); + let wave_dub = (anim_time as f32 * 4.5).sin(); let ori = Vec2::from(orientation); let last_ori = Vec2::from(last_ori); @@ -116,9 +116,9 @@ impl Animation for RollAnimation { next.second.ori = Quaternion::rotation_y(0.0); next.second.scale = Vec3::one() * 0.0; - next.lantern.offset = Vec3::new(0.0, 0.0, 0.0); - next.lantern.ori = Quaternion::rotation_x(0.0); - next.lantern.scale = Vec3::one() * 0.0; + next.lantern.offset = Vec3::new(-5.0, 2.5, 5.5); + next.lantern.ori = Quaternion::rotation_x(0.1) * Quaternion::rotation_y(0.1); + next.lantern.scale = Vec3::one() * 0.65; next.torso.offset = Vec3::new(0.0, 0.0, 0.1 + wave_dub * 16.0) / 11.0 * skeleton_attr.scaler; diff --git a/voxygen/src/anim/character/run.rs b/voxygen/src/anim/character/run.rs index 27643664a1..64d41f5e67 100644 --- a/voxygen/src/anim/character/run.rs +++ b/voxygen/src/anim/character/run.rs @@ -21,28 +21,44 @@ impl Animation for RunAnimation { let speed = Vec2::::from(velocity).magnitude(); *rate = 1.0; + let walkintensity = if speed > 5.0 { 1.0 } else { 0.7 }; + let walk = if speed > 5.0 { 1.0 } else { 0.5 }; + let lower = if speed > 5.0 { 0.0 } else { 2.0 }; + let snapfoot = if speed > 5.0 { 1.1 } else { 2.0 }; let lab = 1.0; let long = (((5.0) - / (1.5 + 3.5 * ((anim_time as f32 * lab as f32 * 8.0).sin()).powf(2.0 as f32))) + / (1.5 + 3.5 * ((anim_time as f32 * lab as f32 * 8.0 * walk).sin()).powf(2.0 as f32))) .sqrt()) - * ((anim_time as f32 * lab as f32 * 8.0).sin()); + * ((anim_time as f32 * lab as f32 * 8.0 * walk).sin()); let short = (((5.0) - / (1.5 + 3.5 * ((anim_time as f32 * lab as f32 * 16.0).sin()).powf(2.0 as f32))) + / (1.5 + + 3.5 * ((anim_time as f32 * lab as f32 * 16.0 * walk).sin()).powf(2.0 as f32))) .sqrt()) - * ((anim_time as f32 * lab as f32 * 16.0).sin()); + * ((anim_time as f32 * lab as f32 * 16.0 * walk).sin()); + let noisea = (anim_time as f32 * 11.0 + PI / 6.0).sin(); + let noiseb = (anim_time as f32 * 19.0 + PI / 4.0).sin(); + + let shorte = (((5.0) + / (4.0 + + 1.0 * ((anim_time as f32 * lab as f32 * 16.0 * walk).sin()).powf(2.0 as f32))) + .sqrt()) + * ((anim_time as f32 * lab as f32 * 16.0 * walk).sin()); let shortalt = (((5.0) / (1.5 + 3.5 - * ((anim_time as f32 * lab as f32 * 16.0 + PI / 2.0).sin()).powf(2.0 as f32))) + * ((anim_time as f32 * lab as f32 * 16.0 * walk + PI / 2.0).sin()) + .powf(2.0 as f32))) .sqrt()) - * ((anim_time as f32 * lab as f32 * 16.0 + PI / 2.0).sin()); + * ((anim_time as f32 * lab as f32 * 16.0 * walk + PI / 2.0).sin()); let foot = (((5.0) - / (1.1 + 3.9 * ((anim_time as f32 * lab as f32 * 16.0).sin()).powf(2.0 as f32))) + / (snapfoot + + (5.0 - snapfoot) + * ((anim_time as f32 * lab as f32 * 16.0 * walk).sin()).powf(2.0 as f32))) .sqrt()) - * ((anim_time as f32 * lab as f32 * 16.0).sin()); + * ((anim_time as f32 * lab as f32 * 16.0 * walk).sin()); let wave_stop = (anim_time as f32 * 26.0).min(PI / 2.0 / 2.0).sin(); @@ -82,50 +98,54 @@ impl Animation for RunAnimation { * Quaternion::rotation_x(head_look.y + 0.35); next.head.scale = Vec3::one() * skeleton_attr.head_scale; - next.chest.offset = Vec3::new(0.0, 0.0, 10.5 + short * 1.1); - next.chest.ori = Quaternion::rotation_z(short * 0.3); + next.chest.offset = Vec3::new(0.0, 0.0, 10.5 + short * 1.1 - lower); + next.chest.ori = Quaternion::rotation_z(short * 0.3 * walkintensity); next.chest.scale = Vec3::one(); next.belt.offset = Vec3::new(0.0, 0.0, -2.0); next.belt.ori = Quaternion::rotation_z(short * 0.25); next.belt.scale = Vec3::one(); + next.back.offset = Vec3::new(0.0, -2.8, 7.25); + next.back.ori = Quaternion::rotation_x(-0.25 + short * 0.1 + noisea * 0.1 + noiseb * 0.1); + next.back.scale = Vec3::one() * 1.02; + next.shorts.offset = Vec3::new(0.0, 0.0, -5.0); next.shorts.ori = Quaternion::rotation_z(short * 0.4); next.shorts.scale = Vec3::one(); next.l_hand.offset = Vec3::new( - -6.0 + wave_stop * -1.0, - -0.25 + short * 3.0, - 5.0 + short * -1.5, + -6.0 + wave_stop * -1.0 * walkintensity, + -0.25 + short * 3.0 * walkintensity, + 5.0 + short * -1.5 * walkintensity, ); - next.l_hand.ori = - Quaternion::rotation_x(0.8 + short * 1.2) * Quaternion::rotation_y(wave_stop * 0.1); + next.l_hand.ori = Quaternion::rotation_x(0.8 + short * 1.2 * walk) + * Quaternion::rotation_y(wave_stop * 0.1); next.l_hand.scale = Vec3::one(); next.r_hand.offset = Vec3::new( - 6.0 + wave_stop * 1.0, - -0.25 + short * -3.0, - 5.0 + short * 1.5, + 6.0 + wave_stop * 1.0 * walkintensity, + -0.25 + short * -3.0 * walkintensity, + 5.0 + short * 1.5 * walkintensity, ); - next.r_hand.ori = - Quaternion::rotation_x(0.8 + short * -1.2) * Quaternion::rotation_y(wave_stop * -0.1); + next.r_hand.ori = Quaternion::rotation_x(0.8 + short * -1.2 * walk) + * Quaternion::rotation_y(wave_stop * -0.1); next.r_hand.scale = Vec3::one(); next.l_foot.offset = Vec3::new(-3.4, foot * 1.0, 9.5); - next.l_foot.ori = Quaternion::rotation_x(foot * -1.2); + next.l_foot.ori = Quaternion::rotation_x(foot * -1.2 * walkintensity); next.l_foot.scale = Vec3::one(); next.r_foot.offset = Vec3::new(3.4, foot * -1.0, 9.5); - next.r_foot.ori = Quaternion::rotation_x(foot * 1.2); + next.r_foot.ori = Quaternion::rotation_x(foot * 1.2 * walkintensity); next.r_foot.scale = Vec3::one(); next.l_shoulder.offset = Vec3::new(-5.0, -1.0, 4.7); - next.l_shoulder.ori = Quaternion::rotation_x(short * 0.15); + next.l_shoulder.ori = Quaternion::rotation_x(short * 0.15 * walkintensity); next.l_shoulder.scale = Vec3::one() * 1.1; next.r_shoulder.offset = Vec3::new(5.0, -1.0, 4.7); - next.r_shoulder.ori = Quaternion::rotation_x(short * -0.15); + next.r_shoulder.ori = Quaternion::rotation_x(short * -0.15 * walkintensity); next.r_shoulder.scale = Vec3::one() * 1.1; next.glider.offset = Vec3::new(0.0, 0.0, 10.0); @@ -134,7 +154,7 @@ impl Animation for RunAnimation { next.main.offset = Vec3::new( -7.0 + skeleton_attr.weapon_x, - -5.0 + skeleton_attr.weapon_y, + -6.5 + skeleton_attr.weapon_y, 15.0, ); next.main.ori = Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57 + short * 0.25); @@ -148,9 +168,10 @@ impl Animation for RunAnimation { next.second.ori = Quaternion::rotation_y(0.0); next.second.scale = Vec3::one() * 0.0; - next.lantern.offset = Vec3::new(0.0, 5.0, 0.0); - next.lantern.ori = Quaternion::rotation_y(0.0); - next.lantern.scale = Vec3::one() * 0.0; + next.lantern.offset = Vec3::new(-5.0, 2.5, 5.5); + next.lantern.ori = + Quaternion::rotation_x(shorte * -0.7 + 0.4) * Quaternion::rotation_y(shorte * 0.4); + next.lantern.scale = Vec3::one() * 0.65; next.torso.offset = Vec3::new(0.0, -0.3 + shortalt * -0.065, 0.0) * skeleton_attr.scaler; next.torso.ori = diff --git a/voxygen/src/anim/character/shoot.rs b/voxygen/src/anim/character/shoot.rs index 26f0d6c443..50525f6a49 100644 --- a/voxygen/src/anim/character/shoot.rs +++ b/voxygen/src/anim/character/shoot.rs @@ -138,6 +138,9 @@ impl Animation for ShootAnimation { * Quaternion::rotation_y(0.0); next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler; } + next.back.offset = Vec3::new(0.0, -2.8, 7.25); + next.back.ori = Quaternion::rotation_x(-0.3); + next.back.scale = Vec3::one() * 1.02; next.l_shoulder.offset = Vec3::new(-5.0, 0.0, 4.7); next.l_shoulder.ori = Quaternion::rotation_x(0.0); @@ -151,9 +154,10 @@ impl Animation for ShootAnimation { next.glider.ori = Quaternion::rotation_y(0.0); next.glider.scale = Vec3::one() * 0.0; - next.lantern.offset = Vec3::new(0.0, 0.0, 0.0); - next.lantern.ori = Quaternion::rotation_x(0.0); - next.lantern.scale = Vec3::one() * 0.0; + next.lantern.offset = Vec3::new(-5.0, 2.5, 5.5); + next.lantern.ori = + Quaternion::rotation_x(exp * -0.7 + 0.4) * Quaternion::rotation_y(exp * 0.4); + next.lantern.scale = Vec3::one() * 0.65; next.l_control.offset = Vec3::new(0.0, 0.0, 0.0); next.l_control.ori = Quaternion::rotation_x(0.0); diff --git a/voxygen/src/anim/character/sit.rs b/voxygen/src/anim/character/sit.rs index 4dadd1e095..e9eea8063b 100644 --- a/voxygen/src/anim/character/sit.rs +++ b/voxygen/src/anim/character/sit.rs @@ -52,6 +52,10 @@ impl Animation for SitAnimation { next.belt.ori = Quaternion::rotation_x(stop * 0.3); next.belt.scale = (Vec3::one() + slow_abs * 0.05) * 1.02; + next.back.offset = Vec3::new(0.0, -2.8, 7.25); + next.back.ori = Quaternion::rotation_z(0.0); + next.back.scale = Vec3::one() * 1.02; + next.shorts.offset = Vec3::new(0.0, stop * 2.5, -5.0 + stop * 0.6); next.shorts.ori = Quaternion::rotation_x(stop * 0.6); next.shorts.scale = Vec3::one(); diff --git a/voxygen/src/anim/character/spin.rs b/voxygen/src/anim/character/spin.rs index 27d3391da9..b82ae289b5 100644 --- a/voxygen/src/anim/character/spin.rs +++ b/voxygen/src/anim/character/spin.rs @@ -106,9 +106,10 @@ impl Animation for SpinAnimation { next.glider.ori = Quaternion::rotation_y(0.0); next.glider.scale = Vec3::one() * 0.0; - next.lantern.offset = Vec3::new(0.0, 0.0, 0.0); - next.lantern.ori = Quaternion::rotation_x(0.0); - next.lantern.scale = Vec3::one() * 0.0; + next.lantern.offset = Vec3::new(-5.0, 2.5, 5.5); + next.lantern.ori = + Quaternion::rotation_x(spin * -0.7 + 0.4) * Quaternion::rotation_y(spin * 0.4); + next.lantern.scale = Vec3::one() * 0.65; next.l_control.offset = Vec3::new(0.0, 0.0, 0.0); next.l_control.ori = Quaternion::rotation_x(0.0); diff --git a/voxygen/src/anim/character/stand.rs b/voxygen/src/anim/character/stand.rs index 74b77ff306..486d350313 100644 --- a/voxygen/src/anim/character/stand.rs +++ b/voxygen/src/anim/character/stand.rs @@ -44,15 +44,19 @@ impl Animation for StandAnimation { next.chest.offset = Vec3::new(0.0, 0.0, 7.0 + slow * 0.3); next.chest.ori = Quaternion::rotation_z(head_look.x * 0.6); - next.chest.scale = Vec3::one() * 1.01 + breathe * 0.05; + next.chest.scale = Vec3::one() * 1.01 + breathe * 0.03; - next.belt.offset = Vec3::new(0.0, 0.0, -2.0); //5 + next.belt.offset = Vec3::new(0.0, 0.0, -2.0); next.belt.ori = Quaternion::rotation_z(head_look.x * -0.1); - next.belt.scale = Vec3::one() + breathe * -0.05; + next.belt.scale = Vec3::one() + breathe * -0.03; - next.shorts.offset = Vec3::new(0.0, 0.0, -5.0); //2 - next.shorts.ori = Quaternion::rotation_x(head_look.x * -0.2); - next.shorts.scale = Vec3::one() + breathe * -0.05; + next.back.offset = Vec3::new(0.0, -2.8, 7.25); + next.back.ori = Quaternion::rotation_z(0.0); + next.back.scale = Vec3::one() * 1.02; + + next.shorts.offset = Vec3::new(0.0, 0.0, -5.0); + next.shorts.ori = Quaternion::rotation_z(head_look.x * -0.2); + next.shorts.scale = Vec3::one() + breathe * -0.03; next.l_hand.offset = Vec3::new(-7.0, -0.25 + slow * 0.15, 5.0 + slow * 0.5); @@ -99,9 +103,9 @@ impl Animation for StandAnimation { next.second.ori = Quaternion::rotation_y(0.0); next.second.scale = Vec3::one() * 0.0; - next.lantern.offset = Vec3::new(0.0, 0.0, 0.0); - next.lantern.ori = Quaternion::rotation_x(0.0); - next.lantern.scale = Vec3::one() * 0.0; + next.lantern.offset = Vec3::new(-5.0, 2.5, 5.5); + next.lantern.ori = Quaternion::rotation_x(0.1) * Quaternion::rotation_y(0.1); + next.lantern.scale = Vec3::one() * 0.65; next.torso.offset = Vec3::new(0.0, -0.1, 0.1) * skeleton_attr.scaler; next.torso.ori = Quaternion::rotation_x(0.0); diff --git a/voxygen/src/anim/character/swim.rs b/voxygen/src/anim/character/swim.rs index 9b5e1193f8..1fd23c4c8a 100644 --- a/voxygen/src/anim/character/swim.rs +++ b/voxygen/src/anim/character/swim.rs @@ -63,6 +63,10 @@ impl Animation for SwimAnimation { next.belt.ori = Quaternion::rotation_z(short * 0.25); next.belt.scale = Vec3::one(); + next.back.offset = Vec3::new(0.0, -2.8, 7.25); + next.back.ori = Quaternion::rotation_z(0.0); + next.back.scale = Vec3::one() * 1.02; + next.shorts.offset = Vec3::new(0.0, 0.0, -5.0); next.shorts.ori = Quaternion::rotation_z(short * 0.4); next.shorts.scale = Vec3::one(); @@ -111,9 +115,9 @@ impl Animation for SwimAnimation { next.second.ori = Quaternion::rotation_y(0.0); next.second.scale = Vec3::one() * 0.0; - next.lantern.offset = Vec3::new(0.0, 5.0, 0.0); - next.lantern.ori = Quaternion::rotation_y(0.0); - next.lantern.scale = Vec3::one() * 0.0; + next.lantern.offset = Vec3::new(-5.0, 2.5, 5.5); + next.lantern.ori = Quaternion::rotation_x(0.0) * Quaternion::rotation_y(0.0); + next.lantern.scale = Vec3::one() * 0.65; next.torso.offset = Vec3::new(0.0, -0.3 + shortalt * -0.065, 0.4) * skeleton_attr.scaler; next.torso.ori = diff --git a/voxygen/src/anim/character/wield.rs b/voxygen/src/anim/character/wield.rs index a35edb4b6c..403796c601 100644 --- a/voxygen/src/anim/character/wield.rs +++ b/voxygen/src/anim/character/wield.rs @@ -17,11 +17,18 @@ impl Animation for WieldAnimation { skeleton_attr: &SkeletonAttr, ) -> Self::Skeleton { *rate = 1.0; + let lab = 1.0; + let mut next = (*skeleton).clone(); let slow_cos = (anim_time as f32 * 6.0 + PI).cos(); let ultra_slow = (anim_time as f32 * 1.0 + PI).sin(); let ultra_slow_cos = (anim_time as f32 * 3.0 + PI).cos(); - + let short = (((5.0) + / (1.5 + 3.5 * ((anim_time as f32 * lab as f32 * 16.0).sin()).powf(2.0 as f32))) + .sqrt()) + * ((anim_time as f32 * lab as f32 * 16.0).sin()); + let noisea = (anim_time as f32 * 11.0 + PI / 6.0).sin(); + let noiseb = (anim_time as f32 * 19.0 + PI / 4.0).sin(); let wave = (anim_time as f32 * 16.0).sin(); match active_tool_kind { //TODO: Inventory @@ -193,6 +200,12 @@ impl Animation for WieldAnimation { next.torso.ori = Quaternion::rotation_x(-0.2); next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler; + next.back.offset = Vec3::new(0.0, -2.8, 7.25); + next.back.ori = Quaternion::rotation_x( + (-0.25 + short * 0.3 + noisea * 0.4 + noiseb * 0.4).min(-0.1), + ); + next.back.scale = Vec3::one() * 1.02; + next.l_control.offset = Vec3::new(0.0, 0.0, 0.0); next.l_control.ori = Quaternion::rotation_x(0.0); next.l_control.scale = Vec3::one(); @@ -232,6 +245,10 @@ impl Animation for WieldAnimation { Quaternion::rotation_y(ultra_slow_cos * 0.03) * Quaternion::rotation_z(0.22); next.belt.scale = Vec3::one() * 1.02; + next.back.offset = Vec3::new(0.0, -2.8, 7.25); + next.back.ori = Quaternion::rotation_x(-0.2); + next.back.scale = Vec3::one() * 1.02; + next.shorts.offset = Vec3::new(0.0, 0.0, -5.0); next.shorts.ori = Quaternion::rotation_z(0.3); next.shorts.scale = Vec3::one(); diff --git a/voxygen/src/anim/critter/mod.rs b/voxygen/src/anim/critter/mod.rs index 4a2c05ef6d..9677461239 100644 --- a/voxygen/src/anim/critter/mod.rs +++ b/voxygen/src/anim/critter/mod.rs @@ -32,7 +32,7 @@ impl CritterSkeleton { impl Skeleton for CritterSkeleton { type Attr = CritterAttr; - fn compute_matrices(&self) -> [FigureBoneData; 18] { + fn compute_matrices(&self) -> [FigureBoneData; 16] { [ FigureBoneData::new(self.head.compute_base_matrix()), FigureBoneData::new(self.chest.compute_base_matrix()), @@ -50,8 +50,6 @@ impl Skeleton for CritterSkeleton { FigureBoneData::default(), FigureBoneData::default(), FigureBoneData::default(), - FigureBoneData::default(), - FigureBoneData::default(), ] } diff --git a/voxygen/src/anim/dragon/mod.rs b/voxygen/src/anim/dragon/mod.rs index 131d78dc24..c3bfec7e61 100644 --- a/voxygen/src/anim/dragon/mod.rs +++ b/voxygen/src/anim/dragon/mod.rs @@ -49,7 +49,7 @@ impl DragonSkeleton { impl Skeleton for DragonSkeleton { type Attr = SkeletonAttr; - fn compute_matrices(&self) -> [FigureBoneData; 18] { + fn compute_matrices(&self) -> [FigureBoneData; 16] { let chest_front_mat = self.chest_front.compute_base_matrix(); let wing_in_l_mat = self.wing_in_l.compute_base_matrix(); let wing_in_r_mat = self.wing_in_r.compute_base_matrix(); @@ -72,8 +72,6 @@ impl Skeleton for DragonSkeleton { FigureBoneData::default(), FigureBoneData::default(), FigureBoneData::default(), - FigureBoneData::default(), - FigureBoneData::default(), ] } diff --git a/voxygen/src/anim/fish_medium/mod.rs b/voxygen/src/anim/fish_medium/mod.rs index 5912719299..b085ed7220 100644 --- a/voxygen/src/anim/fish_medium/mod.rs +++ b/voxygen/src/anim/fish_medium/mod.rs @@ -35,7 +35,7 @@ impl FishMediumSkeleton { impl Skeleton for FishMediumSkeleton { type Attr = SkeletonAttr; - fn compute_matrices(&self) -> [FigureBoneData; 18] { + fn compute_matrices(&self) -> [FigureBoneData; 16] { let torso_mat = self.torso.compute_base_matrix(); let rear_mat = self.rear.compute_base_matrix(); @@ -56,8 +56,6 @@ impl Skeleton for FishMediumSkeleton { FigureBoneData::default(), FigureBoneData::default(), FigureBoneData::default(), - FigureBoneData::default(), - FigureBoneData::default(), ] } diff --git a/voxygen/src/anim/fish_small/mod.rs b/voxygen/src/anim/fish_small/mod.rs index e1b47513f7..ed26ef0247 100644 --- a/voxygen/src/anim/fish_small/mod.rs +++ b/voxygen/src/anim/fish_small/mod.rs @@ -27,7 +27,7 @@ impl FishSmallSkeleton { impl Skeleton for FishSmallSkeleton { type Attr = SkeletonAttr; - fn compute_matrices(&self) -> [FigureBoneData; 18] { + fn compute_matrices(&self) -> [FigureBoneData; 16] { let torso_mat = self.torso.compute_base_matrix(); [ @@ -47,8 +47,6 @@ impl Skeleton for FishSmallSkeleton { FigureBoneData::default(), FigureBoneData::default(), FigureBoneData::default(), - FigureBoneData::default(), - FigureBoneData::default(), ] } diff --git a/voxygen/src/anim/fixture/mod.rs b/voxygen/src/anim/fixture/mod.rs index a243b923a4..92a68d0125 100644 --- a/voxygen/src/anim/fixture/mod.rs +++ b/voxygen/src/anim/fixture/mod.rs @@ -13,7 +13,7 @@ impl FixtureSkeleton { impl Skeleton for FixtureSkeleton { type Attr = SkeletonAttr; - fn compute_matrices(&self) -> [FigureBoneData; 18] { + fn compute_matrices(&self) -> [FigureBoneData; 16] { [ FigureBoneData::new(vek::Mat4::identity()), FigureBoneData::new(vek::Mat4::identity()), @@ -31,8 +31,6 @@ impl Skeleton for FixtureSkeleton { FigureBoneData::new(vek::Mat4::identity()), FigureBoneData::new(vek::Mat4::identity()), FigureBoneData::new(vek::Mat4::identity()), - FigureBoneData::new(vek::Mat4::identity()), - FigureBoneData::new(vek::Mat4::identity()), ] } diff --git a/voxygen/src/anim/mod.rs b/voxygen/src/anim/mod.rs index 13eaff91f1..de57de2c81 100644 --- a/voxygen/src/anim/mod.rs +++ b/voxygen/src/anim/mod.rs @@ -52,7 +52,7 @@ impl Bone { pub trait Skeleton: Send + Sync + 'static { type Attr; - fn compute_matrices(&self) -> [FigureBoneData; 18]; + fn compute_matrices(&self) -> [FigureBoneData; 16]; /// Change the current skeleton to be more like `target`. fn interpolate(&mut self, target: &Self, dt: f32); @@ -81,7 +81,7 @@ impl SkeletonAttr { (Human, Female) => 0.75, (Elf, Male) => 0.85, (Elf, Female) => 0.8, - (Dwarf, Male) => 0.7, + (Dwarf, Male) => 0.1, (Dwarf, Female) => 0.65, (Undead, Male) => 0.8, (Undead, Female) => 0.75, diff --git a/voxygen/src/anim/object/mod.rs b/voxygen/src/anim/object/mod.rs index b9befc2522..d6f3ac510e 100644 --- a/voxygen/src/anim/object/mod.rs +++ b/voxygen/src/anim/object/mod.rs @@ -15,7 +15,7 @@ const SCALE: f32 = 1.0 / 11.0; impl Skeleton for ObjectSkeleton { type Attr = SkeletonAttr; - fn compute_matrices(&self) -> [FigureBoneData; 18] { + fn compute_matrices(&self) -> [FigureBoneData; 16] { [ FigureBoneData::new(Mat4::scaling_3d(Vec3::broadcast(SCALE))), FigureBoneData::new(Mat4::scaling_3d(Vec3::broadcast(SCALE))), @@ -33,8 +33,6 @@ impl Skeleton for ObjectSkeleton { FigureBoneData::new(Mat4::scaling_3d(Vec3::broadcast(SCALE))), FigureBoneData::new(Mat4::scaling_3d(Vec3::broadcast(SCALE))), FigureBoneData::new(Mat4::scaling_3d(Vec3::broadcast(SCALE))), - FigureBoneData::new(Mat4::scaling_3d(Vec3::broadcast(SCALE))), - FigureBoneData::new(Mat4::scaling_3d(Vec3::broadcast(SCALE))), ] } diff --git a/voxygen/src/anim/quadruped_medium/mod.rs b/voxygen/src/anim/quadruped_medium/mod.rs index a30eaa0203..57f36c9c1f 100644 --- a/voxygen/src/anim/quadruped_medium/mod.rs +++ b/voxygen/src/anim/quadruped_medium/mod.rs @@ -31,7 +31,7 @@ impl QuadrupedMediumSkeleton { impl Skeleton for QuadrupedMediumSkeleton { type Attr = SkeletonAttr; - fn compute_matrices(&self) -> [FigureBoneData; 18] { + fn compute_matrices(&self) -> [FigureBoneData; 16] { let ears_mat = self.ears.compute_base_matrix(); let head_upper_mat = self.head_upper.compute_base_matrix(); let head_lower_mat = self.head_lower.compute_base_matrix(); @@ -53,8 +53,6 @@ impl Skeleton for QuadrupedMediumSkeleton { FigureBoneData::default(), FigureBoneData::default(), FigureBoneData::default(), - FigureBoneData::default(), - FigureBoneData::default(), ] } diff --git a/voxygen/src/anim/quadruped_small/mod.rs b/voxygen/src/anim/quadruped_small/mod.rs index a277b5aff8..fb4c559fbf 100644 --- a/voxygen/src/anim/quadruped_small/mod.rs +++ b/voxygen/src/anim/quadruped_small/mod.rs @@ -26,7 +26,7 @@ impl QuadrupedSmallSkeleton { impl Skeleton for QuadrupedSmallSkeleton { type Attr = SkeletonAttr; - fn compute_matrices(&self) -> [FigureBoneData; 18] { + fn compute_matrices(&self) -> [FigureBoneData; 16] { [ FigureBoneData::new(self.head.compute_base_matrix()), FigureBoneData::new(self.chest.compute_base_matrix()), @@ -44,8 +44,6 @@ impl Skeleton for QuadrupedSmallSkeleton { FigureBoneData::default(), FigureBoneData::default(), FigureBoneData::default(), - FigureBoneData::default(), - FigureBoneData::default(), ] } diff --git a/voxygen/src/controller.rs b/voxygen/src/controller.rs index 6fe0d76260..a0a47836c3 100644 --- a/voxygen/src/controller.rs +++ b/voxygen/src/controller.rs @@ -103,9 +103,9 @@ impl From<&crate::settings::GamepadSettings> for ControllerSettings { map.entry(settings.game_buttons.climb_down) .or_default() .push(GameInput::ClimbDown); - map.entry(settings.game_buttons.wall_leap) - .or_default() - .push(GameInput::WallLeap); + /*map.entry(settings.game_buttons.wall_leap) + .or_default() + .push(GameInput::WallLeap);*/ map.entry(settings.game_buttons.mount) .or_default() .push(GameInput::Mount); @@ -157,9 +157,9 @@ impl From<&crate::settings::GamepadSettings> for ControllerSettings { map.entry(settings.game_buttons.swap_loadout) .or_default() .push(GameInput::SwapLoadout); - map.entry(settings.game_buttons.charge) - .or_default() - .push(GameInput::Charge); + /*map.entry(settings.game_buttons.charge) + .or_default() + .push(GameInput::Charge);*/ map }, menu_button_map: { diff --git a/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs index 8bb4a2a7d8..03a813b47a 100644 --- a/voxygen/src/hud/bag.rs +++ b/voxygen/src/hud/bag.rs @@ -1,21 +1,25 @@ use super::{ img_ids::{Imgs, ImgsRot}, - item_imgs::{ItemImgs, ItemKey}, - Event as HudEvent, Show, CRITICAL_HP_COLOR, LOW_HP_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN, - XP_COLOR, + item_imgs::ItemImgs, + slots::{ArmorSlot, EquipSlot, InventorySlot, SlotManager}, + Show, CRITICAL_HP_COLOR, LOW_HP_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN, XP_COLOR, }; use crate::{ i18n::VoxygenLocalization, - ui::{fonts::ConrodVoxygenFonts, ImageFrame, Tooltip, TooltipManager, Tooltipable}, + ui::{ + fonts::ConrodVoxygenFonts, + slot::{ContentSize, SlotMaker}, + ImageFrame, Tooltip, TooltipManager, Tooltipable, + }, }; use client::Client; -use common::comp::{item::ItemKind, Stats}; +use common::comp::Stats; use conrod_core::{ - color, image, + color, widget::{self, Button, Image, Rectangle, Text}, widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, }; -// +use vek::Vec2; widget_ids! { pub struct Ids { @@ -28,10 +32,7 @@ widget_ids! { inv_slots_0, map_title, inv_slots[], - items[], - amounts[], - amounts_bg[], - tooltip[], + //tooltip[], bg, bg_frame, char_ico, @@ -46,7 +47,7 @@ widget_ids! { tab_2, tab_3, tab_4, - //Stats + // Stats stats_alignment, level, exp_rectangle, @@ -56,36 +57,23 @@ widget_ids! { divider, statnames, stats, - //Armor Slots + // Armor Slots slots_bg, - head_bg, - neck_bg, - chest_bg, - shoulder_bg, - hands_bg, - legs_bg, - belt_bg, - ring_r_bg, - ring_l_bg, - foot_bg, - back_bg, - tabard_bg, - mainhand_bg, - offhand_bg, - head_ico, - neck_ico, - chest_ico, - shoulder_ico, - hands_ico, - legs_ico, - belt_ico, - ring_r_ico, - ring_l_ico, - foot_ico, - back_ico, - tabard_ico, - mainhand_ico, - offhand_ico, + head_slot, + neck_slot, + chest_slot, + shoulders_slot, + hands_slot, + legs_slot, + belt_slot, + lantern_slot, + ring_slot, + feet_slot, + back_slot, + tabard_slot, + mainhand_slot, + offhand_slot, + // ??? end_ico, fit_ico, wp_ico, @@ -93,7 +81,6 @@ widget_ids! { } #[derive(WidgetCommon)] -#[allow(dead_code)] pub struct Bag<'a> { client: &'a Client, imgs: &'a Imgs, @@ -103,8 +90,10 @@ pub struct Bag<'a> { common: widget::CommonBuilder, rot_imgs: &'a ImgsRot, tooltip_manager: &'a mut TooltipManager, - pulse: f32, + slot_manager: &'a mut SlotManager, + _pulse: f32, localized_strings: &'a std::sync::Arc, + stats: &'a Stats, show: &'a Show, } @@ -117,6 +106,7 @@ impl<'a> Bag<'a> { fonts: &'a ConrodVoxygenFonts, rot_imgs: &'a ImgsRot, tooltip_manager: &'a mut TooltipManager, + slot_manager: &'a mut SlotManager, pulse: f32, localized_strings: &'a std::sync::Arc, stats: &'a Stats, @@ -130,7 +120,8 @@ impl<'a> Bag<'a> { common: widget::CommonBuilder::default(), rot_imgs, tooltip_manager, - pulse, + slot_manager, + _pulse: pulse, localized_strings, stats, show, @@ -140,18 +131,13 @@ impl<'a> Bag<'a> { pub struct State { ids: Ids, - img_id_cache: Vec>, - selected_slot: Option, } pub enum Event { - HudEvent(HudEvent), Stats, Close, } -/* -*/ impl<'a> Widget for Bag<'a> { type Event = Option; type State = State; @@ -160,8 +146,6 @@ impl<'a> Widget for Bag<'a> { fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { State { ids: Ids::new(id_gen), - img_id_cache: Vec::new(), - selected_slot: None, } } @@ -174,9 +158,15 @@ impl<'a> Widget for Bag<'a> { let invs = self.client.inventories(); let inventory = match invs.get(self.client.entity()) { - Some(inv) => inv, + Some(i) => i, None => return None, }; + let loadouts = self.client.loadouts(); + let loadout = match loadouts.get(self.client.entity()) { + Some(l) => l, + None => return None, + }; + let exp_percentage = (self.stats.exp.current() as f64) / (self.stats.exp.maximum() as f64); let exp_treshold = format!( "{}/{} {}", @@ -226,21 +216,23 @@ impl<'a> Widget for Bag<'a> { .color(Some(UI_HIGHLIGHT_0)) .set(state.ids.bg_frame, ui); // Title - Text::new(&format!( - "{}{}", - &self.stats.name, - &self.localized_strings.get("hud.bag.inventory") - )) + Text::new( + &self + .localized_strings + .get("hud.bag.inventory") + .replace("{playername}", &self.stats.name.to_string().as_str()), + ) .mid_top_with_margin_on(state.ids.bg_frame, 9.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(22)) .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) .set(state.ids.inventory_title_bg, ui); - Text::new(&format!( - "{}{}", - &self.stats.name, - &self.localized_strings.get("hud.bag.inventory") - )) + Text::new( + &self + .localized_strings + .get("hud.bag.inventory") + .replace("{playername}", &self.stats.name.to_string().as_str()), + ) .top_left_with_margins_on(state.ids.inventory_title_bg, 2.0, 2.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(22)) @@ -289,21 +281,23 @@ impl<'a> Widget for Bag<'a> { if !self.show.stats { // Title - Text::new(&format!( - "{}{}", - &self.stats.name, - &self.localized_strings.get("hud.bag.inventory") - )) + Text::new( + &self + .localized_strings + .get("hud.bag.inventory") + .replace("{playername}", &self.stats.name.to_string().as_str()), + ) .mid_top_with_margin_on(state.ids.bg_frame, 9.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(22)) .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) .set(state.ids.inventory_title_bg, ui); - Text::new(&format!( - "{}{}", - &self.stats.name, - &self.localized_strings.get("hud.bag.inventory") - )) + Text::new( + &self + .localized_strings + .get("hud.bag.inventory") + .replace("{playername}", &self.stats.name.to_string().as_str()), + ) .top_left_with_margins_on(state.ids.inventory_title_bg, 2.0, 2.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(22)) @@ -323,192 +317,221 @@ impl<'a> Widget for Bag<'a> { .color(Some(UI_HIGHLIGHT_0)) .set(state.ids.slots_bg, ui);*/ // Armor Slots - //Head - Image::new(self.imgs.armor_slot) - .w_h(45.0, 45.0) + let mut slot_maker = SlotMaker { + empty_slot: self.imgs.armor_slot_empty, + filled_slot: self.imgs.armor_slot, + selected_slot: self.imgs.armor_slot_sel, + background_color: Some(UI_HIGHLIGHT_0), + content_size: ContentSize { + width_height_ratio: 1.0, + max_fraction: 0.75, /* Changes the item image size by setting a maximum + * fraction + * of either the width or height */ + }, + selected_content_scale: 1.067, + amount_font: self.fonts.cyri.conrod_id, + amount_margins: Vec2::new(-4.0, 0.0), + amount_font_size: self.fonts.cyri.scale(12), + amount_text_color: TEXT_COLOR, + content_source: loadout, + image_source: self.item_imgs, + slot_manager: Some(self.slot_manager), + }; + // Head + let (title, desc) = loadout + .head + .as_ref() + .map_or((self.localized_strings.get("hud.bag.head"), ""), |item| { + (item.name(), item.description()) + }); + slot_maker + .fabricate(EquipSlot::Armor(ArmorSlot::Head), [45.0; 2]) .mid_top_with_margin_on(state.ids.bg_frame, 60.0) - .color(Some(UI_HIGHLIGHT_0)) - .set(state.ids.head_bg, ui); - Button::image(self.imgs.head_bg) - .w_h(32.0, 40.0) - .image_color(UI_MAIN) - .middle_of(state.ids.head_bg) - .with_tooltip(self.tooltip_manager, "Helmet", "", &item_tooltip) - .set(state.ids.head_ico, ui); - //Necklace - Image::new(self.imgs.armor_slot) - .w_h(45.0, 45.0) - .mid_bottom_with_margin_on(state.ids.head_bg, -55.0) - .color(Some(UI_HIGHLIGHT_0)) - .set(state.ids.neck_bg, ui); - Button::image(self.imgs.necklace_bg) - .w_h(40.0, 31.0) - .image_color(UI_MAIN) - .middle_of(state.ids.neck_bg) - .with_tooltip(self.tooltip_manager, "Neck", "", &item_tooltip) - .set(state.ids.neck_ico, ui); - //Chest - Image::new(self.imgs.armor_slot) // different graphics for empty/non empty - .w_h(85.0, 85.0) - .mid_bottom_with_margin_on(state.ids.neck_bg, -95.0) - .color(Some(UI_HIGHLIGHT_0)) - .set(state.ids.chest_bg, ui); - Button::image(self.imgs.chest_bg) - .w_h(64.0, 42.0) - .image_color(UI_MAIN) - .middle_of(state.ids.chest_bg) - .with_tooltip(self.tooltip_manager, "Chest", "", &item_tooltip) - .set(state.ids.chest_ico, ui); - //Shoulder - Image::new(self.imgs.armor_slot) - .w_h(70.0, 70.0) - .bottom_left_with_margins_on(state.ids.chest_bg, 0.0, -80.0) - .color(Some(UI_HIGHLIGHT_0)) - .set(state.ids.shoulder_bg, ui); - Button::image(self.imgs.shoulders_bg) - .w_h(60.0, 36.0) - .image_color(UI_MAIN) - .middle_of(state.ids.shoulder_bg) - .with_tooltip(self.tooltip_manager, "Shoulders", "", &item_tooltip) - .set(state.ids.shoulder_ico, ui); - //Hands - Image::new(self.imgs.armor_slot) - .w_h(70.0, 70.0) - .bottom_right_with_margins_on(state.ids.chest_bg, 0.0, -80.0) - .color(Some(UI_HIGHLIGHT_0)) - .set(state.ids.hands_bg, ui); - Button::image(self.imgs.hands_bg) - .w_h(55.0, 60.0) - .image_color(UI_MAIN) - .middle_of(state.ids.hands_bg) - .with_tooltip(self.tooltip_manager, "Hands", "", &item_tooltip) - .set(state.ids.hands_ico, ui); - //Belt - Image::new(self.imgs.armor_slot) - .w_h(45.0, 45.0) - .mid_bottom_with_margin_on(state.ids.chest_bg, -55.0) - .color(Some(UI_HIGHLIGHT_0)) - .set(state.ids.belt_bg, ui); - Button::image(self.imgs.belt_bg) - .w_h(40.0, 23.0) - .image_color(UI_MAIN) - .middle_of(state.ids.belt_bg) - .with_tooltip(self.tooltip_manager, "Belt", "", &item_tooltip) - .set(state.ids.belt_ico, ui); - //Legs - Image::new(self.imgs.armor_slot) - .w_h(85.0, 85.0) - .mid_bottom_with_margin_on(state.ids.belt_bg, -95.0) - .color(Some(UI_HIGHLIGHT_0)) - .set(state.ids.legs_bg, ui); - Button::image(self.imgs.legs_bg) - .w_h(48.0, 70.0) - .image_color(UI_MAIN) - .middle_of(state.ids.legs_bg) - .with_tooltip(self.tooltip_manager, "Legs", "", &item_tooltip) - .set(state.ids.legs_ico, ui); - //Ring-L - Image::new(self.imgs.armor_slot) - .w_h(45.0, 45.0) - .bottom_right_with_margins_on(state.ids.shoulder_bg, -55.0, 0.0) - .color(Some(UI_HIGHLIGHT_0)) - .set(state.ids.ring_l_bg, ui); - Button::image(self.imgs.ring_l_bg) - .w_h(36.0, 40.0) - .image_color(UI_MAIN) - .middle_of(state.ids.ring_l_bg) - .with_tooltip(self.tooltip_manager, "Left Ring", "", &item_tooltip) - .set(state.ids.ring_l_ico, ui); - //Ring-R - Image::new(self.imgs.armor_slot) - .w_h(45.0, 45.0) - .bottom_left_with_margins_on(state.ids.hands_bg, -55.0, 0.0) - .color(Some(UI_HIGHLIGHT_0)) - .set(state.ids.ring_r_bg, ui); - Button::image(self.imgs.ring_r_bg) - .w_h(36.0, 40.0) - .image_color(UI_MAIN) - .middle_of(state.ids.ring_r_bg) - .with_tooltip(self.tooltip_manager, "Right Ring", "", &item_tooltip) - .set(state.ids.ring_r_ico, ui); - //Back - Image::new(self.imgs.armor_slot) - .w_h(45.0, 45.0) - .down_from(state.ids.ring_l_bg, 10.0) - .color(Some(UI_HIGHLIGHT_0)) - .set(state.ids.back_bg, ui); - Button::image(self.imgs.back_bg) - .w_h(33.0, 40.0) - .image_color(UI_MAIN) - .middle_of(state.ids.back_bg) - .with_tooltip(self.tooltip_manager, "Back", "", &item_tooltip) - .set(state.ids.back_ico, ui); - //Foot - Image::new(self.imgs.armor_slot) - .w_h(45.0, 45.0) - .down_from(state.ids.ring_r_bg, 10.0) - .color(Some(UI_HIGHLIGHT_0)) - .set(state.ids.foot_bg, ui); - Button::image(self.imgs.feet_bg) - .w_h(32.0, 40.0) - .image_color(UI_MAIN) - .middle_of(state.ids.foot_bg) - .with_tooltip(self.tooltip_manager, "Feet", "", &item_tooltip) - .set(state.ids.foot_ico, ui); - //Tabard - Image::new(self.imgs.armor_slot) - .w_h(70.0, 70.0) + .with_icon(self.imgs.head_bg, Vec2::new(32.0, 40.0), Some(UI_MAIN)) + .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .set(state.ids.head_slot, ui); + // Necklace + let (title, desc) = loadout + .neck + .as_ref() + .map_or((self.localized_strings.get("hud.bag.neck"), ""), |item| { + (item.name(), item.description()) + }); + slot_maker + .fabricate(EquipSlot::Armor(ArmorSlot::Neck), [45.0; 2]) + .mid_bottom_with_margin_on(state.ids.head_slot, -55.0) + .with_icon(self.imgs.necklace_bg, Vec2::new(40.0, 31.0), Some(UI_MAIN)) + .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .set(state.ids.neck_slot, ui); + // Chest + //Image::new(self.imgs.armor_slot) // different graphics for empty/non empty + let (title, desc) = loadout + .chest + .as_ref() + .map_or((self.localized_strings.get("hud.bag.chest"), ""), |item| { + (item.name(), item.description()) + }); + slot_maker + .fabricate(EquipSlot::Armor(ArmorSlot::Chest), [85.0; 2]) + .mid_bottom_with_margin_on(state.ids.neck_slot, -95.0) + .with_icon(self.imgs.chest_bg, Vec2::new(64.0, 42.0), Some(UI_MAIN)) + .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .set(state.ids.chest_slot, ui); + // Shoulders + let (title, desc) = loadout.shoulder.as_ref().map_or( + (self.localized_strings.get("hud.bag.shoulders"), ""), + |item| (item.name(), item.description()), + ); + slot_maker + .fabricate(EquipSlot::Armor(ArmorSlot::Shoulders), [70.0; 2]) + .bottom_left_with_margins_on(state.ids.chest_slot, 0.0, -80.0) + .with_icon(self.imgs.shoulders_bg, Vec2::new(60.0, 36.0), Some(UI_MAIN)) + .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .set(state.ids.shoulders_slot, ui); + // Hands + let (title, desc) = loadout + .hand + .as_ref() + .map_or((self.localized_strings.get("hud.bag.hands"), ""), |item| { + (item.name(), item.description()) + }); + slot_maker + .fabricate(EquipSlot::Armor(ArmorSlot::Hands), [70.0; 2]) + .bottom_right_with_margins_on(state.ids.chest_slot, 0.0, -80.0) + .with_icon(self.imgs.hands_bg, Vec2::new(55.0, 60.0), Some(UI_MAIN)) + .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .set(state.ids.hands_slot, ui); + // Belt + let (title, desc) = loadout + .belt + .as_ref() + .map_or((self.localized_strings.get("hud.bag.belt"), ""), |item| { + (item.name(), item.description()) + }); + slot_maker + .fabricate(EquipSlot::Armor(ArmorSlot::Belt), [45.0; 2]) + .mid_bottom_with_margin_on(state.ids.chest_slot, -55.0) + .with_icon(self.imgs.belt_bg, Vec2::new(40.0, 23.0), Some(UI_MAIN)) + .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .set(state.ids.belt_slot, ui); + // Legs + let (title, desc) = loadout + .pants + .as_ref() + .map_or((self.localized_strings.get("hud.bag.legs"), ""), |item| { + (item.name(), item.description()) + }); + slot_maker + .fabricate(EquipSlot::Armor(ArmorSlot::Legs), [85.0; 2]) + .mid_bottom_with_margin_on(state.ids.belt_slot, -95.0) + .with_icon(self.imgs.legs_bg, Vec2::new(48.0, 70.0), Some(UI_MAIN)) + .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .set(state.ids.legs_slot, ui); + // Lantern + let (title, desc) = loadout.lantern.as_ref().map_or( + (self.localized_strings.get("hud.bag.lantern"), ""), + |item| (item.name(), item.description()), + ); + slot_maker + .fabricate(EquipSlot::Lantern, [45.0; 2]) + .bottom_right_with_margins_on(state.ids.shoulders_slot, -55.0, 0.0) + .with_icon(self.imgs.lantern_bg, Vec2::new(24.0, 38.0), Some(UI_MAIN)) + .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .set(state.ids.lantern_slot, ui); + // Ring + let (title, desc) = loadout + .ring + .as_ref() + .map_or((self.localized_strings.get("hud.bag.ring"), ""), |item| { + (item.name(), item.description()) + }); + slot_maker + .fabricate(EquipSlot::Armor(ArmorSlot::Ring), [45.0; 2]) + .bottom_left_with_margins_on(state.ids.hands_slot, -55.0, 0.0) + .with_icon(self.imgs.ring_bg, Vec2::new(36.0, 40.0), Some(UI_MAIN)) + .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .set(state.ids.ring_slot, ui); + // Back + let (title, desc) = loadout + .back + .as_ref() + .map_or((self.localized_strings.get("hud.bag.back"), ""), |item| { + (item.name(), item.description()) + }); + slot_maker + .fabricate(EquipSlot::Armor(ArmorSlot::Back), [45.0; 2]) + .down_from(state.ids.lantern_slot, 10.0) + .with_icon(self.imgs.back_bg, Vec2::new(33.0, 40.0), Some(UI_MAIN)) + .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .set(state.ids.back_slot, ui); + // Foot + let (title, desc) = loadout + .foot + .as_ref() + .map_or((self.localized_strings.get("hud.bag.feet"), ""), |item| { + (item.name(), item.description()) + }); + slot_maker + .fabricate(EquipSlot::Armor(ArmorSlot::Feet), [45.0; 2]) + .down_from(state.ids.ring_slot, 10.0) + .with_icon(self.imgs.feet_bg, Vec2::new(32.0, 40.0), Some(UI_MAIN)) + .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .set(state.ids.feet_slot, ui); + // Tabard + let (title, desc) = loadout + .tabard + .as_ref() + .map_or((self.localized_strings.get("hud.bag.tabard"), ""), |item| { + (item.name(), item.description()) + }); + slot_maker + .fabricate(EquipSlot::Armor(ArmorSlot::Tabard), [70.0; 2]) .top_right_with_margins_on(state.ids.bg_frame, 80.5, 53.0) - .color(Some(UI_HIGHLIGHT_0)) - .set(state.ids.tabard_bg, ui); - Button::image(self.imgs.tabard_bg) - .w_h(60.0, 60.0) - .image_color(UI_MAIN) - .middle_of(state.ids.tabard_bg) - .with_tooltip(self.tooltip_manager, "Tabard", "", &item_tooltip) - .set(state.ids.tabard_ico, ui); - //Mainhand/Left-Slot - Image::new(self.imgs.armor_slot) - .w_h(85.0, 85.0) - .bottom_right_with_margins_on(state.ids.back_bg, -95.0, 0.0) - .color(Some(UI_HIGHLIGHT_0)) - .set(state.ids.mainhand_bg, ui); - Button::image(self.imgs.mainhand_bg) - .w_h(75.0, 75.0) - .image_color(UI_MAIN) - .middle_of(state.ids.mainhand_bg) - .with_tooltip(self.tooltip_manager, "Mainhand", "", &item_tooltip) - .set(state.ids.mainhand_ico, ui); - //Offhand/Right-Slot - Image::new(self.imgs.armor_slot) - .w_h(85.0, 85.0) - .bottom_left_with_margins_on(state.ids.foot_bg, -95.0, 0.0) - .color(Some(UI_HIGHLIGHT_0)) - .set(state.ids.offhand_bg, ui); - Button::image(self.imgs.offhand_bg) - .w_h(75.0, 75.0) - .image_color(UI_MAIN) - .middle_of(state.ids.offhand_bg) - .with_tooltip(self.tooltip_manager, "Offhand", "", &item_tooltip) - .set(state.ids.offhand_ico, ui); + .with_icon(self.imgs.tabard_bg, Vec2::new(60.0, 60.0), Some(UI_MAIN)) + .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .set(state.ids.tabard_slot, ui); + // Mainhand/Left-Slot + let (title, desc) = loadout.active_item.as_ref().map(|i| &i.item).map_or( + (self.localized_strings.get("hud.bag.mainhand"), ""), + |item| (item.name(), item.description()), + ); + slot_maker + .fabricate(EquipSlot::Mainhand, [85.0; 2]) + .bottom_right_with_margins_on(state.ids.back_slot, -95.0, 0.0) + .with_icon(self.imgs.mainhand_bg, Vec2::new(75.0, 75.0), Some(UI_MAIN)) + .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .set(state.ids.mainhand_slot, ui); + // Offhand/Right-Slot + let (title, desc) = loadout.second_item.as_ref().map(|i| &i.item).map_or( + (self.localized_strings.get("hud.bag.offhand"), ""), + |item| (item.name(), item.description()), + ); + slot_maker + .fabricate(EquipSlot::Offhand, [85.0; 2]) + .bottom_left_with_margins_on(state.ids.feet_slot, -95.0, 0.0) + .with_icon(self.imgs.offhand_bg, Vec2::new(75.0, 75.0), Some(UI_MAIN)) + .with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .set(state.ids.offhand_slot, ui); } else { // Stats // Title - Text::new(&format!( - "{}{}", - &self.stats.name, - &self.localized_strings.get("hud.bag.stats_title") - )) + Text::new( + &self + .localized_strings + .get("hud.bag.stats_title") + .replace("{playername}", &self.stats.name.to_string().as_str()), + ) .mid_top_with_margin_on(state.ids.bg_frame, 9.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(22)) .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) .set(state.ids.inventory_title_bg, ui); - Text::new(&format!( - "{}{}", - &self.stats.name, - &self.localized_strings.get("hud.bag.stats_title") - )) + Text::new( + &self + .localized_strings + .get("hud.bag.stats_title") + .replace("{playername}", &self.stats.name.to_string().as_str()), + ) .top_left_with_margins_on(state.ids.inventory_title_bg, 2.0, 2.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(22)) @@ -605,57 +628,38 @@ impl<'a> Widget for Bag<'a> { .resize(inventory.len(), &mut ui.widget_id_generator()); }); } - if state.ids.items.len() < inventory.len() { - state.update(|s| { - s.ids - .items - .resize(inventory.len(), &mut ui.widget_id_generator()); - }); - } - if state.ids.amounts.len() < inventory.len() { - state.update(|s| { - s.ids - .amounts - .resize(inventory.len(), &mut ui.widget_id_generator()); - }); - } - if state.ids.amounts_bg.len() < inventory.len() { - state.update(|s| { - s.ids - .amounts_bg - .resize(inventory.len(), &mut ui.widget_id_generator()); - }); - } - // Expand img id cache to the number of slots - if state.img_id_cache.len() < inventory.len() { - state.update(|s| { - s.img_id_cache.resize(inventory.len(), None); - }); - } - // Display inventory contents + let mut slot_maker = SlotMaker { + empty_slot: self.imgs.inv_slot, + filled_slot: self.imgs.inv_slot, + selected_slot: self.imgs.inv_slot_sel, + background_color: Some(UI_MAIN), + content_size: ContentSize { + width_height_ratio: 1.0, + max_fraction: 0.75, + }, + selected_content_scale: 1.067, + amount_font: self.fonts.cyri.conrod_id, + amount_margins: Vec2::new(-4.0, 0.0), + amount_font_size: self.fonts.cyri.scale(12), + amount_text_color: TEXT_COLOR, + content_source: inventory, + image_source: self.item_imgs, + slot_manager: Some(self.slot_manager), + }; for (i, item) in inventory.slots().iter().enumerate() { let x = i % 9; let y = i / 9; - let is_selected = Some(i) == state.selected_slot; - // Slot - - let slot_widget = Button::image(if !is_selected { - self.imgs.inv_slot - } else { - self.imgs.inv_slot_sel - }) - .top_left_with_margins_on( - state.ids.inv_alignment, - 0.0 + y as f64 * (40.0), - 0.0 + x as f64 * (40.0), - ) - .wh([40.0; 2]) - .image_color(UI_MAIN); - - let slot_widget_clicked = if let Some(item) = item { + let slot_widget = slot_maker + .fabricate(InventorySlot(i), [40.0; 2]) + .top_left_with_margins_on( + state.ids.inv_alignment, + 0.0 + y as f64 * (40.0), + 0.0 + x as f64 * (40.0), + ); + if let Some(item) = item { slot_widget .with_tooltip( self.tooltip_manager, @@ -666,82 +670,12 @@ impl<'a> Widget for Bag<'a> { ), &item_tooltip, ) - .set(state.ids.inv_slots[i], ui) + .set(state.ids.inv_slots[i], ui); } else { - slot_widget.set(state.ids.inv_slots[i], ui) - } - .was_clicked(); - - // Item - if slot_widget_clicked { - let selected_slot = match state.selected_slot { - Some(a) => { - if a == i { - event = Some(Event::HudEvent(HudEvent::UseInventorySlot(i))); - } else { - event = Some(Event::HudEvent(HudEvent::SwapInventorySlots(a, i))); - } - None - }, - None if item.is_some() => Some(i), - None => None, - }; - state.update(|s| s.selected_slot = selected_slot); - } - // Item - if let Some(kind) = item.as_ref().map(|i| ItemKey::from(i)) { - //Stack Size - Button::image(match &state.img_id_cache[i] { - Some((cached_kind, id)) if cached_kind == &kind => *id, - _ => { - let id = self - .item_imgs - .img_id(kind.clone()) - .unwrap_or(self.imgs.not_found); - state.update(|s| s.img_id_cache[i] = Some((kind, id))); - id - }, - }) - .wh(if is_selected { [32.0; 2] } else { [30.0; 2] }) - .middle_of(state.ids.inv_slots[i]) - .graphics_for(state.ids.inv_slots[i]) - .set(state.ids.items[i], ui); - } - if let Some(item) = item { - if let Some(amount) = match item.kind { - ItemKind::Tool { .. } | ItemKind::Armor { .. } => None, - ItemKind::Utility { amount, .. } - | ItemKind::Consumable { amount, .. } - | ItemKind::Ingredient { amount, .. } => Some(amount), - } { - if amount > 1 { - Text::new(&format!("{}", &amount)) - .top_right_with_margins_on(state.ids.items[i], -4.0, 0.0) - .font_id(self.fonts.cyri.conrod_id) - .floating(true) - .font_size(self.fonts.cyri.scale(12)) - .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) - .floating(true) - .set(state.ids.amounts_bg[i], ui); - Text::new(&format!("{}", &amount)) - .bottom_left_with_margins_on(state.ids.amounts_bg[i], 1.0, 1.0) - .font_id(self.fonts.cyri.conrod_id) - .font_size(self.fonts.cyri.scale(12)) - .color(TEXT_COLOR) - .floating(true) - .set(state.ids.amounts[i], ui); - } - } + slot_widget.set(state.ids.inv_slots[i], ui); } } - // Drop selected item - if let Some(to_drop) = state.selected_slot { - if ui.widget_input(ui.window).clicks().left().next().is_some() { - event = Some(Event::HudEvent(HudEvent::DropInventorySlot(to_drop))); - state.update(|s| s.selected_slot = None); - } - } // Stats Button if Button::image(self.imgs.button) .w_h(92.0, 22.0) diff --git a/voxygen/src/hud/hotbar.rs b/voxygen/src/hud/hotbar.rs new file mode 100644 index 0000000000..e9958f5d66 --- /dev/null +++ b/voxygen/src/hud/hotbar.rs @@ -0,0 +1,97 @@ +#[derive(Clone, Copy, PartialEq)] +pub enum Slot { + One = 0, + Two = 1, + Three = 2, + Four = 3, + Five = 4, + Six = 5, + Seven = 6, + Eight = 7, + Nine = 8, + Ten = 9, +} + +#[derive(Clone, Copy, PartialEq)] +pub enum SlotContents { + Inventory(usize), + Ability3, +} + +pub struct State { + slots: [Option; 10], + inputs: [bool; 10], +} + +impl State { + pub fn new() -> Self { + Self { + slots: [None; 10], + inputs: [false; 10], + } + } + + /// Returns true if the button was just pressed + pub fn process_input(&mut self, slot: Slot, state: bool) -> bool { + let slot = slot as usize; + let just_pressed = !self.inputs[slot] && state; + self.inputs[slot] = state; + just_pressed + } + + pub fn get(&self, slot: Slot) -> Option { self.slots[slot as usize] } + + pub fn swap(&mut self, a: Slot, b: Slot) { self.slots.swap(a as usize, b as usize); } + + pub fn clear_slot(&mut self, slot: Slot) { self.slots[slot as usize] = None; } + + pub fn add_inventory_link(&mut self, slot: Slot, inventory_index: usize) { + self.slots[slot as usize] = Some(SlotContents::Inventory(inventory_index)); + } + + // TODO: remove + // Adds ability3 slot if it is missing and should be present + // Removes if it is there and shouldn't be present + pub fn maintain_ability3(&mut self, client: &client::Client) { + use specs::WorldExt; + let loadouts = client.state().ecs().read_storage::(); + let loadout = loadouts.get(client.entity()); + let should_be_present = if let Some(loadout) = loadout { + loadout + .active_item + .as_ref() + .map(|i| &i.item.kind) + .filter(|kind| { + use common::comp::item::{ + tool::{StaffKind, Tool, ToolKind}, + ItemKind, + }; + matches!( + kind, + ItemKind::Tool(Tool { + kind: ToolKind::Staff(StaffKind::BasicStaff), + .. + }) + ) + }) + .is_some() + } else { + false + }; + + if should_be_present { + if !self + .slots + .iter() + .any(|s| matches!(s, Some(SlotContents::Ability3))) + { + self.slots[0] = Some(SlotContents::Ability3); + } + } else { + self.slots + .iter_mut() + .filter(|s| matches!(s, Some(SlotContents::Ability3))) + .for_each(|s| *s = None) + } + } +} diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index b62913d453..867202305e 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -20,36 +20,6 @@ rotation_image_ids! { image_ids! { pub struct Imgs { - - // Skillbar - xp_bar_mid: "voxygen.element.skillbar.xp_bar_mid", - xp_bar_left: "voxygen.element.skillbar.xp_bar_left", - xp_bar_right: "voxygen.element.skillbar.xp_bar_right", - skillbar_slot: "voxygen.element.skillbar.skillbar_slot", - skillbar_slot_act: "voxygen.element.skillbar.skillbar_slot_active", - skillbar_slot_l: "voxygen.element.skillbar.skillbar_slot_l", - skillbar_slot_r: "voxygen.element.skillbar.skillbar_slot_r", - skillbar_slot_l_act: "voxygen.element.skillbar.skillbar_slot_l_active", - skillbar_slot_r_act: "voxygen.element.skillbar.skillbar_slot_r_active", - skillbar_slot_bg: "voxygen.element.skillbar.skillbar_slot_bg", - skillbar_slot_big: "voxygen.element.skillbar.skillbar_slot_big", - skillbar_slot_big_act: "voxygen.element.skillbar.skillbar_slot_big_active", - skillbar_slot_big_bg: "voxygen.element.skillbar.skillbar_slot_big_bg", - healthbar_bg: "voxygen.element.skillbar.healthbar_bg", - energybar_bg: "voxygen.element.skillbar.energybar_bg", - bar_content: "voxygen.element.skillbar.bar_content", - level_up: "voxygen.element.misc_bg.level_up", - level_down:"voxygen.element.misc_bg.level_down", - stamina_0:"voxygen.element.skillbar.stamina_wheel-empty", - stamina_1:"voxygen.element.skillbar.stamina_wheel-0", - stamina_2:"voxygen.element.skillbar.stamina_wheel-1", - stamina_3:"voxygen.element.skillbar.stamina_wheel-2", - stamina_4:"voxygen.element.skillbar.stamina_wheel-3", - stamina_5:"voxygen.element.skillbar.stamina_wheel-4", - stamina_6:"voxygen.element.skillbar.stamina_wheel-5", - stamina_7:"voxygen.element.skillbar.stamina_wheel-6", - stamina_8:"voxygen.element.skillbar.stamina_wheel-7", - // Window Parts window_3: "voxygen.element.frames.window_3", tab_bg: "voxygen.element.frames.tab_bg", @@ -151,6 +121,25 @@ image_ids! { + // Skillbar + level_up: "voxygen.element.misc_bg.level_up", + level_down:"voxygen.element.misc_bg.level_down", + xp_bar_mid: "voxygen.element.skillbar.xp_bar_mid", + xp_bar_left: "voxygen.element.skillbar.xp_bar_left", + xp_bar_right: "voxygen.element.skillbar.xp_bar_right", + healthbar_bg: "voxygen.element.skillbar.healthbar_bg", + energybar_bg: "voxygen.element.skillbar.energybar_bg", + bar_content: "voxygen.element.skillbar.bar_content", + skillbar_slot_big: "voxygen.element.skillbar.skillbar_slot_big", + skillbar_slot_big_bg: "voxygen.element.skillbar.skillbar_slot_big", + skillbar_slot_big_act: "voxygen.element.skillbar.skillbar_slot_big", + skillbar_slot: "voxygen.element.skillbar.skillbar_slot", + skillbar_slot_act: "voxygen.element.skillbar.skillbar_slot_active", + skillbar_slot_l: "voxygen.element.skillbar.skillbar_slot_l", + skillbar_slot_r: "voxygen.element.skillbar.skillbar_slot_r", + skillbar_slot_l_act: "voxygen.element.skillbar.skillbar_slot_l_active", + skillbar_slot_r_act: "voxygen.element.skillbar.skillbar_slot_r_active", + // Skill Icons twohsword_m1: "voxygen.element.icons.2hsword_m1", twohsword_m2: "voxygen.element.icons.2hsword_m2", @@ -230,17 +219,19 @@ image_ids! { inv_slots: "voxygen.element.misc_bg.inv_slots", inv_runes: "voxygen.element.misc_bg.inv_runes", armor_slot: "voxygen.element.buttons.armor_slot", + armor_slot_sel: "voxygen.element.buttons.armor_slot_selected", + armor_slot_empty: "voxygen.element.buttons.armor_slot_empty", head_bg: "voxygen.element.icons.head", shoulders_bg: "voxygen.element.icons.shoulders", hands_bg: "voxygen.element.icons.hands", belt_bg: "voxygen.element.icons.belt", legs_bg: "voxygen.element.icons.legs", feet_bg: "voxygen.element.icons.feet", - ring_r_bg: "voxygen.element.icons.ring", - ring_l_bg: "voxygen.element.icons.ring", + ring_bg: "voxygen.element.icons.ring", tabard_bg: "voxygen.element.icons.tabard", chest_bg: "voxygen.element.icons.chest", back_bg: "voxygen.element.icons.back", + lantern_bg: "voxygen.element.icons.lantern", necklace_bg: "voxygen.element.icons.necklace", mainhand_bg: "voxygen.element.icons.mainhand", offhand_bg: "voxygen.element.icons.offhand", diff --git a/voxygen/src/hud/item_imgs.rs b/voxygen/src/hud/item_imgs.rs index 84566e45c0..3c76f58087 100644 --- a/voxygen/src/hud/item_imgs.rs +++ b/voxygen/src/hud/item_imgs.rs @@ -4,8 +4,9 @@ use common::{ comp::item::{ armor::Armor, tool::{Tool, ToolKind}, - Consumable, Ingredient, Item, ItemKind, Utility, + Consumable, Ingredient, Item, ItemKind, Lantern, Utility, }, + figure::Segment, }; use conrod_core::image::Id; use dot_vox::DotVoxData; @@ -19,6 +20,7 @@ use vek::*; #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum ItemKey { Tool(ToolKind), + Lantern(Lantern), Armor(Armor), Utility(Utility), Consumable(Consumable), @@ -29,6 +31,7 @@ impl From<&Item> for ItemKey { fn from(item: &Item) -> Self { match &item.kind { ItemKind::Tool(Tool { kind, .. }) => ItemKey::Tool(kind.clone()), + ItemKind::Lantern(kind) => ItemKey::Lantern(kind.clone()), ItemKind::Armor { kind, .. } => ItemKey::Armor(kind.clone()), ItemKind::Utility { kind, .. } => ItemKey::Utility(kind.clone()), ItemKind::Consumable { kind, .. } => ItemKey::Consumable(kind.clone()), @@ -49,7 +52,7 @@ impl ImageSpec { match self { ImageSpec::Png(specifier) => Graphic::Image(graceful_load_img(&specifier)), ImageSpec::Vox(specifier) => Graphic::Voxel( - graceful_load_vox(&specifier), + graceful_load_segment_no_skin(&specifier), Transform { stretch: false, ..Default::default() @@ -57,7 +60,7 @@ impl ImageSpec { SampleStrat::None, ), ImageSpec::VoxTrans(specifier, offset, [rot_x, rot_y, rot_z], zoom) => Graphic::Voxel( - graceful_load_vox(&specifier), + graceful_load_segment_no_skin(&specifier), Transform { ori: Quaternion::rotation_x(rot_x * std::f32::consts::PI / 180.0) .rotated_y(rot_y * std::f32::consts::PI / 180.0) @@ -82,12 +85,14 @@ impl Asset for ItemImagesSpec { } } +// TODO: when there are more images don't load them all into memory pub struct ItemImgs { map: HashMap, indicator: ReloadIndicator, + not_found: Id, } impl ItemImgs { - pub fn new(ui: &mut Ui) -> Self { + pub fn new(ui: &mut Ui, not_found: Id) -> Self { let mut indicator = ReloadIndicator::new(); Self { map: assets::load_watched::( @@ -97,9 +102,13 @@ impl ItemImgs { .expect("Unable to load item image manifest") .0 .iter() + // TODO: what if multiple kinds map to the same image, it would be nice to use the same + // image id for both, although this does interfere with the current hot-reloading + // strategy .map(|(kind, spec)| (kind.clone(), ui.add_graphic(spec.create_graphic()))) .collect(), indicator, + not_found, } } @@ -139,6 +148,10 @@ impl ItemImgs { }, } } + + pub fn img_id_or_not_found_img(&self, item_kind: ItemKey) -> Id { + self.img_id(item_kind).unwrap_or(self.not_found) + } } // Copied from figure/load.rs @@ -169,3 +182,16 @@ fn graceful_load_img(specifier: &str) -> Arc { }, } } + +fn graceful_load_segment_no_skin(specifier: &str) -> Arc { + use common::figure::{mat_cell::MatCell, MatSegment}; + let mat_seg = MatSegment::from(&*graceful_load_vox(specifier)); + let seg = mat_seg + .map(|mat_cell| match mat_cell { + MatCell::None => None, + MatCell::Mat(_) => Some(MatCell::None), + MatCell::Normal(_) => None, + }) + .to_segment(|_| Rgb::broadcast(255)); + Arc::new(seg) +} diff --git a/voxygen/src/hud/map.rs b/voxygen/src/hud/map.rs index fcb29999d7..9de88cf360 100644 --- a/voxygen/src/hud/map.rs +++ b/voxygen/src/hud/map.rs @@ -13,14 +13,8 @@ use conrod_core::{ widget::{self, Button, Image, Rectangle, Text}, widget_ids, Colorable, Positionable, Sizeable, Widget, WidgetCommon, }; -//use const_tweaker::tweak; use specs::WorldExt; use vek::*; -/*#[tweak(min = 0.0, max = 40.0, step = 1.0)] -const X: f64 = 10.0; -#[tweak(min = 0.0, max = 40.0, step = 1.0)] -const Y: f64 = 10.0;*/ - widget_ids! { struct Ids { frame, @@ -198,17 +192,22 @@ impl<'a> Widget for Map<'a> { .read_storage::() .get(self.client.entity()) .map_or(Vec3::zero(), |pos| pos.0); - - let x = player_pos.x as f64 / worldsize.x * 760.0; - let y = player_pos.y as f64 / worldsize.y * 760.0; - let indic_scale = 0.6; + // Cursor pos relative to playerpos and widget size + // Cursor stops moving on an axis as soon as it's position exceeds the maximum + // size of the widget + let rel = Vec2::from(player_pos).map2(worldsize, |e: f32, sz: f64| { + (e as f64 / sz).clamped(0.0, 1.0) + }); + let xy = rel * 760.0; + let scale = 0.6; + let arrow_sz = Vec2::new(32.0, 37.0) * scale; Image::new(self.rot_imgs.indicator_mmap_small.target_north) .bottom_left_with_margins_on( state.ids.grid, - y - 37.0 * indic_scale / 2.0, - x - 32.0 * indic_scale / 2.0, + xy.y - arrow_sz.y / 2.0, + xy.x - arrow_sz.x / 2.0, ) - .w_h(32.0 * indic_scale, 37.0 * indic_scale) + .w_h(arrow_sz.x, arrow_sz.y) .color(Some(UI_HIGHLIGHT_0)) .floating(true) .parent(ui.window) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index b69cfb199d..9342d91470 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -2,12 +2,14 @@ mod bag; mod buttons; mod chat; mod esc_menu; +mod hotbar; mod img_ids; mod item_imgs; mod map; mod minimap; mod settings_window; mod skillbar; +mod slots; mod social; mod spell; @@ -35,13 +37,14 @@ use crate::{ i18n::{i18n_asset_key, LanguageMetadata, VoxygenLocalization}, render::{AaMode, CloudMode, Consts, FluidMode, Globals, Renderer}, scene::camera::{self, Camera}, - ui::{fonts::ConrodVoxygenFonts, Graphic, Ingameable, ScaleMode, Ui}, + ui::{fonts::ConrodVoxygenFonts, slot, Graphic, Ingameable, ScaleMode, Ui}, window::{Event as WinEvent, GameInput}, GlobalState, }; use client::{Client, Event as ClientEvent}; use common::{assets::load_expect, comp, terrain::TerrainChunk, vol::RectRasterableVol}; use conrod_core::{ + position::Relative, text::cursor::Index, widget::{self, Button, Image, Rectangle, Text}, widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, @@ -205,6 +208,7 @@ pub enum Event { AdjustMouseZoom(u32), ToggleZoomInvert(bool), ToggleMouseYInvert(bool), + ToggleSmoothPan(bool), AdjustViewDistance(u32), AdjustLodDetail(u32), AdjustMusicVolume(f32), @@ -231,12 +235,14 @@ pub enum Event { ToggleDebug(bool), UiScale(ScaleChange), CharacterSelection, - UseInventorySlot(usize), - SwapInventorySlots(usize, usize), - DropInventorySlot(usize), + UseSlot(comp::slot::Slot), + SwapSlots(comp::slot::Slot, comp::slot::Slot), + DropSlot(comp::slot::Slot), + Ability3(bool), Logout, Quit, ChangeLanguage(LanguageMetadata), + ChangeBinding(GameInput), ChangeFreeLookBehavior(PressBehavior), } @@ -306,6 +312,7 @@ pub struct Show { impl Show { fn bag(&mut self, open: bool) { self.bag = open; + self.map = false; self.want_grab = !open; } @@ -362,6 +369,8 @@ impl Show { || self.map || self.social || self.spell + || self.help + || self.intro || match self.open_windows { Windows::None => false, _ => true, @@ -369,6 +378,8 @@ impl Show { { self.bag = false; self.esc_menu = false; + self.help = false; + self.intro = false; self.map = false; self.social = false; self.spell = false; @@ -434,6 +445,9 @@ pub struct Hud { pulse: f32, velocity: f32, voxygen_i18n: std::sync::Arc, + slot_manager: slots::SlotManager, + hotbar: hotbar::State, + events: Vec, } impl Hud { @@ -455,7 +469,7 @@ impl Hud { // Load rotation images. let rot_imgs = ImgsRot::load(&mut ui).expect("Failed to load rot images!"); // Load item images. - let item_imgs = ItemImgs::new(&mut ui); + let item_imgs = ItemImgs::new(&mut ui, imgs.not_found); // Load language let voxygen_i18n = load_expect::(&i18n_asset_key( &global_state.settings.language.selected_language, @@ -464,6 +478,8 @@ impl Hud { let fonts = ConrodVoxygenFonts::load(&voxygen_i18n.fonts, &mut ui) .expect("Impossible to load fonts!"); + let slot_manager = slots::SlotManager::new(ui.id_generator(), Vec2::broadcast(40.0)); + Self { ui, imgs, @@ -502,6 +518,9 @@ impl Hud { pulse: 0.0, velocity: 0.0, voxygen_i18n, + slot_manager, + hotbar: hotbar::State::new(), + events: Vec::new(), } } @@ -518,7 +537,7 @@ impl Hud { debug_info: DebugInfo, dt: Duration, ) -> Vec { - let mut events = Vec::new(); + let mut events = std::mem::replace(&mut self.events, Vec::new()); let (ref mut ui_widgets, ref mut tooltip_manager) = self.ui.set_widgets(); // pulse time for pulsating elements self.pulse = self.pulse + dt.as_secs_f32(); @@ -1240,23 +1259,27 @@ impl Hud { if self.show.intro && !self.show.esc_menu && !self.intro_2 { match global_state.settings.gameplay.intro_show { Intro::Show => { - Rectangle::fill_with([800.0, 850.0], Color::Rgba(0.0, 0.0, 0.0, 0.80)) - .top_left_with_margins_on(ui_widgets.window, 180.0, 10.0) - .floating(true) - .set(self.ids.intro_bg, ui_widgets); + Rectangle::fill_with( + [800.0 * 0.8, 850.0 * 0.8], + Color::Rgba(0.0, 0.0, 0.0, 0.80), + ) + .top_left_with_margins_on(ui_widgets.window, 180.0 * 0.8, 10.0 * 0.8) + .floating(true) + .set(self.ids.intro_bg, ui_widgets); Text::new(intro_text) .top_left_with_margins_on(self.ids.intro_bg, 10.0, 10.0) - .font_size(self.fonts.cyri.scale(20)) + .font_size(self.fonts.cyri.scale(16)) .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) .set(self.ids.intro_text, ui_widgets); if Button::image(self.imgs.button) - .w_h(100.0, 50.0) + .w_h(90.0, 35.0) .mid_bottom_with_margin_on(self.ids.intro_bg, 10.0) .label(&self.voxygen_i18n.get("common.close")) - .label_font_size(self.fonts.cyri.scale(20)) + .label_font_size(self.fonts.cyri.scale(16)) .label_font_id(self.fonts.cyri.conrod_id) .label_color(TEXT_COLOR) + .label_y(Relative::Scalar(4.0)) .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) .set(self.ids.intro_close, ui_widgets) @@ -1485,80 +1508,84 @@ impl Hud { .set(self.ids.num_figures, ui_widgets); // Help Window - Text::new( - &self - .voxygen_i18n - .get("hud.press_key_to_toggle_keybindings_fmt") - .replace( - "{key}", - &format!("{:?}", global_state.settings.controls.help), - ), - ) - .color(TEXT_COLOR) - .down_from(self.ids.num_figures, 5.0) - .font_id(self.fonts.cyri.conrod_id) - .font_size(self.fonts.cyri.scale(14)) - .set(self.ids.help_info, ui_widgets); + if let Some(help_key) = global_state.settings.controls.get_binding(GameInput::Help) { + Text::new( + &self + .voxygen_i18n + .get("hud.press_key_to_toggle_keybindings_fmt") + .replace("{key}", help_key.to_string().as_str()), + ) + .color(TEXT_COLOR) + .down_from(self.ids.num_figures, 5.0) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) + .set(self.ids.help_info, ui_widgets); + } // Info about Debug Shortcut - Text::new( - &self - .voxygen_i18n - .get("hud.press_key_to_toggle_debug_info_fmt") - .replace( - "{key}", - &format!("{:?}", global_state.settings.controls.toggle_debug), - ), - ) - .color(TEXT_COLOR) - .down_from(self.ids.help_info, 5.0) - .font_id(self.fonts.cyri.conrod_id) - .font_size(self.fonts.cyri.scale(14)) - .set(self.ids.debug_info, ui_widgets); + if let Some(toggle_debug_key) = global_state + .settings + .controls + .get_binding(GameInput::ToggleDebug) + { + Text::new( + &self + .voxygen_i18n + .get("hud.press_key_to_toggle_debug_info_fmt") + .replace("{key}", toggle_debug_key.to_string().as_str()), + ) + .color(TEXT_COLOR) + .down_from(self.ids.help_info, 5.0) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) + .set(self.ids.debug_info, ui_widgets); + } } else { // Help Window - Text::new( - &self - .voxygen_i18n - .get("hud.press_key_to_show_keybindings_fmt") - .replace( - "{key}", - &format!("{:?}", global_state.settings.controls.help), - ), - ) - .color(TEXT_COLOR) - .top_left_with_margins_on(ui_widgets.window, 5.0, 5.0) - .font_id(self.fonts.cyri.conrod_id) - .font_size(self.fonts.cyri.scale(16)) - .set(self.ids.help_info, ui_widgets); + if let Some(help_key) = global_state.settings.controls.get_binding(GameInput::Help) { + Text::new( + &self + .voxygen_i18n + .get("hud.press_key_to_show_keybindings_fmt") + .replace("{key}", help_key.to_string().as_str()), + ) + .color(TEXT_COLOR) + .top_left_with_margins_on(ui_widgets.window, 5.0, 5.0) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(16)) + .set(self.ids.help_info, ui_widgets); + } // Info about Debug Shortcut - Text::new( - &self - .voxygen_i18n - .get("hud.press_key_to_show_debug_info_fmt") - .replace( - "{key}", - &format!("{:?}", global_state.settings.controls.toggle_debug), - ), - ) - .color(TEXT_COLOR) - .down_from(self.ids.help_info, 5.0) - .font_id(self.fonts.cyri.conrod_id) - .font_size(self.fonts.cyri.scale(12)) - .set(self.ids.debug_info, ui_widgets); + if let Some(toggle_debug_key) = global_state + .settings + .controls + .get_binding(GameInput::ToggleDebug) + { + Text::new( + &self + .voxygen_i18n + .get("hud.press_key_to_show_debug_info_fmt") + .replace("{key}", toggle_debug_key.to_string().as_str()), + ) + .color(TEXT_COLOR) + .down_from(self.ids.help_info, 5.0) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(12)) + .set(self.ids.debug_info, ui_widgets); + } } // Help Text if self.show.help && !self.show.map && !self.show.esc_menu { Image::new(self.imgs.help) .middle_of(ui_widgets.window) - .w_h(1260.0 * 1.2, 519.0 * 1.2) + .w_h(1260.0, 519.0) .set(self.ids.help, ui_widgets); // Show tips - if Button::image(self.imgs.button) + /*if Button::image(self.imgs.button) .w_h(120.0, 50.0) .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) - .label(&self.voxygen_i18n.get("hud.show_tips")) + .label(&self.voxygen_i18n.get("common.close")) .label_font_size(self.fonts.cyri.scale(20)) .label_font_id(self.fonts.cyri.conrod_id) .label_color(TEXT_COLOR) @@ -1570,7 +1597,7 @@ impl Hud { self.show.intro = false; self.intro = false; self.intro_2 = true; - }; + };*/ // X-button if Button::image(self.imgs.close_button) .w_h(40.0, 40.0) @@ -1631,6 +1658,7 @@ impl Hud { &self.fonts, &self.rot_imgs, tooltip_manager, + &mut self.slot_manager, self.pulse, &self.voxygen_i18n, &player_stats, @@ -1638,7 +1666,6 @@ impl Hud { ) .set(self.ids.bag, ui_widgets) { - Some(bag::Event::HudEvent(event)) => events.push(event), Some(bag::Event::Stats) => self.show.stats = !self.show.stats, Some(bag::Event::Close) => { self.show.bag(false); @@ -1657,23 +1684,39 @@ impl Hud { let energies = ecs.read_storage::(); let character_states = ecs.read_storage::(); let controllers = ecs.read_storage::(); - if let (Some(stats), Some(loadout), Some(energy), Some(character_state), Some(controller)) = ( + let inventories = ecs.read_storage::(); + if let ( + Some(stats), + Some(loadout), + Some(energy), + Some(character_state), + Some(controller), + Some(inventory), + ) = ( stats.get(entity), loadouts.get(entity), energies.get(entity), character_states.get(entity), controllers.get(entity).map(|c| &c.inputs), + inventories.get(entity), ) { Skillbar::new( global_state, &self.imgs, + &self.item_imgs, &self.fonts, + &self.rot_imgs, &stats, &loadout, &energy, &character_state, self.pulse, &controller, + &inventory, + &self.hotbar, + tooltip_manager, + &mut self.slot_manager, + &self.voxygen_i18n, ) .set(self.ids.skillbar, ui_widgets); } @@ -1753,6 +1796,9 @@ impl Hud { settings_window::Event::ToggleMouseYInvert(mouse_y_inverted) => { events.push(Event::ToggleMouseYInvert(mouse_y_inverted)); }, + settings_window::Event::ToggleSmoothPan(smooth_pan_enabled) => { + events.push(Event::ToggleSmoothPan(smooth_pan_enabled)); + }, settings_window::Event::AdjustViewDistance(view_distance) => { events.push(Event::AdjustViewDistance(view_distance)); }, @@ -1816,6 +1862,9 @@ impl Hud { settings_window::Event::AdjustWindowSize(new_size) => { events.push(Event::AdjustWindowSize(new_size)); }, + settings_window::Event::ChangeBinding(game_input) => { + events.push(Event::ChangeBinding(game_input)); + }, settings_window::Event::ChangeFreeLookBehavior(behavior) => { events.push(Event::ChangeFreeLookBehavior(behavior)); }, @@ -1937,6 +1986,54 @@ impl Hud { .set(self.ids.free_look_txt, ui_widgets); } + // Maintain slot manager + for event in self.slot_manager.maintain(ui_widgets) { + use comp::slot::Slot; + use slots::SlotKind::*; + let to_slot = |slot_kind| match slot_kind { + Inventory(i) => Some(Slot::Inventory(i.0)), + Equip(e) => Some(Slot::Equip(e)), + Hotbar(_) => None, + }; + match event { + slot::Event::Dragged(a, b) => { + // Swap between slots + if let (Some(a), Some(b)) = (to_slot(a), to_slot(b)) { + events.push(Event::SwapSlots(a, b)); + } else if let (Inventory(i), Hotbar(h)) = (a, b) { + self.hotbar.add_inventory_link(h, i.0); + } else if let (Hotbar(a), Hotbar(b)) = (a, b) { + self.hotbar.swap(a, b); + } + }, + slot::Event::Dropped(from) => { + // Drop item + if let Some(from) = to_slot(from) { + events.push(Event::DropSlot(from)); + } else if let Hotbar(h) = from { + self.hotbar.clear_slot(h); + } + }, + slot::Event::Used(from) => { + // Item used (selected and then clicked again) + if let Some(from) = to_slot(from) { + events.push(Event::UseSlot(from)); + } else if let Hotbar(h) = from { + self.hotbar.get(h).map(|s| { + match s { + hotbar::SlotContents::Inventory(i) => { + events.push(Event::UseSlot(comp::slot::Slot::Inventory(i))); + }, + hotbar::SlotContents::Ability3 => {}, /* Event::Ability3(true), + * sticks */ + } + }); + } + }, + } + } + self.hotbar.maintain_ability3(client); + events } @@ -1968,6 +2065,30 @@ impl Hud { } pub fn handle_event(&mut self, event: WinEvent, global_state: &mut GlobalState) -> bool { + // Helper + fn handle_slot( + slot: hotbar::Slot, + state: bool, + events: &mut Vec, + slot_manager: &mut slots::SlotManager, + hotbar: &mut hotbar::State, + ) { + if let Some(slots::SlotKind::Inventory(i)) = slot_manager.selected() { + hotbar.add_inventory_link(slot, i.0); + slot_manager.idle(); + } else { + let just_pressed = hotbar.process_input(slot, state); + hotbar.get(slot).map(|s| match s { + hotbar::SlotContents::Inventory(i) => { + if just_pressed { + events.push(Event::UseSlot(comp::slot::Slot::Inventory(i))); + } + }, + hotbar::SlotContents::Ability3 => events.push(Event::Ability3(state)), + }); + } + } + let cursor_grabbed = global_state.window.is_cursor_grabbed(); let handled = match event { WinEvent::Ui(event) => { @@ -2008,46 +2129,147 @@ impl Hud { }, // Press key while not typing - WinEvent::InputUpdate(key, true) if !self.typing() => match key { - GameInput::Command => { + WinEvent::InputUpdate(key, state) if !self.typing() => match key { + GameInput::Command if state => { self.force_chat_input = Some("/".to_owned()); self.force_chat_cursor = Some(Index { line: 0, char: 1 }); self.ui.focus_widget(Some(self.ids.chat)); true }, - GameInput::Map => { + GameInput::Map if state => { self.show.toggle_map(); true }, - GameInput::Bag => { + GameInput::Bag if state => { self.show.toggle_bag(); true }, - GameInput::Social => { + GameInput::Social if state => { self.show.toggle_social(); true }, - GameInput::Spellbook => { + GameInput::Spellbook if state => { self.show.toggle_spell(); true }, - GameInput::Settings => { + GameInput::Settings if state => { self.show.toggle_settings(); true }, - GameInput::Help => { + GameInput::Help if state => { self.show.toggle_help(); true }, - GameInput::ToggleDebug => { + GameInput::ToggleDebug if state => { global_state.settings.gameplay.toggle_debug = !global_state.settings.gameplay.toggle_debug; true }, - GameInput::ToggleIngameUi => { + GameInput::ToggleIngameUi if state => { self.show.ingame = !self.show.ingame; true }, + // Skillbar + GameInput::Slot1 => { + handle_slot( + hotbar::Slot::One, + state, + &mut self.events, + &mut self.slot_manager, + &mut self.hotbar, + ); + true + }, + GameInput::Slot2 => { + handle_slot( + hotbar::Slot::Two, + state, + &mut self.events, + &mut self.slot_manager, + &mut self.hotbar, + ); + true + }, + GameInput::Slot3 => { + handle_slot( + hotbar::Slot::Three, + state, + &mut self.events, + &mut self.slot_manager, + &mut self.hotbar, + ); + true + }, + GameInput::Slot4 => { + handle_slot( + hotbar::Slot::Four, + state, + &mut self.events, + &mut self.slot_manager, + &mut self.hotbar, + ); + true + }, + GameInput::Slot5 => { + handle_slot( + hotbar::Slot::Five, + state, + &mut self.events, + &mut self.slot_manager, + &mut self.hotbar, + ); + true + }, + GameInput::Slot6 => { + handle_slot( + hotbar::Slot::Six, + state, + &mut self.events, + &mut self.slot_manager, + &mut self.hotbar, + ); + true + }, + GameInput::Slot7 => { + handle_slot( + hotbar::Slot::Seven, + state, + &mut self.events, + &mut self.slot_manager, + &mut self.hotbar, + ); + true + }, + GameInput::Slot8 => { + handle_slot( + hotbar::Slot::Eight, + state, + &mut self.events, + &mut self.slot_manager, + &mut self.hotbar, + ); + true + }, + GameInput::Slot9 => { + handle_slot( + hotbar::Slot::Nine, + state, + &mut self.events, + &mut self.slot_manager, + &mut self.hotbar, + ); + true + }, + GameInput::Slot10 => { + handle_slot( + hotbar::Slot::Ten, + state, + &mut self.events, + &mut self.slot_manager, + &mut self.hotbar, + ); + true + }, _ => false, }, // Else the player is typing in chat diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs index aa8f9869e0..688523c21f 100644 --- a/voxygen/src/hud/settings_window.rs +++ b/voxygen/src/hud/settings_window.rs @@ -6,12 +6,15 @@ use crate::{ i18n::{list_localizations, LanguageMetadata, VoxygenLocalization}, render::{AaMode, CloudMode, FluidMode}, ui::{fonts::ConrodVoxygenFonts, ImageSlider, ScaleMode, ToggleButton}, + window::GameInput, GlobalState, }; use conrod_core::{ color, + position::Relative, widget::{self, Button, DropDownList, Image, Rectangle, Scrollbar, Text}, - widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, + widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget, + WidgetCommon, }; const FPS_CHOICES: [u32; 11] = [15, 30, 40, 50, 60, 90, 120, 144, 240, 300, 500]; @@ -27,8 +30,9 @@ widget_ids! { settings_r, settings_l, settings_scrollbar, - controls_text, - controls_controls, + controls_texts[], + controls_buttons[], + controls_alignment_rectangle, button_help, button_help2, show_help_label, @@ -62,6 +66,8 @@ widget_ids! { mouse_zoom_invert_label, mouse_y_invert_button, mouse_y_invert_label, + smooth_pan_toggle_button, + smooth_pan_toggle_label, ch_title, ch_transp_slider, ch_transp_label, @@ -204,6 +210,7 @@ pub enum Event { AdjustMouseZoom(u32), ToggleZoomInvert(bool), ToggleMouseYInvert(bool), + ToggleSmoothPan(bool), AdjustViewDistance(u32), AdjustFOV(u16), AdjustLodDetail(u32), @@ -225,6 +232,7 @@ pub enum Event { SctPlayerBatch(bool), SctDamageBatch(bool), ChangeLanguage(LanguageMetadata), + ChangeBinding(GameInput), ChangeFreeLookBehavior(PressBehavior), } @@ -1238,7 +1246,7 @@ impl<'a> Widget for SettingsWindow<'a> { self.imgs.checkbox_checked, ) .w_h(18.0, 18.0) - .right_from(state.ids.mouse_zoom_invert_button, 250.0) + .right_from(state.ids.mouse_zoom_invert_button, 175.0) .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo) .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked) .set(state.ids.mouse_y_invert_button, ui); @@ -1261,6 +1269,36 @@ impl<'a> Widget for SettingsWindow<'a> { .color(TEXT_COLOR) .set(state.ids.mouse_y_invert_label, ui); + // Mouse Smoothing Toggle + let smooth_pan_enabled = ToggleButton::new( + self.global_state.settings.gameplay.smooth_pan_enable, + self.imgs.checkbox, + self.imgs.checkbox_checked, + ) + .w_h(18.0, 18.0) + .right_from(state.ids.mouse_y_invert_button, 175.0) + .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo) + .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked) + .set(state.ids.smooth_pan_toggle_button, ui); + + if self.global_state.settings.gameplay.smooth_pan_enable != smooth_pan_enabled { + events.push(Event::ToggleSmoothPan( + !self.global_state.settings.gameplay.smooth_pan_enable, + )); + } + + Text::new( + &self + .localized_strings + .get("hud.settings.enable_mouse_smoothing"), + ) + .right_from(state.ids.smooth_pan_toggle_button, 10.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .graphics_for(state.ids.smooth_pan_toggle_button) + .color(TEXT_COLOR) + .set(state.ids.smooth_pan_toggle_label, ui); + // Free look behaviour Text::new( &self @@ -1332,142 +1370,86 @@ impl<'a> Widget for SettingsWindow<'a> { // Contents if let SettingsTab::Controls = self.show.settings_tab { let controls = &self.global_state.settings.controls; - Text::new(&self.localized_strings.get("hud.settings.control_names")) - .color(TEXT_COLOR) - .top_left_with_margins_on(state.ids.settings_content, 5.0, 5.0) - .font_id(self.fonts.cyri.conrod_id) - .font_size(self.fonts.cyri.scale(18)) - .set(state.ids.controls_text, ui); - // TODO: Replace with buttons that show actual keybinds and allow the user to - // change them. - #[rustfmt::skip] - Text::new(&format!( - "{}\n\ - {}\n\ - {}\n\ - {}\n\ - {}\n\ - {}\n\ - {}\n\ - \n\ - \n\ - {}\n\ - {}\n\ - {}\n\ - {}\n\ - \n\ - {}\n\ - \n\ - {}\n\ - \n\ - {}\n\ - \n\ - {}\n\ - \n\ - {}\n\ - \n\ - {}\n\ - \n\ - {}\n\ - \n\ - {}\n\ - \n\ - {}\n\ - \n\ - {}\n\ - \n\ - {}\n\ - \n\ - {}\n\ - \n\ - \n\ - {}\n\ - {}\n\ - \n\ - \n\ - {}\n\ - {}\n\ - {}\n\ - {}\n\ - {}\n\ - {}\n\ - {}\n\ - {}\n\ - {}\n\ - {}\n\ - \n\ - \n\ - {}\n\ - {}\n\ - {}\n\ - {}\n\ - {}\n\ - {}\n\ - \n\ - \n\ - \n\ - {}\n\ - {}\n\ - \n\ - \n\ - {}\n\ - \n\ - \n\ - \n\ - \n\ - ", - controls.toggle_cursor, - controls.help, - controls.toggle_interface, - controls.toggle_debug, - controls.screenshot, - controls.toggle_ingame_ui, - controls.fullscreen, - controls.move_forward, - controls.move_left, - controls.move_back, - controls.move_right, - controls.jump, - controls.glide, - "??", // Dodge - controls.roll, - controls.climb, - controls.climb_down, - "??", // Auto Walk - controls.toggle_wield, - "??", // Put on/Remove Helmet - controls.sit, - controls.mount, - controls.interact, - controls.primary, - controls.secondary, - "1", // Skillbar Slot 1 - "2", // Skillbar Slot 2 - "3", // Skillbar Slot 3 - "4", // Skillbar Slot 4 - "5", // Skillbar Slot 5 - "6", // Skillbar Slot 6 - "7", // Skillbar Slot 7 - "8", // Skillbar Slot 8 - "9", // Skillbar Slot 9 - "0", // Skillbar Slot 10 - controls.escape, - controls.settings, - controls.social, - controls.map, - controls.spellbook, - //controls.character_window, - //controls.quest_log, - controls.bag, - controls.enter, - "Mouse Wheel", // Scroll chat - controls.free_look - )) - .color(TEXT_COLOR) - .right_from(state.ids.controls_text, 0.0) - .font_id(self.fonts.cyri.conrod_id) - .font_size(self.fonts.cyri.scale(18)) - .set(state.ids.controls_controls, ui); + if controls.keybindings.len() > state.ids.controls_texts.len() + || controls.keybindings.len() > state.ids.controls_buttons.len() + { + state.update(|s| { + s.ids + .controls_texts + .resize(controls.keybindings.len(), &mut ui.widget_id_generator()); + s.ids + .controls_buttons + .resize(controls.keybindings.len(), &mut ui.widget_id_generator()); + }); + } + // Used for sequential placement in a flow-down pattern + let mut previous_text_id = None; + let mut keybindings_vec: Vec<&GameInput> = controls.keybindings.keys().collect(); + keybindings_vec.sort(); + // Loop all existing keybindings and the ids for text and button widgets + for (game_input, (&text_id, &button_id)) in keybindings_vec.into_iter().zip( + state + .ids + .controls_texts + .iter() + .zip(state.ids.controls_buttons.iter()), + ) { + if let Some(key) = controls.get_binding(*game_input) { + let loc_key = self + .localized_strings + .get(game_input.get_localization_key()); + let key_string = match self.global_state.window.remapping_keybindings { + Some(game_input_binding) => { + if *game_input == game_input_binding { + String::from(self.localized_strings.get("hud.settings.awaitingkey")) + } else { + key.to_string() + } + }, + None => key.to_string(), + }; + + let text_widget = Text::new(loc_key) + .color(TEXT_COLOR) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(18)); + let button_widget = Button::new() + .label(&key_string) + .label_color(TEXT_COLOR) + .label_font_id(self.fonts.cyri.conrod_id) + .label_font_size(self.fonts.cyri.scale(15)) + .w(150.0) + .rgba(0.0, 0.0, 0.0, 0.0) + .border_rgba(0.0, 0.0, 0.0, 255.0) + .label_y(Relative::Scalar(3.0)); + // Place top-left if it's the first text, else under the previous one + let text_widget = match previous_text_id { + None => text_widget.top_left_with_margins_on( + state.ids.settings_content, + 10.0, + 5.0, + ), + Some(prev_id) => text_widget.down_from(prev_id, 10.0), + }; + let text_width = text_widget.get_w(ui).unwrap_or(0.0); + text_widget.set(text_id, ui); + if button_widget + .right_from(text_id, 350.0 - text_width) + .set(button_id, ui) + .was_clicked() + { + events.push(Event::ChangeBinding(*game_input)); + } + // Set the previous id to the current one for the next cycle + previous_text_id = Some(text_id); + } + } + // Add an empty text widget to simulate some bottom margin, because conrod sucks + if let Some(prev_id) = previous_text_id { + Rectangle::fill_with([1.0, 1.0], color::TRANSPARENT) + .down_from(prev_id, 10.0) + .set(state.ids.controls_alignment_rectangle, ui); + } } // 4) Video Tab ----------------------------------- diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index 2c1f3136dd..c3a9ee7386 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -1,33 +1,34 @@ use super::{ - img_ids::Imgs, BarNumbers, ShortcutNumbers, XpBar, BLACK, CRITICAL_HP_COLOR, HP_COLOR, - LOW_HP_COLOR, MANA_COLOR, TEXT_COLOR, XP_COLOR, + hotbar, + img_ids::{Imgs, ImgsRot}, + item_imgs::ItemImgs, + slots, BarNumbers, ShortcutNumbers, XpBar, BLACK, CRITICAL_HP_COLOR, HP_COLOR, LOW_HP_COLOR, + MANA_COLOR, TEXT_COLOR, XP_COLOR, }; use crate::{ - i18n::{i18n_asset_key, VoxygenLocalization}, - ui::fonts::ConrodVoxygenFonts, + i18n::VoxygenLocalization, + ui::{ + fonts::ConrodVoxygenFonts, + slot::{ContentSize, SlotMaker}, + ImageFrame, Tooltip, TooltipManager, Tooltipable, + }, + window::GameInput, GlobalState, }; -use common::{ - assets::load_expect, - comp::{ - item::{ - tool::{DebugKind, StaffKind, Tool, ToolKind}, - ItemKind, - }, - CharacterState, ControllerInputs, Energy, Loadout, Stats, +use common::comp::{ + item::{ + tool::{DebugKind, StaffKind, Tool, ToolKind}, + ItemKind, }, + CharacterState, ControllerInputs, Energy, Inventory, Loadout, Stats, }; use conrod_core::{ color, widget::{self, Button, Image, Rectangle, Text}, widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, }; - use std::time::{Duration, Instant}; -/* -use const_tweaker::tweak; -#[tweak(min = 0.0, max = 1.0, step = 0.01)] -const RGB: f32 = 0.1;*/ +use vek::*; widget_ids! { struct Ids { @@ -50,45 +51,46 @@ widget_ids! { m1_slot, m1_slot_bg, m1_text, + m1_text_bg, m1_slot_act, m1_content, m2_slot, m2_slot_bg, m2_text, + m2_text_bg, m2_slot_act, m2_content, slot1, - slot1_bg, slot1_text, - slot1_icon, - slot1_act, + slot1_text_bg, + //slot1_act, slot2, - slot2_bg, slot2_text, + slot2_text_bg, slot3, - slot3_bg, slot3_text, + slot3_text_bg, slot4, - slot4_bg, slot4_text, + slot4_text_bg, slot5, - slot5_bg, slot5_text, + slot5_text_bg, slot6, - slot6_bg, slot6_text, + slot6_text_bg, slot7, - slot7_bg, slot7_text, + slot7_text_bg, slot8, - slot8_bg, slot8_text, + slot8_text_bg, slot9, - slot9_bg, slot9_text, - slotq, - slotq_bg, - slotq_text, + slot9_text_bg, + slot10, + slot10_text, + slot10_text_bg, healthbar_bg, healthbar_filling, health_text, @@ -102,7 +104,6 @@ widget_ids! { level_align, level_message, level_message_bg, - stamina_wheel, death_bg, hurt_bg, } @@ -117,12 +118,19 @@ pub enum ResourceType { pub struct Skillbar<'a> { global_state: &'a GlobalState, imgs: &'a Imgs, + item_imgs: &'a ItemImgs, fonts: &'a ConrodVoxygenFonts, + rot_imgs: &'a ImgsRot, stats: &'a Stats, loadout: &'a Loadout, energy: &'a Energy, character_state: &'a CharacterState, controller: &'a ControllerInputs, + inventory: &'a Inventory, + hotbar: &'a hotbar::State, + tooltip_manager: &'a mut TooltipManager, + slot_manager: &'a mut slots::SlotManager, + localized_strings: &'a std::sync::Arc, pulse: f32, #[conrod(common_builder)] common: widget::CommonBuilder, @@ -133,18 +141,27 @@ impl<'a> Skillbar<'a> { pub fn new( global_state: &'a GlobalState, imgs: &'a Imgs, + item_imgs: &'a ItemImgs, fonts: &'a ConrodVoxygenFonts, + rot_imgs: &'a ImgsRot, stats: &'a Stats, loadout: &'a Loadout, energy: &'a Energy, character_state: &'a CharacterState, pulse: f32, controller: &'a ControllerInputs, + inventory: &'a Inventory, + hotbar: &'a hotbar::State, + tooltip_manager: &'a mut TooltipManager, + slot_manager: &'a mut slots::SlotManager, + localized_strings: &'a std::sync::Arc, ) -> Self { Self { global_state, imgs, + item_imgs, fonts, + rot_imgs, stats, loadout, energy, @@ -153,6 +170,11 @@ impl<'a> Skillbar<'a> { character_state, pulse, controller, + inventory, + hotbar, + tooltip_manager, + slot_manager, + localized_strings, } } } @@ -201,44 +223,11 @@ impl<'a> Widget for Skillbar<'a> { let bar_values = self.global_state.settings.gameplay.bar_numbers; let shortcuts = self.global_state.settings.gameplay.shortcut_numbers; - const BG_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 0.8); const BG_COLOR_2: Color = Color::Rgba(0.0, 0.0, 0.0, 0.99); let hp_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8; //Animation timer let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani); - let localized_strings = load_expect::(&i18n_asset_key( - &self.global_state.settings.language.selected_language, - )); - - // Stamina Wheel - /* - let stamina_percentage = - self.stats.health.current() as f64 / self.stats.health.maximum() as f64 * 100.0; - if stamina_percentage < 100.0 { - Image::new(if stamina_percentage <= 0.1 { - self.imgs.stamina_0 - } else if stamina_percentage < 12.5 { - self.imgs.stamina_1 - } else if stamina_percentage < 25.0 { - self.imgs.stamina_2 - } else if stamina_percentage < 37.5 { - self.imgs.stamina_3 - } else if stamina_percentage < 50.0 { - self.imgs.stamina_4 - } else if stamina_percentage < 62.5 { - self.imgs.stamina_5 - } else if stamina_percentage < 75.0 { - self.imgs.stamina_6 - } else if stamina_percentage < 87.5 { - self.imgs.stamina_7 - } else { - self.imgs.stamina_8 - }) - .w_h(37.0 * 3.0, 37.0 * 3.0) - .mid_bottom_with_margin_on(ui.window, 150.0) - .set(state.ids.stamina_wheel, ui); - } - */ + let localized_strings = self.localized_strings; // Level Up Message @@ -300,36 +289,45 @@ impl<'a> Widget for Skillbar<'a> { .set(state.ids.level_down, ui); // Death message if self.stats.is_dead { - Text::new(&localized_strings.get("hud.you_died")) - .middle_of(ui.window) - .font_size(self.fonts.cyri.scale(50)) + if let Some(key) = self + .global_state + .settings + .controls + .get_binding(GameInput::Respawn) + { + Text::new(&localized_strings.get("hud.you_died")) + .middle_of(ui.window) + .font_size(self.fonts.cyri.scale(50)) + .font_id(self.fonts.cyri.conrod_id) + .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) + .set(state.ids.death_message_1_bg, ui); + Text::new( + &localized_strings + .get("hud.press_key_to_respawn") + .replace("{key}", key.to_string().as_str()), + ) + .mid_bottom_with_margin_on(state.ids.death_message_1_bg, -120.0) + .font_size(self.fonts.cyri.scale(30)) .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) - .set(state.ids.death_message_1_bg, ui); - Text::new(&localized_strings.get("hud.press_key_to_respawn").replace( - "{key}", - &format!("{:?}", self.global_state.settings.controls.respawn), - )) - .mid_bottom_with_margin_on(state.ids.death_message_1_bg, -120.0) - .font_size(self.fonts.cyri.scale(30)) - .font_id(self.fonts.cyri.conrod_id) - .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) - .set(state.ids.death_message_2_bg, ui); - Text::new(&localized_strings.get("hud.you_died")) - .bottom_left_with_margins_on(state.ids.death_message_1_bg, 2.0, 2.0) - .font_size(self.fonts.cyri.scale(50)) + .set(state.ids.death_message_2_bg, ui); + Text::new(&localized_strings.get("hud.you_died")) + .bottom_left_with_margins_on(state.ids.death_message_1_bg, 2.0, 2.0) + .font_size(self.fonts.cyri.scale(50)) + .font_id(self.fonts.cyri.conrod_id) + .color(CRITICAL_HP_COLOR) + .set(state.ids.death_message_1, ui); + Text::new( + &localized_strings + .get("hud.press_key_to_respawn") + .replace("{key}", key.to_string().as_str()), + ) + .bottom_left_with_margins_on(state.ids.death_message_2_bg, 2.0, 2.0) + .font_size(self.fonts.cyri.scale(30)) .font_id(self.fonts.cyri.conrod_id) .color(CRITICAL_HP_COLOR) - .set(state.ids.death_message_1, ui); - Text::new(&localized_strings.get("hud.press_key_to_respawn").replace( - "{key}", - &format!("{:?}", self.global_state.settings.controls.respawn), - )) - .bottom_left_with_margins_on(state.ids.death_message_2_bg, 2.0, 2.0) - .font_size(self.fonts.cyri.scale(30)) - .font_id(self.fonts.cyri.conrod_id) - .color(CRITICAL_HP_COLOR) - .set(state.ids.death_message_2, ui); + .set(state.ids.death_message_2, ui); + } } // Experience-Bar match self.global_state.settings.gameplay.xp_bar { @@ -641,27 +639,6 @@ impl<'a> Widget for Skillbar<'a> { .set(state.ids.m1_content, ui); // M2 Slot match self.character_state { - /* - CharacterState::BasicBlock { .. } => { - let fade_pulse = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.6; //Animation timer; - if self.controller.secondary.is_pressed() { - Image::new(self.imgs.skillbar_slot_big) - .w_h(40.0 * scale, 40.0 * scale) - .right_from(state.ids.m1_slot, 0.0) - .set(state.ids.m2_slot, ui); - Image::new(self.imgs.skillbar_slot_big_act) - .w_h(40.0 * scale, 40.0 * scale) - .middle_of(state.ids.m2_slot) - .color(Some(Color::Rgba(1.0, 1.0, 1.0, fade_pulse))) - .floating(true) - .set(state.ids.m2_slot_act, ui); - } else { - Image::new(self.imgs.skillbar_slot_big) - .w_h(40.0 * scale, 40.0 * scale) - .right_from(state.ids.m1_slot, 0.0) - .set(state.ids.m2_slot, ui); - } - },*/ CharacterState::BasicMelee { .. } => { let fade_pulse = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.6; //Animation timer; if self.controller.secondary.is_pressed() { @@ -761,232 +738,412 @@ impl<'a> Widget for Skillbar<'a> { }, ) .set(state.ids.m2_content, ui); - //Slot 5 - Image::new(self.imgs.skillbar_slot) - .w_h(20.0 * scale, 20.0 * scale) - .bottom_left_with_margins_on(state.ids.m1_slot, 0.0, -20.0 * scale) - .set(state.ids.slot5, ui); - Image::new(self.imgs.skillbar_slot_bg) - .w_h(19.0 * scale, 19.0 * scale) - .color(Some(BG_COLOR)) - .middle_of(state.ids.slot5) - .set(state.ids.slot5_bg, ui); - // Slot 4 - Image::new(self.imgs.skillbar_slot) - .w_h(20.0 * scale, 20.0 * scale) - .left_from(state.ids.slot5, 0.0) - .set(state.ids.slot4, ui); - Image::new(self.imgs.skillbar_slot_bg) - .w_h(19.0 * scale, 19.0 * scale) - .color(Some(BG_COLOR)) - .middle_of(state.ids.slot4) - .set(state.ids.slot4_bg, ui); - // Slot 3 - Image::new(self.imgs.skillbar_slot) - .w_h(20.0 * scale, 20.0 * scale) - .left_from(state.ids.slot4, 0.0) - .set(state.ids.slot3, ui); - Image::new(self.imgs.skillbar_slot_bg) - .w_h(19.0 * scale, 19.0 * scale) - .color(Some(BG_COLOR)) - .middle_of(state.ids.slot3) - .set(state.ids.slot3_bg, ui); - // Slot 2 - Image::new(self.imgs.skillbar_slot) - .w_h(20.0 * scale, 20.0 * scale) - .left_from(state.ids.slot3, 0.0) - .set(state.ids.slot2, ui); - Image::new(self.imgs.skillbar_slot_bg) - .w_h(19.0 * scale, 19.0 * scale) - .color(Some(BG_COLOR)) - .middle_of(state.ids.slot2) - .set(state.ids.slot2_bg, ui); - // Slot 1 - // TODO: Don't hardcode this to one Skill... - // Frame flashes whenever the active skill inside this slot is activated - match self.character_state { - /* - CharacterState::Charge { time_left } => { - let fade = time_left.as_secs_f32() * 10.0; - Image::new(self.imgs.skillbar_slot_l) - .w_h(20.0 * scale, 20.0 * scale) - .left_from(state.ids.slot2, 0.0) - .set(state.ids.slot1, ui); - Image::new(self.imgs.skillbar_slot_l_act) - .w_h(20.0 * scale, 20.0 * scale) - .middle_of(state.ids.slot1) - .color(Some(Color::Rgba( - 1.0, - 1.0, - 1.0, - if fade > 0.6 { 0.6 } else { fade }, - ))) - .floating(true) - .set(state.ids.slot1_act, ui); - },*/ - _ => { - Image::new(self.imgs.skillbar_slot_l) - .w_h(20.0 * scale, 20.0 * scale) - .left_from(state.ids.slot2, 0.0) - .set(state.ids.slot1, ui); + // Slots + let content_source = (self.hotbar, self.inventory, self.loadout, self.energy); // TODO: avoid this + let image_source = (self.item_imgs, self.imgs); + + let mut slot_maker = SlotMaker { + // TODO: is a separate image needed for the frame? + empty_slot: self.imgs.skillbar_slot, + filled_slot: self.imgs.skillbar_slot, + selected_slot: self.imgs.skillbar_slot_act, + background_color: None, + content_size: ContentSize { + width_height_ratio: 1.0, + max_fraction: 0.8, /* Changes the item image size by setting a maximum fraction + * of either the width or height */ }, - } - Image::new(self.imgs.skillbar_slot_bg) - .w_h(19.5 * scale, 19.5 * scale) - .color( - match self.loadout.active_item.as_ref().map(|i| &i.item.kind) { - Some(ItemKind::Tool(Tool { kind, .. })) => match kind { - ToolKind::Staff(StaffKind::BasicStaff) => Some(BLACK), - _ => Some(BG_COLOR), - }, - _ => Some(BG_COLOR), - }, + selected_content_scale: 1.0, + amount_font: self.fonts.cyri.conrod_id, + amount_margins: Vec2::new(1.0, 1.0), + amount_font_size: self.fonts.cyri.scale(12), + amount_text_color: TEXT_COLOR, + content_source: &content_source, + image_source: &image_source, + slot_manager: Some(self.slot_manager), + }; + let item_tooltip = Tooltip::new({ + // Edge images [t, b, r, l] + // Corner images [tr, tl, br, bl] + let edge = &self.rot_imgs.tt_side; + let corner = &self.rot_imgs.tt_corner; + ImageFrame::new( + [edge.cw180, edge.none, edge.cw270, edge.cw90], + [corner.none, corner.cw270, corner.cw90, corner.cw180], + Color::Rgba(0.08, 0.07, 0.04, 1.0), + 5.0, ) - .middle_of(state.ids.slot1) - .set(state.ids.slot1_bg, ui); - // TODO: Changeable slot image - match self.loadout.active_item.as_ref().map(|i| &i.item.kind) { - Some(ItemKind::Tool(Tool { kind, .. })) => match kind { - ToolKind::Staff(StaffKind::BasicStaff) => { - Image::new(self.imgs.fire_spell_1) - .w_h(18.0 * scale, 18.0 * scale) - .color(if self.energy.current() as f64 >= 500.0 { - Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)) - } else { - Some(Color::Rgba(0.3, 0.3, 0.3, 0.8)) - }) - .middle_of(state.ids.slot1_bg) - .set(state.ids.slot1_icon, ui); - }, - _ => {}, - }, - _ => {}, + }) + .title_font_size(self.fonts.cyri.scale(15)) + .parent(ui.window) + .desc_font_size(self.fonts.cyri.scale(12)) + .title_text_color(TEXT_COLOR) + .font_id(self.fonts.cyri.conrod_id) + .desc_text_color(TEXT_COLOR); + // Helper + let tooltip_text = |slot| { + content_source + .0 + .get(slot) + .and_then(|content| match content { + hotbar::SlotContents::Inventory(i) => content_source + .1 + .get(i) + .map(|item| (item.name(), item.description())), + hotbar::SlotContents::Ability3 => Some(("Something something fireball", "")), + }) + }; + const SLOT_TOOLTIP_UPSHIFT: f64 = 70.0; + //Slot 5 + let slot = slot_maker + .fabricate(hotbar::Slot::Five, [20.0 * scale as f32; 2]) + .bottom_left_with_margins_on(state.ids.m1_slot, 0.0, -20.0 * scale); + if let Some((title, desc)) = tooltip_text(hotbar::Slot::Five) { + slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .bottom_offset(SLOT_TOOLTIP_UPSHIFT) + .set(state.ids.slot5, ui); + } else { + slot.set(state.ids.slot5, ui); + } + // Slot 4 + let slot = slot_maker + .fabricate(hotbar::Slot::Four, [20.0 * scale as f32; 2]) + .left_from(state.ids.slot5, 0.0); + if let Some((title, desc)) = tooltip_text(hotbar::Slot::Four) { + slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .bottom_offset(SLOT_TOOLTIP_UPSHIFT) + .set(state.ids.slot4, ui); + } else { + slot.set(state.ids.slot4, ui); + } + // Slot 3 + let slot = slot_maker + .fabricate(hotbar::Slot::Three, [20.0 * scale as f32; 2]) + .left_from(state.ids.slot4, 0.0); + if let Some((title, desc)) = tooltip_text(hotbar::Slot::Three) { + slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .bottom_offset(SLOT_TOOLTIP_UPSHIFT) + .set(state.ids.slot3, ui); + } else { + slot.set(state.ids.slot3, ui); + } + // Slot 2 + let slot = slot_maker + .fabricate(hotbar::Slot::Two, [20.0 * scale as f32; 2]) + .left_from(state.ids.slot3, 0.0); + if let Some((title, desc)) = tooltip_text(hotbar::Slot::Two) { + slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .bottom_offset(SLOT_TOOLTIP_UPSHIFT) + .set(state.ids.slot2, ui); + } else { + slot.set(state.ids.slot2, ui); + } + // Slot 1 + slot_maker.empty_slot = self.imgs.skillbar_slot_l; + slot_maker.filled_slot = self.imgs.skillbar_slot_l; + slot_maker.selected_slot = self.imgs.skillbar_slot_l_act; + let slot = slot_maker + .fabricate(hotbar::Slot::One, [20.0 * scale as f32; 2]) + .left_from(state.ids.slot2, 0.0); + if let Some((title, desc)) = tooltip_text(hotbar::Slot::One) { + slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .bottom_offset(SLOT_TOOLTIP_UPSHIFT) + .set(state.ids.slot1, ui); + } else { + slot.set(state.ids.slot1, ui); } // Slot 6 - Image::new(self.imgs.skillbar_slot) - .w_h(20.0 * scale, 20.0 * scale) - .bottom_right_with_margins_on(state.ids.m2_slot, 0.0, -20.0 * scale) - .set(state.ids.slot6, ui); - Image::new(self.imgs.skillbar_slot_bg) - .w_h(19.0 * scale, 19.0 * scale) - .color(Some(BG_COLOR)) - .middle_of(state.ids.slot6) - .set(state.ids.slot6_bg, ui); + slot_maker.empty_slot = self.imgs.skillbar_slot; + slot_maker.filled_slot = self.imgs.skillbar_slot; + slot_maker.selected_slot = self.imgs.skillbar_slot_act; + let slot = slot_maker + .fabricate(hotbar::Slot::Six, [20.0 * scale as f32; 2]) + .bottom_right_with_margins_on(state.ids.m2_slot, 0.0, -20.0 * scale); + if let Some((title, desc)) = tooltip_text(hotbar::Slot::Six) { + slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .bottom_offset(SLOT_TOOLTIP_UPSHIFT) + .set(state.ids.slot6, ui); + } else { + slot.set(state.ids.slot6, ui); + } // Slot 7 - Image::new(self.imgs.skillbar_slot) - .w_h(20.0 * scale, 20.0 * scale) - .right_from(state.ids.slot6, 0.0) - .set(state.ids.slot7, ui); - Image::new(self.imgs.skillbar_slot_bg) - .w_h(19.0 * scale, 19.0 * scale) - .color(Some(BG_COLOR)) - .middle_of(state.ids.slot7) - .set(state.ids.slot7_bg, ui); + let slot = slot_maker + .fabricate(hotbar::Slot::Seven, [20.0 * scale as f32; 2]) + .right_from(state.ids.slot6, 0.0); + if let Some((title, desc)) = tooltip_text(hotbar::Slot::Seven) { + slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .bottom_offset(SLOT_TOOLTIP_UPSHIFT) + .set(state.ids.slot7, ui); + } else { + slot.set(state.ids.slot7, ui); + } // Slot 8 - Image::new(self.imgs.skillbar_slot) - .w_h(20.0 * scale, 20.0 * scale) - .right_from(state.ids.slot7, 0.0) - .set(state.ids.slot8, ui); - Image::new(self.imgs.skillbar_slot_bg) - .w_h(19.0 * scale, 19.0 * scale) - .color(Some(BG_COLOR)) - .middle_of(state.ids.slot8) - .set(state.ids.slot8_bg, ui); + let slot = slot_maker + .fabricate(hotbar::Slot::Eight, [20.0 * scale as f32; 2]) + .right_from(state.ids.slot7, 0.0); + if let Some((title, desc)) = tooltip_text(hotbar::Slot::Eight) { + slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .bottom_offset(SLOT_TOOLTIP_UPSHIFT) + .set(state.ids.slot8, ui); + } else { + slot.set(state.ids.slot8, ui); + } // Slot 9 - Image::new(self.imgs.skillbar_slot) - .w_h(20.0 * scale, 20.0 * scale) - .right_from(state.ids.slot8, 0.0) - .set(state.ids.slot9, ui); - Image::new(self.imgs.skillbar_slot_bg) - .w_h(19.0 * scale, 19.0 * scale) - .color(Some(BG_COLOR)) - .middle_of(state.ids.slot9) - .set(state.ids.slot9_bg, ui); + let slot = slot_maker + .fabricate(hotbar::Slot::Nine, [20.0 * scale as f32; 2]) + .right_from(state.ids.slot8, 0.0); + if let Some((title, desc)) = tooltip_text(hotbar::Slot::Nine) { + slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .bottom_offset(SLOT_TOOLTIP_UPSHIFT) + .set(state.ids.slot9, ui); + } else { + slot.set(state.ids.slot9, ui); + } // Quickslot - Image::new(self.imgs.skillbar_slot_r) - .w_h(20.0 * scale, 20.0 * scale) - .right_from(state.ids.slot9, 0.0) - .set(state.ids.slotq, ui); - Image::new(self.imgs.skillbar_slot_bg) - .w_h(19.0 * scale, 19.0 * scale) - .color(Some(BG_COLOR)) - .middle_of(state.ids.slotq) - .set(state.ids.slotq_bg, ui); - // Shortcuts + slot_maker.empty_slot = self.imgs.skillbar_slot_r; + slot_maker.filled_slot = self.imgs.skillbar_slot_r; + slot_maker.selected_slot = self.imgs.skillbar_slot_r_act; + let slot = slot_maker + .fabricate(hotbar::Slot::Ten, [20.0 * scale as f32; 2]) + .right_from(state.ids.slot9, 0.0); + if let Some((title, desc)) = tooltip_text(hotbar::Slot::Ten) { + slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip) + .bottom_offset(SLOT_TOOLTIP_UPSHIFT) + .set(state.ids.slot10, ui); + } else { + slot.set(state.ids.slot10, ui); + } + // Shortcuts if let ShortcutNumbers::On = shortcuts { - Text::new("1") - .top_right_with_margins_on(state.ids.slot1_bg, 1.0, 1.0) - .font_size(self.fonts.cyri.scale(8)) - .font_id(self.fonts.cyri.conrod_id) - .color(TEXT_COLOR) - .set(state.ids.slot1_text, ui); - Text::new("2") - .top_right_with_margins_on(state.ids.slot2_bg, 1.0, 1.0) - .font_size(self.fonts.cyri.scale(8)) - .font_id(self.fonts.cyri.conrod_id) - .color(TEXT_COLOR) - .set(state.ids.slot2_text, ui); - Text::new("3") - .top_right_with_margins_on(state.ids.slot3_bg, 1.0, 1.0) - .font_size(self.fonts.cyri.scale(8)) - .font_id(self.fonts.cyri.conrod_id) - .color(TEXT_COLOR) - .set(state.ids.slot3_text, ui); - Text::new("4") - .top_right_with_margins_on(state.ids.slot4_bg, 1.0, 1.0) - .font_size(self.fonts.cyri.scale(8)) - .font_id(self.fonts.cyri.conrod_id) - .color(TEXT_COLOR) - .set(state.ids.slot4_text, ui); - Text::new("5") - .top_right_with_margins_on(state.ids.slot5_bg, 1.0, 1.0) - .font_size(self.fonts.cyri.scale(8)) - .font_id(self.fonts.cyri.conrod_id) - .color(TEXT_COLOR) - .set(state.ids.slot5_text, ui); - Text::new("M1") - .top_left_with_margins_on(state.ids.m1_slot, 5.0, 5.0) - .font_size(self.fonts.cyri.scale(8)) - .font_id(self.fonts.cyri.conrod_id) - .color(TEXT_COLOR) - .set(state.ids.m1_text, ui); - Text::new("M2") - .top_right_with_margins_on(state.ids.m2_slot, 5.0, 5.0) - .font_size(self.fonts.cyri.scale(8)) - .font_id(self.fonts.cyri.conrod_id) - .color(TEXT_COLOR) - .set(state.ids.m2_text, ui); - Text::new("6") - .top_left_with_margins_on(state.ids.slot6_bg, 1.0, 1.0) - .font_size(self.fonts.cyri.scale(8)) - .font_id(self.fonts.cyri.conrod_id) - .color(TEXT_COLOR) - .set(state.ids.slot6_text, ui); - Text::new("7") - .top_left_with_margins_on(state.ids.slot7_bg, 1.0, 1.0) - .font_size(self.fonts.cyri.scale(8)) - .font_id(self.fonts.cyri.conrod_id) - .color(TEXT_COLOR) - .set(state.ids.slot7_text, ui); - Text::new("8") - .top_left_with_margins_on(state.ids.slot8_bg, 1.0, 1.0) - .font_size(self.fonts.cyri.scale(8)) - .font_id(self.fonts.cyri.conrod_id) - .color(TEXT_COLOR) - .set(state.ids.slot8_text, ui); - Text::new("9") - .top_left_with_margins_on(state.ids.slot9_bg, 1.0, 1.0) - .font_size(self.fonts.cyri.scale(8)) - .font_id(self.fonts.cyri.conrod_id) - .color(TEXT_COLOR) - .set(state.ids.slot9_text, ui); - Text::new("Q") - .top_left_with_margins_on(state.ids.slotq_bg, 1.0, 1.0) - .font_size(self.fonts.cyri.scale(8)) - .font_id(self.fonts.cyri.conrod_id) - .color(TEXT_COLOR) - .set(state.ids.slotq_text, ui); + if let Some(slot1) = &self + .global_state + .settings + .controls + .get_binding(GameInput::Slot1) + { + Text::new(slot1.to_string().as_str()) + .top_right_with_margins_on(state.ids.slot1, 3.0, 5.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(BLACK) + .set(state.ids.slot1_text_bg, ui); + Text::new(slot1.to_string().as_str()) + .bottom_left_with_margins_on(state.ids.slot1_text_bg, 1.0, 1.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.slot1_text, ui); + } + if let Some(slot2) = &self + .global_state + .settings + .controls + .get_binding(GameInput::Slot2) + { + Text::new(slot2.to_string().as_str()) + .top_right_with_margins_on(state.ids.slot2, 3.0, 5.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(BLACK) + .set(state.ids.slot2_text_bg, ui); + Text::new(slot2.to_string().as_str()) + .bottom_left_with_margins_on(state.ids.slot2_text_bg, 1.0, 1.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.slot2_text, ui); + } + if let Some(slot3) = &self + .global_state + .settings + .controls + .get_binding(GameInput::Slot3) + { + Text::new(slot3.to_string().as_str()) + .top_right_with_margins_on(state.ids.slot3, 3.0, 5.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(BLACK) + .set(state.ids.slot3_text_bg, ui); + Text::new(slot3.to_string().as_str()) + .bottom_left_with_margins_on(state.ids.slot3_text_bg, 1.0, 1.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.slot3_text, ui); + } + if let Some(slot4) = &self + .global_state + .settings + .controls + .get_binding(GameInput::Slot4) + { + Text::new(slot4.to_string().as_str()) + .top_right_with_margins_on(state.ids.slot4, 3.0, 5.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(BLACK) + .set(state.ids.slot4_text_bg, ui); + Text::new(slot4.to_string().as_str()) + .bottom_left_with_margins_on(state.ids.slot4_text_bg, 1.0, 1.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.slot4_text, ui); + } + if let Some(slot5) = &self + .global_state + .settings + .controls + .get_binding(GameInput::Slot5) + { + Text::new(slot5.to_string().as_str()) + .top_right_with_margins_on(state.ids.slot5, 3.0, 5.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(BLACK) + .set(state.ids.slot5_text_bg, ui); + Text::new(slot5.to_string().as_str()) + .bottom_left_with_margins_on(state.ids.slot5_text_bg, 1.0, 1.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.slot5_text, ui); + } + if let Some(m1) = &self + .global_state + .settings + .controls + .get_binding(GameInput::Primary) + { + Text::new(m1.to_string().as_str()) + .top_left_with_margins_on(state.ids.m1_slot, 5.0, 5.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(BLACK) + .set(state.ids.m1_text_bg, ui); + Text::new(m1.to_string().as_str()) + .bottom_right_with_margins_on(state.ids.m1_text_bg, 1.0, 1.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.m1_text, ui); + } + if let Some(m2) = &self + .global_state + .settings + .controls + .get_binding(GameInput::Secondary) + { + Text::new(m2.to_string().as_str()) + .top_right_with_margins_on(state.ids.m2_slot, 5.0, 5.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(BLACK) + .set(state.ids.m2_text_bg, ui); + Text::new(m2.to_string().as_str()) + .bottom_left_with_margins_on(state.ids.m2_text_bg, 1.0, 1.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.m2_text, ui); + } + if let Some(slot6) = &self + .global_state + .settings + .controls + .get_binding(GameInput::Slot6) + { + Text::new(slot6.to_string().as_str()) + .top_right_with_margins_on(state.ids.slot6, 3.0, 5.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(BLACK) + .set(state.ids.slot6_text_bg, ui); + Text::new(slot6.to_string().as_str()) + .bottom_right_with_margins_on(state.ids.slot6_text_bg, 1.0, 1.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.slot6_text, ui); + } + if let Some(slot7) = &self + .global_state + .settings + .controls + .get_binding(GameInput::Slot7) + { + Text::new(slot7.to_string().as_str()) + .top_right_with_margins_on(state.ids.slot7, 3.0, 5.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(BLACK) + .set(state.ids.slot7_text_bg, ui); + Text::new(slot7.to_string().as_str()) + .bottom_right_with_margins_on(state.ids.slot7_text_bg, 1.0, 1.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.slot7_text, ui); + } + if let Some(slot8) = &self + .global_state + .settings + .controls + .get_binding(GameInput::Slot8) + { + Text::new(slot8.to_string().as_str()) + .top_right_with_margins_on(state.ids.slot8, 3.0, 5.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(BLACK) + .set(state.ids.slot8_text_bg, ui); + Text::new(slot8.to_string().as_str()) + .bottom_right_with_margins_on(state.ids.slot8_text_bg, 1.0, 1.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.slot8_text, ui); + } + if let Some(slot9) = &self + .global_state + .settings + .controls + .get_binding(GameInput::Slot9) + { + Text::new(slot9.to_string().as_str()) + .top_right_with_margins_on(state.ids.slot9, 3.0, 5.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(BLACK) + .set(state.ids.slot9_text_bg, ui); + Text::new(slot9.to_string().as_str()) + .bottom_right_with_margins_on(state.ids.slot9_text_bg, 1.0, 1.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.slot9_text, ui); + } + if let Some(slot10) = &self + .global_state + .settings + .controls + .get_binding(GameInput::Slot10) + { + Text::new(slot10.to_string().as_str()) + .top_right_with_margins_on(state.ids.slot10, 3.0, 5.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(BLACK) + .set(state.ids.slot10_text_bg, ui); + Text::new(slot10.to_string().as_str()) + .bottom_right_with_margins_on(state.ids.slot10_text_bg, 1.0, 1.0) + .font_size(self.fonts.cyri.scale(8)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.slot10_text, ui); + } }; // Lifebar diff --git a/voxygen/src/hud/slots.rs b/voxygen/src/hud/slots.rs new file mode 100644 index 0000000000..52797b232e --- /dev/null +++ b/voxygen/src/hud/slots.rs @@ -0,0 +1,159 @@ +use super::{ + hotbar::{self, Slot as HotbarSlot}, + img_ids, + item_imgs::{ItemImgs, ItemKey}, +}; +use crate::ui::slot::{self, SlotKey, SumSlot}; +use common::comp::{item::ItemKind, Energy, Inventory, Loadout}; +use conrod_core::{image, Color}; + +pub use common::comp::slot::{ArmorSlot, EquipSlot}; + +#[derive(Clone, Copy, PartialEq)] +pub enum SlotKind { + Inventory(InventorySlot), + Equip(EquipSlot), + Hotbar(HotbarSlot), + /* Spellbook(SpellbookSlot), TODO */ +} + +pub type SlotManager = slot::SlotManager; + +#[derive(Clone, Copy, PartialEq)] +pub struct InventorySlot(pub usize); + +impl SlotKey for InventorySlot { + type ImageKey = ItemKey; + + fn image_key(&self, source: &Inventory) -> Option<(Self::ImageKey, Option)> { + source.get(self.0).map(|i| (i.into(), None)) + } + + fn amount(&self, source: &Inventory) -> Option { + source + .get(self.0) + .and_then(|item| match item.kind { + ItemKind::Tool { .. } | ItemKind::Lantern(_) | ItemKind::Armor { .. } => None, + ItemKind::Utility { amount, .. } + | ItemKind::Consumable { amount, .. } + | ItemKind::Ingredient { amount, .. } => Some(amount), + }) + .filter(|amount| *amount > 1) + } + + fn image_id(key: &Self::ImageKey, source: &ItemImgs) -> image::Id { + source.img_id_or_not_found_img(key.clone()) + } +} + +impl SlotKey for EquipSlot { + type ImageKey = ItemKey; + + fn image_key(&self, source: &Loadout) -> Option<(Self::ImageKey, Option)> { + let item = match self { + EquipSlot::Armor(ArmorSlot::Shoulders) => source.shoulder.as_ref(), + EquipSlot::Armor(ArmorSlot::Chest) => source.chest.as_ref(), + EquipSlot::Armor(ArmorSlot::Belt) => source.belt.as_ref(), + EquipSlot::Armor(ArmorSlot::Hands) => source.hand.as_ref(), + EquipSlot::Armor(ArmorSlot::Legs) => source.pants.as_ref(), + EquipSlot::Armor(ArmorSlot::Feet) => source.foot.as_ref(), + EquipSlot::Armor(ArmorSlot::Back) => source.back.as_ref(), + EquipSlot::Armor(ArmorSlot::Ring) => source.ring.as_ref(), + EquipSlot::Armor(ArmorSlot::Neck) => source.neck.as_ref(), + EquipSlot::Armor(ArmorSlot::Head) => source.head.as_ref(), + EquipSlot::Armor(ArmorSlot::Tabard) => source.tabard.as_ref(), + EquipSlot::Mainhand => source.active_item.as_ref().map(|i| &i.item), + EquipSlot::Offhand => source.second_item.as_ref().map(|i| &i.item), + EquipSlot::Lantern => source.lantern.as_ref(), + }; + + item.map(|i| (i.into(), None)) + } + + fn amount(&self, _: &Loadout) -> Option { None } + + fn image_id(key: &Self::ImageKey, source: &ItemImgs) -> image::Id { + source.img_id_or_not_found_img(key.clone()) + } +} + +#[derive(Clone, PartialEq)] +pub enum HotbarImage { + Item(ItemKey), + Ability3, +} + +type HotbarSource<'a> = (&'a hotbar::State, &'a Inventory, &'a Loadout, &'a Energy); +type HotbarImageSource<'a> = (&'a ItemImgs, &'a img_ids::Imgs); + +impl<'a> SlotKey, HotbarImageSource<'a>> for HotbarSlot { + type ImageKey = HotbarImage; + + fn image_key( + &self, + (hotbar, inventory, loadout, energy): &HotbarSource<'a>, + ) -> Option<(Self::ImageKey, Option)> { + hotbar.get(*self).and_then(|contents| match contents { + hotbar::SlotContents::Inventory(idx) => inventory + .get(idx) + .map(|item| HotbarImage::Item(item.into())) + .map(|i| (i, None)), + hotbar::SlotContents::Ability3 => loadout + .active_item + .as_ref() + .map(|i| &i.item.kind) + .and_then(|kind| { + use common::comp::item::tool::{StaffKind, Tool, ToolKind}; + matches!( + kind, + ItemKind::Tool(Tool { + kind: ToolKind::Staff(StaffKind::BasicStaff), + .. + }) + ) + .then_some(( + HotbarImage::Ability3, + // Darken if not enough energy to use attack + (energy.current() < 500).then_some(Color::Rgba(0.3, 0.3, 0.3, 0.8)), + )) + }), + }) + } + + fn amount(&self, (hotbar, inventory, _, _): &HotbarSource<'a>) -> Option { + hotbar + .get(*self) + .and_then(|content| match content { + hotbar::SlotContents::Inventory(idx) => inventory.get(idx), + hotbar::SlotContents::Ability3 => None, + }) + .and_then(|item| match item.kind { + ItemKind::Tool { .. } | ItemKind::Lantern(_) | ItemKind::Armor { .. } => None, + ItemKind::Utility { amount, .. } + | ItemKind::Consumable { amount, .. } + | ItemKind::Ingredient { amount, .. } => Some(amount), + }) + .filter(|amount| *amount > 1) + } + + fn image_id(key: &Self::ImageKey, (item_imgs, imgs): &HotbarImageSource<'a>) -> image::Id { + match key { + HotbarImage::Item(key) => item_imgs.img_id_or_not_found_img(key.clone()), + HotbarImage::Ability3 => imgs.fire_spell_1, + } + } +} + +impl From for SlotKind { + fn from(inventory: InventorySlot) -> Self { Self::Inventory(inventory) } +} + +impl From for SlotKind { + fn from(equip: EquipSlot) -> Self { Self::Equip(equip) } +} + +impl From for SlotKind { + fn from(hotbar: HotbarSlot) -> Self { Self::Hotbar(hotbar) } +} + +impl SumSlot for SlotKind {} diff --git a/voxygen/src/lib.rs b/voxygen/src/lib.rs index e1866d1188..7737fa8d9e 100644 --- a/voxygen/src/lib.rs +++ b/voxygen/src/lib.rs @@ -1,5 +1,5 @@ #![deny(unsafe_code)] -#![feature(drain_filter)] +#![feature(drain_filter, bool_to_option)] #![recursion_limit = "2048"] #[macro_use] diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index b68377c67b..3c302d95d0 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -109,6 +109,7 @@ impl PlayState for CharSelectionState { tick: client.get_tick(), body: humanoid_body.clone(), gamma: global_state.settings.graphics.gamma, + mouse_smoothing: global_state.settings.gameplay.smooth_pan_enable, }; self.scene .maintain(global_state.window.renderer_mut(), scene_data); diff --git a/voxygen/src/menu/char_selection/ui.rs b/voxygen/src/menu/char_selection/ui.rs index 2c8d7ac104..4ea9422a5e 100644 --- a/voxygen/src/menu/char_selection/ui.rs +++ b/voxygen/src/menu/char_selection/ui.rs @@ -12,6 +12,7 @@ use crate::{ }; use client::Client; use common::{ + assets, assets::load_expect, comp::{self, humanoid}, }; @@ -350,11 +351,23 @@ impl CharSelectionUi { }), second_item: None, shoulder: None, - chest: None, + chest: Some(assets::load_expect_cloned( + "common.items.armor.starter.rugged_chest", + )), belt: None, hand: None, - pants: None, - foot: None, + pants: Some(assets::load_expect_cloned( + "common.items.armor.starter.rugged_pants", + )), + foot: Some(assets::load_expect_cloned( + "common.items.armor.starter.sandals_0", + )), + back: None, + ring: None, + neck: None, + lantern: None, + head: None, + tabard: None, }; Some(loadout) }, @@ -367,6 +380,15 @@ impl CharSelectionUi { block_ability: None, dodge_ability: None, }); + loadout.chest = Some(assets::load_expect_cloned( + "common.items.armor.starter.rugged_chest", + )); + loadout.pants = Some(assets::load_expect_cloned( + "common.items.armor.starter.rugged_pants", + )); + loadout.foot = Some(assets::load_expect_cloned( + "common.items.armor.starter.sandals_0", + )); Some(loadout.clone()) }, } diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index 3e873f048d..f8f3ca7315 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -270,7 +270,7 @@ fn attempt_login( (server_address, server_port, false), username, Some(global_state.settings.graphics.view_distance), - { password }, + password, )); } } else { diff --git a/voxygen/src/menu/main/ui.rs b/voxygen/src/menu/main/ui.rs index 0ba0007e33..10a9bc2117 100644 --- a/voxygen/src/menu/main/ui.rs +++ b/voxygen/src/menu/main/ui.rs @@ -177,6 +177,8 @@ impl MainMenuUi { "voxygen.background.bg_7", "voxygen.background.bg_8", "voxygen.background.bg_9", + "voxygen.background.bg_10", + "voxygen.background.bg_11", ]; let mut rng = thread_rng(); @@ -205,7 +207,10 @@ impl MainMenuUi { rot_imgs, username: networking.username.clone(), password: "".to_owned(), - server_address: networking.servers[networking.default_server].clone(), + server_address: networking + .servers + .get(networking.default_server) + .map_or_else(|| String::new(), |address| address.clone()), popup: None, connecting: None, show_servers: false, @@ -534,8 +539,10 @@ impl MainMenuUi { .set(self.ids.password_bg, ui_widgets); for event in TextBox::new(&self.password) .w_h(290.0, 30.0) - .mid_bottom_with_margin_on(self.ids.password_bg, 44.0 / 2.0) - .font_size(self.fonts.cyri.scale(22)) + .mid_bottom_with_margin_on(self.ids.password_bg, 17.0) + // the text is smaller to allow longer passwords, conrod limits text length + // this allows 35 characters but can be increased, approximate formula: 420 / scale = length + .font_size(self.fonts.cyri.scale(12)) .font_id(self.fonts.cyri.conrod_id) .text_color(TEXT_COLOR) // transparent background @@ -550,6 +557,7 @@ impl MainMenuUi { self.password = password; }, TextBoxEvent::Enter => { + self.password.pop(); login!(); }, } diff --git a/voxygen/src/mesh/segment.rs b/voxygen/src/mesh/segment.rs index f01513e6df..962a6ed93e 100644 --- a/voxygen/src/mesh/segment.rs +++ b/voxygen/src/mesh/segment.rs @@ -30,28 +30,25 @@ impl Meshable for Segment { faces_to_make(self, pos, true, |vox| vox.is_empty()), offs + pos.map(|e| e as f32), &[[[Rgba::from_opaque(col); 3]; 3]; 3], - |origin, norm, col, ao, light| { + |origin, norm, col, light, ao| { FigureVertex::new( origin, norm, - linear_to_srgb(srgb_to_linear(col) * light.min(ao)), + linear_to_srgb(srgb_to_linear(col) * light), + ao, 0, ) }, &{ - let mut ls = [[[0.0; 3]; 3]; 3]; + let mut ls = [[[None; 3]; 3]; 3]; for x in 0..3 { for y in 0..3 { for z in 0..3 { - ls[z][y][x] = if self + ls[z][y][x] = self .get(pos + Vec3::new(x as i32, y as i32, z as i32) - 1) .map(|v| v.is_empty()) .unwrap_or(true) - { - 1.0 - } else { - 0.0 - }; + .then_some(1.0); } } } @@ -83,14 +80,29 @@ impl Meshable for Segment { faces_to_make(self, pos, true, |vox| vox.is_empty()), offs + pos.map(|e| e as f32), &[[[Rgba::from_opaque(col); 3]; 3]; 3], - |origin, norm, col, ao, light| { + |origin, norm, col, light, ao| { SpriteVertex::new( origin, norm, - linear_to_srgb(srgb_to_linear(col) * ao * light), + linear_to_srgb(srgb_to_linear(col) * light), + ao, ) }, - &[[[1.0; 3]; 3]; 3], + &{ + let mut ls = [[[None; 3]; 3]; 3]; + for x in 0..3 { + for y in 0..3 { + for z in 0..3 { + ls[z][y][x] = self + .get(pos + Vec3::new(x as i32, y as i32, z as i32) - 1) + .map(|v| v.is_empty()) + .unwrap_or(true) + .then_some(1.0); + } + } + } + ls + }, ); } } diff --git a/voxygen/src/mesh/terrain.rs b/voxygen/src/mesh/terrain.rs index d6768a85b5..edec81f945 100644 --- a/voxygen/src/mesh/terrain.rs +++ b/voxygen/src/mesh/terrain.rs @@ -28,7 +28,7 @@ impl Blendable for BlockKind { fn calc_light + ReadVol + Debug>( bounds: Aabb, vol: &VolGrid2d, -) -> impl Fn(Vec3) -> f32 { +) -> impl FnMut(Vec3) -> f32 + '_ { const UNKNOWN: u8 = 255; const OPAQUE: u8 = 254; const SUNLIGHT: u8 = 24; @@ -209,10 +209,7 @@ impl + ReadVol + Debug> Meshable (Mesh, Mesh) { - let mut opaque_mesh = Mesh::new(); - let mut fluid_mesh = Mesh::new(); - - let light = calc_light(range, self); + let mut light = calc_light(range, self); let mut lowest_opaque = range.size().d; let mut highest_opaque = 0; @@ -290,17 +287,47 @@ impl + ReadVol + Debug> Meshable + ReadVol + Debug> Meshable + ReadVol + Debug> Meshable + ReadVol + Debug> Meshable + ReadVol + Debug> Meshable + ReadVol + Debug> Meshable + ReadVol + Debug> Meshable| { + // m.verts().chunks_exact(3).rev().for_each(|vs| { + // opaque_mesh.push(vs[0]); + // opaque_mesh.push(vs[1]); + // opaque_mesh.push(vs[2]); + // }); + // opaque_mesh + // }); + (opaque_mesh, fluid_mesh) } } diff --git a/voxygen/src/mesh/vol.rs b/voxygen/src/mesh/vol.rs index 76979bf2e8..d0ff5af21a 100644 --- a/voxygen/src/mesh/vol.rs +++ b/voxygen/src/mesh/vol.rs @@ -12,19 +12,13 @@ use crate::render::{ fn get_ao_quad( shift: Vec3, dirs: &[Vec3], - darknesses: &[[[f32; 3]; 3]; 3], + darknesses: &[[[Option; 3]; 3]; 3], ) -> Vec4<(f32, f32)> { dirs.windows(2) .map(|offs| { let vox_opaque = |pos: Vec3| { let pos = (pos + 1).map(|e| e as usize); - unsafe { - darknesses - .get_unchecked(pos.z) - .get_unchecked(pos.y) - .get_unchecked(pos.x) - <= &0.0 - } + darknesses[pos.z][pos.y][pos.x].is_none() }; let (s1, s2) = ( @@ -41,17 +35,19 @@ fn get_ao_quad( ); let mut darkness = 0.0; + let mut total = 0.0f32; for x in 0..2 { for y in 0..2 { let dark_pos = shift + offs[0] * x + offs[1] * y + 1; - darkness += unsafe { - darknesses - .get_unchecked(dark_pos.z as usize) - .get_unchecked(dark_pos.y as usize) - .get_unchecked(dark_pos.x as usize) - } / 4.0; + if let Some(dark) = + darknesses[dark_pos.z as usize][dark_pos.y as usize][dark_pos.x as usize] + { + darkness += dark; + total += 1.0; + } } } + let darkness = darkness / total.max(1.0); ( darkness, @@ -60,7 +56,7 @@ fn get_ao_quad( } else { let corner = vox_opaque(shift + offs[0] + offs[1]); // Map both 1 and 2 neighbors to 0.5 occlusion. - if s1 || s2 || corner { 0.5 } else { 1.0 } + if s1 || s2 || corner { 0.4 } else { 1.0 } }, ) }) @@ -114,9 +110,7 @@ fn create_quad, Vec3, Rgb, f32, f32) -> P let ao_map = ao; - if ao[0].min(ao[2]).min(darkness[0]).min(darkness[2]) - < ao[1].min(ao[3]).min(darkness[1]).min(darkness[3]) - { + if ao[0].min(ao[2]) < ao[1].min(ao[3]) { Quad::new( vcons(origin + unit_y, norm, cols[3], darkness[3], ao_map[3]), vcons(origin, norm, cols[0], darkness[0], ao_map[0]), @@ -151,7 +145,7 @@ pub fn push_vox_verts( offs: Vec3, cols: &[[[Rgba; 3]; 3]; 3], vcons: impl Fn(Vec3, Vec3, Rgb, f32, f32) -> P::Vertex, - darknesses: &[[[f32; 3]; 3]; 3], + darknesses: &[[[Option; 3]; 3]; 3], ) { let (x, y, z) = (Vec3::unit_x(), Vec3::unit_y(), Vec3::unit_z()); diff --git a/voxygen/src/render/mesh.rs b/voxygen/src/render/mesh.rs index 00b48ead59..3112098d63 100644 --- a/voxygen/src/render/mesh.rs +++ b/voxygen/src/render/mesh.rs @@ -2,11 +2,21 @@ use super::Pipeline; use std::iter::FromIterator; /// A `Vec`-based mesh structure used to store mesh data on the CPU. -#[derive(Clone)] pub struct Mesh { verts: Vec, } +impl Clone for Mesh

+where + P::Vertex: Clone, +{ + fn clone(&self) -> Self { + Self { + verts: self.verts.clone(), + } + } +} + impl Mesh

{ /// Create a new `Mesh`. pub fn new() -> Self { Self { verts: vec![] } } @@ -56,6 +66,15 @@ impl Mesh

{ self.verts.push(f(vert.clone())); } } + + pub fn iter(&self) -> std::slice::Iter { self.verts.iter() } +} + +impl IntoIterator for Mesh

{ + type IntoIter = std::vec::IntoIter; + type Item = P::Vertex; + + fn into_iter(self) -> Self::IntoIter { self.verts.into_iter() } } impl FromIterator> for Mesh

{ diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs index cab1f90770..a7ba620a99 100644 --- a/voxygen/src/render/mod.rs +++ b/voxygen/src/render/mod.rs @@ -32,7 +32,8 @@ pub use self::{ Globals, Light, Shadow, }, renderer::{ - LodColorFmt, LodTextureFmt, Renderer, TgtColorFmt, TgtDepthFmt, WinColorFmt, WinDepthFmt, + LodColorFmt, LodTextureFmt, Renderer, TgtColorFmt, TgtDepthStencilFmt, WinColorFmt, + WinDepthFmt, }, texture::Texture, }; diff --git a/voxygen/src/render/pipelines/figure.rs b/voxygen/src/render/pipelines/figure.rs index fdb580f457..fe12ec9c6b 100644 --- a/voxygen/src/render/pipelines/figure.rs +++ b/voxygen/src/render/pipelines/figure.rs @@ -1,10 +1,11 @@ use super::{ - super::{util::arr_to_mat, Pipeline, TgtColorFmt, TgtDepthFmt}, + super::{util::arr_to_mat, Pipeline, TgtColorFmt, TgtDepthStencilFmt}, Globals, Light, Shadow, }; use gfx::{ self, gfx_constant_struct_meta, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, gfx_pipeline_inner, gfx_vertex_struct_meta, + state::{ColorMask, Comparison, Stencil, StencilOp}, }; use vek::*; @@ -13,12 +14,14 @@ gfx_defines! { pos: [f32; 3] = "v_pos", norm: [f32; 3] = "v_norm", col: [f32; 3] = "v_col", + ao: f32 = "v_ao", bone_idx: u8 = "v_bone_idx", } constant Locals { model_mat: [[f32; 4]; 4] = "model_mat", model_col: [f32; 4] = "model_col", + flags: u32 = "flags", } constant BoneData { @@ -39,17 +42,18 @@ gfx_defines! { noise: gfx::TextureSampler = "t_noise", - tgt_color: gfx::RenderTarget = "tgt_color", - tgt_depth: gfx::DepthTarget = gfx::preset::depth::LESS_EQUAL_WRITE, + tgt_color: gfx::BlendTarget = ("tgt_color", ColorMask::all(), gfx::preset::blend::ALPHA), + tgt_depth_stencil: gfx::DepthStencilTarget = (gfx::preset::depth::LESS_EQUAL_WRITE,Stencil::new(Comparison::Always,0xff,(StencilOp::Keep,StencilOp::Keep,StencilOp::Replace))), } } impl Vertex { - pub fn new(pos: Vec3, norm: Vec3, col: Rgb, bone_idx: u8) -> Self { + pub fn new(pos: Vec3, norm: Vec3, col: Rgb, ao: f32, bone_idx: u8) -> Self { Self { pos: pos.into_array(), col: col.into_array(), norm: norm.into_array(), + ao, bone_idx, } } @@ -61,16 +65,20 @@ impl Vertex { } impl Locals { - pub fn new(model_mat: Mat4, col: Rgba) -> Self { + pub fn new(model_mat: Mat4, col: Rgba, is_player: bool) -> Self { + let mut flags = 0; + flags |= is_player as u32; + Self { model_mat: arr_to_mat(model_mat.into_col_array()), model_col: col.into_array(), + flags, } } } impl Default for Locals { - fn default() -> Self { Self::new(Mat4::identity(), Rgba::broadcast(1.0)) } + fn default() -> Self { Self::new(Mat4::identity(), Rgba::broadcast(1.0), false) } } impl BoneData { diff --git a/voxygen/src/render/pipelines/fluid.rs b/voxygen/src/render/pipelines/fluid.rs index 2f092057d9..5edabcc4c0 100644 --- a/voxygen/src/render/pipelines/fluid.rs +++ b/voxygen/src/render/pipelines/fluid.rs @@ -1,10 +1,11 @@ use super::{ - super::{Pipeline, TerrainLocals, TgtColorFmt, TgtDepthFmt}, + super::{Pipeline, TerrainLocals, TgtColorFmt, TgtDepthStencilFmt}, Globals, Light, Shadow, }; use gfx::{ self, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, gfx_pipeline_inner, - gfx_vertex_struct_meta, state::ColorMask, + gfx_vertex_struct_meta, + state::{ColorMask, Comparison, Stencil, StencilOp}, }; use std::ops::Mul; use vek::*; @@ -29,7 +30,7 @@ gfx_defines! { waves: gfx::TextureSampler<[f32; 4]> = "t_waves", tgt_color: gfx::BlendTarget = ("tgt_color", ColorMask::all(), gfx::preset::blend::ALPHA), - tgt_depth: gfx::DepthTarget = gfx::preset::depth::LESS_EQUAL_TEST, + tgt_depth_stencil: gfx::DepthStencilTarget = (gfx::preset::depth::LESS_EQUAL_TEST,Stencil::new(Comparison::Always,0xff,(StencilOp::Keep,StencilOp::Keep,StencilOp::Keep))), } } @@ -41,14 +42,16 @@ impl Vertex { .enumerate() .find(|(_i, e)| **e != 0.0) .unwrap_or((0, &1.0)); - let norm_bits = (norm_axis << 1) | if *norm_dir > 0.0 { 1 } else { 0 }; + let norm_bits = ((norm_axis << 1) | if *norm_dir > 0.0 { 1 } else { 0 }) as u32; + + const EXTRA_NEG_Z: f32 = 65536.0; Self { pos_norm: 0 - | ((pos.x as u32) & 0x00FF) << 0 - | ((pos.y as u32) & 0x00FF) << 8 - | ((pos.z.max(0.0).min((1 << 13) as f32) as u32) & 0x1FFF) << 16 - | ((norm_bits as u32) & 0x7) << 29, + | ((pos.x as u32) & 0x003F) << 0 + | ((pos.y as u32) & 0x003F) << 6 + | (((pos.z + EXTRA_NEG_Z).max(0.0).min((1 << 17) as f32) as u32) & 0x1FFFF) << 12 + | (norm_bits & 0x7) << 29, col_light: 0 | ((col.r.mul(200.0) as u32) & 0xFF) << 8 | ((col.g.mul(200.0) as u32) & 0xFF) << 16 diff --git a/voxygen/src/render/pipelines/lod_terrain.rs b/voxygen/src/render/pipelines/lod_terrain.rs index e1213adc9a..32172b938f 100644 --- a/voxygen/src/render/pipelines/lod_terrain.rs +++ b/voxygen/src/render/pipelines/lod_terrain.rs @@ -1,10 +1,11 @@ use super::{ - super::{Pipeline, TgtColorFmt, TgtDepthFmt}, + super::{Pipeline, TgtColorFmt, TgtDepthStencilFmt}, Globals, }; use gfx::{ self, gfx_constant_struct_meta, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, gfx_pipeline_inner, gfx_vertex_struct_meta, + state::{Comparison, Stencil, StencilOp}, }; use vek::*; @@ -28,7 +29,7 @@ gfx_defines! { noise: gfx::TextureSampler = "t_noise", tgt_color: gfx::RenderTarget = "tgt_color", - tgt_depth: gfx::DepthTarget = gfx::preset::depth::LESS_EQUAL_WRITE, + tgt_depth_stencil: gfx::DepthStencilTarget = (gfx::preset::depth::LESS_EQUAL_WRITE,Stencil::new(Comparison::Always,0xff,(StencilOp::Keep,StencilOp::Keep,StencilOp::Keep))), } } diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs index 67cbd39073..07df9826e8 100644 --- a/voxygen/src/render/pipelines/mod.rs +++ b/voxygen/src/render/pipelines/mod.rs @@ -8,6 +8,7 @@ pub mod terrain; pub mod ui; use super::util::arr_to_mat; +use crate::scene::camera::CameraMode; use common::terrain::BlockKind; use gfx::{self, gfx_constant_struct_meta, gfx_defines, gfx_impl_struct_meta}; use vek::*; @@ -35,6 +36,7 @@ gfx_defines! { medium: [u32; 4] = "medium", select_pos: [i32; 4] = "select_pos", gamma: [f32; 4] = "gamma", + cam_mode: u32 = "cam_mode", } constant Light { @@ -64,6 +66,7 @@ impl Globals { medium: BlockKind, select_pos: Option>, gamma: f32, + cam_mode: CameraMode, ) -> Self { Self { view_mat: arr_to_mat(view_mat.into_col_array()), @@ -82,6 +85,7 @@ impl Globals { .unwrap_or(Vec4::zero()) .into_array(), gamma: [gamma; 4], + cam_mode: cam_mode as u32, } } } @@ -103,6 +107,7 @@ impl Default for Globals { BlockKind::Air, None, 1.0, + CameraMode::ThirdPerson, ) } } diff --git a/voxygen/src/render/pipelines/skybox.rs b/voxygen/src/render/pipelines/skybox.rs index 9baf76596d..e6cbd95649 100644 --- a/voxygen/src/render/pipelines/skybox.rs +++ b/voxygen/src/render/pipelines/skybox.rs @@ -1,10 +1,11 @@ use super::{ - super::{Mesh, Pipeline, Quad, TgtColorFmt, TgtDepthFmt}, + super::{Mesh, Pipeline, Quad, TgtColorFmt, TgtDepthStencilFmt}, Globals, }; use gfx::{ self, gfx_constant_struct_meta, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, gfx_pipeline_inner, gfx_vertex_struct_meta, + state::{Comparison, Stencil, StencilOp}, }; gfx_defines! { @@ -25,7 +26,7 @@ gfx_defines! { noise: gfx::TextureSampler = "t_noise", tgt_color: gfx::RenderTarget = "tgt_color", - tgt_depth: gfx::DepthTarget = gfx::preset::depth::LESS_EQUAL_WRITE, + tgt_depth_stencil: gfx::DepthStencilTarget = (gfx::preset::depth::LESS_EQUAL_WRITE,Stencil::new(Comparison::Always,0xff,(StencilOp::Keep,StencilOp::Keep,StencilOp::Keep))), } } diff --git a/voxygen/src/render/pipelines/sprite.rs b/voxygen/src/render/pipelines/sprite.rs index 81a981d774..e77d9f9e01 100644 --- a/voxygen/src/render/pipelines/sprite.rs +++ b/voxygen/src/render/pipelines/sprite.rs @@ -1,10 +1,11 @@ use super::{ - super::{util::arr_to_mat, Pipeline, TgtColorFmt, TgtDepthFmt}, + super::{util::arr_to_mat, Pipeline, TgtColorFmt, TgtDepthStencilFmt}, Globals, Light, Shadow, }; use gfx::{ self, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, gfx_pipeline_inner, - gfx_vertex_struct_meta, state::ColorMask, + gfx_vertex_struct_meta, + state::{ColorMask, Comparison, Stencil, StencilOp}, }; use vek::*; @@ -13,6 +14,7 @@ gfx_defines! { pos: [f32; 3] = "v_pos", norm: [f32; 3] = "v_norm", col: [f32; 3] = "v_col", + ao: f32 = "v_ao", } vertex Instance { @@ -38,16 +40,17 @@ gfx_defines! { noise: gfx::TextureSampler = "t_noise", tgt_color: gfx::BlendTarget = ("tgt_color", ColorMask::all(), gfx::preset::blend::ALPHA), - tgt_depth: gfx::DepthTarget = gfx::preset::depth::LESS_EQUAL_WRITE, + tgt_depth_stencil: gfx::DepthStencilTarget = (gfx::preset::depth::LESS_EQUAL_WRITE,Stencil::new(Comparison::Always,0xff,(StencilOp::Keep,StencilOp::Keep,StencilOp::Keep))), } } impl Vertex { - pub fn new(pos: Vec3, norm: Vec3, col: Rgb) -> Self { + pub fn new(pos: Vec3, norm: Vec3, col: Rgb, ao: f32) -> Self { Self { pos: pos.into_array(), col: col.into_array(), norm: norm.into_array(), + ao, } } } diff --git a/voxygen/src/render/pipelines/terrain.rs b/voxygen/src/render/pipelines/terrain.rs index 41eb8edd53..fe987505f2 100644 --- a/voxygen/src/render/pipelines/terrain.rs +++ b/voxygen/src/render/pipelines/terrain.rs @@ -1,10 +1,11 @@ use super::{ - super::{Pipeline, TgtColorFmt, TgtDepthFmt}, + super::{Pipeline, TgtColorFmt, TgtDepthStencilFmt}, Globals, Light, Shadow, }; use gfx::{ self, gfx_constant_struct_meta, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, gfx_pipeline_inner, gfx_vertex_struct_meta, + state::{Comparison, Stencil, StencilOp}, }; use std::ops::Mul; use vek::*; @@ -34,23 +35,26 @@ gfx_defines! { noise: gfx::TextureSampler = "t_noise", tgt_color: gfx::RenderTarget = "tgt_color", - tgt_depth: gfx::DepthTarget = gfx::preset::depth::LESS_EQUAL_WRITE, + tgt_depth_stencil: gfx::DepthStencilTarget = (gfx::preset::depth::LESS_EQUAL_WRITE,Stencil::new(Comparison::Always,0xff,(StencilOp::Keep,StencilOp::Keep,StencilOp::Keep))), } } impl Vertex { - pub fn new(norm_bits: u32, light: u32, pos: Vec3, col: Rgb) -> Self { + pub fn new(norm_bits: u32, light: u32, ao: u32, pos: Vec3, col: Rgb) -> Self { + const EXTRA_NEG_Z: f32 = 65536.0; + Self { pos_norm: 0 - | ((pos.x as u32) & 0x00FF) << 0 - | ((pos.y as u32) & 0x00FF) << 8 - | ((pos.z.max(0.0).min((1 << 13) as f32) as u32) & 0x1FFF) << 16 + | ((pos.x as u32) & 0x003F) << 0 + | ((pos.y as u32) & 0x003F) << 6 + | (((pos + EXTRA_NEG_Z).z.max(0.0).min((1 << 17) as f32) as u32) & 0xFFFFF) << 12 | (norm_bits & 0x7) << 29, col_light: 0 | ((col.r.mul(255.0) as u32) & 0xFF) << 8 | ((col.g.mul(255.0) as u32) & 0xFF) << 16 | ((col.b.mul(255.0) as u32) & 0xFF) << 24 - | (light & 0xFF) << 0, + | (ao >> 6) << 6 + | ((light >> 2) & 0x3F) << 0, } } } diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index c62b9dda42..9e9cf6614f 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -15,6 +15,7 @@ use common::assets::{self, watch::ReloadIndicator}; use gfx::{ self, handle::Sampler, + state::{Comparison, Stencil, StencilOp}, traits::{Device, Factory, FactoryExt}, }; use glsl_include::Context as IncludeContext; @@ -23,8 +24,8 @@ use vek::*; /// Represents the format of the pre-processed color target. pub type TgtColorFmt = gfx::format::Srgba8; -/// Represents the format of the pre-processed depth target. -pub type TgtDepthFmt = gfx::format::Depth; +/// Represents the format of the pre-processed depth and stencil target. +pub type TgtDepthStencilFmt = gfx::format::DepthStencil; /// Represents the format of the window's color target. pub type WinColorFmt = gfx::format::Srgba8; @@ -34,7 +35,8 @@ pub type WinDepthFmt = gfx::format::Depth; /// A handle to a pre-processed color target. pub type TgtColorView = gfx::handle::RenderTargetView; /// A handle to a pre-processed depth target. -pub type TgtDepthView = gfx::handle::DepthStencilView; +pub type TgtDepthStencilView = + gfx::handle::DepthStencilView; /// A handle to a window color target. pub type WinColorView = gfx::handle::RenderTargetView; @@ -66,7 +68,7 @@ pub struct Renderer { win_depth_view: WinDepthView, tgt_color_view: TgtColorView, - tgt_depth_view: TgtDepthView, + tgt_depth_stencil_view: TgtDepthStencilView, tgt_color_res: TgtColorRes, @@ -80,6 +82,7 @@ pub struct Renderer { ui_pipeline: GfxPipeline>, lod_terrain_pipeline: GfxPipeline>, postprocess_pipeline: GfxPipeline>, + player_shadow_pipeline: GfxPipeline>, shader_reload_indicator: ReloadIndicator, @@ -113,6 +116,7 @@ impl Renderer { ui_pipeline, lod_terrain_pipeline, postprocess_pipeline, + player_shadow_pipeline, ) = create_pipelines( &mut factory, aa_mode, @@ -122,7 +126,7 @@ impl Renderer { )?; let dims = win_color_view.get_dimensions(); - let (tgt_color_view, tgt_depth_view, tgt_color_res) = + let (tgt_color_view, tgt_depth_stencil_view, tgt_color_res) = Self::create_rt_views(&mut factory, (dims.0, dims.1), aa_mode)?; let sampler = factory.create_sampler_linear(); @@ -144,7 +148,7 @@ impl Renderer { win_depth_view, tgt_color_view, - tgt_depth_view, + tgt_depth_stencil_view, tgt_color_res, sampler, @@ -157,6 +161,7 @@ impl Renderer { ui_pipeline, lod_terrain_pipeline, postprocess_pipeline, + player_shadow_pipeline, shader_reload_indicator, @@ -171,8 +176,8 @@ impl Renderer { /// Get references to the internal render target views that get rendered to /// before post-processing. #[allow(dead_code)] - pub fn tgt_views(&self) -> (&TgtColorView, &TgtDepthView) { - (&self.tgt_color_view, &self.tgt_depth_view) + pub fn tgt_views(&self) -> (&TgtColorView, &TgtDepthStencilView) { + (&self.tgt_color_view, &self.tgt_depth_stencil_view) } /// Get references to the internal render target views that get displayed @@ -185,8 +190,8 @@ impl Renderer { /// Get mutable references to the internal render target views that get /// rendered to before post-processing. #[allow(dead_code)] - pub fn tgt_views_mut(&mut self) -> (&mut TgtColorView, &mut TgtDepthView) { - (&mut self.tgt_color_view, &mut self.tgt_depth_view) + pub fn tgt_views_mut(&mut self) -> (&mut TgtColorView, &mut TgtDepthStencilView) { + (&mut self.tgt_color_view, &mut self.tgt_depth_stencil_view) } /// Get mutable references to the internal render target views that get @@ -241,11 +246,11 @@ impl Renderer { // Avoid panics when creating texture with w,h of 0,0. if dims.0 != 0 && dims.1 != 0 { - let (tgt_color_view, tgt_depth_view, tgt_color_res) = + let (tgt_color_view, tgt_depth_stencil_view, tgt_color_res) = Self::create_rt_views(&mut self.factory, (dims.0, dims.1), self.aa_mode)?; self.tgt_color_res = tgt_color_res; self.tgt_color_view = tgt_color_view; - self.tgt_depth_view = tgt_depth_view; + self.tgt_depth_stencil_view = tgt_depth_stencil_view; } Ok(()) @@ -255,7 +260,7 @@ impl Renderer { factory: &mut gfx_device_gl::Factory, size: (u16, u16), aa_mode: AaMode, - ) -> Result<(TgtColorView, TgtDepthView, TgtColorRes), RenderError> { + ) -> Result<(TgtColorView, TgtDepthStencilView, TgtColorRes), RenderError> { let kind = match aa_mode { AaMode::None | AaMode::Fxaa => { gfx::texture::Kind::D2(size.0, size.1, gfx::texture::AaMode::Single) @@ -292,17 +297,18 @@ impl Renderer { )?; let tgt_color_view = factory.view_texture_as_render_target(&tgt_color_tex, 0, None)?; - let depth_cty = <::Channel as gfx::format::ChannelTyped>::get_channel_type(); - let tgt_depth_tex = factory.create_texture( + let depth_stencil_cty = <::Channel as gfx::format::ChannelTyped>::get_channel_type(); + let tgt_depth_stencil_tex = factory.create_texture( kind, levels, gfx::memory::Bind::DEPTH_STENCIL, gfx::memory::Usage::Data, - Some(depth_cty), + Some(depth_stencil_cty), )?; - let tgt_depth_view = factory.view_texture_as_depth_stencil_trivial(&tgt_depth_tex)?; + let tgt_depth_stencil_view = + factory.view_texture_as_depth_stencil_trivial(&tgt_depth_stencil_tex)?; - Ok((tgt_color_view, tgt_depth_view, tgt_color_res)) + Ok((tgt_color_view, tgt_depth_stencil_view, tgt_color_res)) } /// Get the resolution of the render target. @@ -316,7 +322,8 @@ impl Renderer { /// Queue the clearing of the depth target ready for a new frame to be /// rendered. pub fn clear(&mut self) { - self.encoder.clear_depth(&self.tgt_depth_view, 1.0); + self.encoder.clear_depth(&self.tgt_depth_stencil_view, 1.0); + self.encoder.clear_stencil(&self.tgt_depth_stencil_view, 0); self.encoder.clear_depth(&self.win_depth_view, 1.0); } @@ -350,6 +357,7 @@ impl Renderer { ui_pipeline, lod_terrain_pipeline, postprocess_pipeline, + player_shadow_pipeline, )) => { self.skybox_pipeline = skybox_pipeline; self.figure_pipeline = figure_pipeline; @@ -359,6 +367,7 @@ impl Renderer { self.ui_pipeline = ui_pipeline; self.lod_terrain_pipeline = lod_terrain_pipeline; self.postprocess_pipeline = postprocess_pipeline; + self.player_shadow_pipeline = player_shadow_pipeline; }, Err(e) => error!( "Could not recreate shaders from assets due to an error: {:#?}", @@ -523,7 +532,7 @@ impl Renderer { globals: globals.buf.clone(), noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), tgt_color: self.tgt_color_view.clone(), - tgt_depth: self.tgt_depth_view.clone(), + tgt_depth_stencil: (self.tgt_depth_stencil_view.clone(), (1, 1)), }, ); } @@ -560,7 +569,81 @@ impl Renderer { map: (map.srv.clone(), map.sampler.clone()), horizon: (horizon.srv.clone(), horizon.sampler.clone()), tgt_color: self.tgt_color_view.clone(), - tgt_depth: self.tgt_depth_view.clone(), + tgt_depth_stencil: (self.tgt_depth_stencil_view.clone(), (1, 1)), + }, + ); + } + + /// Queue the rendering of the player silhouette in the upcoming frame. + pub fn render_player_shadow( + &mut self, + model: &Model, + globals: &Consts, + locals: &Consts, + bones: &Consts, + lights: &Consts, + shadows: &Consts, + map: &Texture, + horizon: &Texture, + ) { + self.encoder.draw( + &gfx::Slice { + start: model.vertex_range().start, + end: model.vertex_range().end, + base_vertex: 0, + instances: None, + buffer: gfx::IndexBuffer::Auto, + }, + &self.player_shadow_pipeline.pso, + &figure::pipe::Data { + vbuf: model.vbuf.clone(), + locals: locals.buf.clone(), + globals: globals.buf.clone(), + bones: bones.buf.clone(), + lights: lights.buf.clone(), + shadows: shadows.buf.clone(), + noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), + map: (map.srv.clone(), map.sampler.clone()), + horizon: (horizon.srv.clone(), horizon.sampler.clone()), + tgt_color: self.tgt_color_view.clone(), + tgt_depth_stencil: (self.tgt_depth_stencil_view.clone(), (0, 0)), + }, + ); + } + + /// Queue the rendering of the player model in the upcoming frame. + pub fn render_player( + &mut self, + model: &Model, + globals: &Consts, + locals: &Consts, + bones: &Consts, + lights: &Consts, + shadows: &Consts, + map: &Texture, + horizon: &Texture, + ) { + self.encoder.draw( + &gfx::Slice { + start: model.vertex_range().start, + end: model.vertex_range().end, + base_vertex: 0, + instances: None, + buffer: gfx::IndexBuffer::Auto, + }, + &self.figure_pipeline.pso, + &figure::pipe::Data { + vbuf: model.vbuf.clone(), + locals: locals.buf.clone(), + globals: globals.buf.clone(), + bones: bones.buf.clone(), + lights: lights.buf.clone(), + shadows: shadows.buf.clone(), + noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), + map: (map.srv.clone(), map.sampler.clone()), + horizon: (horizon.srv.clone(), horizon.sampler.clone()), + tgt_color: self.tgt_color_view.clone(), + tgt_depth_stencil: (self.tgt_depth_stencil_view.clone(), (1, 1)), }, ); } @@ -596,7 +679,7 @@ impl Renderer { map: (map.srv.clone(), map.sampler.clone()), horizon: (horizon.srv.clone(), horizon.sampler.clone()), tgt_color: self.tgt_color_view.clone(), - tgt_depth: self.tgt_depth_view.clone(), + tgt_depth_stencil: (self.tgt_depth_stencil_view.clone(), (1, 1)), }, ); } @@ -634,7 +717,7 @@ impl Renderer { noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), waves: (waves.srv.clone(), waves.sampler.clone()), tgt_color: self.tgt_color_view.clone(), - tgt_depth: self.tgt_depth_view.clone(), + tgt_depth_stencil: (self.tgt_depth_stencil_view.clone(), (1, 1)), }, ); } @@ -670,7 +753,7 @@ impl Renderer { map: (map.srv.clone(), map.sampler.clone()), horizon: (horizon.srv.clone(), horizon.sampler.clone()), tgt_color: self.tgt_color_view.clone(), - tgt_depth: self.tgt_depth_view.clone(), + tgt_depth_stencil: (self.tgt_depth_stencil_view.clone(), (1, 1)), }, ); } @@ -702,7 +785,7 @@ impl Renderer { map: (map.srv.clone(), map.sampler.clone()), horizon: (horizon.srv.clone(), horizon.sampler.clone()), tgt_color: self.tgt_color_view.clone(), - tgt_depth: self.tgt_depth_view.clone(), + tgt_depth_stencil: (self.tgt_depth_stencil_view.clone(), (1, 1)), }, ); } @@ -795,6 +878,7 @@ fn create_pipelines( GfxPipeline>, GfxPipeline>, GfxPipeline>, + GfxPipeline>, ), RenderError, > { @@ -965,6 +1049,31 @@ fn create_pipelines( gfx::state::CullFace::Back, )?; + // Construct a pipeline for rendering the player silhouette + let player_shadow_pipeline = create_pipeline( + factory, + figure::pipe::Init { + tgt_depth_stencil: ( + gfx::preset::depth::PASS_TEST, + Stencil::new( + Comparison::Equal, + 0xff, + (StencilOp::Keep, StencilOp::Keep, StencilOp::Keep), + ), + ), + ..figure::pipe::new() + }, + &assets::load_watched::("voxygen.shaders.figure-vert", shader_reload_indicator) + .unwrap(), + &assets::load_watched::( + "voxygen.shaders.player-shadow-frag", + shader_reload_indicator, + ) + .unwrap(), + &include_ctx, + gfx::state::CullFace::Back, + )?; + Ok(( skybox_pipeline, figure_pipeline, @@ -974,6 +1083,7 @@ fn create_pipelines( ui_pipeline, lod_terrain_pipeline, postprocess_pipeline, + player_shadow_pipeline, )) } diff --git a/voxygen/src/scene/camera.rs b/voxygen/src/scene/camera.rs index e3f1e3cf96..e512e88d12 100644 --- a/voxygen/src/scene/camera.rs +++ b/voxygen/src/scene/camera.rs @@ -8,13 +8,14 @@ const FAR_PLANE: f32 = 100000.0; const FIRST_PERSON_INTERP_TIME: f32 = 0.1; const THIRD_PERSON_INTERP_TIME: f32 = 0.1; +const LERP_ORI_RATE: f32 = 15.0; pub const MIN_ZOOM: f32 = 0.1; // Possible TODO: Add more modes #[derive(PartialEq, Clone, Copy, Eq, Hash)] pub enum CameraMode { - FirstPerson, - ThirdPerson, + FirstPerson = 0, + ThirdPerson = 1, } impl Default for CameraMode { @@ -31,6 +32,7 @@ pub struct Dependents { pub struct Camera { tgt_focus: Vec3, focus: Vec3, + tgt_ori: Vec3, ori: Vec3, tgt_dist: f32, dist: f32, @@ -49,6 +51,7 @@ impl Camera { Self { tgt_focus: Vec3::unit_z() * 10.0, focus: Vec3::unit_z() * 10.0, + tgt_ori: Vec3::zero(), ori: Vec3::zero(), tgt_dist: 10.0, dist: 10.0, @@ -70,7 +73,7 @@ impl Camera { /// and position of the camera. pub fn compute_dependents(&mut self, terrain: &impl ReadVol) { let dist = { - /*let (start, end) = ( + let (start, end) = ( self.focus + (Vec3::new( -f32::sin(self.ori.x) * f32::cos(self.ori.y), @@ -91,8 +94,8 @@ impl Camera { (_, Ok(None)) => self.dist, (_, Err(_)) => self.dist, } - .max(0.0) */ - self.dist.max(0.0) + .max(0.0) + // self.dist.max(0.0) }; self.dependents.view_mat = Mat4::::identity() @@ -122,21 +125,33 @@ impl Camera { /// accordingly. pub fn rotate_by(&mut self, delta: Vec3) { // Wrap camera yaw - self.ori.x = (self.ori.x + delta.x) % (2.0 * PI); + self.tgt_ori.x = (self.tgt_ori.x + delta.x).rem_euclid(2.0 * PI); // Clamp camera pitch to the vertical limits - self.ori.y = (self.ori.y + delta.y).min(PI / 2.0).max(-PI / 2.0); + self.tgt_ori.y = (self.tgt_ori.y + delta.y) + .min(PI / 2.0 - 0.0001) + .max(-PI / 2.0 + 0.0001); // Wrap camera roll - self.ori.z = (self.ori.z + delta.z) % (2.0 * PI); + self.tgt_ori.z = (self.tgt_ori.z + delta.z).rem_euclid(2.0 * PI); } /// Set the orientation of the camera about its focus. - pub fn set_orientation(&mut self, orientation: Vec3) { + pub fn set_orientation(&mut self, ori: Vec3) { // Wrap camera yaw - self.ori.x = orientation.x % (2.0 * PI); + self.tgt_ori.x = ori.x.rem_euclid(2.0 * PI); // Clamp camera pitch to the vertical limits - self.ori.y = orientation.y.min(PI / 2.0).max(-PI / 2.0); + self.tgt_ori.y = ori.y.min(PI / 2.0 - 0.0001).max(-PI / 2.0 + 0.0001); // Wrap camera roll - self.ori.z = orientation.z % (2.0 * PI); + self.tgt_ori.z = ori.z.rem_euclid(2.0 * PI); + } + + /// Set the orientation of the camera about its focus without lerping. + pub fn set_ori_instant(&mut self, ori: Vec3) { + // Wrap camera yaw + self.ori.x = ori.x.rem_euclid(2.0 * PI); + // Clamp camera pitch to the vertical limits + self.ori.y = ori.y.min(PI / 2.0 - 0.0001).max(-PI / 2.0 + 0.0001); + // Wrap camera roll + self.ori.z = ori.z.rem_euclid(2.0 * PI); } /// Zoom the camera by the given delta, limiting the input accordingly. @@ -176,7 +191,7 @@ impl Camera { /// Set the distance of the camera from the target (i.e., zoom). pub fn set_distance(&mut self, dist: f32) { self.tgt_dist = dist; } - pub fn update(&mut self, time: f64) { + pub fn update(&mut self, time: f64, dt: f32, smoothing_enabled: bool) { // This is horribly frame time dependent, but so is most of the game let delta = self.last_time.replace(time).map_or(0.0, |t| time - t); if (self.dist - self.tgt_dist).abs() > 0.01 { @@ -194,6 +209,24 @@ impl Camera { (delta as f32) / self.interp_time(), ); } + + let lerp_angle = |a: f32, b: f32, rate: f32| { + let offs = [-2.0 * PI, 0.0, 2.0 * PI] + .iter() + .min_by_key(|offs: &&f32| ((a - (b + *offs)).abs() * 1000.0) as i32) + .unwrap(); + Lerp::lerp(a, b + *offs, rate) + }; + + if smoothing_enabled { + self.set_ori_instant(Vec3::new( + lerp_angle(self.ori.x, self.tgt_ori.x, LERP_ORI_RATE * dt), + Lerp::lerp(self.ori.y, self.tgt_ori.y, LERP_ORI_RATE * dt), + lerp_angle(self.ori.z, self.tgt_ori.z, LERP_ORI_RATE * dt), + )); + } else { + self.set_ori_instant(self.tgt_ori) + }; } pub fn interp_time(&self) -> f32 { diff --git a/voxygen/src/scene/figure/cache.rs b/voxygen/src/scene/figure/cache.rs index 26961df98c..7903b30d3c 100644 --- a/voxygen/src/scene/figure/cache.rs +++ b/voxygen/src/scene/figure/cache.rs @@ -30,6 +30,8 @@ struct CharacterCacheKey { shoulder: Option, chest: Option, belt: Option, + back: Option, + lantern: Option, hand: Option, pants: Option, foot: Option, @@ -49,6 +51,8 @@ impl CharacterCacheKey { shoulder: loadout.shoulder.clone(), chest: loadout.chest.clone(), belt: loadout.belt.clone(), + back: loadout.back.clone(), + lantern: loadout.lantern.clone(), hand: loadout.hand.clone(), pants: loadout.pants.clone(), foot: loadout.foot.clone(), @@ -118,6 +122,10 @@ impl FigureModelCache { HumArmorHandSpec::load_watched(&mut self.manifest_indicator); let humanoid_armor_belt_spec = HumArmorBeltSpec::load_watched(&mut self.manifest_indicator); + let humanoid_armor_back_spec = + HumArmorBackSpec::load_watched(&mut self.manifest_indicator); + let humanoid_armor_lantern_spec = + HumArmorLanternSpec::load_watched(&mut self.manifest_indicator); let humanoid_armor_pants_spec = HumArmorPantsSpec::load_watched(&mut self.manifest_indicator); let humanoid_armor_foot_spec = @@ -158,6 +166,12 @@ impl FigureModelCache { }, CameraMode::FirstPerson => None, }, + match camera_mode { + CameraMode::ThirdPerson => { + Some(humanoid_armor_back_spec.mesh_back(&body, loadout)) + }, + CameraMode::FirstPerson => None, + }, match camera_mode { CameraMode::ThirdPerson => Some( humanoid_armor_pants_spec.mesh_pants(&body, loadout), @@ -224,9 +238,8 @@ impl FigureModelCache { } else { None }, - Some(mesh_lantern()), - None, None, + Some(humanoid_armor_lantern_spec.mesh_lantern(&body, loadout)), None, ] }, diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index fcb221726d..5f9762bcf3 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -12,9 +12,9 @@ use common::{ dragon, fish_medium, fish_small, humanoid::{Body, BodyType, EyeColor, Eyebrows, Race, Skin}, item::{ - armor::{Armor, Belt, Chest, Foot, Hand, Pants, Shoulder}, + armor::{Armor, Back, Belt, Chest, Foot, Hand, Head, Pants, Shoulder, Tabard}, tool::{Tool, ToolKind}, - ItemKind, + ItemKind, Lantern, }, object, quadruped_medium::{BodyType as QMBodyType, Species as QMSpecies}, @@ -251,11 +251,19 @@ pub struct HumArmorHandSpec(ArmorVoxSpecMap); #[derive(Serialize, Deserialize)] pub struct HumArmorBeltSpec(ArmorVoxSpecMap); #[derive(Serialize, Deserialize)] +pub struct HumArmorBackSpec(ArmorVoxSpecMap); +#[derive(Serialize, Deserialize)] pub struct HumArmorPantsSpec(ArmorVoxSpecMap); #[derive(Serialize, Deserialize)] pub struct HumArmorFootSpec(ArmorVoxSpecMap); #[derive(Serialize, Deserialize)] pub struct HumMainWeaponSpec(HashMap); +#[derive(Serialize, Deserialize)] +pub struct HumArmorLanternSpec(ArmorVoxSpecMap); +#[derive(Serialize, Deserialize)] +pub struct HumArmorHeadSpec(ArmorVoxSpecMap); +#[derive(Serialize, Deserialize)] +pub struct HumArmorTabardSpec(ArmorVoxSpecMap); impl Asset for HumArmorShoulderSpec { const ENDINGS: &'static [&'static str] = &["ron"]; @@ -285,6 +293,13 @@ impl Asset for HumArmorBeltSpec { ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) } } +impl Asset for HumArmorBackSpec { + const ENDINGS: &'static [&'static str] = &["ron"]; + + fn parse(buf_reader: BufReader) -> Result { + ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) + } +} impl Asset for HumArmorPantsSpec { const ENDINGS: &'static [&'static str] = &["ron"]; @@ -299,6 +314,27 @@ impl Asset for HumArmorFootSpec { ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) } } +impl Asset for HumArmorLanternSpec { + const ENDINGS: &'static [&'static str] = &["ron"]; + + fn parse(buf_reader: BufReader) -> Result { + ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) + } +} +impl Asset for HumArmorHeadSpec { + const ENDINGS: &'static [&'static str] = &["ron"]; + + fn parse(buf_reader: BufReader) -> Result { + ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) + } +} +impl Asset for HumArmorTabardSpec { + const ENDINGS: &'static [&'static str] = &["ron"]; + + fn parse(buf_reader: BufReader) -> Result { + ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) + } +} impl Asset for HumMainWeaponSpec { const ENDINGS: &'static [&'static str] = &["ron"]; @@ -306,7 +342,7 @@ impl Asset for HumMainWeaponSpec { ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) } } - +// Shoulder impl HumArmorShoulderSpec { pub fn load_watched(indicator: &mut ReloadIndicator) -> Arc { assets::load_watched::("voxygen.voxel.humanoid_armor_shoulder_manifest", indicator) @@ -363,7 +399,7 @@ impl HumArmorShoulderSpec { self.mesh_shoulder(body, loadout, false) } } - +// Chest impl HumArmorChestSpec { pub fn load_watched(indicator: &mut ReloadIndicator) -> Arc { assets::load_watched::("voxygen.voxel.humanoid_armor_chest_manifest", indicator) @@ -414,7 +450,7 @@ impl HumArmorChestSpec { generate_mesh(&chest, Vec3::from(spec.vox_spec.1)) } } - +// Hand impl HumArmorHandSpec { pub fn load_watched(indicator: &mut ReloadIndicator) -> Arc { assets::load_watched::("voxygen.voxel.humanoid_armor_hand_manifest", indicator) @@ -466,7 +502,7 @@ impl HumArmorHandSpec { self.mesh_hand(body, loadout, false) } } - +// Belt impl HumArmorBeltSpec { pub fn load_watched(indicator: &mut ReloadIndicator) -> Arc { assets::load_watched::("voxygen.voxel.humanoid_armor_belt_manifest", indicator) @@ -500,7 +536,41 @@ impl HumArmorBeltSpec { generate_mesh(&belt_segment, Vec3::from(spec.vox_spec.1)) } } +// Cape +impl HumArmorBackSpec { + pub fn load_watched(indicator: &mut ReloadIndicator) -> Arc { + assets::load_watched::("voxygen.voxel.humanoid_armor_back_manifest", indicator) + .unwrap() + } + pub fn mesh_back(&self, body: &Body, loadout: &Loadout) -> Mesh { + let spec = if let Some(ItemKind::Armor { + kind: Armor::Back(back), + .. + }) = loadout.back.as_ref().map(|i| &i.kind) + { + match self.0.map.get(&back) { + Some(spec) => spec, + None => { + error!("No back specification exists for {:?}", back); + return load_mesh("not_found", Vec3::new(-4.0, -3.5, 2.0)); + }, + } + } else { + &self.0.default + }; + + let back_segment = color_segment( + graceful_load_mat_segment(&spec.vox_spec.0), + body.race.skin_color(body.skin), + body.race.hair_color(body.hair_color), + body.race.eye_color(body.eye_color), + ); + + generate_mesh(&back_segment, Vec3::from(spec.vox_spec.1)) + } +} +// Legs impl HumArmorPantsSpec { pub fn load_watched(indicator: &mut ReloadIndicator) -> Arc { assets::load_watched::("voxygen.voxel.humanoid_armor_pants_manifest", indicator) @@ -551,7 +621,7 @@ impl HumArmorPantsSpec { generate_mesh(&pants, Vec3::from(spec.vox_spec.1)) } } - +// Foot impl HumArmorFootSpec { pub fn load_watched(indicator: &mut ReloadIndicator) -> Arc { assets::load_watched::("voxygen.voxel.humanoid_armor_foot_manifest", indicator) @@ -623,14 +693,140 @@ impl HumMainWeaponSpec { generate_mesh(&tool_kind_segment, Vec3::from(spec.vox_spec.1)) } } +// Lantern +impl HumArmorLanternSpec { + pub fn load_watched(indicator: &mut ReloadIndicator) -> Arc { + assets::load_watched::("voxygen.voxel.humanoid_lantern_manifest", indicator).unwrap() + } + pub fn mesh_lantern(&self, body: &Body, loadout: &Loadout) -> Mesh { + let spec = + if let Some(ItemKind::Lantern(lantern)) = loadout.lantern.as_ref().map(|i| &i.kind) { + match self.0.map.get(&lantern) { + Some(spec) => spec, + None => { + error!("No lantern specification exists for {:?}", lantern); + return load_mesh("not_found", Vec3::new(-4.0, -3.5, 2.0)); + }, + } + } else { + &self.0.default + }; + + let lantern_segment = color_segment( + graceful_load_mat_segment(&spec.vox_spec.0), + body.race.skin_color(body.skin), + body.race.hair_color(body.hair_color), + body.race.eye_color(body.eye_color), + ); + + generate_mesh(&lantern_segment, Vec3::from(spec.vox_spec.1)) + } +} +impl HumArmorHeadSpec { + pub fn load_watched(indicator: &mut ReloadIndicator) -> Arc { + assets::load_watched::("voxygen.voxel.humanoid_armor_head_manifest", indicator) + .unwrap() + } + + pub fn mesh_head(&self, body: &Body, loadout: &Loadout) -> Mesh { + let spec = if let Some(ItemKind::Armor { + kind: Armor::Head(head), + .. + }) = loadout.head.as_ref().map(|i| &i.kind) + { + match self.0.map.get(&head) { + Some(spec) => spec, + None => { + error!("No head specification exists for {:?}", head); + return load_mesh("not_found", Vec3::new(-5.0, -3.5, 1.0)); + }, + } + } else { + &self.0.default + }; + + let color = |mat_segment| { + color_segment( + mat_segment, + body.race.skin_color(body.skin), + body.race.hair_color(body.hair_color), + body.race.eye_color(body.eye_color), + ) + }; + + let bare_head = graceful_load_mat_segment("armor.empty"); + + let mut head_armor = graceful_load_mat_segment(&spec.vox_spec.0); + + if let Some(color) = spec.color { + let head_color = Vec3::from(color); + head_armor = head_armor.map_rgb(|rgb| recolor_grey(rgb, Rgb::from(head_color))); + } + + let head = DynaUnionizer::new() + .add(color(bare_head), Vec3::new(0, 0, 0)) + .add(color(head_armor), Vec3::new(0, 0, 0)) + .unify() + .0; + + generate_mesh(&head, Vec3::from(spec.vox_spec.1)) + } +} +impl HumArmorTabardSpec { + pub fn load_watched(indicator: &mut ReloadIndicator) -> Arc { + assets::load_watched::("voxygen.voxel.humanoid_armor_tabard_manifest", indicator) + .unwrap() + } + + pub fn mesh_tabard(&self, body: &Body, loadout: &Loadout) -> Mesh { + let spec = if let Some(ItemKind::Armor { + kind: Armor::Tabard(tabard), + .. + }) = loadout.tabard.as_ref().map(|i| &i.kind) + { + match self.0.map.get(&tabard) { + Some(spec) => spec, + None => { + error!("No tabard specification exists for {:?}", tabard); + return load_mesh("not_found", Vec3::new(-5.0, -3.5, 1.0)); + }, + } + } else { + &self.0.default + }; + + let color = |mat_segment| { + color_segment( + mat_segment, + body.race.skin_color(body.skin), + body.race.hair_color(body.hair_color), + body.race.eye_color(body.eye_color), + ) + }; + + let bare_tabard = graceful_load_mat_segment("armor.empty"); + + let mut tabard_armor = graceful_load_mat_segment(&spec.vox_spec.0); + + if let Some(color) = spec.color { + let tabard_color = Vec3::from(color); + tabard_armor = tabard_armor.map_rgb(|rgb| recolor_grey(rgb, Rgb::from(tabard_color))); + } + + let tabard = DynaUnionizer::new() + .add(color(bare_tabard), Vec3::new(0, 0, 0)) + .add(color(tabard_armor), Vec3::new(0, 0, 0)) + .unify() + .0; + + generate_mesh(&tabard, Vec3::from(spec.vox_spec.1)) + } +} // TODO: Inventory pub fn mesh_glider() -> Mesh { load_mesh("object.glider", Vec3::new(-26.0, -26.0, -5.0)) } -pub fn mesh_lantern() -> Mesh { - load_mesh("object.glider", Vec3::new(-26.0, -26.0, -5.0)) -} ///////// #[derive(Serialize, Deserialize)] diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index fba637036e..545feec3c3 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -144,6 +144,8 @@ impl FigureMgr { ) .join() { + let is_player = scene_data.player_entity == entity; + let (pos, ori) = interpolated .map(|i| (Pos(i.pos), *i.ori)) .unwrap_or((*pos, Vec3::unit_y())); @@ -380,7 +382,7 @@ impl FigureMgr { let col = stats .map(|s| { Rgba::broadcast(1.0) - + Rgba::new(2.0, 2.0, 2.0, 0.0).map(|c| { + + Rgba::new(2.0, 2.0, 2., 0.00).map(|c| { (c / (1.0 + DAMAGE_FADE_COEFFICIENT * s.health.last_change.0)) as f32 }) }) @@ -627,6 +629,7 @@ impl FigureMgr { state_animation_rate, lpindex, true, + is_player, ); }, Body::QuadrupedSmall(_) => { @@ -707,6 +710,7 @@ impl FigureMgr { state_animation_rate, lpindex, true, + is_player, ); }, Body::QuadrupedMedium(_) => { @@ -789,6 +793,7 @@ impl FigureMgr { state_animation_rate, lpindex, true, + is_player, ); }, Body::BirdMedium(_) => { @@ -863,6 +868,7 @@ impl FigureMgr { state_animation_rate, lpindex, true, + is_player, ); }, Body::FishMedium(_) => { @@ -937,6 +943,7 @@ impl FigureMgr { state_animation_rate, lpindex, true, + is_player, ); }, Body::Dragon(_) => { @@ -1011,6 +1018,7 @@ impl FigureMgr { state_animation_rate, lpindex, true, + is_player, ); }, Body::Critter(_) => { @@ -1085,6 +1093,7 @@ impl FigureMgr { state_animation_rate, lpindex, true, + is_player, ); }, Body::BirdSmall(_) => { @@ -1159,6 +1168,7 @@ impl FigureMgr { state_animation_rate, lpindex, true, + is_player, ); }, Body::FishSmall(_) => { @@ -1233,6 +1243,7 @@ impl FigureMgr { state_animation_rate, lpindex, true, + is_player, ); }, Body::BipedLarge(_) => { @@ -1307,6 +1318,7 @@ impl FigureMgr { state_animation_rate, lpindex, true, + is_player, ); }, Body::Object(_) => { @@ -1326,6 +1338,7 @@ impl FigureMgr { state_animation_rate, lpindex, true, + is_player, ); }, } @@ -1387,201 +1400,121 @@ impl FigureMgr { .filter(|(_, _, _, _, stats, _, _)| stats.map_or(true, |s| !s.is_dead)) { let is_player = entity == player_entity; - let player_camera_mode = if is_player { - camera.get_mode() - } else { - CameraMode::default() - }; - let character_state = if is_player { character_state } else { None }; - let FigureMgr { - model_cache, - critter_model_cache, - quadruped_small_model_cache, - quadruped_medium_model_cache, - bird_medium_model_cache, - bird_small_model_cache, - dragon_model_cache, - fish_medium_model_cache, - fish_small_model_cache, - biped_large_model_cache, - character_states, - quadruped_small_states, - quadruped_medium_states, - bird_medium_states, - fish_medium_states, - critter_states, - dragon_states, - bird_small_states, - fish_small_states, - biped_large_states, - object_states, - } = self; - if let Some((locals, bone_consts, model)) = match body { - Body::Humanoid(_) => character_states - .get(&entity) - .filter(|state| state.visible) - .map(|state| { - ( - state.locals(), - state.bone_consts(), - &model_cache - .get_or_create_model( - renderer, - *body, - loadout, - tick, - player_camera_mode, - character_state, - ) - .0, - ) - }), - Body::QuadrupedSmall(_) => quadruped_small_states.get(&entity).map(|state| { - ( - state.locals(), - state.bone_consts(), - &quadruped_small_model_cache - .get_or_create_model( - renderer, - *body, - loadout, - tick, - player_camera_mode, - character_state, - ) - .0, - ) - }), - Body::QuadrupedMedium(_) => quadruped_medium_states.get(&entity).map(|state| { - ( - state.locals(), - state.bone_consts(), - &quadruped_medium_model_cache - .get_or_create_model( - renderer, - *body, - loadout, - tick, - player_camera_mode, - character_state, - ) - .0, - ) - }), - Body::BirdMedium(_) => bird_medium_states.get(&entity).map(|state| { - ( - state.locals(), - state.bone_consts(), - &bird_medium_model_cache - .get_or_create_model( - renderer, - *body, - loadout, - tick, - player_camera_mode, - character_state, - ) - .0, - ) - }), - Body::FishMedium(_) => fish_medium_states.get(&entity).map(|state| { - ( - state.locals(), - state.bone_consts(), - &fish_medium_model_cache - .get_or_create_model( - renderer, - *body, - loadout, - tick, - player_camera_mode, - character_state, - ) - .0, - ) - }), - Body::Critter(_) => critter_states.get(&entity).map(|state| { - ( - state.locals(), - state.bone_consts(), - &critter_model_cache - .get_or_create_model( - renderer, - *body, - loadout, - tick, - player_camera_mode, - character_state, - ) - .0, - ) - }), - Body::Dragon(_) => dragon_states.get(&entity).map(|state| { - ( - state.locals(), - state.bone_consts(), - &dragon_model_cache - .get_or_create_model( - renderer, - *body, - loadout, - tick, - player_camera_mode, - character_state, - ) - .0, - ) - }), - Body::BirdSmall(_) => bird_small_states.get(&entity).map(|state| { - ( - state.locals(), - state.bone_consts(), - &bird_small_model_cache - .get_or_create_model( - renderer, - *body, - loadout, - tick, - player_camera_mode, - character_state, - ) - .0, - ) - }), - Body::FishSmall(_) => fish_small_states.get(&entity).map(|state| { - ( - state.locals(), - state.bone_consts(), - &fish_small_model_cache - .get_or_create_model( - renderer, - *body, - loadout, - tick, - player_camera_mode, - character_state, - ) - .0, - ) - }), - Body::BipedLarge(_) => biped_large_states.get(&entity).map(|state| { - ( - state.locals(), - state.bone_consts(), - &biped_large_model_cache - .get_or_create_model( - renderer, - *body, - loadout, - tick, - player_camera_mode, - character_state, - ) - .0, - ) - }), - Body::Object(_) => object_states.get(&entity).map(|state| { + if !is_player { + self.render_figure( + renderer, + tick, + globals, + lights, + shadows, + lod, + camera, + character_state, + entity, + body, + loadout, + false, + ); + } + } + } + + pub fn render_player( + &mut self, + renderer: &mut Renderer, + state: &State, + player_entity: EcsEntity, + tick: u64, + globals: &Consts, + lights: &Consts, + shadows: &Consts, + lod: &Lod, + camera: &Camera, + ) { + let ecs = state.ecs(); + + let character_state_storage = state.read_storage::(); + let character_state = character_state_storage.get(player_entity); + + if let Some(body) = ecs.read_storage::().get(player_entity) { + let stats_storage = state.read_storage::(); + let stats = stats_storage.get(player_entity); + + if stats.map_or(false, |s| s.is_dead) { + return; + } + + let loadout_storage = ecs.read_storage::(); + let loadout = loadout_storage.get(player_entity); + + self.render_figure( + renderer, + tick, + globals, + lights, + shadows, + lod, + camera, + character_state, + player_entity, + body, + loadout, + true, + ); + } + } + + fn render_figure( + &mut self, + renderer: &mut Renderer, + tick: u64, + globals: &Consts, + lights: &Consts, + shadows: &Consts, + lod: &Lod, + camera: &Camera, + character_state: Option<&CharacterState>, + entity: EcsEntity, + body: &Body, + loadout: Option<&Loadout>, + is_player: bool, + ) { + let player_camera_mode = if is_player { + camera.get_mode() + } else { + CameraMode::default() + }; + let character_state = if is_player { character_state } else { None }; + + let FigureMgr { + model_cache, + critter_model_cache, + quadruped_small_model_cache, + quadruped_medium_model_cache, + bird_medium_model_cache, + bird_small_model_cache, + dragon_model_cache, + fish_medium_model_cache, + fish_small_model_cache, + biped_large_model_cache, + character_states, + quadruped_small_states, + quadruped_medium_states, + bird_medium_states, + fish_medium_states, + critter_states, + dragon_states, + bird_small_states, + fish_small_states, + biped_large_states, + object_states, + } = self; + if let Some((locals, bone_consts, model)) = match body { + Body::Humanoid(_) => character_states + .get(&entity) + .filter(|state| state.visible) + .map(|state| { ( state.locals(), state.bone_consts(), @@ -1597,8 +1530,179 @@ impl FigureMgr { .0, ) }), - } { - renderer.render_figure( + Body::QuadrupedSmall(_) => quadruped_small_states.get(&entity).map(|state| { + ( + state.locals(), + state.bone_consts(), + &quadruped_small_model_cache + .get_or_create_model( + renderer, + *body, + loadout, + tick, + player_camera_mode, + character_state, + ) + .0, + ) + }), + Body::QuadrupedMedium(_) => quadruped_medium_states.get(&entity).map(|state| { + ( + state.locals(), + state.bone_consts(), + &quadruped_medium_model_cache + .get_or_create_model( + renderer, + *body, + loadout, + tick, + player_camera_mode, + character_state, + ) + .0, + ) + }), + Body::BirdMedium(_) => bird_medium_states.get(&entity).map(|state| { + ( + state.locals(), + state.bone_consts(), + &bird_medium_model_cache + .get_or_create_model( + renderer, + *body, + loadout, + tick, + player_camera_mode, + character_state, + ) + .0, + ) + }), + Body::FishMedium(_) => fish_medium_states.get(&entity).map(|state| { + ( + state.locals(), + state.bone_consts(), + &fish_medium_model_cache + .get_or_create_model( + renderer, + *body, + loadout, + tick, + player_camera_mode, + character_state, + ) + .0, + ) + }), + Body::Critter(_) => critter_states.get(&entity).map(|state| { + ( + state.locals(), + state.bone_consts(), + &critter_model_cache + .get_or_create_model( + renderer, + *body, + loadout, + tick, + player_camera_mode, + character_state, + ) + .0, + ) + }), + Body::Dragon(_) => dragon_states.get(&entity).map(|state| { + ( + state.locals(), + state.bone_consts(), + &dragon_model_cache + .get_or_create_model( + renderer, + *body, + loadout, + tick, + player_camera_mode, + character_state, + ) + .0, + ) + }), + Body::BirdSmall(_) => bird_small_states.get(&entity).map(|state| { + ( + state.locals(), + state.bone_consts(), + &bird_small_model_cache + .get_or_create_model( + renderer, + *body, + loadout, + tick, + player_camera_mode, + character_state, + ) + .0, + ) + }), + Body::FishSmall(_) => fish_small_states.get(&entity).map(|state| { + ( + state.locals(), + state.bone_consts(), + &fish_small_model_cache + .get_or_create_model( + renderer, + *body, + loadout, + tick, + player_camera_mode, + character_state, + ) + .0, + ) + }), + Body::BipedLarge(_) => biped_large_states.get(&entity).map(|state| { + ( + state.locals(), + state.bone_consts(), + &biped_large_model_cache + .get_or_create_model( + renderer, + *body, + loadout, + tick, + player_camera_mode, + character_state, + ) + .0, + ) + }), + Body::Object(_) => object_states.get(&entity).map(|state| { + ( + state.locals(), + state.bone_consts(), + &model_cache + .get_or_create_model( + renderer, + *body, + loadout, + tick, + player_camera_mode, + character_state, + ) + .0, + ) + }), + } { + if is_player { + renderer.render_player( + model, + globals, + locals, + bone_consts, + lights, + shadows, + &lod.map, + &lod.horizon, + ); + renderer.render_player_shadow( model, globals, locals, @@ -1609,8 +1713,19 @@ impl FigureMgr { &lod.horizon, ); } else { - trace!("Body has no saved figure"); + renderer.render_figure( + model, + globals, + locals, + bone_consts, + lights, + shadows, + &lod.map, + &lod.horizon, + ); } + } else { + trace!("Body has no saved figure"); } } @@ -1715,6 +1830,7 @@ impl FigureState { state_animation_rate: f32, lpindex: u8, visible: bool, + is_player: bool, ) { self.visible = visible; self.lpindex = lpindex; @@ -1730,7 +1846,7 @@ impl FigureState { * Mat4::rotation_x(ori.z.atan2(Vec2::from(ori).magnitude())) * Mat4::scaling_3d(Vec3::from(0.8 * scale)); - let locals = FigureLocals::new(mat, col); + let locals = FigureLocals::new(mat, col, is_player); renderer.update_consts(&mut self.locals, &[locals]).unwrap(); renderer diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 1d393f6757..b82053f56c 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -83,6 +83,8 @@ pub struct SceneData<'a> { pub view_distance: u32, pub tick: u64, pub thread_pool: &'a uvth::ThreadPool, + pub gamma: f32, + pub mouse_smoothing: bool, } impl Scene { @@ -187,7 +189,6 @@ impl Scene { &mut self, renderer: &mut Renderer, audio: &mut AudioFrontend, - settings: &Settings, scene_data: &SceneData, ) { // Get player position. @@ -247,11 +248,15 @@ impl Scene { }; self.camera.set_focus_pos( - player_pos + Vec3::unit_z() * (up + dist * 0.15 - tilt.min(0.0) * dist * 0.75), + player_pos + Vec3::unit_z() * (up + dist * 0.15 - tilt.min(0.0) * dist * 0.4), ); // Tick camera for interpolation. - self.camera.update(scene_data.state.get_time()); + self.camera.update( + scene_data.state.get_time(), + scene_data.state.get_delta_time(), + scene_data.mouse_smoothing, + ); // Compute camera matrices. self.camera.compute_dependents(&*scene_data.state.terrain()); @@ -358,7 +363,8 @@ impl Scene { .map(|b| b.kind()) .unwrap_or(BlockKind::Air), self.select_pos, - settings.graphics.gamma, + scene_data.gamma, + self.camera.get_mode(), )]) .expect("Failed to update global constants"); @@ -405,7 +411,12 @@ impl Scene { &self.lod, self.camera.get_focus_pos(), ); - self.figure_mgr.render( + self.lod.render(renderer, &self.globals); + + // Render the skybox. + renderer.render_skybox(&self.skybox.model, &self.globals, &self.skybox.locals); + + self.figure_mgr.render_player( renderer, state, player_entity, @@ -416,10 +427,6 @@ impl Scene { &self.lod, &self.camera, ); - self.lod.render(renderer, &self.globals); - - // Render the skybox. - renderer.render_skybox(&self.skybox.model, &self.globals, &self.skybox.locals); self.terrain.render_translucent( renderer, diff --git a/voxygen/src/scene/simple.rs b/voxygen/src/scene/simple.rs index c8837429c3..8e936b9ae3 100644 --- a/voxygen/src/scene/simple.rs +++ b/voxygen/src/scene/simple.rs @@ -78,6 +78,7 @@ pub struct SceneData { pub tick: u64, pub body: Option, pub gamma: f32, + pub mouse_smoothing: bool, } impl Scene { @@ -159,7 +160,8 @@ impl Scene { } pub fn maintain(&mut self, renderer: &mut Renderer, scene_data: SceneData) { - self.camera.update(scene_data.time); + self.camera + .update(scene_data.time, 1.0 / 60.0, scene_data.mouse_smoothing); self.camera.compute_dependents(&VoidVol); let camera::Dependents { @@ -185,6 +187,7 @@ impl Scene { BlockKind::Air, None, scene_data.gamma, + self.camera.get_mode(), )]) { error!("Renderer failed to update: {:?}", err); } @@ -214,6 +217,7 @@ impl Scene { 1.0, 0, true, + false, ); } diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index ac72b88091..d6249ef9a4 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -59,6 +59,22 @@ struct SpriteConfig { fn sprite_config_for(kind: BlockKind) -> Option { match kind { + BlockKind::Window1 => Some(SpriteConfig { + variations: 1, + wind_sway: 0.0, + }), + BlockKind::Window2 => Some(SpriteConfig { + variations: 1, + wind_sway: 0.0, + }), + BlockKind::Window3 => Some(SpriteConfig { + variations: 1, + wind_sway: 0.0, + }), + BlockKind::Window4 => Some(SpriteConfig { + variations: 1, + wind_sway: 0.0, + }), BlockKind::LargeCactus => Some(SpriteConfig { variations: 2, wind_sway: 0.0, @@ -151,7 +167,7 @@ fn sprite_config_for(kind: BlockKind) -> Option { wind_sway: 0.1, }), BlockKind::Pumpkin => Some(SpriteConfig { - variations: 5, + variations: 7, wind_sway: 0.0, }), BlockKind::LingonBerry => Some(SpriteConfig { @@ -170,7 +186,62 @@ fn sprite_config_for(kind: BlockKind) -> Option { variations: 4, wind_sway: 0.1, }), - + BlockKind::Ember => Some(SpriteConfig { + variations: 1, + wind_sway: 0.8, + }), + BlockKind::Corn => Some(SpriteConfig { + variations: 6, + wind_sway: 0.4, + }), + BlockKind::WheatYellow => Some(SpriteConfig { + variations: 10, + wind_sway: 0.4, + }), + BlockKind::WheatGreen => Some(SpriteConfig { + variations: 10, + wind_sway: 0.4, + }), + BlockKind::Cabbage => Some(SpriteConfig { + variations: 3, + wind_sway: 0.0, + }), + BlockKind::Flax => Some(SpriteConfig { + variations: 6, + wind_sway: 0.4, + }), + BlockKind::Carrot => Some(SpriteConfig { + variations: 6, + wind_sway: 0.1, + }), + BlockKind::Tomato => Some(SpriteConfig { + variations: 5, + wind_sway: 0.0, + }), + BlockKind::Radish => Some(SpriteConfig { + variations: 5, + wind_sway: 0.1, + }), + BlockKind::Turnip => Some(SpriteConfig { + variations: 6, + wind_sway: 0.1, + }), + BlockKind::Coconut => Some(SpriteConfig { + variations: 1, + wind_sway: 0.0, + }), + BlockKind::Scarecrow => Some(SpriteConfig { + variations: 1, + wind_sway: 0.0, + }), + BlockKind::StreetLamp => Some(SpriteConfig { + variations: 1, + wind_sway: 0.0, + }), + BlockKind::Door => Some(SpriteConfig { + variations: 1, + wind_sway: 0.0, + }), _ => None, } } @@ -199,16 +270,17 @@ fn mesh_worker + RectRasterableVol + ReadVol + Debug>( let wpos = Vec3::from(pos * V::RECT_SIZE.map(|e: u32| e as i32)) + Vec3::new(x, y, z); - let kind = volume.get(wpos).unwrap_or(&Block::empty()).kind(); + let block = volume.get(wpos).ok().copied().unwrap_or(Block::empty()); - if let Some(cfg) = sprite_config_for(kind) { + if let Some(cfg) = sprite_config_for(block.kind()) { let seed = wpos.x as u64 * 3 + wpos.y as u64 * 7 + wpos.x as u64 * wpos.y as u64; // Awful PRNG + let ori = block.get_ori().unwrap_or((seed % 4) as u8 * 2); let instance = SpriteInstance::new( Mat4::identity() - .rotated_z(f32::consts::PI * 0.5 * (seed % 4) as f32) + .rotated_z(f32::consts::PI * 0.25 * ori as f32) .translated_3d( wpos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0), ), @@ -217,7 +289,7 @@ fn mesh_worker + RectRasterableVol + ReadVol + Debug>( ); instances - .entry((kind, seed as usize % cfg.variations)) + .entry((block.kind(), seed as usize % cfg.variations)) .or_insert_with(|| Vec::new()) .push(instance); } @@ -272,6 +344,35 @@ impl Terrain { mesh_recv: recv, mesh_todo: HashMap::default(), sprite_models: vec![ + // Windows + ( + (BlockKind::Window1, 0), + make_model( + "voxygen.voxel.sprite.window.window-0", + Vec3::new(-5.5, -5.5, 0.0), + ), + ), + ( + (BlockKind::Window2, 0), + make_model( + "voxygen.voxel.sprite.window.window-1", + Vec3::new(-5.5, -5.5, 0.0), + ), + ), + ( + (BlockKind::Window3, 0), + make_model( + "voxygen.voxel.sprite.window.window-2", + Vec3::new(-5.5, -5.5, 0.0), + ), + ), + ( + (BlockKind::Window4, 0), + make_model( + "voxygen.voxel.sprite.window.window-3", + Vec3::new(-5.5, -5.5, 0.0), + ), + ), // Cacti ( (BlockKind::LargeCactus, 0), @@ -831,6 +932,20 @@ impl Terrain { Vec3::new(-6.0, -6.0, -0.0), ), ), + ( + (BlockKind::Pumpkin, 5), + make_model( + "voxygen.voxel.sprite.pumpkin.6", + Vec3::new(-7.0, -6.5, -0.0), + ), + ), + ( + (BlockKind::Pumpkin, 6), + make_model( + "voxygen.voxel.sprite.pumpkin.7", + Vec3::new(-7.0, -9.5, -0.0), + ), + ), //Lingonberries ( (BlockKind::LingonBerry, 0), @@ -1066,6 +1181,435 @@ impl Terrain { Vec3::new(-6.0, -6.0, -0.0), ), ), + // Ember + ( + (BlockKind::Ember, 0), + make_model("voxygen.voxel.sprite.ember.1", Vec3::new(-7.0, -7.0, -2.9)), + ), + // Corn + ( + (BlockKind::Corn, 0), + make_model( + "voxygen.voxel.sprite.corn.corn-0", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::Corn, 1), + make_model( + "voxygen.voxel.sprite.corn.corn-1", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::Corn, 2), + make_model( + "voxygen.voxel.sprite.corn.corn-2", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::Corn, 3), + make_model( + "voxygen.voxel.sprite.corn.corn-3", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::Corn, 4), + make_model( + "voxygen.voxel.sprite.corn.corn-4", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::Corn, 5), + make_model( + "voxygen.voxel.sprite.corn.corn-5", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + // Yellow Wheat + ( + (BlockKind::WheatYellow, 0), + make_model( + "voxygen.voxel.sprite.wheat_yellow.wheat-0", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatYellow, 1), + make_model( + "voxygen.voxel.sprite.wheat_yellow.wheat-1", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatYellow, 2), + make_model( + "voxygen.voxel.sprite.wheat_yellow.wheat-2", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatYellow, 3), + make_model( + "voxygen.voxel.sprite.wheat_yellow.wheat-3", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatYellow, 4), + make_model( + "voxygen.voxel.sprite.wheat_yellow.wheat-4", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatYellow, 5), + make_model( + "voxygen.voxel.sprite.wheat_yellow.wheat-5", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatYellow, 6), + make_model( + "voxygen.voxel.sprite.wheat_yellow.wheat-6", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatYellow, 7), + make_model( + "voxygen.voxel.sprite.wheat_yellow.wheat-7", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatYellow, 8), + make_model( + "voxygen.voxel.sprite.wheat_yellow.wheat-8", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatYellow, 9), + make_model( + "voxygen.voxel.sprite.wheat_yellow.wheat-9", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + // Green Wheat + ( + (BlockKind::WheatGreen, 0), + make_model( + "voxygen.voxel.sprite.wheat_green.wheat-0", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatGreen, 1), + make_model( + "voxygen.voxel.sprite.wheat_green.wheat-1", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatGreen, 2), + make_model( + "voxygen.voxel.sprite.wheat_green.wheat-2", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatGreen, 3), + make_model( + "voxygen.voxel.sprite.wheat_green.wheat-3", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatGreen, 4), + make_model( + "voxygen.voxel.sprite.wheat_green.wheat-4", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatGreen, 5), + make_model( + "voxygen.voxel.sprite.wheat_green.wheat-5", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatGreen, 6), + make_model( + "voxygen.voxel.sprite.wheat_green.wheat-6", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatGreen, 7), + make_model( + "voxygen.voxel.sprite.wheat_green.wheat-7", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatGreen, 8), + make_model( + "voxygen.voxel.sprite.wheat_green.wheat-8", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::WheatGreen, 9), + make_model( + "voxygen.voxel.sprite.wheat_green.wheat-9", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + // Cabbage + ( + (BlockKind::Cabbage, 0), + make_model( + "voxygen.voxel.sprite.cabbage.cabbage-0", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::Cabbage, 1), + make_model( + "voxygen.voxel.sprite.cabbage.cabbage-1", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::Cabbage, 2), + make_model( + "voxygen.voxel.sprite.cabbage.cabbage-2", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + // Flax + ( + (BlockKind::Flax, 0), + make_model( + "voxygen.voxel.sprite.flax.flax-0", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::Flax, 1), + make_model( + "voxygen.voxel.sprite.flax.flax-1", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::Flax, 2), + make_model( + "voxygen.voxel.sprite.flax.flax-2", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::Flax, 3), + make_model( + "voxygen.voxel.sprite.flax.flax-3", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::Flax, 4), + make_model( + "voxygen.voxel.sprite.flax.flax-4", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + ( + (BlockKind::Flax, 5), + make_model( + "voxygen.voxel.sprite.flax.flax-5", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + // Carrot + ( + (BlockKind::Carrot, 0), + make_model( + "voxygen.voxel.sprite.carrot.0", + Vec3::new(-5.5, -5.5, -0.25), + ), + ), + ( + (BlockKind::Carrot, 1), + make_model( + "voxygen.voxel.sprite.carrot.1", + Vec3::new(-5.5, -5.5, -0.25), + ), + ), + ( + (BlockKind::Carrot, 2), + make_model( + "voxygen.voxel.sprite.carrot.2", + Vec3::new(-5.5, -5.5, -0.25), + ), + ), + ( + (BlockKind::Carrot, 3), + make_model( + "voxygen.voxel.sprite.carrot.3", + Vec3::new(-5.5, -5.5, -0.25), + ), + ), + ( + (BlockKind::Carrot, 4), + make_model( + "voxygen.voxel.sprite.carrot.4", + Vec3::new(-5.5, -5.5, -0.25), + ), + ), + ( + (BlockKind::Carrot, 5), + make_model( + "voxygen.voxel.sprite.carrot.5", + Vec3::new(-5.5, -5.5, -0.25), + ), + ), + ( + (BlockKind::Tomato, 0), + make_model("voxygen.voxel.sprite.tomato.0", Vec3::new(-5.5, -5.5, 0.0)), + ), + ( + (BlockKind::Tomato, 1), + make_model("voxygen.voxel.sprite.tomato.1", Vec3::new(-5.5, -5.5, 0.0)), + ), + ( + (BlockKind::Tomato, 2), + make_model("voxygen.voxel.sprite.tomato.2", Vec3::new(-5.5, -5.5, 0.0)), + ), + ( + (BlockKind::Tomato, 3), + make_model("voxygen.voxel.sprite.tomato.3", Vec3::new(-5.5, -5.5, 0.0)), + ), + ( + (BlockKind::Tomato, 4), + make_model("voxygen.voxel.sprite.tomato.4", Vec3::new(-5.5, -5.5, 0.0)), + ), + // Radish + ( + (BlockKind::Radish, 0), + make_model( + "voxygen.voxel.sprite.radish.0", + Vec3::new(-5.5, -5.5, -0.25), + ), + ), + ( + (BlockKind::Radish, 1), + make_model( + "voxygen.voxel.sprite.radish.1", + Vec3::new(-5.5, -5.5, -0.25), + ), + ), + ( + (BlockKind::Radish, 2), + make_model( + "voxygen.voxel.sprite.radish.2", + Vec3::new(-5.5, -5.5, -0.25), + ), + ), + ( + (BlockKind::Radish, 3), + make_model( + "voxygen.voxel.sprite.radish.3", + Vec3::new(-5.5, -5.5, -0.25), + ), + ), + ( + (BlockKind::Radish, 4), + make_model( + "voxygen.voxel.sprite.radish.4", + Vec3::new(-5.5, -5.5, -0.25), + ), + ), + // Turnip + ( + (BlockKind::Turnip, 0), + make_model( + "voxygen.voxel.sprite.turnip.turnip-0", + Vec3::new(-5.5, -5.5, -0.25), + ), + ), + ( + (BlockKind::Turnip, 1), + make_model( + "voxygen.voxel.sprite.turnip.turnip-1", + Vec3::new(-5.5, -5.5, -0.25), + ), + ), + ( + (BlockKind::Turnip, 2), + make_model( + "voxygen.voxel.sprite.turnip.turnip-2", + Vec3::new(-5.5, -5.5, -0.25), + ), + ), + ( + (BlockKind::Turnip, 3), + make_model( + "voxygen.voxel.sprite.turnip.turnip-3", + Vec3::new(-5.5, -5.5, -0.25), + ), + ), + ( + (BlockKind::Turnip, 4), + make_model( + "voxygen.voxel.sprite.turnip.turnip-4", + Vec3::new(-5.5, -5.5, -0.25), + ), + ), + ( + (BlockKind::Turnip, 5), + make_model( + "voxygen.voxel.sprite.turnip.turnip-5", + Vec3::new(-5.5, -5.5, -0.25), + ), + ), + // Coconut + ( + (BlockKind::Coconut, 0), + make_model( + "voxygen.voxel.sprite.fruit.coconut", + Vec3::new(-6.0, -6.0, 0.0), + ), + ), + // Scarecrow + ( + (BlockKind::Scarecrow, 0), + make_model( + "voxygen.voxel.sprite.misc.scarecrow", + Vec3::new(-9.5, -3.0, -0.25), + ), + ), + // Street Light + ( + (BlockKind::StreetLamp, 0), + make_model( + "voxygen.voxel.sprite.misc.street_lamp", + Vec3::new(-4.5, -4.5, 0.0), + ), + ), + // Door + ( + (BlockKind::Door, 0), + make_model( + "voxygen.voxel.sprite.door.door-0", + Vec3::new(-5.5, -5.5, 0.0), + ), + ), ] .into_iter() .collect(), @@ -1479,6 +2023,9 @@ impl Terrain { .as_ref() .map(|model| (model, &chunk.locals)) }) + .collect::>() + .into_iter() + .rev() // Render back-to-front .for_each(|(model, locals)| { renderer.render_fluid_chunk( model, diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 6bc4f79d9b..4dc84d10f3 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -286,9 +286,6 @@ impl PlayState for SessionState { } }, - Event::InputUpdate(GameInput::Ability3, state) => { - self.inputs.ability3.set_state(state); - }, Event::InputUpdate(GameInput::Roll, state) => { let client = self.client.borrow(); if client @@ -340,9 +337,9 @@ impl PlayState for SessionState { Event::InputUpdate(GameInput::ClimbDown, state) => { self.key_state.climb_down = state; }, - Event::InputUpdate(GameInput::WallLeap, state) => { + /*Event::InputUpdate(GameInput::WallLeap, state) => { self.inputs.wall_leap.set_state(state) - }, + },*/ Event::InputUpdate(GameInput::ToggleWield, state) if state != self.key_state.toggle_wield => { @@ -424,9 +421,9 @@ impl PlayState for SessionState { } } }, - Event::InputUpdate(GameInput::Charge, state) => { + /*Event::InputUpdate(GameInput::Charge, state) => { self.inputs.charge.set_state(state); - }, + },*/ Event::InputUpdate(GameInput::FreeLook, state) => { match (global_state.settings.gameplay.free_look_behavior, state) { (PressBehavior::Toggle, true) => { @@ -586,6 +583,10 @@ impl PlayState for SessionState { global_state.settings.gameplay.mouse_y_inversion = mouse_y_inverted; global_state.settings.save_to_file_warn(); }, + HudEvent::ToggleSmoothPan(smooth_pan_enabled) => { + global_state.settings.gameplay.smooth_pan_enable = smooth_pan_enabled; + global_state.settings.save_to_file_warn(); + }, HudEvent::AdjustViewDistance(view_distance) => { self.client.borrow_mut().set_view_distance(view_distance); @@ -653,13 +654,10 @@ impl PlayState for SessionState { global_state.settings.graphics.max_fps = fps; global_state.settings.save_to_file_warn(); }, - HudEvent::UseInventorySlot(x) => self.client.borrow_mut().use_inventory_slot(x), - HudEvent::SwapInventorySlots(a, b) => { - self.client.borrow_mut().swap_inventory_slots(a, b) - }, - HudEvent::DropInventorySlot(x) => { - self.client.borrow_mut().drop_inventory_slot(x) - }, + HudEvent::UseSlot(x) => self.client.borrow_mut().use_slot(x), + HudEvent::SwapSlots(a, b) => self.client.borrow_mut().swap_slots(a, b), + HudEvent::DropSlot(x) => self.client.borrow_mut().drop_slot(x), + HudEvent::Ability3(state) => self.inputs.ability3.set_state(state), HudEvent::ChangeFOV(new_fov) => { global_state.settings.graphics.fov = new_fov; global_state.settings.save_to_file_warn(); @@ -723,6 +721,9 @@ impl PlayState for SessionState { global_state.settings.graphics.window_size = new_size; global_state.settings.save_to_file_warn(); }, + HudEvent::ChangeBinding(game_input) => { + global_state.window.set_keybinding_mode(game_input); + }, HudEvent::ChangeFreeLookBehavior(behavior) => { global_state.settings.gameplay.free_look_behavior = behavior; }, @@ -741,11 +742,12 @@ impl PlayState for SessionState { view_distance: client.view_distance().unwrap_or(1), tick: client.get_tick(), thread_pool: client.thread_pool(), + gamma: global_state.settings.graphics.gamma, + mouse_smoothing: global_state.settings.gameplay.smooth_pan_enable, }; self.scene.maintain( global_state.window.renderer_mut(), &mut global_state.audio, - &global_state.settings, &scene_data, ); } diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index 1d7974c3c3..baec31a12b 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -3,7 +3,7 @@ use crate::{ i18n, render::{AaMode, CloudMode, FluidMode}, ui::ScaleMode, - window::KeyMouse, + window::{GameInput, KeyMouse}, }; use directories::{ProjectDirs, UserDirs}; use glutin::{MouseButton, VirtualKeyCode}; @@ -12,46 +12,47 @@ use log::warn; use serde_derive::{Deserialize, Serialize}; use std::{fs, io::prelude::*, path::PathBuf}; +// ControlSetting-like struct used by Serde, to handle not serializing/building +// post-deserializing the inverse_keybindings hashmap +#[derive(Serialize, Deserialize)] +struct ControlSettingsSerde { + keybindings: HashMap, +} + +impl From for ControlSettingsSerde { + fn from(control_settings: ControlSettings) -> Self { + let mut user_bindings: HashMap = HashMap::new(); + // Do a delta between default() ControlSettings and the argument, and let + // keybindings be only the custom keybindings chosen by the user. + for (k, v) in control_settings.keybindings { + if ControlSettings::default_binding(k) != v { + // Keybinding chosen by the user + user_bindings.insert(k, v); + } + } + ControlSettingsSerde { + keybindings: user_bindings, + } + } +} + /// `ControlSettings` contains keybindings. #[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(default)] +#[serde(from = "ControlSettingsSerde", into = "ControlSettingsSerde")] pub struct ControlSettings { - pub primary: KeyMouse, - pub secondary: KeyMouse, - pub ability3: KeyMouse, - pub toggle_cursor: KeyMouse, - pub escape: KeyMouse, - pub enter: KeyMouse, - pub command: KeyMouse, - pub move_forward: KeyMouse, - pub move_left: KeyMouse, - pub move_back: KeyMouse, - pub move_right: KeyMouse, - pub jump: KeyMouse, - pub sit: KeyMouse, - pub glide: KeyMouse, - pub climb: KeyMouse, - pub climb_down: KeyMouse, - pub wall_leap: KeyMouse, - pub mount: KeyMouse, - pub map: KeyMouse, - pub bag: KeyMouse, - pub social: KeyMouse, - pub spellbook: KeyMouse, - pub settings: KeyMouse, - pub help: KeyMouse, - pub toggle_interface: KeyMouse, - pub toggle_debug: KeyMouse, - pub fullscreen: KeyMouse, - pub screenshot: KeyMouse, - pub toggle_ingame_ui: KeyMouse, - pub roll: KeyMouse, - pub respawn: KeyMouse, - pub interact: KeyMouse, - pub toggle_wield: KeyMouse, - pub swap_loadout: KeyMouse, - pub charge: KeyMouse, - pub free_look: KeyMouse, + pub keybindings: HashMap, + pub inverse_keybindings: HashMap>, // used in event loop +} + +impl From for ControlSettings { + fn from(control_serde: ControlSettingsSerde) -> Self { + let user_keybindings = control_serde.keybindings; + let mut control_settings = ControlSettings::default(); + for (k, v) in user_keybindings { + control_settings.modify_binding(k, v); + } + control_settings + } } /// Since Macbook trackpads lack middle click, on OS X we default to LShift @@ -64,46 +65,153 @@ const MIDDLE_CLICK_KEY: KeyMouse = KeyMouse::Key(VirtualKeyCode::LShift); #[cfg(not(target_os = "macos"))] const MIDDLE_CLICK_KEY: KeyMouse = KeyMouse::Mouse(MouseButton::Middle); +impl ControlSettings { + pub fn get_binding(&self, game_input: GameInput) -> Option { + self.keybindings.get(&game_input).copied() + } + + pub fn get_associated_game_inputs(&self, key_mouse: &KeyMouse) -> Option<&HashSet> { + self.inverse_keybindings.get(key_mouse) + } + + pub fn insert_binding(&mut self, game_input: GameInput, key_mouse: KeyMouse) { + self.keybindings.insert(game_input, key_mouse); + self.inverse_keybindings + .entry(key_mouse) + .or_default() + .insert(game_input); + } + + pub fn modify_binding(&mut self, game_input: GameInput, key_mouse: KeyMouse) { + // For the KeyMouse->GameInput hashmap, we first need to remove the GameInput + // from the old binding + if let Some(old_binding) = self.get_binding(game_input) { + self.inverse_keybindings + .entry(old_binding) + .or_default() + .remove(&game_input); + } + // then we add the GameInput to the proper key + self.inverse_keybindings + .entry(key_mouse) + .or_default() + .insert(game_input); + // For the GameInput->KeyMouse hashmap, just overwrite the value + self.keybindings.insert(game_input, key_mouse); + } + + pub fn default_binding(game_input: GameInput) -> KeyMouse { + // If a new GameInput is added, be sure to update ControlSettings::default() + // too! + match game_input { + GameInput::Primary => KeyMouse::Mouse(MouseButton::Left), + GameInput::Secondary => KeyMouse::Mouse(MouseButton::Right), + GameInput::ToggleCursor => KeyMouse::Key(VirtualKeyCode::Tab), + GameInput::Escape => KeyMouse::Key(VirtualKeyCode::Escape), + GameInput::Enter => KeyMouse::Key(VirtualKeyCode::Return), + GameInput::Command => KeyMouse::Key(VirtualKeyCode::Slash), + GameInput::MoveForward => KeyMouse::Key(VirtualKeyCode::W), + GameInput::MoveLeft => KeyMouse::Key(VirtualKeyCode::A), + GameInput::MoveBack => KeyMouse::Key(VirtualKeyCode::S), + GameInput::MoveRight => KeyMouse::Key(VirtualKeyCode::D), + GameInput::Jump => KeyMouse::Key(VirtualKeyCode::Space), + GameInput::Sit => KeyMouse::Key(VirtualKeyCode::K), + GameInput::Glide => KeyMouse::Key(VirtualKeyCode::LShift), + GameInput::Climb => KeyMouse::Key(VirtualKeyCode::Space), + GameInput::ClimbDown => KeyMouse::Key(VirtualKeyCode::LControl), + //GameInput::WallLeap => MIDDLE_CLICK_KEY, + GameInput::Mount => KeyMouse::Key(VirtualKeyCode::F), + GameInput::Map => KeyMouse::Key(VirtualKeyCode::M), + GameInput::Bag => KeyMouse::Key(VirtualKeyCode::B), + GameInput::Social => KeyMouse::Key(VirtualKeyCode::O), + GameInput::Spellbook => KeyMouse::Key(VirtualKeyCode::P), + GameInput::Settings => KeyMouse::Key(VirtualKeyCode::N), + GameInput::Help => KeyMouse::Key(VirtualKeyCode::F1), + GameInput::ToggleInterface => KeyMouse::Key(VirtualKeyCode::F2), + GameInput::ToggleDebug => KeyMouse::Key(VirtualKeyCode::F3), + GameInput::Fullscreen => KeyMouse::Key(VirtualKeyCode::F11), + GameInput::Screenshot => KeyMouse::Key(VirtualKeyCode::F4), + GameInput::ToggleIngameUi => KeyMouse::Key(VirtualKeyCode::F6), + GameInput::Roll => MIDDLE_CLICK_KEY, + GameInput::Respawn => KeyMouse::Key(VirtualKeyCode::Space), + GameInput::Interact => KeyMouse::Mouse(MouseButton::Right), + GameInput::ToggleWield => KeyMouse::Key(VirtualKeyCode::T), + //GameInput::Charge => KeyMouse::Key(VirtualKeyCode::Key1), + GameInput::FreeLook => KeyMouse::Key(VirtualKeyCode::L), + GameInput::Slot1 => KeyMouse::Key(VirtualKeyCode::Key1), + GameInput::Slot2 => KeyMouse::Key(VirtualKeyCode::Key2), + GameInput::Slot3 => KeyMouse::Key(VirtualKeyCode::Key3), + GameInput::Slot4 => KeyMouse::Key(VirtualKeyCode::Key4), + GameInput::Slot5 => KeyMouse::Key(VirtualKeyCode::Key5), + GameInput::Slot6 => KeyMouse::Key(VirtualKeyCode::Key6), + GameInput::Slot7 => KeyMouse::Key(VirtualKeyCode::Key7), + GameInput::Slot8 => KeyMouse::Key(VirtualKeyCode::Key8), + GameInput::Slot9 => KeyMouse::Key(VirtualKeyCode::Key9), + GameInput::Slot10 => KeyMouse::Key(VirtualKeyCode::Q), + GameInput::SwapLoadout => KeyMouse::Key(VirtualKeyCode::LAlt), + } + } +} impl Default for ControlSettings { fn default() -> Self { - Self { - primary: KeyMouse::Mouse(MouseButton::Left), - secondary: KeyMouse::Mouse(MouseButton::Right), - ability3: KeyMouse::Key(VirtualKeyCode::Key1), - toggle_cursor: KeyMouse::Key(VirtualKeyCode::Tab), - escape: KeyMouse::Key(VirtualKeyCode::Escape), - enter: KeyMouse::Key(VirtualKeyCode::Return), - command: KeyMouse::Key(VirtualKeyCode::Slash), - move_forward: KeyMouse::Key(VirtualKeyCode::W), - move_left: KeyMouse::Key(VirtualKeyCode::A), - move_back: KeyMouse::Key(VirtualKeyCode::S), - move_right: KeyMouse::Key(VirtualKeyCode::D), - jump: KeyMouse::Key(VirtualKeyCode::Space), - sit: KeyMouse::Key(VirtualKeyCode::K), - glide: KeyMouse::Key(VirtualKeyCode::LShift), - climb: KeyMouse::Key(VirtualKeyCode::Space), - climb_down: KeyMouse::Key(VirtualKeyCode::LControl), - wall_leap: MIDDLE_CLICK_KEY, - mount: KeyMouse::Key(VirtualKeyCode::F), - map: KeyMouse::Key(VirtualKeyCode::M), - bag: KeyMouse::Key(VirtualKeyCode::B), - social: KeyMouse::Key(VirtualKeyCode::O), - spellbook: KeyMouse::Key(VirtualKeyCode::P), - settings: KeyMouse::Key(VirtualKeyCode::N), - help: KeyMouse::Key(VirtualKeyCode::F1), - toggle_interface: KeyMouse::Key(VirtualKeyCode::F2), - toggle_debug: KeyMouse::Key(VirtualKeyCode::F3), - fullscreen: KeyMouse::Key(VirtualKeyCode::F11), - screenshot: KeyMouse::Key(VirtualKeyCode::F4), - toggle_ingame_ui: KeyMouse::Key(VirtualKeyCode::F6), - roll: MIDDLE_CLICK_KEY, - respawn: KeyMouse::Key(VirtualKeyCode::Space), - interact: KeyMouse::Mouse(MouseButton::Right), - toggle_wield: KeyMouse::Key(VirtualKeyCode::T), - swap_loadout: KeyMouse::Key(VirtualKeyCode::Q), - charge: KeyMouse::Key(VirtualKeyCode::Key1), - free_look: KeyMouse::Key(VirtualKeyCode::L), + let mut new_settings = Self { + keybindings: HashMap::new(), + inverse_keybindings: HashMap::new(), + }; + // Sets the initial keybindings for those GameInputs. If a new one is created in + // future, you'll have to update default_binding, and you should update this vec + // too. + let game_inputs = vec![ + GameInput::Primary, + GameInput::Secondary, + GameInput::ToggleCursor, + GameInput::MoveForward, + GameInput::MoveBack, + GameInput::MoveLeft, + GameInput::MoveRight, + GameInput::Jump, + GameInput::Sit, + GameInput::Glide, + GameInput::Climb, + GameInput::ClimbDown, + //GameInput::WallLeap, + GameInput::Mount, + GameInput::Enter, + GameInput::Command, + GameInput::Escape, + GameInput::Map, + GameInput::Bag, + GameInput::Social, + GameInput::Spellbook, + GameInput::Settings, + GameInput::ToggleInterface, + GameInput::Help, + GameInput::ToggleDebug, + GameInput::Fullscreen, + GameInput::Screenshot, + GameInput::ToggleIngameUi, + GameInput::Roll, + GameInput::Respawn, + GameInput::Interact, + GameInput::ToggleWield, + //GameInput::Charge, + GameInput::FreeLook, + GameInput::Slot1, + GameInput::Slot2, + GameInput::Slot3, + GameInput::Slot4, + GameInput::Slot5, + GameInput::Slot6, + GameInput::Slot7, + GameInput::Slot8, + GameInput::Slot9, + GameInput::Slot10, + GameInput::SwapLoadout, + ]; + for game_input in game_inputs { + new_settings.insert_binding(game_input, ControlSettings::default_binding(game_input)); } + new_settings } } @@ -166,7 +274,7 @@ pub mod con_settings { pub glide: Button, pub climb: Button, pub climb_down: Button, - pub wall_leap: Button, + //pub wall_leap: Button, pub mount: Button, pub map: Button, pub bag: Button, @@ -186,7 +294,7 @@ pub mod con_settings { pub interact: Button, pub toggle_wield: Button, pub swap_loadout: Button, - pub charge: Button, + //pub charge: Button, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -252,7 +360,7 @@ pub mod con_settings { glide: Button::Simple(GilButton::LeftTrigger), climb: Button::Simple(GilButton::South), climb_down: Button::Simple(GilButton::Unknown), - wall_leap: Button::Simple(GilButton::Unknown), + //wall_leap: Button::Simple(GilButton::Unknown), mount: Button::Simple(GilButton::North), map: Button::Simple(GilButton::DPadRight), bag: Button::Simple(GilButton::DPadDown), @@ -272,7 +380,7 @@ pub mod con_settings { interact: Button::Simple(GilButton::LeftTrigger2), toggle_wield: Button::Simple(GilButton::DPadLeft), swap_loadout: Button::Simple(GilButton::Unknown), - charge: Button::Simple(GilButton::Unknown), + //charge: Button::Simple(GilButton::Unknown), } } } @@ -340,6 +448,7 @@ pub struct GameplaySettings { pub sct_player_batch: bool, pub sct_damage_batch: bool, pub mouse_y_inversion: bool, + pub smooth_pan_enable: bool, pub crosshair_transp: f32, pub chat_transp: f32, pub crosshair_type: CrosshairType, @@ -358,6 +467,7 @@ impl Default for GameplaySettings { zoom_sensitivity: 100, zoom_inversion: false, mouse_y_inversion: false, + smooth_pan_enable: true, toggle_debug: false, sct: true, sct_player_batch: true, diff --git a/voxygen/src/ui/graphic/mod.rs b/voxygen/src/ui/graphic/mod.rs index f1431aff08..acb3aa5be3 100644 --- a/voxygen/src/ui/graphic/mod.rs +++ b/voxygen/src/ui/graphic/mod.rs @@ -4,7 +4,7 @@ mod renderer; pub use renderer::{SampleStrat, Transform}; use crate::render::{Renderer, Texture}; -use dot_vox::DotVoxData; +use common::figure::Segment; use guillotiere::{size2, SimpleAtlasAllocator}; use hashbrown::{hash_map::Entry, HashMap}; use image::{DynamicImage, RgbaImage}; @@ -16,7 +16,8 @@ use vek::*; #[derive(Clone)] pub enum Graphic { Image(Arc), - Voxel(Arc, Transform, SampleStrat), + // Note: none of the users keep this Arc currently + Voxel(Arc, Transform, SampleStrat), Blank, } @@ -292,8 +293,8 @@ fn draw_graphic(graphic_map: &GraphicMap, graphic_id: Id, dims: Vec2) -> Op u32::from(dims.x), u32::from(dims.y), )), - Some(Graphic::Voxel(ref vox, trans, sample_strat)) => Some(renderer::draw_vox( - &vox.as_ref().into(), + Some(Graphic::Voxel(ref segment, trans, sample_strat)) => Some(renderer::draw_vox( + &segment, dims, trans.clone(), *sample_strat, diff --git a/voxygen/src/ui/img_ids.rs b/voxygen/src/ui/img_ids.rs index ea5a8ac294..2e64e904f7 100644 --- a/voxygen/src/ui/img_ids.rs +++ b/voxygen/src/ui/img_ids.rs @@ -1,7 +1,11 @@ use super::{Graphic, SampleStrat, Transform}; -use common::assets::{load, Error}; +use common::{ + assets::{load, Error}, + figure::Segment, +}; use dot_vox::DotVoxData; use image::DynamicImage; +use std::sync::Arc; use vek::*; pub enum BlankGraphic {} @@ -25,17 +29,25 @@ impl<'a> GraphicCreator<'a> for ImageGraphic { } pub enum VoxelGraphic {} +// TODO: Are these uneeded now that we have PixArtGraphic? pub enum VoxelSsGraphic {} pub enum VoxelSs4Graphic {} pub enum VoxelSs9Graphic {} + pub enum VoxelPixArtGraphic {} +fn load_segment(specifier: &str) -> Result, Error> { + let dot_vox = load::(specifier)?; + let seg = dot_vox.as_ref().into(); + Ok(Arc::new(seg)) +} + impl<'a> GraphicCreator<'a> for VoxelGraphic { type Specifier = &'a str; fn new_graphic(specifier: Self::Specifier) -> Result { Ok(Graphic::Voxel( - load::(specifier)?, + load_segment(specifier)?, Transform { ori: Quaternion::rotation_x(-std::f32::consts::PI / 2.0), ..Default::default() @@ -49,7 +61,7 @@ impl<'a> GraphicCreator<'a> for VoxelSsGraphic { fn new_graphic(specifier: Self::Specifier) -> Result { Ok(Graphic::Voxel( - load::(specifier.0)?, + load_segment(specifier.0)?, Transform { ori: Quaternion::rotation_x(-std::f32::consts::PI / 2.0), ..Default::default() @@ -63,7 +75,7 @@ impl<'a> GraphicCreator<'a> for VoxelSs4Graphic { fn new_graphic(specifier: Self::Specifier) -> Result { Ok(Graphic::Voxel( - load::(specifier)?, + load_segment(specifier)?, Transform { ori: Quaternion::rotation_x(-std::f32::consts::PI / 2.0), ..Default::default() @@ -77,7 +89,7 @@ impl<'a> GraphicCreator<'a> for VoxelSs9Graphic { fn new_graphic(specifier: Self::Specifier) -> Result { Ok(Graphic::Voxel( - load::(specifier)?, + load_segment(specifier)?, Transform { ori: Quaternion::rotation_x(-std::f32::consts::PI / 2.0), ..Default::default() @@ -91,7 +103,7 @@ impl<'a> GraphicCreator<'a> for VoxelPixArtGraphic { fn new_graphic(specifier: Self::Specifier) -> Result { Ok(Graphic::Voxel( - load::(specifier)?, + load_segment(specifier)?, Transform { ori: Quaternion::rotation_x(-std::f32::consts::PI / 2.0), ..Default::default() diff --git a/voxygen/src/ui/mod.rs b/voxygen/src/ui/mod.rs index a1e18915f6..4fcd0cb170 100644 --- a/voxygen/src/ui/mod.rs +++ b/voxygen/src/ui/mod.rs @@ -16,6 +16,7 @@ pub use widgets::{ image_slider::ImageSlider, ingame::{Ingame, Ingameable}, radio_list::RadioList, + slot, toggle_button::ToggleButton, tooltip::{Tooltip, TooltipManager, Tooltipable}, }; @@ -28,6 +29,7 @@ use crate::{ window::Window, Error, }; +#[rustfmt::skip] use ::image::GenericImageView; use cache::Cache; use common::{assets, util::srgba_to_linear}; diff --git a/voxygen/src/ui/widgets/ghost_image.rs b/voxygen/src/ui/widgets/ghost_image.rs new file mode 100644 index 0000000000..4ee1eb6010 --- /dev/null +++ b/voxygen/src/ui/widgets/ghost_image.rs @@ -0,0 +1,54 @@ +use conrod_core::{ + image, + widget::{ + self, + image::{State, Style}, + }, + Widget, WidgetCommon, +}; + +/// This widget is like conrod's `Image` widget except it always returns false +/// for is_over +#[derive(WidgetCommon)] +pub struct GhostImage { + #[conrod(common_builder)] + common: widget::CommonBuilder, + image_id: image::Id, + style: Style, +} + +impl GhostImage { + pub fn new(image_id: image::Id) -> Self { + Self { + common: widget::CommonBuilder::default(), + image_id, + style: Style::default(), + } + } +} + +impl Widget for GhostImage { + type Event = (); + type State = State; + type Style = Style; + + fn init_state(&self, _: widget::id::Generator) -> Self::State { + State { + src_rect: None, + image_id: self.image_id, + } + } + + fn style(&self) -> Self::Style { self.style.clone() } + + fn update(self, args: widget::UpdateArgs) -> Self::Event { + let widget::UpdateArgs { state, .. } = args; + + if state.image_id != self.image_id { + state.update(|state| state.image_id = self.image_id) + } + } + + // This is what we are here for + fn is_over(&self) -> widget::IsOverFn { |_, _, _| widget::IsOver::Bool(false) } +} diff --git a/voxygen/src/ui/widgets/mod.rs b/voxygen/src/ui/widgets/mod.rs index 7c26721f56..b27ecd39e7 100644 --- a/voxygen/src/ui/widgets/mod.rs +++ b/voxygen/src/ui/widgets/mod.rs @@ -1,6 +1,8 @@ +pub mod ghost_image; pub mod image_frame; pub mod image_slider; pub mod ingame; pub mod radio_list; +pub mod slot; pub mod toggle_button; pub mod tooltip; diff --git a/voxygen/src/ui/widgets/slot.rs b/voxygen/src/ui/widgets/slot.rs new file mode 100644 index 0000000000..d45bbb5e20 --- /dev/null +++ b/voxygen/src/ui/widgets/slot.rs @@ -0,0 +1,563 @@ +//! A widget for selecting a single value along some linear range. +use conrod_core::{ + builder_methods, image, + input::state::mouse, + text::font, + widget::{self, Image, Text}, + widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, +}; +use vek::*; + +const AMOUNT_SHADOW_OFFSET: [f64; 2] = [1.0, 1.0]; + +pub trait SlotKey: Copy { + type ImageKey: PartialEq + Send + 'static; + /// Returns an Option since the slot could be empty + fn image_key(&self, source: &C) -> Option<(Self::ImageKey, Option)>; + fn amount(&self, source: &C) -> Option; + fn image_id(key: &Self::ImageKey, source: &I) -> image::Id; +} + +pub trait SumSlot: Sized + PartialEq + Copy + Send + 'static {} + +pub struct ContentSize { + // Width divided by height + pub width_height_ratio: f32, + // Max fraction of slot widget size that each side can be + pub max_fraction: f32, +} + +pub struct SlotMaker<'a, C, I, S: SumSlot> { + pub empty_slot: image::Id, + pub filled_slot: image::Id, + pub selected_slot: image::Id, + // Is this useful? + pub background_color: Option, + pub content_size: ContentSize, + // How to scale content size relative to base content size when selected + pub selected_content_scale: f32, + pub amount_font: font::Id, + pub amount_font_size: u32, + pub amount_margins: Vec2, + pub amount_text_color: Color, + pub content_source: &'a C, + pub image_source: &'a I, + pub slot_manager: Option<&'a mut SlotManager>, +} + +impl<'a, C, I, S> SlotMaker<'a, C, I, S> +where + S: SumSlot, +{ + pub fn fabricate + Into>( + &mut self, + contents: K, + wh: [f32; 2], + ) -> Slot { + let content_size = { + let ContentSize { + max_fraction, + width_height_ratio, + } = self.content_size; + let w_max = max_fraction * wh[0]; + let h_max = max_fraction * wh[1]; + let max_ratio = w_max / h_max; + let (w, h) = if max_ratio > width_height_ratio { + (width_height_ratio * h_max, w_max) + } else { + (w_max, w_max / width_height_ratio) + }; + Vec2::new(w, h) + }; + Slot::new( + contents, + self.empty_slot, + self.filled_slot, + self.selected_slot, + content_size, + self.selected_content_scale, + self.amount_font, + self.amount_font_size, + self.amount_margins, + self.amount_text_color, + self.content_source, + self.image_source, + ) + .wh([wh[0] as f64, wh[1] as f64]) + .and_then(self.background_color, |s, c| s.with_background_color(c)) + .and_then(self.slot_manager.as_mut(), |s, m| s.with_manager(m)) + } +} + +#[derive(Clone, Copy)] +enum ManagerState { + Dragging(widget::Id, K, image::Id), + Selected(widget::Id, K), + Idle, +} + +enum Interaction { + Selected, + Dragging, + None, +} + +pub enum Event { + // Dragged to another slot + Dragged(K, K), + // Dragged to open space + Dropped(K), + // Clicked while selected + Used(K), +} +// Handles interactions with slots +pub struct SlotManager { + state: ManagerState, + // Rebuilt every frame + slot_ids: Vec, + // Rebuilt every frame + slots: Vec, + events: Vec>, + // Widget id for dragging image + drag_id: widget::Id, + // Size to display dragged content + // Note: could potentially be specialized for each slot if needed + drag_img_size: Vec2, +} + +impl SlotManager +where + S: SumSlot, +{ + pub fn new(mut gen: widget::id::Generator, drag_img_size: Vec2) -> Self { + Self { + state: ManagerState::Idle, + slot_ids: Vec::new(), + slots: Vec::new(), + events: Vec::new(), + drag_id: gen.next(), + drag_img_size, + } + } + + pub fn maintain(&mut self, ui: &mut conrod_core::UiCell) -> Vec> { + // Clear + let slot_ids = std::mem::replace(&mut self.slot_ids, Vec::new()); + let slots = std::mem::replace(&mut self.slots, Vec::new()); + + // Detect drops by of selected item by clicking in empty space + if let ManagerState::Selected(_, slot) = self.state { + if ui.widget_input(ui.window).clicks().left().next().is_some() { + self.state = ManagerState::Idle; + self.events.push(Event::Dropped(slot)); + } + } + + // If dragging and mouse is released check if there is a slot widget under the + // mouse + if let ManagerState::Dragging(_, slot, content_img) = &self.state { + let content_img = *content_img; + let input = &ui.global_input().current; + if let mouse::ButtonPosition::Up = input.mouse.buttons.left() { + // Get widget under the mouse + if let Some(id) = input.widget_under_mouse { + // If over the window widget drop the contents + if id == ui.window { + self.events.push(Event::Dropped(*slot)); + } else if let Some(idx) = slot_ids.iter().position(|slot_id| *slot_id == id) { + // If widget is a slot widget swap with it + self.events.push(Event::Dragged(*slot, slots[idx])); + } + } + // Mouse released stop dragging + self.state = ManagerState::Idle; + } + // Draw image of contents being dragged + let [mouse_x, mouse_y] = input.mouse.xy; + let size = self.drag_img_size.map(|e| e as f64).into_array(); + super::ghost_image::GhostImage::new(content_img) + .wh(size) + .xy([mouse_x, mouse_y]) + .set(self.drag_id, ui); + } + + std::mem::replace(&mut self.events, Vec::new()) + } + + fn update( + &mut self, + widget: widget::Id, + slot: S, + ui: &conrod_core::Ui, + content_img: Option, + ) -> Interaction { + // Add to list of slots + self.slot_ids.push(widget); + self.slots.push(slot); + + let filled = content_img.is_some(); + // If the slot is no longer filled deselect it or cancel dragging + match &self.state { + ManagerState::Selected(id, _) | ManagerState::Dragging(id, _, _) + if *id == widget && !filled => + { + self.state = ManagerState::Idle; + } + _ => (), + } + + // If this is the selected/dragged widget make sure the slot value is up to date + match &mut self.state { + ManagerState::Selected(id, stored_slot) + | ManagerState::Dragging(id, stored_slot, _) + if *id == widget => + { + *stored_slot = slot + }, + _ => (), + } + + let input = ui.widget_input(widget); + // TODO: make more robust wrt multiple events in the same frame (eg event order + // may matter) TODO: handle taps as well + let click_count = input.clicks().left().count(); + if click_count > 0 { + let odd_num_clicks = click_count % 2 == 1; + self.state = if let ManagerState::Selected(id, other_slot) = self.state { + if id != widget { + // Swap + if slot != other_slot { + self.events.push(Event::Dragged(other_slot, slot)); + } + if click_count == 1 { + ManagerState::Idle + } else if click_count == 2 { + // Was clicked again + ManagerState::Selected(widget, slot) + } else { + // Clicked more than once after swap, use and deselect + self.events.push(Event::Used(slot)); + ManagerState::Idle + } + } else { + // Clicked widget was already selected + // Deselect and emit use if clicked while selected + self.events.push(Event::Used(slot)); + ManagerState::Idle + } + } else { + // No widgets were selected + if odd_num_clicks && filled { + ManagerState::Selected(widget, slot) + } else { + // Selected and then deselected with one or more clicks + ManagerState::Idle + } + }; + } + + // Use on right click if not dragging + if input.clicks().right().next().is_some() { + match self.state { + ManagerState::Selected(_, _) | ManagerState::Idle => { + self.events.push(Event::Used(slot)); + // If something is selected, deselect + self.state = ManagerState::Idle; + }, + ManagerState::Dragging(_, _, _) => {}, + } + } + + // If not dragging and there is a drag event on this slot start dragging + if input.drags().left().next().is_some() + && !matches!(self.state, ManagerState::Dragging(_, _, _)) + { + // Start dragging if widget is filled + if let Some(img) = content_img { + self.state = ManagerState::Dragging(widget, slot, img); + } + } + + // Determine whether this slot is being interacted with + match self.state { + ManagerState::Selected(id, _) if id == widget => Interaction::Selected, + ManagerState::Dragging(id, _, _) if id == widget => Interaction::Dragging, + _ => Interaction::None, + } + } + + /// Returns Some(slot) if a slot is selected + pub fn selected(&self) -> Option { + if let ManagerState::Selected(_, s) = self.state { + Some(s) + } else { + None + } + } + + /// Sets the SlotManager into an idle state + pub fn idle(&mut self) { self.state = ManagerState::Idle; } +} + +#[derive(WidgetCommon)] +pub struct Slot<'a, K: SlotKey + Into, C, I, S: SumSlot> { + slot_key: K, + + // Images for slot background and frame + empty_slot: image::Id, + filled_slot: image::Id, + selected_slot: image::Id, + background_color: Option, + + // Size of content image + content_size: Vec2, + selected_content_scale: f32, + + icon: Option<(image::Id, Vec2, Option)>, + + // Amount styling + amount_font: font::Id, + amount_font_size: u32, + amount_margins: Vec2, + amount_text_color: Color, + + slot_manager: Option<&'a mut SlotManager>, + // Should we just pass in the ImageKey? + content_source: &'a C, + image_source: &'a I, + + #[conrod(common_builder)] + common: widget::CommonBuilder, +} + +widget_ids! { + // Note: icon, amount, and amount_bg are not always used. Is there any cost to having them? + struct Ids { + background, + icon, + amount, + amount_bg, + content, + } +} + +/// Represents the state of the Slot widget. +pub struct State { + ids: Ids, + cached_image: Option<(K, image::Id)>, +} + +impl<'a, K, C, I, S> Slot<'a, K, C, I, S> +where + K: SlotKey + Into, + S: SumSlot, +{ + builder_methods! { + pub with_background_color { background_color = Some(Color) } + } + + pub fn with_manager(mut self, slot_manager: &'a mut SlotManager) -> Self { + self.slot_manager = Some(slot_manager); + self + } + + pub fn with_icon(mut self, img: image::Id, size: Vec2, color: Option) -> Self { + self.icon = Some((img, size, color)); + self + } + + fn new( + slot_key: K, + empty_slot: image::Id, + filled_slot: image::Id, + selected_slot: image::Id, + content_size: Vec2, + selected_content_scale: f32, + amount_font: font::Id, + amount_font_size: u32, + amount_margins: Vec2, + amount_text_color: Color, + content_source: &'a C, + image_source: &'a I, + ) -> Self { + Self { + slot_key, + empty_slot, + filled_slot, + selected_slot, + background_color: None, + content_size, + selected_content_scale, + icon: None, + amount_font, + amount_font_size, + amount_margins, + amount_text_color, + slot_manager: None, + content_source, + image_source, + common: widget::CommonBuilder::default(), + } + } +} + +impl<'a, K, C, I, S> Widget for Slot<'a, K, C, I, S> +where + K: SlotKey + Into, + S: SumSlot, +{ + type Event = (); + type State = State; + type Style = (); + + fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { + State { + ids: Ids::new(id_gen), + cached_image: None, + } + } + + fn style(&self) -> Self::Style { () } + + /// Update the state of the Slider. + fn update(self, args: widget::UpdateArgs) -> Self::Event { + let widget::UpdateArgs { + id, + state, + rect, + ui, + .. + } = args; + let Slot { + slot_key, + empty_slot, + filled_slot, + selected_slot, + background_color, + content_size, + selected_content_scale, + icon, + amount_font, + amount_font_size, + amount_margins, + amount_text_color, + content_source, + image_source, + .. + } = self; + + // If the key changed update the cached image id + let (image_key, content_color) = slot_key + .image_key(content_source) + .map_or((None, None), |(i, c)| (Some(i), c)); + if state.cached_image.as_ref().map(|c| &c.0) != image_key.as_ref() { + state.update(|state| { + state.cached_image = image_key.map(|key| { + let image_id = K::image_id(&key, &image_source); + (key, image_id) + }); + }); + } + + // Get image ids + let content_image = state.cached_image.as_ref().map(|c| c.1); + // Get whether this slot is selected + let interaction = self.slot_manager.map_or(Interaction::None, |m| { + m.update(id, slot_key.into(), ui, content_image) + }); + // No content if it is being dragged + let content_image = if let Interaction::Dragging = interaction { + None + } else { + content_image + }; + // Go back to getting image ids + let slot_image = if let Interaction::Selected = interaction { + selected_slot + } else if content_image.is_some() { + filled_slot + } else { + empty_slot + }; + + // Get amount (None => no amount text) + let amount = if let Interaction::Dragging = interaction { + None // Don't show amount if being dragged + } else { + slot_key.amount(content_source) + }; + + // Get slot widget dimensions and position + let (x, y, w, h) = rect.x_y_w_h(); + + // Draw slot frame/background + Image::new(slot_image) + .x_y(x, y) + .w_h(w, h) + .parent(id) + .graphics_for(id) + .color(background_color) + .set(state.ids.background, ui); + + // Draw icon (only when there is not content) + // Note: this could potentially be done by the user instead + if let (Some((icon_image, size, color)), true) = (icon, content_image.is_none()) { + let wh = size.map(|e| e as f64).into_array(); + Image::new(icon_image) + .x_y(x, y) + .wh(wh) + .parent(id) + .graphics_for(id) + .color(color) + .set(state.ids.icon, ui); + } + + // Draw contents + if let Some(content_image) = content_image { + Image::new(content_image) + .x_y(x, y) + .wh((content_size + * if let Interaction::Selected = interaction { + selected_content_scale + } else { + 1.0 + }) + .map(|e| e as f64) + .into_array()) + .color(content_color) + .parent(id) + .graphics_for(id) + .set(state.ids.content, ui); + } + + // Draw amount + if let Some(amount) = amount { + let amount = format!("{}", &amount); + // Text shadow + Text::new(&amount) + .font_id(amount_font) + .font_size(amount_font_size) + .bottom_right_with_margins_on( + state.ids.content, + amount_margins.x as f64, + amount_margins.y as f64, + ) + .parent(id) + .graphics_for(id) + .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) + .set(state.ids.amount_bg, ui); + Text::new(&amount) + .parent(id) + .graphics_for(id) + .bottom_left_with_margins_on( + state.ids.amount_bg, + AMOUNT_SHADOW_OFFSET[0], + AMOUNT_SHADOW_OFFSET[1], + ) + .font_id(amount_font) + .font_size(amount_font_size) + .color(amount_text_color) + .set(state.ids.amount, ui); + } + } +} diff --git a/voxygen/src/ui/widgets/tooltip.rs b/voxygen/src/ui/widgets/tooltip.rs index 73314f361a..9402991dfe 100644 --- a/voxygen/src/ui/widgets/tooltip.rs +++ b/voxygen/src/ui/widgets/tooltip.rs @@ -99,6 +99,7 @@ impl TooltipManager { img_id: Option, image_dims: Option<(f64, f64)>, src_id: widget::Id, + bottom_offset: f64, ui: &mut UiCell, ) { let tooltip_id = self.tooltip_id; @@ -115,13 +116,13 @@ impl TooltipManager { .image_dims(image_dims); let [t_w, t_h] = tooltip.get_wh(ui).unwrap_or([0.0, 0.0]); - let [m_x, m_y] = mouse_pos; + let [m_x, m_y] = [mouse_pos[0], mouse_pos[1]]; let (w_w, w_h) = (ui.win_w, ui.win_h); // Determine position based on size and mouse position // Flow to the bottom right of the mouse let x = (m_x + t_w / 2.0).min(w_w / 2.0 - t_w / 2.0); - let y = (m_y - mp_h - t_h / 2.0).max(-w_h / 2.0 + t_h / 2.0); + let y = (m_y - mp_h - t_h / 2.0).max(-w_h / 2.0 + t_h / 2.0 + bottom_offset); tooltip .floating(true) .transparency(transparency) @@ -154,6 +155,8 @@ pub struct Tooltipped<'a, W> { desc_text: &'a str, img_id: Option, image_dims: Option<(f64, f64)>, + // Offsets limit of bottom of tooltip + bottom_offset: Option, tooltip: &'a Tooltip<'a>, } impl<'a, W: Widget> Tooltipped<'a, W> { @@ -167,6 +170,11 @@ impl<'a, W: Widget> Tooltipped<'a, W> { self } + pub fn bottom_offset(mut self, off: f64) -> Self { + self.bottom_offset = Some(off); + self + } + pub fn set(self, id: widget::Id, ui: &mut UiCell) -> W::Event { let event = self.inner.set(id, ui); self.tooltip_manager.set_tooltip( @@ -176,6 +184,7 @@ impl<'a, W: Widget> Tooltipped<'a, W> { self.img_id, self.image_dims, id, + self.bottom_offset.unwrap_or(0.0), ui, ); event @@ -209,6 +218,7 @@ impl Tooltipable for W { desc_text, img_id: None, image_dims: None, + bottom_offset: None, tooltip, } } diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index f54e0eabf1..45ef3bc0e3 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -1,22 +1,32 @@ use crate::{ controller::*, render::{Renderer, WinColorFmt, WinDepthFmt}, - settings::Settings, + settings::{ControlSettings, Settings}, ui, Error, }; use gilrs::{EventType, Gilrs}; use hashbrown::HashMap; + use log::{error, warn}; use serde_derive::{Deserialize, Serialize}; use std::fmt; use vek::*; /// Represents a key that the game recognises after input mapping. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)] pub enum GameInput { Primary, Secondary, - Ability3, + Slot1, + Slot2, + Slot3, + Slot4, + Slot5, + Slot6, + Slot7, + Slot8, + Slot9, + Slot10, ToggleCursor, MoveForward, MoveBack, @@ -27,15 +37,13 @@ pub enum GameInput { Glide, Climb, ClimbDown, - WallLeap, + //WallLeap, Mount, Enter, Command, Escape, Map, Bag, - QuestLog, - CharacterWindow, Social, Spellbook, Settings, @@ -49,11 +57,63 @@ pub enum GameInput { Respawn, Interact, ToggleWield, - Charge, + //Charge, SwapLoadout, FreeLook, } +impl GameInput { + pub fn get_localization_key(&self) -> &str { + match *self { + GameInput::Primary => "gameinput.primary", + GameInput::Secondary => "gameinput.secondary", + GameInput::ToggleCursor => "gameinput.togglecursor", + GameInput::MoveForward => "gameinput.moveforward", + GameInput::MoveLeft => "gameinput.moveleft", + GameInput::MoveRight => "gameinput.moveright", + GameInput::MoveBack => "gameinput.moveback", + GameInput::Jump => "gameinput.jump", + GameInput::Sit => "gameinput.sit", + GameInput::Glide => "gameinput.glide", + GameInput::Climb => "gameinput.climb", + GameInput::ClimbDown => "gameinput.climbdown", + //GameInput::WallLeap => "gameinput.wallleap", + GameInput::Mount => "gameinput.mount", + GameInput::Enter => "gameinput.enter", + GameInput::Command => "gameinput.command", + GameInput::Escape => "gameinput.escape", + GameInput::Map => "gameinput.map", + GameInput::Bag => "gameinput.bag", + GameInput::Social => "gameinput.social", + GameInput::Spellbook => "gameinput.spellbook", + GameInput::Settings => "gameinput.settings", + GameInput::ToggleInterface => "gameinput.toggleinterface", + GameInput::Help => "gameinput.help", + GameInput::ToggleDebug => "gameinput.toggledebug", + GameInput::Fullscreen => "gameinput.fullscreen", + GameInput::Screenshot => "gameinput.screenshot", + GameInput::ToggleIngameUi => "gameinput.toggleingameui", + GameInput::Roll => "gameinput.roll", + GameInput::Respawn => "gameinput.respawn", + GameInput::Interact => "gameinput.interact", + GameInput::ToggleWield => "gameinput.togglewield", + //GameInput::Charge => "gameinput.charge", + GameInput::FreeLook => "gameinput.freelook", + GameInput::Slot1 => "gameinput.slot1", + GameInput::Slot2 => "gameinput.slot2", + GameInput::Slot3 => "gameinput.slot3", + GameInput::Slot4 => "gameinput.slot4", + GameInput::Slot5 => "gameinput.slot5", + GameInput::Slot6 => "gameinput.slot6", + GameInput::Slot7 => "gameinput.slot7", + GameInput::Slot8 => "gameinput.slot8", + GameInput::Slot9 => "gameinput.slot9", + GameInput::Slot10 => "gameinput.slot10", + GameInput::SwapLoadout => "gameinput.swaploadout", + } + } +} + /// Represents a key that the game menus recognise after input mapping #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] pub enum MenuInput { @@ -299,8 +359,8 @@ impl fmt::Display for KeyMouse { Key(Copy) => "Copy", Key(Paste) => "Paste", Key(Cut) => "Cut", - Mouse(MouseButton::Left) => "Mouse L-Click", - Mouse(MouseButton::Right) => "Mouse R-Click", + Mouse(MouseButton::Left) => "Mouse Left", + Mouse(MouseButton::Right) => "Mouse Right", Mouse(MouseButton::Middle) => "Mouse Middle-Click", Mouse(MouseButton::Other(button)) => return write!(f, "Unknown Mouse Button: {:?}", button), @@ -319,8 +379,8 @@ pub struct Window { pub mouse_y_inversion: bool, fullscreen: bool, needs_refresh_resize: bool, - key_map: HashMap>, keypress_map: HashMap, + pub remapping_keybindings: Option, supplement_events: Vec, focused: bool, gilrs: Option, @@ -355,116 +415,6 @@ impl Window { ) .map_err(|err| Error::BackendError(Box::new(err)))?; - let mut map: HashMap<_, Vec<_>> = HashMap::new(); - map.entry(settings.controls.primary) - .or_default() - .push(GameInput::Primary); - map.entry(settings.controls.secondary) - .or_default() - .push(GameInput::Secondary); - map.entry(settings.controls.ability3) - .or_default() - .push(GameInput::Ability3); - map.entry(settings.controls.toggle_cursor) - .or_default() - .push(GameInput::ToggleCursor); - map.entry(settings.controls.escape) - .or_default() - .push(GameInput::Escape); - map.entry(settings.controls.enter) - .or_default() - .push(GameInput::Enter); - map.entry(settings.controls.command) - .or_default() - .push(GameInput::Command); - map.entry(settings.controls.move_forward) - .or_default() - .push(GameInput::MoveForward); - map.entry(settings.controls.move_left) - .or_default() - .push(GameInput::MoveLeft); - map.entry(settings.controls.move_back) - .or_default() - .push(GameInput::MoveBack); - map.entry(settings.controls.move_right) - .or_default() - .push(GameInput::MoveRight); - map.entry(settings.controls.jump) - .or_default() - .push(GameInput::Jump); - map.entry(settings.controls.sit) - .or_default() - .push(GameInput::Sit); - map.entry(settings.controls.glide) - .or_default() - .push(GameInput::Glide); - map.entry(settings.controls.climb) - .or_default() - .push(GameInput::Climb); - map.entry(settings.controls.climb_down) - .or_default() - .push(GameInput::ClimbDown); - map.entry(settings.controls.wall_leap) - .or_default() - .push(GameInput::WallLeap); - map.entry(settings.controls.mount) - .or_default() - .push(GameInput::Mount); - map.entry(settings.controls.map) - .or_default() - .push(GameInput::Map); - map.entry(settings.controls.bag) - .or_default() - .push(GameInput::Bag); - map.entry(settings.controls.social) - .or_default() - .push(GameInput::Social); - map.entry(settings.controls.spellbook) - .or_default() - .push(GameInput::Spellbook); - map.entry(settings.controls.settings) - .or_default() - .push(GameInput::Settings); - map.entry(settings.controls.help) - .or_default() - .push(GameInput::Help); - map.entry(settings.controls.toggle_interface) - .or_default() - .push(GameInput::ToggleInterface); - map.entry(settings.controls.toggle_debug) - .or_default() - .push(GameInput::ToggleDebug); - map.entry(settings.controls.fullscreen) - .or_default() - .push(GameInput::Fullscreen); - map.entry(settings.controls.screenshot) - .or_default() - .push(GameInput::Screenshot); - map.entry(settings.controls.toggle_ingame_ui) - .or_default() - .push(GameInput::ToggleIngameUi); - map.entry(settings.controls.roll) - .or_default() - .push(GameInput::Roll); - map.entry(settings.controls.respawn) - .or_default() - .push(GameInput::Respawn); - map.entry(settings.controls.interact) - .or_default() - .push(GameInput::Interact); - map.entry(settings.controls.toggle_wield) - .or_default() - .push(GameInput::ToggleWield); - map.entry(settings.controls.swap_loadout) - .or_default() - .push(GameInput::SwapLoadout); - map.entry(settings.controls.charge) - .or_default() - .push(GameInput::Charge); - map.entry(settings.controls.free_look) - .or_default() - .push(GameInput::FreeLook); - let keypress_map = HashMap::new(); let gilrs = match Gilrs::new() { @@ -510,8 +460,8 @@ impl Window { mouse_y_inversion: settings.gameplay.mouse_y_inversion, fullscreen: false, needs_refresh_resize: false, - key_map: map, keypress_map, + remapping_keybindings: None, supplement_events: vec![], focused: true, gilrs, @@ -543,8 +493,9 @@ impl Window { let cursor_grabbed = self.cursor_grabbed; let renderer = &mut self.renderer; let window = &mut self.window; + let remapping_keybindings = &mut self.remapping_keybindings; let focused = &mut self.focused; - let key_map = &self.key_map; + let controls = &mut settings.controls; let keypress_map = &mut self.keypress_map; let pan_sensitivity = self.pan_sensitivity; let zoom_sensitivity = self.zoom_sensitivity; @@ -577,9 +528,14 @@ impl Window { }, glutin::WindowEvent::ReceivedCharacter(c) => events.push(Event::Char(c)), glutin::WindowEvent::MouseInput { button, state, .. } => { - if let (true, Some(game_inputs)) = - (cursor_grabbed, key_map.get(&KeyMouse::Mouse(button))) - { + if let (true, Some(game_inputs)) = ( + cursor_grabbed, + Window::map_input( + KeyMouse::Mouse(button), + controls, + remapping_keybindings, + ), + ) { for game_input in game_inputs { events.push(Event::InputUpdate( *game_input, @@ -589,11 +545,13 @@ impl Window { } events.push(Event::MouseButton(button, state)); }, - glutin::WindowEvent::KeyboardInput { input, .. } => match input.virtual_keycode - { - Some(key) => { - let game_inputs = key_map.get(&KeyMouse::Key(key)); - if let Some(game_inputs) = game_inputs { + glutin::WindowEvent::KeyboardInput { input, .. } => { + if let Some(key) = input.virtual_keycode { + if let Some(game_inputs) = Window::map_input( + KeyMouse::Key(key), + controls, + remapping_keybindings, + ) { for game_input in game_inputs { match game_input { GameInput::Fullscreen => { @@ -631,9 +589,9 @@ impl Window { } } } - }, - _ => {}, + } }, + glutin::WindowEvent::Focused(state) => { *focused = state; events.push(Event::Focused(state)); @@ -1000,4 +958,33 @@ impl Window { ) { map.insert(input, state); } + + // Function used to handle Mouse and Key events. It first checks if we're in + // remapping mode for a specific GameInput. If we are, we modify the binding + // of that GameInput with the KeyMouse passed. Else, we return an iterator of + // the GameInputs for that KeyMouse. + fn map_input<'a>( + key_mouse: KeyMouse, + controls: &'a mut ControlSettings, + remapping: &mut Option, + ) -> Option> { + match *remapping { + Some(game_input) => { + controls.modify_binding(game_input, key_mouse); + *remapping = None; + None + }, + None => { + if let Some(game_inputs) = controls.get_associated_game_inputs(&key_mouse) { + Some(game_inputs.iter()) + } else { + None + } + }, + } + } + + pub fn set_keybinding_mode(&mut self, game_input: GameInput) { + self.remapping_keybindings = Some(game_input); + } } diff --git a/world/examples/city.rs b/world/examples/city.rs deleted file mode 100644 index 0331c0ddaf..0000000000 --- a/world/examples/city.rs +++ /dev/null @@ -1,35 +0,0 @@ -use rand::thread_rng; - -use vek::*; -use veloren_world::sim::Settlement; - -const W: usize = 640; -const H: usize = 480; - -fn main() { - let mut win = - minifb::Window::new("City Viewer", W, H, minifb::WindowOptions::default()).unwrap(); - - let settlement = Settlement::generate(&mut thread_rng()); - - while win.is_open() { - let mut buf = vec![0; W * H]; - - for i in 0..W { - for j in 0..H { - let pos = Vec2::new(i as f32, j as f32) * 0.002; - - let seed = settlement.get_at(pos).map(|b| b.seed).unwrap_or(0); - - buf[j * W + i] = u32::from_le_bytes([ - (seed >> 0) as u8, - (seed >> 8) as u8, - (seed >> 16) as u8, - (seed >> 24) as u8, - ]); - } - } - - win.update_with_buffer_size(&buf, W, H).unwrap(); - } -} diff --git a/world/examples/settlement_viewer.rs b/world/examples/settlement_viewer.rs new file mode 100644 index 0000000000..ccfb4e321d --- /dev/null +++ b/world/examples/settlement_viewer.rs @@ -0,0 +1,57 @@ +use rand::thread_rng; +use vek::*; +use veloren_world::site::Settlement; + +const W: usize = 640; +const H: usize = 480; + +fn main() { + let mut win = + minifb::Window::new("Settlement Viewer", W, H, minifb::WindowOptions::default()).unwrap(); + + let settlement = Settlement::generate(Vec2::zero(), None, &mut thread_rng()); + + let mut focus = Vec2::::zero(); + let mut zoom = 1.0; + + while win.is_open() { + let mut buf = vec![0; W * H]; + + let win_to_pos = + |wp: Vec2| (wp.map(|e| e as f32) - Vec2::new(W as f32, H as f32) * 0.5) * zoom; + + for i in 0..W { + for j in 0..H { + let pos = focus + win_to_pos(Vec2::new(i, j)) * zoom; + + let color = settlement + .get_color(pos.map(|e| e.floor() as i32)) + .unwrap_or(Rgb::new(35, 50, 20)); + + buf[j * W + i] = u32::from_le_bytes([color.b, color.g, color.r, 255]); + } + } + + let spd = 20.0; + if win.is_key_down(minifb::Key::W) { + focus.y -= spd * zoom; + } + if win.is_key_down(minifb::Key::A) { + focus.x -= spd * zoom; + } + if win.is_key_down(minifb::Key::S) { + focus.y += spd * zoom; + } + if win.is_key_down(minifb::Key::D) { + focus.x += spd * zoom; + } + if win.is_key_down(minifb::Key::Q) { + zoom *= 1.05; + } + if win.is_key_down(minifb::Key::E) { + zoom /= 1.05; + } + + win.update_with_buffer_size(&buf, W, H).unwrap(); + } +} diff --git a/world/examples/view.rs b/world/examples/view.rs index 330338fbd8..f80bbca0de 100644 --- a/world/examples/view.rs +++ b/world/examples/view.rs @@ -27,21 +27,21 @@ fn main() { for j in 0..H { let pos = focus + Vec2::new(i as i32, j as i32) * scale; - let (alt, location) = sampler + let (alt, place) = sampler .get(pos) .map(|sample| { ( sample.alt.sub(64.0).add(gain).mul(0.7).max(0.0).min(255.0) as u8, - sample.location, + sample.chunk.place, ) }) .unwrap_or((0, None)); - let loc_color = location - .map(|l| (l.loc_idx as u8 * 17, l.loc_idx as u8 * 13)) + let place_color = place + .map(|p| ((p.id() % 256) as u8 * 17, (p.id() % 256) as u8 * 13)) .unwrap_or((0, 0)); - buf[j * W + i] = u32::from_le_bytes([loc_color.0, loc_color.1, alt, alt]); + buf[j * W + i] = u32::from_le_bytes([place_color.0, place_color.1, alt, alt]); } } diff --git a/world/examples/water.rs b/world/examples/water.rs index 5ceff64654..a1a833d695 100644 --- a/world/examples/water.rs +++ b/world/examples/water.rs @@ -243,12 +243,25 @@ fn main() { } if win.get_mouse_down(minifb::MouseButton::Left) { if let Some((mx, my)) = win.get_mouse_pos(minifb::MouseMode::Clamp) { - let pos = (Vec2::::from(focus) + (Vec2::new(mx as f64, my as f64) * scale)) + let chunk_pos = (Vec2::::from(focus) + + (Vec2::new(mx as f64, my as f64) * scale)) .map(|e| e as i32); + let block_pos = chunk_pos.map2(TerrainChunkSize::RECT_SIZE, |e, f| e * f as i32); println!( - "Chunk position: {:?}", - pos.map2(TerrainChunkSize::RECT_SIZE, |e, f| e * f as i32) + "Block: ({}, {}), Chunk: ({}, {})", + block_pos.x, block_pos.y, chunk_pos.x, chunk_pos.y ); + if let Some(chunk) = sampler.get(chunk_pos) { + //println!("Chunk info: {:#?}", chunk); + if let Some(id) = &chunk.place { + let place = world.civs().place(*id); + println!("Place {} info: {:#?}", id.id(), place); + + if let Some(site) = world.civs().sites().find(|site| site.place == *id) { + println!("Site: {}", site); + } + } + } } } let is_camera = win.is_key_down(minifb::Key::C); diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index 105d4b8a87..fa1619b367 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -2,7 +2,6 @@ mod natural; use crate::{ column::{ColumnGen, ColumnSample}, - generator::{Generator, TownGen}, util::{RandomField, Sampler, SmallCache}, CONFIG, }; @@ -51,11 +50,7 @@ impl<'a> BlockGen<'a> { cache, Vec2::from(*cliff_pos), ) { - Some(cliff_sample) - if cliff_sample.is_cliffs - && cliff_sample.spawn_rate > 0.5 - && cliff_sample.spawn_rules.cliffs => - { + Some(cliff_sample) if cliff_sample.is_cliffs && cliff_sample.spawn_rate > 0.5 => { let cliff_pos3d = Vec3::from(*cliff_pos); // Conservative range of height: [15.70, 49.33] @@ -71,16 +66,24 @@ impl<'a> BlockGen<'a> { // Conservative range of radius: [8, 47] let radius = RandomField::new(seed + 2).get(cliff_pos3d) % 48 + 8; - max_height.max( - if cliff_pos.map(|e| e as f32).distance_squared(wpos) - < (radius as f32 + tolerance).powf(2.0) - { - cliff_sample.alt + height * (1.0 - cliff_sample.chaos) + cliff_hill - } else { - 0.0 - }, - ) - } + if cliff_sample + .water_dist + .map(|d| d > radius as f32) + .unwrap_or(true) + { + max_height.max( + if cliff_pos.map(|e| e as f32).distance_squared(wpos) + < (radius as f32 + tolerance).powf(2.0) + { + cliff_sample.alt + height * (1.0 - cliff_sample.chaos) + cliff_hill + } else { + 0.0 + }, + ) + } else { + max_height + } + }, _ => max_height, }, ) @@ -169,7 +172,6 @@ impl<'a> BlockGen<'a> { close_cliffs, temp, humidity, - chunk, stone_col, .. } = sample; @@ -178,7 +180,7 @@ impl<'a> BlockGen<'a> { let wposf = wpos.map(|e| e as f64); - let (block, height) = if !only_structures { + let (block, _height) = if !only_structures { let (_definitely_underground, height, on_cliff, basement_height, water_height) = if (wposf.z as f32) < alt - 64.0 * chaos { // Shortcut warping @@ -397,14 +399,6 @@ impl<'a> BlockGen<'a> { (None, sample.alt) }; - // Structures (like towns) - let block = chunk - .structures - .town - .as_ref() - .and_then(|town| TownGen.get((town, wpos, sample, height))) - .or(block); - let block = structures .iter() .find_map(|st| { @@ -419,7 +413,7 @@ impl<'a> BlockGen<'a> { pub struct ZCache<'a> { wpos: Vec2, - sample: ColumnSample<'a>, + pub sample: ColumnSample<'a>, structures: [Option<(StructureInfo, ColumnSample<'a>)>; 9], } @@ -471,19 +465,6 @@ impl<'a> ZCache<'a> { let min = min + structure_min; let max = (ground_max + structure_max).max(self.sample.water_level + 2.0); - // Structures - let (min, max) = self - .sample - .chunk - .structures - .town - .as_ref() - .map(|town| { - let (town_min, town_max) = TownGen.get_z_limits(town, self.wpos, &self.sample); - (town_min.min(min), town_max.max(max)) - }) - .unwrap_or((min, max)); - let structures_only_min_z = ground_max.max(self.sample.water_level + 2.0); (min, structures_only_min_z, max) @@ -579,9 +560,8 @@ pub fn block_from_structure( ) -> Option { let field = RandomField::new(structure_seed + 0); - let lerp = 0.5 - + ((field.get(Vec3::from(structure_pos)) % 256) as f32 / 256.0 - 0.5) * 0.85 - + ((field.get(Vec3::from(pos)) % 256) as f32 / 256.0 - 0.5) * 0.15; + let lerp = ((field.get(Vec3::from(structure_pos)).rem_euclid(256)) as f32 / 255.0) * 0.85 + + ((field.get(pos + std::i32::MAX / 2).rem_euclid(256)) as f32 / 255.0) * 0.15; match sblock { StructureBlock::None => None, @@ -631,12 +611,17 @@ pub fn block_from_structure( StructureBlock::Fruit => Some(if field.get(pos + structure_pos) % 3 > 0 { Block::empty() } else { - Block::new(BlockKind::Apple, Rgb::new(194, 30, 37)) + Block::new(BlockKind::Apple, Rgb::new(1, 1, 1)) + }), + StructureBlock::Coconut => Some(if field.get(pos + structure_pos) % 3 > 0 { + Block::empty() + } else { + Block::new(BlockKind::Coconut, Rgb::new(1, 1, 1)) }), StructureBlock::Chest => Some(if structure_seed % 10 < 7 { Block::empty() } else { - Block::new(BlockKind::Chest, Rgb::new(0, 0, 0)) + Block::new(BlockKind::Chest, Rgb::new(1, 1, 1)) }), StructureBlock::Liana => Some(Block::new( BlockKind::Liana, diff --git a/world/src/block/natural.rs b/world/src/block/natural.rs index 0c2814dada..232708dad6 100644 --- a/world/src/block/natural.rs +++ b/world/src/block/natural.rs @@ -28,7 +28,8 @@ pub fn structure_gen<'a>( if (st_sample.tree_density as f64) < random_seed || st_sample.alt < st_sample.water_level || st_sample.spawn_rate < 0.5 - || !st_sample.spawn_rules.trees + || st_sample.water_dist.map(|d| d < 8.0).unwrap_or(false) + || st_sample.path.map(|(d, _)| d < 12.0).unwrap_or(false) { return None; } diff --git a/world/src/civ/econ.rs b/world/src/civ/econ.rs new file mode 100644 index 0000000000..5245627305 --- /dev/null +++ b/world/src/civ/econ.rs @@ -0,0 +1,81 @@ +#![allow(dead_code)] + +use super::GenCtx; +use rand::prelude::*; + +pub struct SellOrder { + pub quantity: f32, + pub price: f32, + + // The money returned to the seller + pub q_sold: f32, +} + +pub struct BuyOrder { + quantity: f32, + max_price: f32, +} + +#[derive(Clone, Debug)] +pub struct Belief { + pub price: f32, + pub confidence: f32, +} + +impl Belief { + pub fn choose_price(&self, ctx: &mut GenCtx) -> f32 { + self.price + ctx.rng.gen_range(-1.0, 1.0) * self.confidence + } + + pub fn update_buyer(&mut self, _years: f32, new_price: f32) { + if (self.price - new_price).abs() < self.confidence { + self.confidence *= 0.8; + } else { + self.price += (new_price - self.price) * 0.5; // TODO: Make this vary with `years` + self.confidence = (self.price - new_price).abs(); + } + } + + pub fn update_seller(&mut self, proportion: f32) { + self.price *= 1.0 + (proportion - 0.5) * 0.25; + self.confidence /= 1.0 + (proportion - 0.5) * 0.25; + } +} + +pub fn buy_units<'a>( + _ctx: &mut GenCtx, + sellers: impl Iterator, + max_quantity: f32, + max_price: f32, + max_spend: f32, +) -> (f32, f32) { + let mut sell_orders = sellers.filter(|so| so.quantity > 0.0).collect::>(); + // Sort sell orders by price, cheapest first + sell_orders.sort_by(|a, b| { + a.price + .partial_cmp(&b.price) + .unwrap_or_else(|| panic!("{} and {}", a.price, b.price)) + }); + + let mut quantity = 0.0; + let mut spent = 0.0; + + for order in sell_orders { + if quantity >= max_quantity || // We've purchased enough + spent >= max_spend || // We've spent enough + order.price > max_price + // Price is too high + { + break; + } else { + let q = (max_quantity - quantity) + .min(order.quantity - order.q_sold) + .min((max_spend - spent) / order.price); + order.q_sold += q; + quantity += q; + spent += q * order.price; + } + } + + (quantity, spent) +} diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs new file mode 100644 index 0000000000..a8a9d83846 --- /dev/null +++ b/world/src/civ/mod.rs @@ -0,0 +1,986 @@ +#![allow(dead_code)] + +mod econ; + +use crate::{ + sim::WorldSim, + site::{Dungeon, Settlement, Site as WorldSite}, + util::{attempt, seed_expan, CARDINALS, NEIGHBORS}, +}; +use common::{ + astar::Astar, + path::Path, + spiral::Spiral2d, + store::{Id, Store}, + terrain::TerrainChunkSize, + vol::RectVolSize, +}; +use hashbrown::{HashMap, HashSet}; +use rand::prelude::*; +use rand_chacha::ChaChaRng; +use std::{fmt, hash::Hash, ops::Range}; +use vek::*; + +const INITIAL_CIV_COUNT: usize = 32; + +#[derive(Default)] +pub struct Civs { + civs: Store, + places: Store, + + tracks: Store, + track_map: HashMap, HashMap, Id>>, + + sites: Store, +} + +pub struct GenCtx<'a, R: Rng> { + sim: &'a mut WorldSim, + rng: R, +} + +impl<'a, R: Rng> GenCtx<'a, R> { + pub fn reseed(&mut self) -> GenCtx<'_, impl Rng> { + GenCtx { + sim: self.sim, + rng: ChaChaRng::from_seed(self.rng.gen()), + } + } +} + +impl Civs { + pub fn generate(seed: u32, sim: &mut WorldSim) -> Self { + let mut this = Self::default(); + let rng = ChaChaRng::from_seed(seed_expan::rng_state(seed)); + let mut ctx = GenCtx { sim, rng }; + + for _ in 0..INITIAL_CIV_COUNT { + log::info!("Creating civilisation..."); + if this.birth_civ(&mut ctx.reseed()).is_none() { + log::warn!("Failed to find starting site for civilisation."); + } + } + + for _ in 0..INITIAL_CIV_COUNT * 2 { + attempt(5, || { + let loc = find_site_loc(&mut ctx, None)?; + this.establish_site(&mut ctx.reseed(), loc, |place| Site { + kind: SiteKind::Dungeon, + center: loc, + place, + + population: 0.0, + + stocks: Stocks::from_default(100.0), + surplus: Stocks::from_default(0.0), + values: Stocks::from_default(None), + + labors: MapVec::from_default(0.01), + yields: MapVec::from_default(1.0), + productivity: MapVec::from_default(1.0), + + last_exports: Stocks::from_default(0.0), + export_targets: Stocks::from_default(0.0), + trade_states: Stocks::default(), + coin: 1000.0, + }) + }); + } + + // Tick + const SIM_YEARS: usize = 1000; + for _ in 0..SIM_YEARS { + this.tick(&mut ctx, 1.0); + } + + // Flatten ground around sites + for site in this.sites.iter() { + if let SiteKind::Settlement = &site.kind { + } else { + continue; + } + + let radius = 48i32; + let wpos = site.center * Vec2::from(TerrainChunkSize::RECT_SIZE).map(|e: u32| e as i32); + + // Flatten ground + let flatten_radius = 10.0; + if let Some(center_alt) = ctx.sim.get_alt_approx(wpos) { + for offs in Spiral2d::new().take(radius.pow(2) as usize) { + let center_alt = center_alt + + if offs.magnitude_squared() <= 6i32.pow(2) { + 16.0 + } else { + 0.0 + }; // Raise the town centre up a little + let pos = site.center + offs; + let factor = (1.0 + - (site.center - pos).map(|e| e as f32).magnitude() / flatten_radius) + * 1.15; + ctx.sim + .get_mut(pos) + // Don't disrupt chunks that are near water + .filter(|chunk| !chunk.river.near_water()) + .map(|chunk| { + let diff = Lerp::lerp_precise(chunk.alt, center_alt, factor) - chunk.alt; + chunk.alt += diff; + chunk.basement += diff; + chunk.rockiness = 0.0; + chunk.warp_factor = 0.0; + }); + } + } + } + + // Place sites in world + for site in this.sites.iter() { + let wpos = site + .center + .map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| { + e * sz as i32 + sz as i32 / 2 + }); + + let world_site = match &site.kind { + SiteKind::Settlement => { + WorldSite::from(Settlement::generate(wpos, Some(ctx.sim), &mut ctx.rng)) + }, + SiteKind::Dungeon => { + WorldSite::from(Dungeon::generate(wpos, Some(ctx.sim), &mut ctx.rng)) + }, + }; + + let radius_chunks = + (world_site.radius() / TerrainChunkSize::RECT_SIZE.x as f32).ceil() as usize; + for pos in Spiral2d::new() + .map(|offs| site.center + offs) + .take((radius_chunks * 2).pow(2)) + { + ctx.sim + .get_mut(pos) + .map(|chunk| chunk.sites.push(world_site.clone())); + } + log::info!("Placed site at {:?}", site.center); + } + + //this.display_info(); + + this + } + + pub fn place(&self, id: Id) -> &Place { self.places.get(id) } + + pub fn sites(&self) -> impl Iterator + '_ { self.sites.iter() } + + #[allow(dead_code)] + fn display_info(&self) { + for (id, civ) in self.civs.iter_ids() { + println!("# Civilisation {:?}", id); + println!("Name: {}", ""); + println!("Homeland: {:#?}", self.places.get(civ.homeland)); + } + + for (id, site) in self.sites.iter_ids() { + println!("# Site {:?}", id); + println!("{:#?}", site); + } + } + + /// Return the direct track between two places + fn track_between(&self, a: Id, b: Id) -> Option> { + self.track_map + .get(&a) + .and_then(|dests| dests.get(&b)) + .or_else(|| self.track_map.get(&b).and_then(|dests| dests.get(&a))) + .copied() + } + + /// Return an iterator over a site's neighbors + fn neighbors(&self, site: Id) -> impl Iterator> + '_ { + let to = self + .track_map + .get(&site) + .map(|dests| dests.keys()) + .into_iter() + .flatten(); + let fro = self + .track_map + .iter() + .filter(move |(_, dests)| dests.contains_key(&site)) + .map(|(p, _)| p); + to.chain(fro).filter(move |p| **p != site).copied() + } + + /// Find the cheapest route between two places + fn route_between(&self, a: Id, b: Id) -> Option<(Path>, f32)> { + let heuristic = move |p: &Id| { + (self + .sites + .get(*p) + .center + .distance_squared(self.sites.get(b).center) as f32) + .sqrt() + }; + let neighbors = |p: &Id| self.neighbors(*p); + let transition = + |a: &Id, b: &Id| self.tracks.get(self.track_between(*a, *b).unwrap()).cost; + let satisfied = |p: &Id| *p == b; + let mut astar = Astar::new(100, a, heuristic); + astar + .poll(100, heuristic, neighbors, transition, satisfied) + .into_path() + .and_then(|path| astar.get_cheapest_cost().map(|cost| (path, cost))) + } + + fn birth_civ(&mut self, ctx: &mut GenCtx) -> Option> { + let site = attempt(5, || { + let loc = find_site_loc(ctx, None)?; + self.establish_site(ctx, loc, |place| Site { + kind: SiteKind::Settlement, + center: loc, + place, + + population: 24.0, + + stocks: Stocks::from_default(100.0), + surplus: Stocks::from_default(0.0), + values: Stocks::from_default(None), + + labors: MapVec::from_default(0.01), + yields: MapVec::from_default(1.0), + productivity: MapVec::from_default(1.0), + + last_exports: Stocks::from_default(0.0), + export_targets: Stocks::from_default(0.0), + trade_states: Stocks::default(), + coin: 1000.0, + }) + })?; + + let civ = self.civs.insert(Civ { + capital: site, + homeland: self.sites.get(site).place, + }); + + Some(civ) + } + + fn establish_place( + &mut self, + ctx: &mut GenCtx, + loc: Vec2, + area: Range, + ) -> Option> { + let mut dead = HashSet::new(); + let mut alive = HashSet::new(); + alive.insert(loc); + + // Fill the surrounding area + while let Some(cloc) = alive.iter().choose(&mut ctx.rng).copied() { + for dir in CARDINALS.iter() { + if site_in_dir(&ctx.sim, cloc, *dir) { + let rloc = cloc + *dir; + if !dead.contains(&rloc) + && ctx + .sim + .get(rloc) + .map(|c| c.place.is_none()) + .unwrap_or(false) + { + alive.insert(rloc); + } + } + } + alive.remove(&cloc); + dead.insert(cloc); + + if dead.len() + alive.len() >= area.end { + break; + } + } + // Make sure the place is large enough + if dead.len() + alive.len() <= area.start { + return None; + } + + let place = self.places.insert(Place { + center: loc, + nat_res: NaturalResources::default(), + }); + + // Write place to map + for cell in dead.union(&alive) { + if let Some(chunk) = ctx.sim.get_mut(*cell) { + chunk.place = Some(place); + self.places.get_mut(place).nat_res.include_chunk(ctx, *cell); + } + } + + Some(place) + } + + fn establish_site( + &mut self, + ctx: &mut GenCtx, + loc: Vec2, + site_fn: impl FnOnce(Id) -> Site, + ) -> Option> { + const SITE_AREA: Range = 64..256; + + let place = match ctx.sim.get(loc).and_then(|site| site.place) { + Some(place) => place, + None => self.establish_place(ctx, loc, SITE_AREA)?, + }; + + let site = self.sites.insert(site_fn(place)); + + // Find neighbors + const MAX_NEIGHBOR_DISTANCE: f32 = 250.0; + let mut nearby = self + .sites + .iter_ids() + .map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt())) + .filter(|(_, dist)| *dist < MAX_NEIGHBOR_DISTANCE) + .collect::>(); + nearby.sort_by_key(|(_, dist)| *dist as i32); + + if let SiteKind::Settlement = self.sites[site].kind { + for (nearby, _) in nearby.into_iter().take(5) { + // Find a novel path + if let Some((path, cost)) = find_path(ctx, loc, self.sites.get(nearby).center) { + // Find a path using existing paths + if self + .route_between(site, nearby) + // If the novel path isn't efficient compared to existing routes, don't use it + .filter(|(_, route_cost)| *route_cost < cost * 3.0) + .is_none() + { + // Write the track to the world as a path + for locs in path.nodes().windows(3) { + let to_prev_idx = NEIGHBORS + .iter() + .enumerate() + .find(|(_, dir)| **dir == locs[0] - locs[1]) + .expect("Track locations must be neighbors") + .0; + let to_next_idx = NEIGHBORS + .iter() + .enumerate() + .find(|(_, dir)| **dir == locs[2] - locs[1]) + .expect("Track locations must be neighbors") + .0; + + ctx.sim.get_mut(locs[0]).unwrap().path.neighbors |= + 1 << ((to_prev_idx as u8 + 4) % 8); + ctx.sim.get_mut(locs[2]).unwrap().path.neighbors |= + 1 << ((to_next_idx as u8 + 4) % 8); + let mut chunk = ctx.sim.get_mut(locs[1]).unwrap(); + chunk.path.neighbors |= + (1 << (to_prev_idx as u8)) | (1 << (to_next_idx as u8)); + chunk.path.offset = Vec2::new( + ctx.rng.gen_range(-16.0, 16.0), + ctx.rng.gen_range(-16.0, 16.0), + ); + } + + // Take note of the track + let track = self.tracks.insert(Track { cost, path }); + self.track_map + .entry(site) + .or_default() + .insert(nearby, track); + } + } + } + } + + Some(site) + } + + fn tick(&mut self, _ctx: &mut GenCtx, years: f32) { + for site in self.sites.iter_mut() { + site.simulate(years, &self.places.get(site.place).nat_res); + } + + // Trade stocks + // let mut stocks = TRADE_STOCKS; + // stocks.shuffle(ctx.rng); // Give each stock a chance to be traded + // first for stock in stocks.iter().copied() { + // let mut sell_orders = self.sites + // .iter_ids() + // .map(|(id, site)| (id, { + // econ::SellOrder { + // quantity: + // site.export_targets[stock].max(0.0).min(site.stocks[stock]), + // price: + // site.trade_states[stock].sell_belief.choose_price(ctx) * 1.25, // + // Trade cost q_sold: 0.0, + // } + // })) + // .filter(|(_, order)| order.quantity > 0.0) + // .collect::>(); + + // let mut sites = self.sites + // .ids() + // .collect::>(); + // sites.shuffle(ctx.rng); // Give all sites a chance to buy first + // for site in sites { + // let (max_spend, max_price, max_import) = { + // let site = self.sites.get(site); + // let budget = site.coin * 0.5; + // let total_value = site.values.iter().map(|(_, v)| + // (*v).unwrap_or(0.0)).sum::(); ( + // 100000.0,//(site.values[stock].unwrap_or(0.1) / + // total_value * budget).min(budget), + // site.trade_states[stock].buy_belief.price, + // -site.export_targets[stock].min(0.0), ) + // }; + // let (quantity, spent) = econ::buy_units(ctx, sell_orders + // .iter_mut() + // .filter(|(id, _)| site != *id && self.track_between(site, + // *id).is_some()) .map(|(_, order)| order), + // max_import, + // 1000000.0, // Max price TODO + // max_spend, + // ); + // let mut site = self.sites.get_mut(site); + // site.coin -= spent; + // if quantity > 0.0 { + // site.stocks[stock] += quantity; + // site.last_exports[stock] = -quantity; + // site.trade_states[stock].buy_belief.update_buyer(years, + // spent / quantity); println!("Belief: {:?}", + // site.trade_states[stock].buy_belief); } + // } + + // for (site, order) in sell_orders { + // let mut site = self.sites.get_mut(site); + // site.coin += order.q_sold * order.price; + // if order.q_sold > 0.0 { + // site.stocks[stock] -= order.q_sold; + // site.last_exports[stock] = order.q_sold; + // + // site.trade_states[stock].sell_belief.update_seller(order.q_sold / + // order.quantity); } + // } + // } + } +} + +/// Attempt to find a path between two locations +fn find_path( + ctx: &mut GenCtx, + a: Vec2, + b: Vec2, +) -> Option<(Path>, f32)> { + let sim = &ctx.sim; + let heuristic = move |l: &Vec2| (l.distance_squared(b) as f32).sqrt(); + let neighbors = |l: &Vec2| { + let l = *l; + NEIGHBORS + .iter() + .filter(move |dir| walk_in_dir(sim, l, **dir).is_some()) + .map(move |dir| l + *dir) + }; + let transition = + |a: &Vec2, b: &Vec2| 1.0 + walk_in_dir(sim, *a, *b - *a).unwrap_or(10000.0); + let satisfied = |l: &Vec2| *l == b; + let mut astar = Astar::new(20000, a, heuristic); + astar + .poll(20000, heuristic, neighbors, transition, satisfied) + .into_path() + .and_then(|path| astar.get_cheapest_cost().map(|cost| (path, cost))) +} + +/// Return Some if travel between a location and a chunk next to it is permitted +/// If permitted, the approximate relative const of traversal is given +// (TODO: by whom?) +fn walk_in_dir(sim: &WorldSim, a: Vec2, dir: Vec2) -> Option { + if loc_suitable_for_walking(sim, a) && loc_suitable_for_walking(sim, a + dir) { + let a_chunk = sim.get(a)?; + let b_chunk = sim.get(a + dir)?; + + let hill_cost = ((b_chunk.alt - a_chunk.alt).abs() / 2.5).powf(2.0); + let water_cost = if b_chunk.river.near_water() { + 50.0 + } else { + 0.0 + }; + let wild_cost = if b_chunk.path.is_path() { + 0.0 // Traversing existing paths has no additional cost! + } else { + 2.0 + }; + Some(1.0 + hill_cost + water_cost + wild_cost) + } else { + None + } +} + +/// Return true if a position is suitable for walking on +fn loc_suitable_for_walking(sim: &WorldSim, loc: Vec2) -> bool { + if let Some(chunk) = sim.get(loc) { + !chunk.river.is_ocean() && !chunk.river.is_lake() + } else { + false + } +} + +/// Return true if a site could be constructed between a location and a chunk +/// next to it is permitted (TODO: by whom?) +fn site_in_dir(sim: &WorldSim, a: Vec2, dir: Vec2) -> bool { + loc_suitable_for_site(sim, a) && loc_suitable_for_site(sim, a + dir) +} + +/// Return true if a position is suitable for site construction (TODO: +/// criteria?) +fn loc_suitable_for_site(sim: &WorldSim, loc: Vec2) -> bool { + if let Some(chunk) = sim.get(loc) { + !chunk.river.is_ocean() + && !chunk.river.is_lake() + && sim + .get_gradient_approx(loc) + .map(|grad| grad < 1.0) + .unwrap_or(false) + } else { + false + } +} + +/// Attempt to search for a location that's suitable for site construction +fn find_site_loc(ctx: &mut GenCtx, near: Option<(Vec2, f32)>) -> Option> { + const MAX_ATTEMPTS: usize = 100; + let mut loc = None; + for _ in 0..MAX_ATTEMPTS { + let test_loc = loc.unwrap_or_else(|| match near { + Some((origin, dist)) => { + origin + + (Vec2::new(ctx.rng.gen_range(-1.0, 1.0), ctx.rng.gen_range(-1.0, 1.0)) + .try_normalized() + .unwrap_or(Vec2::zero()) + * ctx.rng.gen::() + * dist) + .map(|e| e as i32) + }, + None => Vec2::new( + ctx.rng.gen_range(0, ctx.sim.get_size().x as i32), + ctx.rng.gen_range(0, ctx.sim.get_size().y as i32), + ), + }); + + if loc_suitable_for_site(&ctx.sim, test_loc) { + return Some(test_loc); + } + + loc = ctx.sim.get(test_loc).and_then(|c| { + Some( + c.downhill? + .map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| { + e / (sz as i32) + }), + ) + }); + } + None +} + +#[derive(Debug)] +pub struct Civ { + capital: Id, + homeland: Id, +} + +#[derive(Debug)] +pub struct Place { + center: Vec2, + nat_res: NaturalResources, +} + +// Productive capacity per year +#[derive(Default, Debug)] +pub struct NaturalResources { + wood: f32, + rock: f32, + river: f32, + farmland: f32, +} + +impl NaturalResources { + fn include_chunk(&mut self, ctx: &mut GenCtx, loc: Vec2) { + let chunk = if let Some(chunk) = ctx.sim.get(loc) { + chunk + } else { + return; + }; + + self.wood += chunk.tree_density; + self.rock += chunk.rockiness; + self.river += if chunk.river.is_river() { 5.0 } else { 0.0 }; + self.farmland += if chunk.humidity > 0.35 + && chunk.temp > -0.3 + && chunk.temp < 0.75 + && chunk.chaos < 0.5 + && ctx + .sim + .get_gradient_approx(loc) + .map(|grad| grad < 0.7) + .unwrap_or(false) + { + 1.0 + } else { + 0.0 + }; + } +} + +pub struct Track { + /// Cost of using this track relative to other paths. This cost is an + /// arbitrary unit and doesn't make sense unless compared to other track + /// costs. + cost: f32, + path: Path>, +} + +#[derive(Debug)] +pub struct Site { + pub kind: SiteKind, + pub center: Vec2, + pub place: Id, + + population: f32, + + // Total amount of each stock + stocks: Stocks, + // Surplus stock compared to demand orders + surplus: Stocks, + // For some goods, such a goods without any supply, it doesn't make sense to talk about value + values: Stocks>, + + // Proportion of individuals dedicated to an industry + labors: MapVec, + // Per worker, per year, of their output good + yields: MapVec, + productivity: MapVec, + + last_exports: Stocks, + export_targets: Stocks, + trade_states: Stocks, + coin: f32, +} + +impl fmt::Display for Site { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.kind { + SiteKind::Settlement => writeln!(f, "Settlement")?, + SiteKind::Dungeon => writeln!(f, "Dungeon")?, + } + writeln!(f, "- population: {}", self.population.floor() as u32)?; + writeln!(f, "- coin: {}", self.coin.floor() as u32)?; + writeln!(f, "Stocks")?; + for (stock, q) in self.stocks.iter() { + writeln!(f, "- {}: {}", stock, q.floor())?; + } + writeln!(f, "Values")?; + for stock in TRADE_STOCKS.iter() { + writeln!( + f, + "- {}: {}", + stock, + self.values[*stock] + .map(|x| x.to_string()) + .unwrap_or_else(|| "N/A".to_string()) + )?; + } + writeln!(f, "Laborers")?; + for (labor, n) in self.labors.iter() { + writeln!(f, "- {}: {}", labor, (*n * self.population).floor() as u32)?; + } + writeln!(f, "Export targets")?; + for (stock, n) in self.export_targets.iter() { + writeln!(f, "- {}: {}", stock, n)?; + } + + Ok(()) + } +} + +#[derive(Debug)] +pub enum SiteKind { + Settlement, + Dungeon, +} + +impl Site { + pub fn simulate(&mut self, years: f32, nat_res: &NaturalResources) { + // Insert natural resources into the economy + if self.stocks[FISH] < nat_res.river { + self.stocks[FISH] = nat_res.river; + } + if self.stocks[WHEAT] < nat_res.farmland { + self.stocks[WHEAT] = nat_res.farmland; + } + if self.stocks[LOGS] < nat_res.wood { + self.stocks[LOGS] = nat_res.wood; + } + if self.stocks[GAME] < nat_res.wood { + self.stocks[GAME] = nat_res.wood; + } + if self.stocks[ROCK] < nat_res.rock { + self.stocks[ROCK] = nat_res.rock; + } + + let orders = vec![ + (None, vec![(FOOD, 0.5)]), + (Some(COOK), vec![(FLOUR, 16.0), (MEAT, 4.0), (WOOD, 3.0)]), + (Some(LUMBERJACK), vec![(LOGS, 4.5)]), + (Some(MINER), vec![(ROCK, 7.5)]), + (Some(FISHER), vec![(FISH, 4.0)]), + (Some(HUNTER), vec![(GAME, 4.0)]), + (Some(FARMER), vec![(WHEAT, 4.0)]), + ] + .into_iter() + .collect::>>(); + + // Per labourer, per year + let production = Stocks::from_list(&[ + (FARMER, (FLOUR, 2.0)), + (LUMBERJACK, (WOOD, 1.5)), + (MINER, (STONE, 0.6)), + (FISHER, (MEAT, 3.0)), + (HUNTER, (MEAT, 0.25)), + (COOK, (FOOD, 20.0)), + ]); + + let mut demand = Stocks::from_default(0.0); + for (labor, orders) in &orders { + let scale = if let Some(labor) = labor { + self.labors[*labor] + } else { + 1.0 + } * self.population; + for (stock, amount) in orders { + demand[*stock] += *amount * scale; + } + } + + let mut supply = Stocks::from_default(0.0); + for (labor, (output_stock, _)) in production.iter() { + supply[*output_stock] += self.yields[labor] * self.labors[labor] * self.population; + } + + let last_exports = &self.last_exports; + let stocks = &self.stocks; + self.surplus = demand + .clone() + .map(|stock, _| supply[stock] + stocks[stock] - demand[stock] - last_exports[stock]); + + // Update values according to the surplus of each stock + let values = &mut self.values; + self.surplus.iter().for_each(|(stock, surplus)| { + let val = 3.5f32.powf(1.0 - *surplus / demand[stock]); + values[stock] = if val > 0.001 && val < 1000.0 { + Some(val) + } else { + None + }; + }); + + // Update export targets based on relative values + let value_avg = values + .iter() + .map(|(_, v)| (*v).unwrap_or(0.0)) + .sum::() + .max(0.01) + / values.iter().filter(|(_, v)| v.is_some()).count() as f32; + let export_targets = &mut self.export_targets; + let last_exports = &self.last_exports; + self.values.iter().for_each(|(stock, value)| { + let rvalue = (*value).map(|v| v - value_avg).unwrap_or(0.0); + //let factor = if export_targets[stock] > 0.0 { 1.0 / rvalue } else { rvalue }; + export_targets[stock] = last_exports[stock] - rvalue * 0.1; // + (trade_states[stock].sell_belief.price - trade_states[stock].buy_belief.price) * 0.025; + }); + + let population = self.population; + + // Redistribute workforce according to relative good values + let labor_ratios = production.clone().map(|labor, (output_stock, _)| { + self.productivity[labor] * demand[output_stock] / supply[output_stock].max(0.001) + }); + let labor_ratio_sum = labor_ratios.iter().map(|(_, r)| *r).sum::().max(0.01); + production.iter().for_each(|(labor, _)| { + let smooth = 0.8; + self.labors[labor] = smooth * self.labors[labor] + + (1.0 - smooth) + * (labor_ratios[labor].max(labor_ratio_sum / 1000.0) / labor_ratio_sum); + }); + + // Production + let stocks_before = self.stocks.clone(); + for (labor, orders) in orders.iter() { + let scale = if let Some(labor) = labor { + self.labors[*labor] + } else { + 1.0 + } * population; + + // For each order, we try to find the minimum satisfaction rate - this limits + // how much we can produce! For example, if we need 0.25 fish and + // 0.75 oats to make 1 unit of food, but only 0.5 units of oats are + // available then we only need to consume 2/3rds + // of other ingredients and leave the rest in stock + // In effect, this is the productivity + let productivity = orders + .iter() + .map(|(stock, amount)| { + // What quantity is this order requesting? + let _quantity = *amount * scale; + // What proportion of this order is the economy able to satisfy? + let satisfaction = (stocks_before[*stock] / demand[*stock]).min(1.0); + satisfaction + }) + .min_by(|a, b| a.partial_cmp(b).unwrap()) + .unwrap_or_else(|| { + panic!("Industry {:?} requires at least one input order", labor) + }); + + for (stock, amount) in orders { + // What quantity is this order requesting? + let quantity = *amount * scale; + // What amount gets actually used in production? + let used = quantity * productivity; + + // Deplete stocks accordingly + self.stocks[*stock] = (self.stocks[*stock] - used).max(0.0); + } + + // Industries produce things + if let Some(labor) = labor { + let (stock, rate) = production[*labor]; + let workers = self.labors[*labor] * population; + let final_rate = rate; + let yield_per_worker = productivity * final_rate; + self.yields[*labor] = yield_per_worker; + self.productivity[*labor] = productivity; + self.stocks[stock] += yield_per_worker * workers.powf(1.1); + } + } + + // Denature stocks + self.stocks.iter_mut().for_each(|(_, v)| *v *= 0.9); + + // Births/deaths + const NATURAL_BIRTH_RATE: f32 = 0.15; + const DEATH_RATE: f32 = 0.05; + let birth_rate = if self.surplus[FOOD] > 0.0 { + NATURAL_BIRTH_RATE + } else { + 0.0 + }; + self.population += years * self.population * (birth_rate - DEATH_RATE); + } +} + +type Occupation = &'static str; +const FARMER: Occupation = "farmer"; +const LUMBERJACK: Occupation = "lumberjack"; +const MINER: Occupation = "miner"; +const FISHER: Occupation = "fisher"; +const HUNTER: Occupation = "hunter"; +const COOK: Occupation = "cook"; + +type Stock = &'static str; +const WHEAT: Stock = "wheat"; +const FLOUR: Stock = "flour"; +const MEAT: Stock = "meat"; +const FISH: Stock = "fish"; +const GAME: Stock = "game"; +const FOOD: Stock = "food"; +const LOGS: Stock = "logs"; +const WOOD: Stock = "wood"; +const ROCK: Stock = "rock"; +const STONE: Stock = "stone"; +const TRADE_STOCKS: [Stock; 5] = [FLOUR, MEAT, FOOD, WOOD, STONE]; + +#[derive(Debug, Clone)] +struct TradeState { + buy_belief: econ::Belief, + sell_belief: econ::Belief, +} + +impl Default for TradeState { + fn default() -> Self { + Self { + buy_belief: econ::Belief { + price: 1.0, + confidence: 0.25, + }, + sell_belief: econ::Belief { + price: 1.0, + confidence: 0.25, + }, + } + } +} + +pub type Stocks = MapVec; + +#[derive(Default, Clone, Debug)] +pub struct MapVec { + entries: HashMap, + default: T, +} + +impl MapVec { + pub fn from_list<'a>(i: impl IntoIterator) -> Self + where + K: 'a, + T: 'a, + { + Self { + entries: i.into_iter().cloned().collect(), + default: T::default(), + } + } + + pub fn from_default(default: T) -> Self { + Self { + entries: HashMap::default(), + default, + } + } + + pub fn get_mut(&mut self, entry: K) -> &mut T { + let default = &self.default; + self.entries.entry(entry).or_insert_with(|| default.clone()) + } + + pub fn get(&self, entry: K) -> &T { self.entries.get(&entry).unwrap_or(&self.default) } + + pub fn map(self, mut f: impl FnMut(K, T) -> U) -> MapVec { + MapVec { + entries: self + .entries + .into_iter() + .map(|(s, v)| (s.clone(), f(s, v))) + .collect(), + default: U::default(), + } + } + + pub fn iter(&self) -> impl Iterator + '_ { + self.entries.iter().map(|(s, v)| (*s, v)) + } + + pub fn iter_mut(&mut self) -> impl Iterator + '_ { + self.entries.iter_mut().map(|(s, v)| (*s, v)) + } +} + +impl std::ops::Index for MapVec { + type Output = T; + + fn index(&self, entry: K) -> &Self::Output { self.get(entry) } +} + +impl std::ops::IndexMut for MapVec { + fn index_mut(&mut self, entry: K) -> &mut Self::Output { self.get_mut(entry) } +} diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index dcab7e5906..7cd17bef73 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -1,11 +1,7 @@ use crate::{ all::ForestKind, block::StructureMeta, - generator::{Generator, SpawnRules, TownGen}, - sim::{ - local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, LocationInfo, RiverKind, SimChunk, - WorldSim, - }, + sim::{local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, RiverKind, SimChunk, WorldSim}, util::{RandomPerm, Sampler, UnitChooser}, CONFIG, }; @@ -221,11 +217,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let sim = &self.sim; - let turb = Vec2::new( + let _turb = Vec2::new( sim.gen_ctx.turb_x_nz.get((wposf.div(48.0)).into_array()) as f32, sim.gen_ctx.turb_y_nz.get((wposf.div(48.0)).into_array()) as f32, ) * 12.0; - let wposf_turb = wposf + turb.map(|e| e as f64); + let wposf_turb = wposf; // + turb.map(|e| e as f64); let chaos = sim.get_interpolated(wpos, |chunk| chunk.chaos)?; let temp = sim.get_interpolated(wpos, |chunk| chunk.temp)?; @@ -234,6 +230,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let tree_density = sim.get_interpolated(wpos, |chunk| chunk.tree_density)?; let spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?; let alt = sim.get_interpolated_monotone(wpos, |chunk| chunk.alt)?; + let chunk_warp_factor = sim.get_interpolated_monotone(wpos, |chunk| chunk.warp_factor)?; let sim_chunk = sim.get(chunk_pos)?; let neighbor_coef = TerrainChunkSize::RECT_SIZE.map(|e| e as f64); let my_chunk_idx = vec2_as_uniform_idx(chunk_pos); @@ -593,223 +590,280 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let near_cliffs = sim_chunk.near_cliffs; let river_gouge = 0.5; - let (in_water, alt_, water_level, warp_factor) = if let Some(( - max_border_river_pos, - river_chunk, - max_border_river, - max_border_river_dist, - )) = max_river + let (_in_water, water_dist, alt_, water_level, riverless_alt, warp_factor) = if let Some( + (max_border_river_pos, river_chunk, max_border_river, max_border_river_dist), + ) = + max_river { // This is flowing into a lake, or a lake, or is at least a non-ocean tile. // // If we are <= water_alt, we are in the lake; otherwise, we are flowing into // it. - let (in_water, new_alt, new_water_alt, warp_factor) = max_border_river - .river_kind - .and_then(|river_kind| { - if let RiverKind::River { cross_section } = river_kind { - if max_border_river_dist.map(|(_, dist, _, _)| dist) != Some(Vec2::zero()) { - return None; - } - let (_, _, river_width, (river_t, (river_pos, _), downhill_river_chunk)) = - max_border_river_dist.unwrap(); - let river_alt = Lerp::lerp( - river_chunk.alt.max(river_chunk.water_alt), - downhill_river_chunk.alt.max(downhill_river_chunk.water_alt), - river_t as f32, - ); - let new_alt = river_alt - river_gouge; - let river_dist = wposf.distance(river_pos); - let river_height_factor = river_dist / (river_width * 0.5); + let (in_water, water_dist, new_alt, new_water_alt, riverless_alt, warp_factor) = + max_border_river + .river_kind + .and_then(|river_kind| { + if let RiverKind::River { cross_section } = river_kind { + if max_border_river_dist.map(|(_, dist, _, _)| dist) + != Some(Vec2::zero()) + { + return None; + } + let ( + _, + _, + river_width, + (river_t, (river_pos, _), downhill_river_chunk), + ) = max_border_river_dist.unwrap(); + let river_alt = Lerp::lerp( + river_chunk.alt.max(river_chunk.water_alt), + downhill_river_chunk.alt.max(downhill_river_chunk.water_alt), + river_t as f32, + ); + let new_alt = river_alt - river_gouge; + let river_dist = wposf.distance(river_pos); + let river_height_factor = river_dist / (river_width * 0.5); - Some(( - true, - Lerp::lerp( + let valley_alt = Lerp::lerp( new_alt - cross_section.y.max(1.0), new_alt - 1.0, (river_height_factor * river_height_factor) as f32, - ), - new_alt, - 0.0, - )) - } else { - None - } - }) - .unwrap_or_else(|| { - max_border_river - .river_kind - .and_then(|river_kind| { - match river_kind { - RiverKind::Ocean => { - let ( - _, - dist, - river_width, - (river_t, (river_pos, _), downhill_river_chunk), - ) = if let Some(dist) = max_border_river_dist { - dist - } else { - log::error!( - "Ocean: {:?} Here: {:?}, Ocean: {:?}", - max_border_river, - chunk_pos, - max_border_river_pos - ); - panic!( - "Oceans should definitely have a downhill! ...Right?" - ); - }; - let lake_water_alt = Lerp::lerp( - river_chunk.alt.max(river_chunk.water_alt), - downhill_river_chunk - .alt - .max(downhill_river_chunk.water_alt), - river_t as f32, - ); + ); - if dist == Vec2::zero() { - let river_dist = wposf.distance(river_pos); - let _river_height_factor = river_dist / (river_width * 0.5); - return Some(( - true, - alt_for_river.min(lake_water_alt - 1.0 - river_gouge), - lake_water_alt - river_gouge, - 0.0, - )); - } - - Some(( - river_scale_factor <= 1.0, - alt_for_river, - downhill_water_alt, - river_scale_factor as f32, - )) - }, - RiverKind::Lake { .. } => { - let lake_dist = (max_border_river_pos.map(|e| e as f64) - * neighbor_coef) - .distance(wposf); - let downhill_river_chunk = max_border_river_pos; - let lake_id_dist = downhill_river_chunk - chunk_pos; - let in_bounds = lake_id_dist.x >= -1 - && lake_id_dist.y >= -1 - && lake_id_dist.x <= 1 - && lake_id_dist.y <= 1; - let in_bounds = - in_bounds && (lake_id_dist.x >= 0 && lake_id_dist.y >= 0); - let (_, dist, _, (river_t, _, downhill_river_chunk)) = - if let Some(dist) = max_border_river_dist { + Some(( + true, + Some((river_dist - river_width * 0.5) as f32), + valley_alt, + new_alt, + river_alt, + 0.0, + )) + } else { + None + } + }) + .unwrap_or_else(|| { + max_border_river + .river_kind + .and_then(|river_kind| { + match river_kind { + RiverKind::Ocean => { + let ( + _, + dist, + river_width, + (river_t, (river_pos, _), downhill_river_chunk), + ) = if let Some(dist) = max_border_river_dist { dist } else { - if lake_dist - <= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0 - || in_bounds - { - let gouge_factor = 0.0; - return Some(( - in_bounds - || downhill_water_alt + log::error!( + "Ocean: {:?} Here: {:?}, Ocean: {:?}", + max_border_river, + chunk_pos, + max_border_river_pos + ); + panic!( + "Oceans should definitely have a downhill! \ + ...Right?" + ); + }; + let lake_water_alt = Lerp::lerp( + river_chunk.alt.max(river_chunk.water_alt), + downhill_river_chunk + .alt + .max(downhill_river_chunk.water_alt), + river_t as f32, + ); + + if dist == Vec2::zero() { + let river_dist = wposf.distance(river_pos); + let _river_height_factor = + river_dist / (river_width * 0.5); + return Some(( + true, + Some((river_dist - river_width * 0.5) as f32), + alt_for_river + .min(lake_water_alt - 1.0 - river_gouge), + lake_water_alt - river_gouge, + alt_for_river.max(lake_water_alt), + 0.0, + )); + } + + Some(( + river_scale_factor <= 1.0, + Some( + (wposf.distance(river_pos) - river_width * 0.5) + as f32, + ), + alt_for_river, + downhill_water_alt, + alt_for_river, + river_scale_factor as f32, + )) + }, + RiverKind::Lake { .. } => { + let lake_dist = (max_border_river_pos.map(|e| e as f64) + * neighbor_coef) + .distance(wposf); + let downhill_river_chunk = max_border_river_pos; + let lake_id_dist = downhill_river_chunk - chunk_pos; + let in_bounds = lake_id_dist.x >= -1 + && lake_id_dist.y >= -1 + && lake_id_dist.x <= 1 + && lake_id_dist.y <= 1; + let in_bounds = in_bounds + && (lake_id_dist.x >= 0 && lake_id_dist.y >= 0); + let (_, dist, _, (river_t, _, downhill_river_chunk)) = + if let Some(dist) = max_border_river_dist { + dist + } else { + if lake_dist + <= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0 + || in_bounds + { + let gouge_factor = 0.0; + return Some(( + in_bounds + || downhill_water_alt + .max(river_chunk.water_alt) + > alt_for_river, + Some(lake_dist as f32), + alt_for_river, + (downhill_water_alt .max(river_chunk.water_alt) - > alt_for_river, - alt_for_river, - (downhill_water_alt.max(river_chunk.water_alt) - - river_gouge), - river_scale_factor as f32 - * (1.0 - gouge_factor), + - river_gouge), + alt_for_river, + river_scale_factor as f32 + * (1.0 - gouge_factor), + )); + } else { + return Some(( + false, + Some(lake_dist as f32), + alt_for_river, + downhill_water_alt, + alt_for_river, + river_scale_factor as f32, + )); + } + }; + + let lake_dist = dist.y; + let lake_water_alt = Lerp::lerp( + river_chunk.alt.max(river_chunk.water_alt), + downhill_river_chunk + .alt + .max(downhill_river_chunk.water_alt), + river_t as f32, + ); + if dist == Vec2::zero() { + return Some(( + true, + Some(lake_dist as f32), + alt_for_river + .min(lake_water_alt - 1.0 - river_gouge), + lake_water_alt - river_gouge, + alt_for_river.max(lake_water_alt), + 0.0, + )); + } + if lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0 + || in_bounds + { + let gouge_factor = if in_bounds && lake_dist <= 1.0 { + 1.0 + } else { + 0.0 + }; + let in_bounds_ = lake_dist + <= TerrainChunkSize::RECT_SIZE.x as f64 * 0.5; + if gouge_factor == 1.0 { + return Some(( + true, + Some(lake_dist as f32), + alt.min(lake_water_alt - 1.0 - river_gouge), + downhill_water_alt.max(lake_water_alt) + - river_gouge, + alt.max(lake_water_alt), + 0.0, )); } else { return Some(( - false, + true, + None, alt_for_river, - downhill_water_alt, - river_scale_factor as f32, + if in_bounds_ { + downhill_water_alt.max(lake_water_alt) + } else { + downhill_water_alt + } - river_gouge, + alt_for_river, + river_scale_factor as f32 + * (1.0 - gouge_factor), )); } - }; - - let lake_dist = dist.y; - let lake_water_alt = Lerp::lerp( - river_chunk.alt.max(river_chunk.water_alt), - downhill_river_chunk - .alt - .max(downhill_river_chunk.water_alt), - river_t as f32, - ); - if dist == Vec2::zero() { - return Some(( - true, - alt_for_river.min(lake_water_alt - 1.0 - river_gouge), - lake_water_alt - river_gouge, - 0.0, - )); - } - if lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0 - || in_bounds - { - let gouge_factor = if in_bounds && lake_dist <= 1.0 { - 1.0 - } else { - 0.0 - }; - let in_bounds_ = - lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 0.5; - if gouge_factor == 1.0 { - return Some(( - true, - alt.min(lake_water_alt - 1.0 - river_gouge), - downhill_water_alt.max(lake_water_alt) - - river_gouge, - 0.0, - )); - } else { - return Some(( - true, - alt_for_river, - if in_bounds_ { - downhill_water_alt.max(lake_water_alt) - - river_gouge - } else { - downhill_water_alt - river_gouge - }, - river_scale_factor as f32 * (1.0 - gouge_factor), - )); } - } - Some(( - river_scale_factor <= 1.0, - alt_for_river, - downhill_water_alt, - river_scale_factor as f32, - )) - }, - RiverKind::River { .. } => { - // FIXME: Make water altitude accurate. - Some(( - river_scale_factor <= 1.0, - alt_for_river, - downhill_water_alt, - river_scale_factor as f32, - )) - }, - } - }) - .unwrap_or(( - false, - alt_for_river, - downhill_water_alt, - river_scale_factor as f32, - )) - }); - (in_water, new_alt, new_water_alt, warp_factor) + Some(( + river_scale_factor <= 1.0, + Some(lake_dist as f32), + alt_for_river, + downhill_water_alt, + alt_for_river, + river_scale_factor as f32, + )) + }, + RiverKind::River { .. } => { + let (_, _, river_width, (_, (river_pos, _), _)) = + max_border_river_dist.unwrap(); + let river_dist = wposf.distance(river_pos); + + // FIXME: Make water altitude accurate. + Some(( + river_scale_factor <= 1.0, + Some((river_dist - river_width * 0.5) as f32), + alt_for_river, + downhill_water_alt, + alt_for_river, + river_scale_factor as f32, + )) + }, + } + }) + .unwrap_or(( + false, + None, + alt_for_river, + downhill_water_alt, + alt_for_river, + river_scale_factor as f32, + )) + }); + ( + in_water, + water_dist, + new_alt, + new_water_alt, + riverless_alt, + warp_factor, + ) } else { - (false, alt_for_river, downhill_water_alt, 1.0) + ( + false, + None, + alt_for_river, + downhill_water_alt, + alt_for_river, + 1.0, + ) }; + let warp_factor = warp_factor * chunk_warp_factor; // NOTE: To disable warp, uncomment this line. // let warp_factor = 0.0; let riverless_alt_delta = Lerp::lerp(0.0, riverless_alt_delta, warp_factor); let alt = alt_ + riverless_alt_delta; + let riverless_alt = riverless_alt + riverless_alt_delta; let basement = alt + sim.get_interpolated_monotone(wpos, |chunk| chunk.basement.sub(chunk.alt))?; @@ -1061,8 +1115,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { 5.0 }; + let path = sim.get_nearest_path(wpos); + Some(ColumnSample { alt, + riverless_alt, basement, chaos, water_level, @@ -1076,7 +1133,17 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { ), sub_surface_color, // No growing directly on bedrock. - tree_density: Lerp::lerp(0.0, tree_density, alt.sub(2.0).sub(basement).mul(0.5)), + // And, no growing on sites that don't want them TODO: More precise than this when we + // apply trees as a post-processing layer + tree_density: if sim_chunk + .sites + .iter() + .all(|site| site.spawn_rules(wpos).trees) + { + Lerp::lerp(0.0, tree_density, alt.sub(2.0).sub(basement).mul(0.5)) + } else { + 0.0 + }, forest_kind: sim_chunk.forest_kind, close_structures: self.gen_close_structures(wpos), cave_xy, @@ -1091,20 +1158,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { temp, humidity, spawn_rate, - location: sim_chunk.location.as_ref(), stone_col, + water_dist, + path, chunk: sim_chunk, - spawn_rules: sim_chunk - .structures - .town - .as_ref() - .map(|town| TownGen.spawn_rules(town, wpos)) - .unwrap_or(SpawnRules::default()) - .and(SpawnRules { - cliffs: !in_water, - trees: true, - }), }) } } @@ -1112,6 +1170,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { #[derive(Clone)] pub struct ColumnSample<'a> { pub alt: f32, + pub riverless_alt: f32, pub basement: f32, pub chaos: f32, pub water_level: f32, @@ -1133,11 +1192,11 @@ pub struct ColumnSample<'a> { pub temp: f32, pub humidity: f32, pub spawn_rate: f32, - pub location: Option<&'a LocationInfo>, pub stone_col: Rgb, + pub water_dist: Option, + pub path: Option<(f32, Vec2)>, pub chunk: &'a SimChunk, - pub spawn_rules: SpawnRules, } #[derive(Copy, Clone)] diff --git a/world/src/generator/mod.rs b/world/src/generator/mod.rs deleted file mode 100644 index 46c9d41640..0000000000 --- a/world/src/generator/mod.rs +++ /dev/null @@ -1,39 +0,0 @@ -mod town; - -// Reexports -pub use self::town::{TownGen, TownState}; - -use crate::{column::ColumnSample, util::Sampler}; -use common::terrain::Block; -use vek::*; - -#[derive(Copy, Clone, Debug)] -pub struct SpawnRules { - pub trees: bool, - pub cliffs: bool, -} - -impl Default for SpawnRules { - fn default() -> Self { - Self { - trees: true, - cliffs: true, - } - } -} - -impl SpawnRules { - pub fn and(self, other: Self) -> Self { - Self { - trees: self.trees && other.trees, - cliffs: self.cliffs && other.cliffs, - } - } -} - -pub trait Generator<'a, T: 'a>: - Sampler<'a, Index = (&'a T, Vec3, &'a ColumnSample<'a>, f32), Sample = Option> -{ - fn get_z_limits(&self, state: &'a T, wpos: Vec2, sample: &ColumnSample) -> (f32, f32); - fn spawn_rules(&self, town: &'a TownState, wpos: Vec2) -> SpawnRules; -} diff --git a/world/src/generator/town/mod.rs b/world/src/generator/town/mod.rs deleted file mode 100644 index b04798dba5..0000000000 --- a/world/src/generator/town/mod.rs +++ /dev/null @@ -1,710 +0,0 @@ -mod util; -mod vol; - -use super::{Generator, SpawnRules}; -use crate::{ - block::{block_from_structure, BlockGen}, - column::ColumnSample, - util::Sampler, - CONFIG, -}; -use common::{ - assets, - terrain::{Block, BlockKind, Structure}, - vol::{ReadVol, Vox, WriteVol}, -}; -use hashbrown::HashSet; -use lazy_static::lazy_static; -use rand::prelude::*; -use std::{ops::Add, sync::Arc}; -use vek::*; - -use self::vol::{CellKind, ColumnKind, Module, TownCell, TownColumn, TownVol}; - -const CELL_SIZE: i32 = 9; -const CELL_HEIGHT: i32 = 9; - -pub struct TownGen; - -impl<'a> Sampler<'a> for TownGen { - type Index = (&'a TownState, Vec3, &'a ColumnSample<'a>, f32); - type Sample = Option; - - fn get(&self, (town, wpos, sample, height): Self::Index) -> Self::Sample { - let cell_pos = (wpos - town.center) - .map2(Vec3::new(CELL_SIZE, CELL_SIZE, CELL_HEIGHT), |e, sz| { - e.div_euclid(sz) - }) - .add(Vec3::from(town.vol.size() / 2)); - let inner_pos = (wpos - town.center) - .map2(Vec3::new(CELL_SIZE, CELL_SIZE, CELL_HEIGHT), |e, sz| { - e.rem_euclid(sz) - }); - - let cell = town.vol.get(cell_pos).ok()?; - - match (modules_from_kind(&cell.kind), &cell.module) { - (Some(module_list), Some(module)) => { - let transform = [ - (Vec2::new(0, 0), Vec2::unit_x(), Vec2::unit_y()), - (Vec2::new(0, 1), -Vec2::unit_y(), Vec2::unit_x()), - (Vec2::new(1, 1), -Vec2::unit_x(), -Vec2::unit_y()), - (Vec2::new(1, 0), Vec2::unit_y(), -Vec2::unit_x()), - ]; - - module_list[module.vol_idx] - .0 - .get( - Vec3::from( - transform[module.dir].0 * (CELL_SIZE - 1) - + transform[module.dir].1 * inner_pos.x - + transform[module.dir].2 * inner_pos.y, - ) + Vec3::unit_z() * inner_pos.z, - ) - .ok() - .and_then(|sb| { - block_from_structure(*sb, BlockKind::Normal, wpos, wpos.into(), 0, sample) - }) - }, - _ => match cell.kind { - CellKind::Empty => None, - CellKind::Park => None, - CellKind::Rock => Some(Block::new(BlockKind::Normal, Rgb::broadcast(100))), - CellKind::Wall => Some(Block::new(BlockKind::Normal, Rgb::broadcast(175))), - CellKind::Well => Some(Block::new(BlockKind::Normal, Rgb::broadcast(0))), - CellKind::Road => { - if (wpos.z as f32) < height - 1.0 { - Some(Block::new( - BlockKind::Normal, - Lerp::lerp( - Rgb::new(150.0, 140.0, 50.0), - Rgb::new(100.0, 95.0, 30.0), - sample.marble_small, - ) - .map(|e| e as u8), - )) - } else { - Some(Block::empty()) - } - }, - CellKind::House(idx) => Some(Block::new(BlockKind::Normal, town.houses[idx].color)), - }, - } - } -} - -impl<'a> Generator<'a, TownState> for TownGen { - fn get_z_limits( - &self, - _town: &'a TownState, - _wpos: Vec2, - sample: &ColumnSample, - ) -> (f32, f32) { - (sample.alt - 32.0, sample.alt + 75.0) - } - - fn spawn_rules(&self, town: &'a TownState, wpos: Vec2) -> SpawnRules { - SpawnRules { - trees: wpos.distance_squared(town.center.into()) > (town.radius + 32).pow(2), - cliffs: false, - } - } -} - -struct House { - color: Rgb, -} - -pub struct TownState { - center: Vec3, - radius: i32, - vol: TownVol, - houses: Vec, -} - -impl TownState { - pub fn generate(center: Vec2, gen: &mut BlockGen, rng: &mut impl Rng) -> Option { - let radius = rng.gen_range(18, 20) * 9; - let size = Vec2::broadcast(radius * 2 / 9 - 2); - - let center_col = BlockGen::sample_column(&gen.column_gen, &mut gen.column_cache, center); - - if center_col.map(|sample| sample.chaos).unwrap_or(0.0) > 0.35 - || center_col.map(|sample| sample.alt).unwrap_or(0.0) < CONFIG.sea_level + 10.0 - { - return None; - } - - let alt = center_col.map(|sample| sample.alt).unwrap_or(0.0) as i32; - - let mut vol = TownVol::generate_from( - size, - |pos| { - let wpos = center + (pos - size / 2) * CELL_SIZE + CELL_SIZE / 2; - let rel_alt = BlockGen::sample_column(&gen.column_gen, &mut gen.column_cache, wpos) - .map(|sample| sample.alt) - .unwrap_or(0.0) as i32 - + CELL_HEIGHT / 2 - - alt; - - let col = TownColumn { - ground: rel_alt.div_euclid(CELL_HEIGHT), - kind: None, - }; - - (col.ground, col) - }, - |(col, pos)| { - if pos.z >= col.ground { - TownCell::empty() - } else { - TownCell::from(CellKind::Rock) - } - }, - ); - - // Generation passes - vol.setup(rng); - vol.gen_roads(rng, 30); - vol.gen_parks(rng, 3); - vol.emplace_columns(); - let houses = vol.gen_houses(rng, 50); - vol.gen_wells(rng, 5); - vol.gen_walls(rng); - vol.resolve_modules(rng); - vol.cull_unused(); - - Some(Self { - center: Vec3::new(center.x, center.y, alt), - radius, - vol, - houses, - }) - } - - pub fn center(&self) -> Vec3 { self.center } - - pub fn radius(&self) -> i32 { self.radius } -} - -impl TownVol { - fn floodfill( - &self, - limit: Option, - mut opens: HashSet>, - mut f: impl FnMut(Vec2, &TownColumn) -> bool, - ) -> HashSet> { - let mut closed = HashSet::new(); - - while opens.len() > 0 { - let mut new_opens = HashSet::new(); - - 'search: for open in opens.iter() { - for i in -1..2 { - for j in -1..2 { - let pos = *open + Vec2::new(i, j); - - if let Some(col) = self.col(pos) { - if !closed.contains(&pos) && !opens.contains(&pos) && f(pos, col) { - match limit { - Some(limit) - if limit - <= new_opens.len() + closed.len() + opens.len() => - { - break 'search; - } - _ => { - new_opens.insert(pos); - }, - } - } - } - } - } - } - - closed = closed.union(&opens).copied().collect(); - opens = new_opens; - } - - closed - } - - fn setup(&mut self, rng: &mut impl Rng) { - // Place a single road tile at first - let root_road = self - .size() - .map(|sz| (sz / 8) * 2 + rng.gen_range(0, sz / 4) * 2); - self.set_col_kind(root_road, Some(ColumnKind::Road)); - } - - fn gen_roads(&mut self, rng: &mut impl Rng, n: usize) { - const ATTEMPTS: usize = 5; - - let mut junctions = HashSet::new(); - junctions.insert(self.choose_column(rng, |_, col| col.is_road()).unwrap()); - - for _road in 0..n { - for _ in 0..ATTEMPTS { - let start = *junctions.iter().choose(rng).unwrap(); - //let start = self.choose_column(rng, |pos, col| pos.map(|e| e % 2 == - // 0).reduce_and() && col.is_road()).unwrap(); - let dir = util::gen_dir(rng); - - // If the direction we want to paint a path in is obstructed, abandon this - // attempt - if self - .col(start + dir) - .map(|col| !col.is_empty()) - .unwrap_or(true) - { - continue; - } - - // How long should this road be? - let len = rng.gen_range(1, 10) * 2 + 1; - - // Paint the road until we hit an obstacle - let success = (1..len) - .map(|i| start + dir * i) - .try_for_each(|pos| { - if self.col(pos).map(|col| col.is_empty()).unwrap_or(false) { - self.set_col_kind(pos, Some(ColumnKind::Road)); - Ok(()) - } else { - junctions.insert(pos); - Err(()) - } - }) - .is_ok(); - - if success { - junctions.insert(start + dir * (len - 1)); - } - - break; - } - } - } - - fn gen_parks(&mut self, rng: &mut impl Rng, n: usize) { - const ATTEMPTS: usize = 5; - - for _ in 0..n { - for _ in 0..ATTEMPTS { - let start = self - .choose_column(rng, |pos, col| { - col.is_empty() - && (0..4).any(|i| { - self.col(pos + util::dir(i)) - .map(|col| col.is_road()) - .unwrap_or(false) - }) - }) - .unwrap(); - - let park = self.floodfill(Some(16), [start].iter().copied().collect(), |_, col| { - col.is_empty() - }); - - if park.len() < 4 { - continue; - } - - for cell in park { - self.set_col_kind(cell, Some(ColumnKind::Internal)); - let col = self.col(cell).unwrap(); - let ground = col.ground; - let _ = self.set(Vec3::new(cell.x, cell.y, ground), CellKind::Park.into()); - } - - break; - } - } - } - - fn gen_walls(&mut self, _rng: &mut impl Rng) { - let mut outer = HashSet::new(); - for i in 0..self.size().x { - outer.insert(Vec2::new(i, 0)); - outer.insert(Vec2::new(i, self.size().y - 1)); - } - for j in 0..self.size().y { - outer.insert(Vec2::new(0, j)); - outer.insert(Vec2::new(self.size().x - 1, j)); - } - - let outer = self.floodfill(None, outer, |_, col| col.is_empty()); - - let mut walls = HashSet::new(); - let _inner = self.floodfill( - None, - [self.size() / 2].iter().copied().collect(), - |pos, _| { - if outer.contains(&pos) { - walls.insert(pos); - false - } else { - true - } - }, - ); - - while let Some(wall) = walls - .iter() - .filter(|pos| { - let lateral_count = (0..4) - .filter(|i| walls.contains(&(**pos + util::dir(*i)))) - .count(); - let max_quadrant_count = (0..4) - .map(|i| { - let units = util::unit(i); - (0..2) - .map(|i| (0..2).map(move |j| (i, j))) - .flatten() - .filter(|(i, j)| walls.contains(&(**pos + units.0 * *i + units.1 * *j))) - .count() - }) - .max() - .unwrap(); - - lateral_count < 2 || (lateral_count == 2 && max_quadrant_count == 4) - }) - .next() - { - let wall = *wall; - walls.remove(&wall); - } - - for wall in walls.iter() { - let col = self.col(*wall).unwrap(); - let ground = col.ground; - for z in -1..3 { - let _ = self.set(Vec3::new(wall.x, wall.y, ground + z), CellKind::Wall.into()); - } - } - } - - fn emplace_columns(&mut self) { - for i in 0..self.size().x { - for j in 0..self.size().y { - let col = self.col(Vec2::new(i, j)).unwrap(); - let ground = col.ground; - - match col.kind { - None => {}, - Some(ColumnKind::Internal) => {}, - //Some(ColumnKind::External) => {} - Some(ColumnKind::Road) => { - for z in -1..2 { - let _ = self.set(Vec3::new(i, j, ground + z), CellKind::Road.into()); - } - }, - } - } - } - } - - fn gen_wells(&mut self, rng: &mut impl Rng, n: usize) { - for _ in 0..n { - if let Some(cell) = self.choose_cell(rng, |_, cell| { - if let CellKind::Park = cell.kind { - true - } else { - false - } - }) { - let _ = self.set(cell, CellKind::Well.into()); - } - } - } - - fn gen_houses(&mut self, rng: &mut impl Rng, n: usize) -> Vec { - const ATTEMPTS: usize = 10; - - let mut houses = Vec::new(); - for _ in 0..n { - for _ in 0..ATTEMPTS { - let entrance = { - let start_col = self.choose_column(rng, |_, col| col.is_road()).unwrap(); - let start = Vec3::new( - start_col.x, - start_col.y, - self.col(start_col).unwrap().ground, - ); - let dir = Vec3::from(util::gen_dir(rng)); - - if self - .get(start + dir) - .map(|col| !col.is_empty()) - .unwrap_or(true) - || self - .get(start + dir - Vec3::unit_z()) - .map(|col| !col.is_foundation()) - .unwrap_or(true) - { - continue; - } else { - start + dir - } - }; - - let mut cells = HashSet::new(); - - let mut energy = 2300; - while energy > 0 { - energy -= 1; - - let parent = *cells.iter().choose(rng).unwrap_or(&entrance); - let dir = util::UNITS_3D - .choose_weighted(rng, |pos| 1 + pos.z.max(0)) - .unwrap(); - - if self - .get(parent + dir) - .map(|cell| cell.is_empty()) - .unwrap_or(false) - && self - .get(parent + dir - Vec3::unit_z()) - .map(|cell| { - cell.is_foundation() - || cells.contains(&(parent + dir - Vec3::unit_z())) - }) - .unwrap_or(false) - && parent.z + dir.z <= entrance.z + 2 - // Maximum house height - { - cells.insert(parent + dir); - energy -= 10; - } - } - - // Remove cells that are too isolated - loop { - let cells_copy = cells.clone(); - - let mut any_removed = false; - cells.retain(|pos| { - let neighbour_count = (0..6) - .filter(|i| { - let neighbour = pos + util::dir_3d(*i); - cells_copy.contains(&neighbour) - }) - .count(); - - if neighbour_count < 3 { - any_removed = true; - false - } else { - true - } - }); - - if !any_removed { - break; - } - } - - // Get rid of houses that are too small - if cells.len() < 6 { - continue; - } - - for cell in cells { - let _ = self.set(cell, CellKind::House(houses.len()).into()); - self.set_col_kind(Vec2::from(cell), Some(ColumnKind::Internal)); - } - - houses.push(House { - color: Rgb::new(rng.gen(), rng.gen(), rng.gen()), - }); - } - } - - houses - } - - fn cull_unused(&mut self) { - for x in 0..self.size().x { - for y in 0..self.size().y { - for z in self.col_range(Vec2::new(x, y)).unwrap().rev() { - let pos = Vec3::new(x, y, z); - - // Remove foundations that don't have anything on top of them - if self.get(pos).unwrap().is_foundation() - && self - .get(pos + Vec3::unit_z()) - .map(TownCell::is_space) - .unwrap_or(true) - { - let _ = self.set(pos, TownCell::empty()); - } - } - } - } - } - - fn resolve_modules(&mut self, rng: &mut impl Rng) { - fn classify(cell: &TownCell, this_cell: &TownCell) -> ModuleKind { - match (&cell.kind, &this_cell.kind) { - (CellKind::House(a), CellKind::House(b)) if a == b => ModuleKind::This, - (CellKind::Wall, CellKind::Wall) => ModuleKind::This, - _ => ModuleKind::That, - } - } - - for x in 0..self.size().x { - for y in 0..self.size().y { - for z in self.col_range(Vec2::new(x, y)).unwrap() { - let pos = Vec3::new(x, y, z); - let this_cell = if let Ok(this_cell) = self.get(pos) { - this_cell - } else { - continue; - }; - - let mut signature = [ModuleKind::That; 6]; - for i in 0..6 { - signature[i] = self - .get(pos + util::dir_3d(i)) - .map(|cell| classify(cell, this_cell)) - .unwrap_or(ModuleKind::That); - } - - let module_list = if let Some(modules) = modules_from_kind(&this_cell.kind) { - modules - } else { - continue; - }; - - let module = module_list - .iter() - .enumerate() - .filter_map(|(i, module)| { - let perms = [[0, 1, 2, 3], [3, 0, 1, 2], [2, 3, 0, 1], [1, 2, 3, 0]]; - - let mut rotated_signature = [ModuleKind::That; 6]; - for (dir, perm) in perms.iter().enumerate() { - rotated_signature[perm[0]] = signature[0]; - rotated_signature[perm[1]] = signature[1]; - rotated_signature[perm[2]] = signature[2]; - rotated_signature[perm[3]] = signature[3]; - rotated_signature[4] = signature[4]; - rotated_signature[5] = signature[5]; - - if &module.1[0..6] == &rotated_signature[0..6] { - return Some(Module { vol_idx: i, dir }); - } - } - - None - }) - .choose(rng); - - if let Some(module) = module { - let kind = this_cell.kind.clone(); - let _ = self.set(pos, TownCell { - kind, - module: Some(module), - }); - } - } - } - } - } -} - -#[derive(Copy, Clone, PartialEq)] -pub enum ModuleKind { - This, - That, -} - -fn module(name: &str, sig: [ModuleKind; 6]) -> (Arc, [ModuleKind; 6]) { - ( - assets::load(&format!("world.module.{}", name)).unwrap(), - sig, - ) -} - -fn modules_from_kind(kind: &CellKind) -> Option<&'static [(Arc, [ModuleKind; 6])]> { - match kind { - CellKind::House(_) => Some(&HOUSE_MODULES), - CellKind::Wall => Some(&WALL_MODULES), - CellKind::Well => Some(&WELL_MODULES), - _ => None, - } -} - -lazy_static! { - pub static ref HOUSE_MODULES: Vec<(Arc, [ModuleKind; 6])> = { - use ModuleKind::*; - vec![ - module("human.floor_ground", [This, This, This, This, This, That]), - module("human.stair_ground", [This, This, This, This, This, That]), - module("human.corner_ground", [This, This, That, That, This, That]), - module("human.window_corner_ground", [ - This, This, That, That, This, That, - ]), - module("human.wall_ground", [This, This, This, That, This, That]), - module("human.door_ground", [This, This, This, That, This, That]), - module("human.window_ground", [This, This, This, That, This, That]), - module("human.floor_roof", [This, This, This, This, That, This]), - module("human.corner_roof", [This, This, That, That, That, This]), - module("human.chimney_roof", [This, This, That, That, That, This]), - module("human.wall_roof", [This, This, This, That, That, This]), - module("human.floor_upstairs", [This, This, This, This, This, This]), - module("human.balcony_upstairs", [ - This, This, This, This, This, This, - ]), - module("human.corner_upstairs", [ - This, This, That, That, This, This, - ]), - module("human.window_corner_upstairs", [ - This, This, That, That, This, This, - ]), - module("human.wall_upstairs", [This, This, This, That, This, This]), - module("human.window_upstairs", [ - This, This, This, That, This, This, - ]), - ] - }; - pub static ref WALL_MODULES: Vec<(Arc, [ModuleKind; 6])> = { - use ModuleKind::*; - vec![ - module("wall.edge_ground", [This, That, This, That, This, That]), - module("wall.edge_mid", [This, That, This, That, This, This]), - module("wall.edge_top", [This, That, This, That, That, This]), - module("wall.corner_ground", [This, This, That, That, This, That]), - module("wall.corner_mid", [This, This, That, That, This, This]), - module("wall.corner_top", [This, This, That, That, That, This]), - module("wall.end_top", [That, This, That, That, That, This]), - module("wall.single_top", [That, That, That, That, That, This]), - ] - }; - pub static ref WELL_MODULES: Vec<(Arc, [ModuleKind; 6])> = { - use ModuleKind::*; - vec![module("misc.well", [That; 6])] - }; -} - -/* -// TODO -struct ModuleModel { - near: u64, - mask: u64, - vol: Arc, -} - -#[derive(Copy, Clone)] -pub enum NearKind { - This, - That, -} - -impl ModuleModel { - pub fn generate_list(_details: &[(&str, &[([i32; 3], NearKind)])]) -> Vec { - unimplemented!() - } -} -*/ diff --git a/world/src/generator/town/util.rs b/world/src/generator/town/util.rs deleted file mode 100644 index 46fde3926f..0000000000 --- a/world/src/generator/town/util.rs +++ /dev/null @@ -1,36 +0,0 @@ -use rand::prelude::*; -use vek::*; - -pub const UNITS: [Vec2; 4] = [ - Vec2 { x: 1, y: 0 }, - Vec2 { x: 0, y: 1 }, - Vec2 { x: -1, y: 0 }, - Vec2 { x: 0, y: -1 }, -]; - -pub fn dir(i: usize) -> Vec2 { UNITS[i % 4] } - -pub fn unit(i: usize) -> (Vec2, Vec2) { (UNITS[i % 4], UNITS[(i + 1) % 4]) } - -// unused -//pub fn gen_unit(rng: &mut impl Rng) -> (Vec2, Vec2) { -// unit(rng.gen_range(0, 4)) -//} - -pub fn gen_dir(rng: &mut impl Rng) -> Vec2 { UNITS[rng.gen_range(0, 4)] } - -pub const UNITS_3D: [Vec3; 6] = [ - Vec3 { x: 1, y: 0, z: 0 }, - Vec3 { x: 0, y: 1, z: 0 }, - Vec3 { x: -1, y: 0, z: 0 }, - Vec3 { x: 0, y: -1, z: 0 }, - Vec3 { x: 0, y: 0, z: 1 }, - Vec3 { x: 0, y: 0, z: -1 }, -]; - -pub fn dir_3d(i: usize) -> Vec3 { UNITS_3D[i % 6] } - -// unused -//pub fn gen_dir_3d(rng: &mut impl Rng) -> Vec3 { -// UNITS_3D[rng.gen_range(0, 6)] -//} diff --git a/world/src/generator/town/vol.rs b/world/src/generator/town/vol.rs deleted file mode 100644 index 1d6d041ec5..0000000000 --- a/world/src/generator/town/vol.rs +++ /dev/null @@ -1,215 +0,0 @@ -use crate::util::Grid; -use common::vol::{BaseVol, ReadVol, Vox, WriteVol}; -use rand::prelude::*; -use std::ops::Range; -use vek::*; - -#[derive(Clone)] -pub enum ColumnKind { - Road, - //Wall, - Internal, - //External, // Outside the boundary wall -} - -#[derive(Clone, Default)] -pub struct TownColumn { - pub ground: i32, - pub kind: Option, -} - -impl TownColumn { - pub fn is_empty(&self) -> bool { self.kind.is_none() } - - pub fn is_road(&self) -> bool { - self.kind - .as_ref() - .map(|kind| match kind { - ColumnKind::Road => true, - _ => false, - }) - .unwrap_or(false) - } -} - -#[derive(Clone, PartialEq)] -pub struct Module { - pub vol_idx: usize, - pub dir: usize, -} - -#[derive(Clone, PartialEq)] -pub enum CellKind { - Empty, - Park, - Rock, - Road, - Wall, - House(usize), - Well, -} - -#[derive(Clone, PartialEq)] -pub struct TownCell { - pub kind: CellKind, - pub module: Option, -} - -impl TownCell { - pub fn is_space(&self) -> bool { - match self.kind { - CellKind::Empty => true, - CellKind::Park => true, - CellKind::Road => true, - _ => false, - } - } - - pub fn is_foundation(&self) -> bool { - match self.kind { - CellKind::Rock => true, - _ => false, - } - } -} - -impl Vox for TownCell { - fn empty() -> Self { - Self { - kind: CellKind::Empty, - module: None, - } - } - - fn is_empty(&self) -> bool { - match self.kind { - CellKind::Empty => true, - _ => false, - } - } -} - -impl From for TownCell { - fn from(kind: CellKind) -> Self { Self { kind, module: None } } -} - -#[derive(Debug)] -pub enum TownError { - OutOfBounds, -} - -const HEIGHT: usize = 24; -const UNDERGROUND_DEPTH: i32 = 5; - -type GridItem = (i32, TownColumn, Vec); - -pub struct TownVol { - grid: Grid, -} - -impl TownVol { - pub fn generate_from( - size: Vec2, - mut f: impl FnMut(Vec2) -> (i32, TownColumn), - mut g: impl FnMut((&TownColumn, Vec3)) -> TownCell, - ) -> Self { - let mut this = Self { - grid: Grid::new( - (0, TownColumn::default(), vec![TownCell::empty(); HEIGHT]), - size, - ), - }; - - for (pos, (base, col, cells)) in this.grid.iter_mut() { - let column = f(pos); - *base = column.0; - *col = column.1; - for z in 0..HEIGHT { - cells[z] = g(( - col, - Vec3::new(pos.x, pos.y, *base - UNDERGROUND_DEPTH + z as i32), - )); - } - } - - this - } - - pub fn size(&self) -> Vec2 { self.grid.size() } - - pub fn set_col_kind(&mut self, pos: Vec2, kind: Option) { - self.grid.get_mut(pos).map(|col| col.1.kind = kind); - } - - pub fn col(&self, pos: Vec2) -> Option<&TownColumn> { - self.grid.get(pos).map(|col| &col.1) - } - - pub fn col_range(&self, pos: Vec2) -> Option> { - self.grid.get(pos).map(|col| { - let lower = col.0 - UNDERGROUND_DEPTH; - lower..lower + HEIGHT as i32 - }) - } - - pub fn choose_column( - &self, - rng: &mut impl Rng, - mut f: impl FnMut(Vec2, &TownColumn) -> bool, - ) -> Option> { - self.grid - .iter() - .filter(|(pos, col)| f(*pos, &col.1)) - .choose(rng) - .map(|(pos, _)| pos) - } - - pub fn choose_cell( - &self, - rng: &mut impl Rng, - mut f: impl FnMut(Vec3, &TownCell) -> bool, - ) -> Option> { - self.grid - .iter() - .map(|(pos, (base, _, cells))| { - cells.iter().enumerate().map(move |(i, cell)| { - ( - Vec3::new(pos.x, pos.y, *base - UNDERGROUND_DEPTH + i as i32), - cell, - ) - }) - }) - .flatten() - .filter(|(pos, cell)| f(*pos, *cell)) - .choose(rng) - .map(|(pos, _)| pos) - } -} - -impl BaseVol for TownVol { - type Error = TownError; - type Vox = TownCell; -} - -impl ReadVol for TownVol { - fn get(&self, pos: Vec3) -> Result<&Self::Vox, Self::Error> { - match self.grid.get(Vec2::from(pos)) { - Some((base, _, cells)) => cells - .get((pos.z + UNDERGROUND_DEPTH - *base) as usize) - .ok_or(TownError::OutOfBounds), - None => Err(TownError::OutOfBounds), - } - } -} - -impl WriteVol for TownVol { - fn set(&mut self, pos: Vec3, vox: Self::Vox) -> Result<(), Self::Error> { - match self.grid.get_mut(Vec2::from(pos)) { - Some((base, _, cells)) => cells - .get_mut((pos.z + UNDERGROUND_DEPTH - *base) as usize) - .map(|cell| *cell = vox) - .ok_or(TownError::OutOfBounds), - None => Err(TownError::OutOfBounds), - } - } -} diff --git a/world/src/layer/mod.rs b/world/src/layer/mod.rs new file mode 100644 index 0000000000..6b3ebfbb50 --- /dev/null +++ b/world/src/layer/mod.rs @@ -0,0 +1,95 @@ +use crate::{ + column::ColumnSample, + util::{RandomField, Sampler}, +}; +use common::{ + terrain::{Block, BlockKind}, + vol::{BaseVol, ReadVol, RectSizedVol, Vox, WriteVol}, +}; +use std::f32; +use vek::*; + +pub fn apply_paths_to<'a>( + wpos2d: Vec2, + mut get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, + vol: &mut (impl BaseVol + RectSizedVol + ReadVol + WriteVol), +) { + for y in 0..vol.size_xy().y as i32 { + for x in 0..vol.size_xy().x as i32 { + let offs = Vec2::new(x, y); + + let wpos2d = wpos2d + offs; + + // Sample terrain + let col_sample = if let Some(col_sample) = get_column(offs) { + col_sample + } else { + continue; + }; + let surface_z = col_sample.riverless_alt.floor() as i32; + + let noisy_color = |col: Rgb, factor: u32| { + let nz = RandomField::new(0).get(Vec3::new(wpos2d.x, wpos2d.y, surface_z)); + col.map(|e| { + (e as u32 + nz % (factor * 2)) + .saturating_sub(factor) + .min(255) as u8 + }) + }; + + if let Some((path_dist, path_nearest)) = col_sample.path.filter(|(dist, _)| *dist < 5.0) + { + let inset = 0; + + // Try to use the column at the centre of the path for sampling to make them + // flatter + let col_pos = (offs - wpos2d).map(|e| e as f32) + path_nearest; + let col00 = get_column(col_pos.map(|e| e.floor() as i32) + Vec2::new(0, 0)); + let col10 = get_column(col_pos.map(|e| e.floor() as i32) + Vec2::new(1, 0)); + let col01 = get_column(col_pos.map(|e| e.floor() as i32) + Vec2::new(0, 1)); + let col11 = get_column(col_pos.map(|e| e.floor() as i32) + Vec2::new(1, 1)); + let col_attr = |col: &ColumnSample| { + Vec3::new(col.riverless_alt, col.alt, col.water_dist.unwrap_or(1000.0)) + }; + let [riverless_alt, alt, water_dist] = match (col00, col10, col01, col11) { + (Some(col00), Some(col10), Some(col01), Some(col11)) => Lerp::lerp( + Lerp::lerp(col_attr(col00), col_attr(col10), path_nearest.x.fract()), + Lerp::lerp(col_attr(col01), col_attr(col11), path_nearest.x.fract()), + path_nearest.y.fract(), + ), + _ => col_attr(col_sample), + } + .into_array(); + let (bridge_offset, depth) = ( + ((water_dist.max(0.0) * 0.2).min(f32::consts::PI).cos() + 1.0) * 5.0, + ((1.0 - ((water_dist + 2.0) * 0.3).min(0.0).cos().abs()) + * (riverless_alt + 5.0 - alt).max(0.0) + * 1.75 + + 3.0) as i32, + ); + let surface_z = (riverless_alt + bridge_offset).floor() as i32; + + for z in inset - depth..inset { + let _ = vol.set( + Vec3::new(offs.x, offs.y, surface_z + z), + if bridge_offset >= 2.0 && path_dist >= 3.0 || z < inset - 1 { + Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 80, 100), 8)) + } else { + let path_color = col_sample + .sub_surface_color + .map(|e| (e * 255.0 * 0.7) as u8); + Block::new(BlockKind::Normal, noisy_color(path_color, 8)) + }, + ); + } + let head_space = (8 - (path_dist * 0.25).powf(6.0).round() as i32).max(1); + for z in inset..inset + head_space { + let pos = Vec3::new(offs.x, offs.y, surface_z + z); + if vol.get(pos).unwrap().kind() != BlockKind::Water { + let _ = vol.set(pos, Block::empty()); + } + } + } + } + } +} diff --git a/world/src/lib.rs b/world/src/lib.rs index acb9b94ca4..6f47c7a311 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -4,10 +4,12 @@ mod all; mod block; +pub mod civ; mod column; pub mod config; -pub mod generator; +pub mod layer; pub mod sim; +pub mod site; pub mod util; // Reexports @@ -16,10 +18,11 @@ pub use crate::config::CONFIG; use crate::{ block::BlockGen, column::{ColumnGen, ColumnSample}, - util::Sampler, + util::{Grid, Sampler}, }; use common::{ - generation::{ChunkSupplement, EntityInfo, EntityKind}, + comp::{self, bird_medium, critter, quadruped_medium, quadruped_small}, + generation::{ChunkSupplement, EntityInfo}, terrain::{Block, BlockKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize}, vol::{ReadVol, RectVolSize, Vox, WriteVol}, }; @@ -34,17 +37,20 @@ pub enum Error { pub struct World { sim: sim::WorldSim, + civs: civ::Civs, } impl World { pub fn generate(seed: u32, opts: sim::WorldOpts) -> Self { - Self { - sim: sim::WorldSim::generate(seed, opts), - } + let mut sim = sim::WorldSim::generate(seed, opts); + let civs = civ::Civs::generate(seed, &mut sim); + Self { sim, civs } } pub fn sim(&self) -> &sim::WorldSim { &self.sim } + pub fn civs(&self) -> &civ::Civs { &self.civs } + pub fn tick(&self, _dt: Duration) { // TODO } @@ -63,8 +69,24 @@ impl World { // TODO: misleading name mut should_continue: impl FnMut() -> bool, ) -> Result<(TerrainChunk, ChunkSupplement), ()> { + let mut sampler = self.sample_blocks(); + + let chunk_wpos2d = Vec2::from(chunk_pos) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32); + let grid_border = 4; + let zcache_grid = Grid::populate_from( + TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + grid_border * 2, + |offs| sampler.get_z_cache(chunk_wpos2d - grid_border + offs), + ); + let air = Block::empty(); - let stone = Block::new(BlockKind::Dense, Rgb::new(200, 220, 255)); + let stone = Block::new( + BlockKind::Dense, + zcache_grid + .get(grid_border + TerrainChunkSize::RECT_SIZE.map(|e| e as i32) / 2) + .and_then(|zcache| zcache.as_ref()) + .map(|zcache| zcache.sample.stone_col) + .unwrap_or(Rgb::new(125, 120, 130)), + ); let water = Block::new(BlockKind::Water, Rgb::new(60, 90, 190)); let _chunk_size2d = TerrainChunkSize::RECT_SIZE; @@ -93,9 +115,6 @@ impl World { }; let meta = TerrainChunkMeta::new(sim_chunk.get_name(&self.sim), sim_chunk.get_biome()); - let mut sampler = self.sample_blocks(); - - let chunk_block_pos = Vec3::from(chunk_pos) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32); let mut chunk = TerrainChunk::new(base_z, stone, air, meta); for y in 0..TerrainChunkSize::RECT_SIZE.y as i32 { @@ -103,12 +122,12 @@ impl World { if should_continue() { return Err(()); }; - let wpos2d = Vec2::new(x, y) - + Vec2::from(chunk_pos) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32); - let z_cache = match sampler.get_z_cache(wpos2d) { - Some(z_cache) => z_cache, - None => continue, + let offs = Vec2::new(x, y); + + let z_cache = match zcache_grid.get(grid_border + offs) { + Some(Some(z_cache)) => z_cache, + _ => continue, }; let (min_z, only_structures_min_z, max_z) = z_cache.get_z_limits(&mut sampler); @@ -119,7 +138,7 @@ impl World { (min_z as i32..max_z as i32).for_each(|z| { let lpos = Vec3::new(x, y, z); - let wpos = chunk_block_pos + lpos; + let wpos = Vec3::from(chunk_wpos2d) + lpos; let only_structures = lpos.z >= only_structures_min_z as i32; if let Some(block) = @@ -131,45 +150,73 @@ impl World { } } + let sample_get = |offs| { + zcache_grid + .get(grid_border + offs) + .map(Option::as_ref) + .flatten() + .map(|zc| &zc.sample) + }; + + let mut rng = rand::thread_rng(); + + // Apply site generation + sim_chunk + .sites + .iter() + .for_each(|site| site.apply_to(chunk_wpos2d, sample_get, &mut chunk)); + + // Apply paths + layer::apply_paths_to(chunk_wpos2d, sample_get, &mut chunk); + let gen_entity_pos = || { let lpos2d = TerrainChunkSize::RECT_SIZE - .map(|sz| rand::thread_rng().gen::().rem_euclid(sz)); - let mut lpos = Vec3::new(lpos2d.x as i32, lpos2d.y as i32, 0); + .map(|sz| rand::thread_rng().gen::().rem_euclid(sz) as i32); + let mut lpos = Vec3::new( + lpos2d.x, + lpos2d.y, + sample_get(lpos2d).map(|s| s.alt as i32 - 32).unwrap_or(0), + ); while chunk.get(lpos).map(|vox| !vox.is_empty()).unwrap_or(false) { lpos.z += 1; } - (chunk_block_pos + lpos).map(|e| e as f32) + 0.5 + (Vec3::from(chunk_wpos2d) + lpos).map(|e: i32| e as f32) + 0.5 }; const SPAWN_RATE: f32 = 0.1; - const BOSS_RATE: f32 = 0.03; let mut supplement = ChunkSupplement { - entities: if rand::thread_rng().gen::() < SPAWN_RATE + entities: if rng.gen::() < SPAWN_RATE && sim_chunk.chaos < 0.5 && !sim_chunk.is_underwater() { - vec![EntityInfo { - pos: gen_entity_pos(), - kind: if rand::thread_rng().gen::() < BOSS_RATE { - EntityKind::Boss - } else { - EntityKind::Enemy - }, - }] + let entity = EntityInfo::at(gen_entity_pos()) + .with_alignment(comp::Alignment::Wild) + .do_if(rng.gen_range(0, 8) == 0, |e| e.into_giant()) + .with_body(match rng.gen_range(0, 4) { + 0 => comp::Body::QuadrupedMedium(quadruped_medium::Body::random()), + 1 => comp::Body::BirdMedium(bird_medium::Body::random()), + 2 => comp::Body::Critter(critter::Body::random()), + _ => comp::Body::QuadrupedSmall(quadruped_small::Body::random()), + }) + .with_automatic_name(); + + vec![entity] } else { Vec::new() }, }; if sim_chunk.contains_waypoint { - supplement = supplement.with_entity(EntityInfo { - pos: gen_entity_pos(), - kind: EntityKind::Waypoint, - }); + supplement.add_entity(EntityInfo::at(gen_entity_pos()).into_waypoint()); } + // Apply site supplementary information + sim_chunk.sites.iter().for_each(|site| { + site.apply_supplement(&mut rng, chunk_wpos2d, sample_get, &mut supplement) + }); + Ok((chunk, supplement)) } } diff --git a/world/src/sim/erosion.rs b/world/src/sim/erosion.rs index d359e04baf..27d4f05014 100644 --- a/world/src/sim/erosion.rs +++ b/world/src/sim/erosion.rs @@ -110,6 +110,14 @@ pub enum RiverKind { } impl RiverKind { + pub fn is_ocean(&self) -> bool { + if let RiverKind::Ocean = *self { + true + } else { + false + } + } + pub fn is_river(&self) -> bool { if let RiverKind::River { .. } = *self { true @@ -187,6 +195,13 @@ pub struct RiverData { } impl RiverData { + pub fn is_ocean(&self) -> bool { + self.river_kind + .as_ref() + .map(RiverKind::is_ocean) + .unwrap_or(false) + } + pub fn is_river(&self) -> bool { self.river_kind .as_ref() @@ -200,6 +215,10 @@ impl RiverData { .map(RiverKind::is_lake) .unwrap_or(false) } + + pub fn near_river(&self) -> bool { self.is_river() || self.neighbor_rivers.len() > 0 } + + pub fn near_water(&self) -> bool { self.near_river() || self.is_lake() || self.is_ocean() } } /// Draw rivers and assign them heights, widths, and velocities. Take some diff --git a/world/src/sim/location.rs b/world/src/sim/location.rs index c337357afe..2eeb1c7a3d 100644 --- a/world/src/sim/location.rs +++ b/world/src/sim/location.rs @@ -1,4 +1,3 @@ -use super::Settlement; use hashbrown::HashSet; use rand::{seq::SliceRandom, Rng}; use vek::*; @@ -9,7 +8,6 @@ pub struct Location { pub(crate) center: Vec2, pub(crate) kingdom: Option, pub(crate) neighbours: HashSet, - pub(crate) settlement: Settlement, } impl Location { @@ -19,7 +17,6 @@ impl Location { center, kingdom: None, neighbours: HashSet::default(), - settlement: Settlement::generate(rng), } } diff --git a/world/src/sim/map.rs b/world/src/sim/map.rs index 2835743fbb..af8b07300b 100644 --- a/world/src/sim/map.rs +++ b/world/src/sim/map.rs @@ -234,6 +234,8 @@ impl<'a> MapConfig<'a> { downhill, river_kind, spline_derivative, + is_path, + near_site, ) = sampler .get(pos) .map(|sample| { @@ -247,6 +249,12 @@ impl<'a> MapConfig<'a> { sample.downhill, sample.river.river_kind, sample.river.spline_derivative, + sample.path.is_path(), + sample.sites.iter().any(|site| { + site.get_origin() + .distance_squared(pos * TerrainChunkSize::RECT_SIZE.x as i32) + < 64i32.pow(2) + }), ) }) .unwrap_or(( @@ -259,6 +267,8 @@ impl<'a> MapConfig<'a> { None, None, Vec2::zero(), + false, + false, )); let humidity = humidity.min(1.0).max(0.0); @@ -368,6 +378,14 @@ impl<'a> MapConfig<'a> { ) }, }; + // TODO: Make principled. + let rgb = if near_site { + Rgb::new(0x57, 0x39, 0x33) + } else if is_path { + Rgb::new(0x37, 0x29, 0x23) + } else { + rgb + }; MapSample { rgb, diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index eb51bcefe3..675008713c 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -2,7 +2,7 @@ mod diffusion; mod erosion; mod location; mod map; -mod settlement; +mod path; mod util; // Reexports @@ -15,7 +15,7 @@ pub use self::{ }, location::Location, map::{MapConfig, MapDebug, MapSample}, - settlement::Settlement, + path::PathData, util::{ cdf_irwin_hall, downhill, get_horizon_map, get_oceans, local_cells, map_edge_factor, neighbors, uniform_idx_as_vec2, uniform_noise, uphill, vec2_as_uniform_idx, InverseCdf, @@ -26,18 +26,19 @@ pub use self::{ use crate::{ all::ForestKind, block::BlockGen, + civ::Place, column::ColumnGen, - generator::TownState, - util::{seed_expan, FastNoise, RandomField, Sampler, StructureGen2d}, + site::Site, + util::{seed_expan, FastNoise, RandomField, Sampler, StructureGen2d, LOCALITY, NEIGHBORS}, CONFIG, }; use common::{ assets, msg::server::WorldMapMsg, + store::Id, terrain::{BiomeKind, TerrainChunkSize}, vol::RectVolSize, }; -use hashbrown::HashMap; use noise::{ BasicMulti, Billow, Fbm, HybridMulti, MultiFractal, NoiseFn, RangeFunction, RidgedMulti, Seedable, SuperSimplex, Worley, @@ -53,7 +54,6 @@ use std::{ io::{BufReader, BufWriter}, ops::{Add, Div, Mul, Neg, Sub}, path::PathBuf, - sync::Arc, }; use vek::*; @@ -1306,6 +1306,8 @@ impl WorldSim { this } + pub fn get_size(&self) -> Vec2 { WORLD_SIZE.map(|e| e as u32) } + /// Draw a map of the world based on chunk information. Returns a buffer of /// u32s. pub fn get_map(&self) -> WorldMapMsg { @@ -1478,6 +1480,7 @@ impl WorldSim { }); // Place the locations onto the world + /* let gen = StructureGen2d::new(self.seed, cell_size as u32, cell_size as u32 / 2); self.chunks @@ -1517,74 +1520,9 @@ impl WorldSim { .cloned() .unwrap_or(None) .map(|loc_idx| LocationInfo { loc_idx, near }); - - let town_size = 200; - let in_town = chunk - .location - .as_ref() - .map(|l| { - locations[l.loc_idx] - .center - .map(|e| e as i64) - .distance_squared(block_pos.map(|e| e as i64)) - < town_size * town_size - }) - .unwrap_or(false); - - if in_town { - chunk.spawn_rate = 0.0; - } } }); - - // Stage 2 - towns! - let chunk_idx_center = |e: Vec2| { - e.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| { - e * sz as i32 + sz as i32 / 2 - }) - }; - let maybe_towns = self - .gen_ctx - .town_gen - .par_iter( - chunk_idx_center(Vec2::zero()), - chunk_idx_center(WORLD_SIZE.map(|e| e as i32)), - ) - .map_init( - || Box::new(BlockGen::new(ColumnGen::new(self))), - |mut block_gen, (pos, seed)| { - let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed)); - // println!("Town: {:?}", town); - TownState::generate(pos, &mut block_gen, &mut rng).map(|t| (pos, Arc::new(t))) - }, - ) - .filter_map(|x| x) - .collect::>(); - - let gen_ctx = &self.gen_ctx; - self.chunks - .par_iter_mut() - .enumerate() - .for_each(|(ij, chunk)| { - let chunk_pos = uniform_idx_as_vec2(ij); - let wpos = chunk_idx_center(chunk_pos); - - let near_towns = gen_ctx.town_gen.get(wpos); - let town = near_towns - .iter() - .min_by_key(|(pos, _seed)| wpos.distance_squared(*pos)); - - let maybe_town = town - .and_then(|(pos, _seed)| maybe_towns.get(pos)) - // Only care if we're close to the town - .filter(|town| { - Vec2::from(town.center()).distance_squared(wpos) - < town.radius().add(64).pow(2) - }) - .cloned(); - - chunk.structures.town = maybe_town; - }); + */ // Create waypoints const WAYPOINT_EVERY: usize = 16; @@ -1645,10 +1583,28 @@ impl WorldSim { } } + pub fn get_gradient_approx(&self, chunk_pos: Vec2) -> Option { + let a = self.get(chunk_pos)?; + if let Some(downhill) = a.downhill { + let b = self.get( + downhill.map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| { + e / (sz as i32) + }), + )?; + Some((a.alt - b.alt).abs() / TerrainChunkSize::RECT_SIZE.x as f32) + } else { + Some(0.0) + } + } + + pub fn get_alt_approx(&self, wpos: Vec2) -> Option { + self.get_interpolated(wpos, |chunk| chunk.alt) + } + pub fn get_wpos(&self, wpos: Vec2) -> Option<&SimChunk> { self.get( wpos.map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| { - e / sz as i32 + e.div_euclid(sz as i32) }), ) } @@ -1844,8 +1800,78 @@ impl WorldSim { Some(z0 + z1 + z2 + z3) } + + pub fn get_nearest_path(&self, wpos: Vec2) -> Option<(f32, Vec2)> { + let chunk_pos = wpos.map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| { + e.div_euclid(sz as i32) + }); + let get_chunk_centre = |chunk_pos: Vec2| { + chunk_pos.map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| { + e * sz as i32 + sz as i32 / 2 + }) + }; + + LOCALITY + .iter() + .filter_map(|ctrl| { + let chunk = self.get(chunk_pos + *ctrl)?; + let ctrl_pos = + get_chunk_centre(chunk_pos + *ctrl).map(|e| e as f32) + chunk.path.offset; + + let chunk_connections = chunk.path.neighbors.count_ones(); + if chunk_connections == 0 { + return None; + } + + let (start_pos, _start_idx) = if chunk_connections != 2 { + (ctrl_pos, None) + } else { + let (start_idx, start_rpos) = NEIGHBORS + .iter() + .copied() + .enumerate() + .find(|(i, _)| chunk.path.neighbors & (1 << *i as u8) != 0) + .unwrap(); + let start_pos_chunk = chunk_pos + *ctrl + start_rpos; + ( + get_chunk_centre(start_pos_chunk).map(|e| e as f32) + + self.get(start_pos_chunk)?.path.offset, + Some(start_idx), + ) + }; + + Some( + NEIGHBORS + .iter() + .enumerate() + .filter(move |(i, _)| chunk.path.neighbors & (1 << *i as u8) != 0) + .filter_map(move |(_, end_rpos)| { + let end_pos_chunk = chunk_pos + *ctrl + end_rpos; + let end_pos = get_chunk_centre(end_pos_chunk).map(|e| e as f32) + + self.get(end_pos_chunk)?.path.offset; + + let bez = QuadraticBezier2 { + start: (start_pos + ctrl_pos) / 2.0, + ctrl: ctrl_pos, + end: (end_pos + ctrl_pos) / 2.0, + }; + let nearest_interval = bez + .binary_search_point_by_steps(wpos.map(|e| e as f32), 16, 0.001) + .0 + .clamped(0.0, 1.0); + let pos = bez.evaluate(nearest_interval); + let dist_sqrd = pos.distance_squared(wpos.map(|e| e as f32)); + Some((dist_sqrd, pos)) + }), + ) + }) + .flatten() + .min_by_key(|(dist_sqrd, _)| (dist_sqrd * 1024.0) as i32) + .map(|(dist, pos)| (dist.sqrt(), pos)) + } } +#[derive(Debug)] pub struct SimChunk { pub chaos: f32, pub alt: f32, @@ -1861,10 +1887,12 @@ pub struct SimChunk { pub tree_density: f32, pub forest_kind: ForestKind, pub spawn_rate: f32, - pub location: Option, pub river: RiverData, + pub warp_factor: f32, - pub structures: Structures, + pub sites: Vec, + pub place: Option>, + pub path: PathData, pub contains_waypoint: bool, } @@ -1876,17 +1904,6 @@ pub struct RegionInfo { pub seed: u32, } -#[derive(Clone)] -pub struct LocationInfo { - pub loc_idx: usize, - pub near: Vec, -} - -#[derive(Clone)] -pub struct Structures { - pub town: Option>, -} - impl SimChunk { fn generate(posi: usize, gen_ctx: &GenCtx, gen_cdf: &GenCdf) -> Self { let pos = uniform_idx_as_vec2(posi); @@ -1951,7 +1968,9 @@ impl SimChunk { ) }; - let cliff = gen_ctx.cliff_nz.get((wposf.div(2048.0)).into_array()) as f32 + chaos * 0.2; + //let cliff = gen_ctx.cliff_nz.get((wposf.div(2048.0)).into_array()) as f32 + + // chaos * 0.2; + let cliff = 0.0; // Disable cliffs // Logistic regression. Make sure x ∈ (0, 1). let logit = |x: f64| x.ln() - x.neg().ln_1p(); @@ -2106,23 +2125,33 @@ impl SimChunk { } }, spawn_rate: 1.0, - location: None, river, - structures: Structures { town: None }, + warp_factor: 1.0, + + sites: Vec::new(), + place: None, + path: PathData::default(), contains_waypoint: false, } } - pub fn is_underwater(&self) -> bool { self.river.river_kind.is_some() } + pub fn is_underwater(&self) -> bool { + self.water_alt > self.alt || self.river.river_kind.is_some() + } pub fn get_base_z(&self) -> f32 { self.alt - self.chaos * 50.0 - 16.0 } - pub fn get_name(&self, world: &WorldSim) -> Option { + pub fn get_name(&self, _world: &WorldSim) -> Option { + // TODO + None + + /* if let Some(loc) = &self.location { Some(world.locations[loc.loc_idx].name().to_string()) } else { None } + */ } pub fn get_biome(&self) -> BiomeKind { diff --git a/world/src/sim/path.rs b/world/src/sim/path.rs new file mode 100644 index 0000000000..525069a1d8 --- /dev/null +++ b/world/src/sim/path.rs @@ -0,0 +1,21 @@ +use vek::*; + +#[derive(Debug)] +pub struct PathData { + pub offset: Vec2, /* Offset from centre of chunk: must not be more than half chunk + * width in any direction */ + pub neighbors: u8, // One bit for each neighbor +} + +impl PathData { + pub fn is_path(&self) -> bool { self.neighbors != 0 } +} + +impl Default for PathData { + fn default() -> Self { + Self { + offset: Vec2::zero(), + neighbors: 0, + } + } +} diff --git a/world/src/sim/settlement.rs b/world/src/sim/settlement.rs deleted file mode 100644 index 951350bcfb..0000000000 --- a/world/src/sim/settlement.rs +++ /dev/null @@ -1,86 +0,0 @@ -use rand::Rng; -use vek::*; - -#[derive(Clone, Debug)] -pub struct Settlement { - lot: Lot, -} - -impl Settlement { - pub fn generate(rng: &mut impl Rng) -> Self { - Self { - lot: Lot::generate(0, 32.0, 1.0, rng), - } - } - - pub fn get_at(&self, pos: Vec2) -> Option<&Building> { self.lot.get_at(pos) } -} - -#[derive(Clone, Debug)] -pub struct Building { - pub seed: u32, -} - -#[derive(Clone, Debug)] -enum Lot { - None, - One(Building), - Many { split_x: bool, lots: Vec }, -} - -impl Lot { - pub fn generate(deep: usize, depth: f32, aspect: f32, rng: &mut impl Rng) -> Self { - let depth = if deep < 3 { 8.0 } else { depth }; - - if (depth < 1.0 || deep > 6) && !(deep < 3 || deep % 2 == 1) { - if rng.gen::() < 0.5 { - Lot::One(Building { seed: rng.gen() }) - } else { - Lot::None - } - } else { - Lot::Many { - split_x: aspect > 1.0, - lots: { - let pow2 = 1 + rng.gen::() % 1; - let n = 1 << pow2; - - let new_aspect = if aspect > 1.0 { - aspect / n as f32 - } else { - aspect * n as f32 - }; - - let vari = (rng.gen::() - 0.35) * 2.8; - let new_depth = depth * 0.5 * (1.0 + vari); - - (0..n) - .map(|_| Lot::generate(deep + 1, new_depth, new_aspect, rng)) - .collect() - }, - } - } - } - - pub fn get_at(&self, pos: Vec2) -> Option<&Building> { - match self { - Lot::None => None, - Lot::One(building) => { - if pos.map(|e| e > 0.1 && e < 0.9).reduce_and() { - Some(building) - } else { - None - } - }, - Lot::Many { split_x, lots } => { - let split_dim = if *split_x { pos.x } else { pos.y }; - let idx = (split_dim * lots.len() as f32).floor() as usize; - lots[idx.min(lots.len() - 1)].get_at(if *split_x { - Vec2::new((pos.x * lots.len() as f32).fract(), pos.y) - } else { - Vec2::new(pos.x, (pos.y * lots.len() as f32).fract()) - }) - }, - } - } -} diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs new file mode 100644 index 0000000000..a696907e7a --- /dev/null +++ b/world/src/site/dungeon/mod.rs @@ -0,0 +1,491 @@ +use super::SpawnRules; +use crate::{ + column::ColumnSample, + sim::WorldSim, + site::BlockMask, + util::{attempt, Grid, RandomField, Sampler, CARDINALS, DIRS}, +}; +use common::{ + assets, + astar::Astar, + comp, + generation::{ChunkSupplement, EntityInfo}, + store::{Id, Store}, + terrain::{Block, BlockKind, TerrainChunkSize}, + vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol}, +}; +use rand::prelude::*; +use std::f32; +use vek::*; + +impl WorldSim { + #[allow(dead_code)] + fn can_host_dungeon(&self, pos: Vec2) -> bool { + self.get(pos) + .map(|chunk| !chunk.near_cliffs && !chunk.river.is_river() && !chunk.river.is_lake()) + .unwrap_or(false) + && self + .get_gradient_approx(pos) + .map(|grad| grad > 0.25 && grad < 1.5) + .unwrap_or(false) + } +} + +pub struct Dungeon { + origin: Vec2, + alt: i32, + #[allow(dead_code)] + noise: RandomField, + floors: Vec, +} + +pub struct GenCtx<'a, R: Rng> { + sim: Option<&'a WorldSim>, + rng: &'a mut R, +} + +impl Dungeon { + pub fn generate(wpos: Vec2, sim: Option<&WorldSim>, rng: &mut impl Rng) -> Self { + let mut ctx = GenCtx { sim, rng }; + let this = Self { + origin: wpos, + alt: ctx + .sim + .and_then(|sim| sim.get_alt_approx(wpos)) + .unwrap_or(0.0) as i32 + + 6, + noise: RandomField::new(ctx.rng.gen()), + floors: (0..6) + .scan(Vec2::zero(), |stair_tile, level| { + let (floor, st) = Floor::generate(&mut ctx, *stair_tile, level); + *stair_tile = st; + Some(floor) + }) + .collect(), + }; + + this + } + + pub fn get_origin(&self) -> Vec2 { self.origin } + + pub fn radius(&self) -> f32 { 1200.0 } + + pub fn spawn_rules(&self, _wpos: Vec2) -> SpawnRules { + SpawnRules { + ..SpawnRules::default() + } + } + + pub fn apply_to<'a>( + &'a self, + wpos2d: Vec2, + _get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, + vol: &mut (impl BaseVol + RectSizedVol + ReadVol + WriteVol), + ) { + for y in 0..vol.size_xy().y as i32 { + for x in 0..vol.size_xy().x as i32 { + let offs = Vec2::new(x, y); + + let wpos2d = wpos2d + offs; + let rpos = wpos2d - self.origin; + + let mut z = self.alt; + for floor in &self.floors { + z -= floor.total_depth(); + + let mut sampler = floor.col_sampler(rpos, z); + + for rz in 0..floor.total_depth() { + if let Some(block) = sampler(rz).finish() { + let _ = vol.set(Vec3::new(offs.x, offs.y, z + rz), block); + } + } + } + } + } + } + + pub fn apply_supplement<'a>( + &'a self, + rng: &mut impl Rng, + wpos2d: Vec2, + _get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, + supplement: &mut ChunkSupplement, + ) { + let rpos = wpos2d - self.origin; + let area = Aabr { + min: rpos, + max: rpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32), + }; + + if area.contains_point(Vec2::zero()) { + let offs = Vec2::new(rng.gen_range(-1.0, 1.0), rng.gen_range(-1.0, 1.0)) + .try_normalized() + .unwrap_or(Vec2::unit_y()) + * 16.0; + supplement.add_entity( + EntityInfo::at( + Vec3::new(self.origin.x, self.origin.y, self.alt + 4).map(|e| e as f32) + + Vec3::from(offs), + ) + .into_waypoint(), + ); + } + + let mut z = self.alt; + for floor in &self.floors { + z -= floor.total_depth(); + let origin = Vec3::new(self.origin.x, self.origin.y, z); + floor.apply_supplement(rng, area, origin, supplement); + } + } +} + +const TILE_SIZE: i32 = 13; + +#[derive(Clone)] +pub enum Tile { + UpStair, + DownStair, + Room(Id), + Tunnel, + Solid, +} + +impl Tile { + fn is_passable(&self) -> bool { + match self { + Tile::UpStair => true, + Tile::DownStair => true, + Tile::Room(_) => true, + Tile::Tunnel => true, + _ => false, + } + } +} + +pub struct Room { + seed: u32, + loot_density: f32, + enemy_density: f32, + area: Rect, +} + +pub struct Floor { + tile_offset: Vec2, + tiles: Grid, + rooms: Store, + solid_depth: i32, + hollow_depth: i32, + #[allow(dead_code)] + stair_tile: Vec2, +} + +const FLOOR_SIZE: Vec2 = Vec2::new(18, 18); + +impl Floor { + pub fn generate( + ctx: &mut GenCtx, + stair_tile: Vec2, + level: i32, + ) -> (Self, Vec2) { + let new_stair_tile = std::iter::from_fn(|| { + Some(FLOOR_SIZE.map(|sz| ctx.rng.gen_range(-sz / 2 + 2, sz / 2 - 1))) + }) + .filter(|pos| *pos != stair_tile) + .take(8) + .max_by_key(|pos| (*pos - stair_tile).map(|e| e.abs()).sum()) + .unwrap(); + + let tile_offset = -FLOOR_SIZE / 2; + let mut this = Floor { + tile_offset, + tiles: Grid::new(FLOOR_SIZE, Tile::Solid), + rooms: Store::default(), + solid_depth: if level == 0 { 80 } else { 13 * 2 }, + hollow_depth: 13, + stair_tile: new_stair_tile - tile_offset, + }; + + // Create rooms for entrance and exit + this.create_room(Room { + seed: ctx.rng.gen(), + loot_density: 0.0, + enemy_density: 0.0, + area: Rect::from((stair_tile - tile_offset - 1, Extent2::broadcast(3))), + }); + this.tiles.set(stair_tile - tile_offset, Tile::UpStair); + this.create_room(Room { + seed: ctx.rng.gen(), + loot_density: 0.0, + enemy_density: 0.0, + area: Rect::from((new_stair_tile - tile_offset - 1, Extent2::broadcast(3))), + }); + this.tiles + .set(new_stair_tile - tile_offset, Tile::DownStair); + + this.create_rooms(ctx, level, 7); + // Create routes between all rooms + let room_areas = this.rooms.iter().map(|r| r.area).collect::>(); + for a in room_areas.iter() { + for b in room_areas.iter() { + this.create_route(ctx, a.center(), b.center()); + } + } + + (this, new_stair_tile) + } + + fn create_room(&mut self, room: Room) -> Id { + let area = room.area; + let id = self.rooms.insert(room); + for x in 0..area.extent().w { + for y in 0..area.extent().h { + self.tiles + .set(area.position() + Vec2::new(x, y), Tile::Room(id)); + } + } + id + } + + fn create_rooms(&mut self, ctx: &mut GenCtx, level: i32, n: usize) { + let dim_limits = (3, 6); + + for _ in 0..n { + let area = match attempt(64, || { + let sz = Vec2::::zero().map(|_| ctx.rng.gen_range(dim_limits.0, dim_limits.1)); + let pos = FLOOR_SIZE.map2(sz, |floor_sz, room_sz| { + ctx.rng.gen_range(0, floor_sz + 1 - room_sz) + }); + let area = Rect::from((pos, Extent2::from(sz))); + let area_border = Rect::from((pos - 1, Extent2::from(sz) + 2)); // The room, but with some personal space + + // Ensure no overlap + if self + .rooms + .iter() + .any(|r| r.area.collides_with_rect(area_border)) + { + return None; + } + + Some(area) + }) { + Some(area) => area, + None => return, + }; + + self.create_room(Room { + seed: ctx.rng.gen(), + loot_density: 0.000025 + level as f32 * 0.00015, + enemy_density: 0.001 + level as f32 * 0.00004, + area, + }); + } + } + + fn create_route(&mut self, _ctx: &mut GenCtx, a: Vec2, b: Vec2) { + let heuristic = move |l: &Vec2| (l - b).map(|e| e.abs()).reduce_max() as f32; + let neighbors = |l: &Vec2| { + let l = *l; + CARDINALS + .iter() + .map(move |dir| l + dir) + .filter(|pos| self.tiles.get(*pos).is_some()) + }; + let transition = |_a: &Vec2, b: &Vec2| match self.tiles.get(*b) { + Some(Tile::Room(_)) | Some(Tile::Tunnel) => 1.0, + Some(Tile::Solid) => 25.0, + Some(Tile::UpStair) | Some(Tile::DownStair) => 0.0, + _ => 100000.0, + }; + let satisfied = |l: &Vec2| *l == b; + let mut astar = Astar::new(20000, a, heuristic); + let path = astar + .poll( + FLOOR_SIZE.product() as usize + 1, + heuristic, + neighbors, + transition, + satisfied, + ) + .into_path() + .expect("No route between locations - this shouldn't be able to happen"); + + for pos in path.iter() { + if let Some(tile @ Tile::Solid) = self.tiles.get_mut(*pos) { + *tile = Tile::Tunnel; + } + } + } + + pub fn apply_supplement( + &self, + rng: &mut impl Rng, + area: Aabr, + origin: Vec3, + supplement: &mut ChunkSupplement, + ) { + let align = |e: i32| { + e.div_euclid(TILE_SIZE) + + if e.rem_euclid(TILE_SIZE) > TILE_SIZE / 2 { + 1 + } else { + 0 + } + }; + let aligned_area = Aabr { + min: area.min.map(align) + self.tile_offset, + max: area.max.map(align) + self.tile_offset, + }; + + for x in aligned_area.min.x..aligned_area.max.x { + for y in aligned_area.min.y..aligned_area.max.y { + let tile_pos = Vec2::new(x, y); + if let Some(Tile::Room(room)) = self.tiles.get(tile_pos) { + let room = &self.rooms[*room]; + + for x in 0..TILE_SIZE { + for y in 0..TILE_SIZE { + let pos = tile_pos * TILE_SIZE + Vec2::new(x, y); + + let nth_block = + pos.x + TILE_SIZE + (pos.y + TILE_SIZE) * TILE_SIZE * FLOOR_SIZE.x; + if nth_block.rem_euclid(room.enemy_density.recip() as i32) == 0 { + // Bad + let entity = EntityInfo::at( + (origin + + Vec3::from(self.tile_offset + tile_pos) * TILE_SIZE + + TILE_SIZE / 2) + .map(|e| e as f32) + // Randomly displace them a little + + Vec3::::iota() + .map(|e| (RandomField::new(room.seed.wrapping_add(10 + e)).get(Vec3::from(tile_pos)) % 32) as i32 - 16) + .map(|e| e as f32 / 16.0), + ) + .do_if(RandomField::new(room.seed.wrapping_add(1)).chance(Vec3::from(tile_pos), 0.2), |e| e.into_giant()) + .with_alignment(comp::Alignment::Enemy) + .with_body(comp::Body::Humanoid(comp::humanoid::Body::random())) + .with_automatic_name() + .with_main_tool(assets::load_expect_cloned(match rng.gen_range(0, 6) { + 0 => "common.items.weapons.starter_axe", + 1 => "common.items.weapons.starter_sword", + 2 => "common.items.weapons.short_sword_0", + 3 => "common.items.weapons.hammer_1", + 4 => "common.items.weapons.starter_staff", + _ => "common.items.weapons.starter_bow", + })); + + supplement.add_entity(entity); + } + } + } + } + } + } + } + + pub fn total_depth(&self) -> i32 { self.solid_depth + self.hollow_depth } + + pub fn nearest_wall(&self, rpos: Vec2) -> Option> { + let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE)); + + DIRS.iter() + .map(|dir| tile_pos + *dir) + .filter(|other_tile_pos| { + self.tiles + .get(*other_tile_pos) + .filter(|tile| tile.is_passable()) + .is_none() + }) + .map(|other_tile_pos| { + rpos.clamped( + other_tile_pos * TILE_SIZE, + (other_tile_pos + 1) * TILE_SIZE - 1, + ) + }) + .min_by_key(|nearest| rpos.distance_squared(*nearest)) + } + + pub fn col_sampler(&self, pos: Vec2, floor_z: i32) -> impl FnMut(i32) -> BlockMask + '_ { + let rpos = pos - self.tile_offset * TILE_SIZE; + let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE)); + let tile_center = tile_pos * TILE_SIZE + TILE_SIZE / 2; + let rtile_pos = rpos - tile_center; + + let empty = BlockMask::new(Block::empty(), 1); + + let make_staircase = move |pos: Vec3, radius: f32, inner_radius: f32, stretch: f32| { + let stone = BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(150, 150, 175)), 5); + + if (pos.xy().magnitude_squared() as f32) < inner_radius.powf(2.0) { + stone + } else if (pos.xy().magnitude_squared() as f32) < radius.powf(2.0) { + if ((pos.x as f32).atan2(pos.y as f32) / (f32::consts::PI * 2.0) * stretch + + (floor_z + pos.z) as f32) + .rem_euclid(stretch) + < 1.5 + { + stone + } else { + empty + } + } else { + BlockMask::nothing() + } + }; + + let wall_thickness = 3.0; + let dist_to_wall = self + .nearest_wall(rpos) + .map(|nearest| (nearest.distance_squared(rpos) as f32).sqrt()) + .unwrap_or(TILE_SIZE as f32); + let tunnel_dist = + 1.0 - (dist_to_wall - wall_thickness).max(0.0) / (TILE_SIZE as f32 - wall_thickness); + + move |z| match self.tiles.get(tile_pos) { + Some(Tile::Solid) => BlockMask::nothing(), + Some(Tile::Tunnel) => { + if dist_to_wall >= wall_thickness && (z as f32) < 8.0 - 8.0 * tunnel_dist.powf(4.0) + { + empty + } else { + BlockMask::nothing() + } + }, + Some(Tile::Room(_)) | Some(Tile::DownStair) + if dist_to_wall < wall_thickness + || z as f32 >= self.hollow_depth as f32 - 13.0 * tunnel_dist.powf(4.0) => + { + BlockMask::nothing() + }, + Some(Tile::Room(room)) => { + let room = &self.rooms[*room]; + if z == 0 && RandomField::new(room.seed).chance(Vec3::from(pos), room.loot_density) + { + BlockMask::new(Block::new(BlockKind::Chest, Rgb::white()), 1) + } else { + empty + } + }, + Some(Tile::DownStair) => { + make_staircase(Vec3::new(rtile_pos.x, rtile_pos.y, z), 0.0, 0.5, 9.0) + .resolve_with(empty) + }, + Some(Tile::UpStair) => { + let mut block = make_staircase( + Vec3::new(rtile_pos.x, rtile_pos.y, z), + TILE_SIZE as f32 / 2.0, + 0.5, + 9.0, + ); + if z < self.hollow_depth { + block = block.resolve_with(empty); + } + block + }, + None => BlockMask::nothing(), + } + } +} diff --git a/world/src/site/mod.rs b/world/src/site/mod.rs new file mode 100644 index 0000000000..b0fab2d161 --- /dev/null +++ b/world/src/site/mod.rs @@ -0,0 +1,134 @@ +mod dungeon; +mod settlement; + +// Reexports +pub use self::{dungeon::Dungeon, settlement::Settlement}; + +use crate::column::ColumnSample; +use common::{ + generation::ChunkSupplement, + terrain::Block, + vol::{BaseVol, ReadVol, RectSizedVol, Vox, WriteVol}, +}; +use rand::Rng; +use std::{fmt, sync::Arc}; +use vek::*; + +#[derive(Copy, Clone)] +pub struct BlockMask { + block: Block, + priority: i32, +} + +impl BlockMask { + pub fn new(block: Block, priority: i32) -> Self { Self { block, priority } } + + pub fn nothing() -> Self { + Self { + block: Block::empty(), + priority: 0, + } + } + + pub fn with_priority(mut self, priority: i32) -> Self { + self.priority = priority; + self + } + + pub fn resolve_with(self, other: Self) -> Self { + if self.priority >= other.priority { + self + } else { + other + } + } + + pub fn finish(self) -> Option { + if self.priority > 0 { + Some(self.block) + } else { + None + } + } +} + +pub struct SpawnRules { + pub trees: bool, +} + +impl Default for SpawnRules { + fn default() -> Self { Self { trees: true } } +} + +#[derive(Clone)] +pub enum Site { + Settlement(Arc), + Dungeon(Arc), +} + +impl Site { + pub fn radius(&self) -> f32 { + match self { + Site::Settlement(settlement) => settlement.radius(), + Site::Dungeon(dungeon) => dungeon.radius(), + } + } + + pub fn get_origin(&self) -> Vec2 { + match self { + Site::Settlement(s) => s.get_origin(), + Site::Dungeon(d) => d.get_origin(), + } + } + + pub fn spawn_rules(&self, wpos: Vec2) -> SpawnRules { + match self { + Site::Settlement(s) => s.spawn_rules(wpos), + Site::Dungeon(d) => d.spawn_rules(wpos), + } + } + + pub fn apply_to<'a>( + &'a self, + wpos2d: Vec2, + get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, + vol: &mut (impl BaseVol + RectSizedVol + ReadVol + WriteVol), + ) { + match self { + Site::Settlement(settlement) => settlement.apply_to(wpos2d, get_column, vol), + Site::Dungeon(dungeon) => dungeon.apply_to(wpos2d, get_column, vol), + } + } + + pub fn apply_supplement<'a>( + &'a self, + rng: &mut impl Rng, + wpos2d: Vec2, + get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, + supplement: &mut ChunkSupplement, + ) { + match self { + Site::Settlement(settlement) => { + settlement.apply_supplement(rng, wpos2d, get_column, supplement) + }, + Site::Dungeon(dungeon) => dungeon.apply_supplement(rng, wpos2d, get_column, supplement), + } + } +} + +impl From for Site { + fn from(settlement: Settlement) -> Self { Site::Settlement(Arc::new(settlement)) } +} + +impl From for Site { + fn from(dungeon: Dungeon) -> Self { Site::Dungeon(Arc::new(dungeon)) } +} + +impl fmt::Debug for Site { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Site::Settlement(_) => write!(f, "Settlement"), + Site::Dungeon(_) => write!(f, "Dungeon"), + } + } +} diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs new file mode 100644 index 0000000000..cc499d61e2 --- /dev/null +++ b/world/src/site/settlement/building/archetype/house.rs @@ -0,0 +1,454 @@ +#![allow(dead_code)] + +use super::{super::skeleton::*, Archetype}; +use crate::{ + site::BlockMask, + util::{RandomField, Sampler}, +}; +use common::{ + terrain::{Block, BlockKind}, + vol::Vox, +}; +use rand::prelude::*; +use vek::*; + +const COLOR_THEMES: [Rgb; 11] = [ + Rgb::new(0x1D, 0x4D, 0x45), + Rgb::new(0xB3, 0x7D, 0x60), + Rgb::new(0xAC, 0x5D, 0x26), + Rgb::new(0x32, 0x46, 0x6B), + Rgb::new(0x2B, 0x19, 0x0F), + Rgb::new(0x93, 0x78, 0x51), + Rgb::new(0x92, 0x57, 0x24), + Rgb::new(0x4A, 0x4E, 0x4E), + Rgb::new(0x2F, 0x32, 0x47), + Rgb::new(0x8F, 0x35, 0x43), + Rgb::new(0x6D, 0x1E, 0x3A), +]; + +pub struct House { + roof_color: Rgb, + noise: RandomField, + roof_ribbing: bool, + roof_ribbing_diagonal: bool, +} + +enum Pillar { + None, + Chimney(i32), + Tower(i32), +} + +enum RoofStyle { + Hip, + Gable, + Rounded, +} + +enum StoreyFill { + None, + Upper, + All, +} + +impl StoreyFill { + fn has_lower(&self) -> bool { + if let StoreyFill::All = self { + true + } else { + false + } + } + + fn has_upper(&self) -> bool { + if let StoreyFill::None = self { + false + } else { + true + } + } +} + +pub struct Attr { + central_supports: bool, + storey_fill: StoreyFill, + roof_style: RoofStyle, + mansard: i32, + pillar: Pillar, +} + +impl Attr { + fn generate(rng: &mut R, locus: i32) -> Self { + Self { + central_supports: rng.gen(), + storey_fill: match rng.gen_range(0, 2) { + //0 => StoreyFill::None, + 0 => StoreyFill::Upper, + _ => StoreyFill::All, + }, + roof_style: match rng.gen_range(0, 3) { + 0 => RoofStyle::Hip, + 1 => RoofStyle::Gable, + _ => RoofStyle::Rounded, + }, + mansard: rng.gen_range(-7, 4).max(0), + pillar: match rng.gen_range(0, 4) { + 0 => Pillar::Chimney(9 + locus + rng.gen_range(0, 4)), + _ => Pillar::None, + }, + } + } +} + +impl Archetype for House { + type Attr = Attr; + + fn generate(rng: &mut R) -> (Self, Skeleton) { + let len = rng.gen_range(-8, 24).clamped(0, 20); + let locus = 6 + rng.gen_range(0, 5); + let branches_per_side = 1 + len as usize / 20; + let skel = Skeleton { + offset: -rng.gen_range(0, len + 7).clamped(0, len), + ori: if rng.gen() { Ori::East } else { Ori::North }, + root: Branch { + len, + attr: Attr { + storey_fill: StoreyFill::All, + mansard: 0, + pillar: match rng.gen_range(0, 3) { + 0 => Pillar::Chimney(9 + locus + rng.gen_range(0, 4)), + 1 => Pillar::Tower(15 + locus + rng.gen_range(0, 4)), + _ => Pillar::None, + }, + ..Attr::generate(rng, locus) + }, + locus, + border: 4, + children: [1, -1] + .iter() + .map(|flip| (0..branches_per_side).map(move |i| (i, *flip))) + .flatten() + .filter_map(|(i, flip)| { + if rng.gen() { + Some(( + i as i32 * len / (branches_per_side - 1).max(1) as i32, + Branch { + len: rng.gen_range(8, 16) * flip, + attr: Attr::generate(rng, locus), + locus: (6 + rng.gen_range(0, 3)).min(locus), + border: 4, + children: Vec::new(), + }, + )) + } else { + None + } + }) + .collect(), + }, + }; + + let this = Self { + roof_color: COLOR_THEMES + .choose(rng) + .unwrap() + .map(|e| e.saturating_add(rng.gen_range(0, 20)) - 10), + noise: RandomField::new(rng.gen()), + roof_ribbing: rng.gen(), + roof_ribbing_diagonal: rng.gen(), + }; + + (this, skel) + } + + fn draw( + &self, + dist: i32, + bound_offset: Vec2, + center_offset: Vec2, + z: i32, + ori: Ori, + branch: &Branch, + ) -> BlockMask { + let profile = Vec2::new(bound_offset.x, z); + + let make_meta = |ori| { + Rgb::new( + match ori { + Ori::East => 0, + Ori::North => 2, + }, + 0, + 0, + ) + }; + + let make_block = |r, g, b| { + let nz = self + .noise + .get(Vec3::new(center_offset.x, center_offset.y, z * 8)); + BlockMask::new( + Block::new( + BlockKind::Normal, + Rgb::new(r, g, b) + .map(|e: u8| e.saturating_add((nz & 0x0F) as u8).saturating_sub(8)), + ), + 2, + ) + }; + + let facade_layer = 3; + let structural_layer = facade_layer + 1; + let foundation_layer = structural_layer + 1; + let floor_layer = foundation_layer + 1; + + let foundation = make_block(100, 100, 100).with_priority(foundation_layer); + let log = make_block(60, 45, 30); + let floor = make_block(100, 75, 50); + let wall = make_block(200, 180, 150).with_priority(facade_layer); + let roof = make_block(self.roof_color.r, self.roof_color.g, self.roof_color.b) + .with_priority(facade_layer); + let empty = BlockMask::nothing(); + let internal = BlockMask::new(Block::empty(), structural_layer); + let end_window = BlockMask::new( + Block::new(BlockKind::Window1, make_meta(ori.flip())), + structural_layer, + ); + let fire = BlockMask::new(Block::new(BlockKind::Ember, Rgb::white()), foundation_layer); + + let ceil_height = 6; + let lower_width = branch.locus - 1; + let upper_width = branch.locus; + let width = if profile.y >= ceil_height { + upper_width + } else { + lower_width + }; + let foundation_height = 0 - (dist - width - 1).max(0); + let roof_top = 8 + width; + + if let Pillar::Chimney(chimney_top) = branch.attr.pillar { + // Chimney shaft + if center_offset.map(|e| e.abs()).reduce_max() == 0 + && profile.y >= foundation_height + 1 + { + return if profile.y == foundation_height + 1 { + fire + } else { + internal.with_priority(foundation_layer) + }; + } + + // Chimney + if center_offset.map(|e| e.abs()).reduce_max() <= 1 && profile.y < chimney_top { + // Fireplace + if center_offset.product() == 0 + && profile.y > foundation_height + 1 + && profile.y <= foundation_height + 3 + { + return internal; + } else { + return foundation; + } + } + } + + if profile.y <= foundation_height && dist < width + 3 { + // Foundations + if branch.attr.storey_fill.has_lower() { + if dist == width - 1 { + // Floor lining + return log.with_priority(floor_layer); + } else if dist < width - 1 && profile.y == foundation_height { + // Floor + return floor.with_priority(floor_layer); + } + } + + if dist < width && profile.y < foundation_height && profile.y >= foundation_height - 3 { + // Basement + return internal; + } else { + return foundation.with_priority(1); + } + } + + // Roofs and walls + let do_roof_wall = + |profile: Vec2, width, dist, bound_offset: Vec2, roof_top, mansard| { + // Roof + + let (roof_profile, roof_dist) = match &branch.attr.roof_style { + RoofStyle::Hip => (Vec2::new(dist, profile.y), dist), + RoofStyle::Gable => (profile, dist), + RoofStyle::Rounded => { + let circular_dist = (bound_offset.map(|e| e.pow(4) as f32).sum().powf(0.25) + - 0.5) + .ceil() as i32; + (Vec2::new(circular_dist, profile.y), circular_dist) + }, + }; + + let roof_level = roof_top - roof_profile.x.max(mansard); + + if profile.y > roof_level { + return None; + } + + // Roof + if profile.y == roof_level && roof_dist <= width + 2 { + let is_ribbing = ((profile.y - ceil_height) % 3 == 0 && self.roof_ribbing) + || (bound_offset.x == bound_offset.y && self.roof_ribbing_diagonal); + if (roof_profile.x == 0 && mansard == 0) || roof_dist == width + 2 || is_ribbing + { + // Eaves + return Some(log); + } else { + return Some(roof); + } + } + + // Wall + + if dist == width && profile.y < roof_level { + // Doors + if center_offset.x > 0 + && center_offset.y > 0 + && bound_offset.x > 0 + && bound_offset.x < width + && profile.y < ceil_height + && branch.attr.storey_fill.has_lower() + { + return Some( + if (bound_offset.x == (width - 1) / 2 + || bound_offset.x == (width - 1) / 2 + 1) + && profile.y <= foundation_height + 3 + { + if profile.y == foundation_height + 1 { + BlockMask::new( + Block::new( + BlockKind::Door, + if bound_offset.x == (width - 1) / 2 { + make_meta(ori.flip()) + } else { + make_meta(ori.flip()) + Rgb::new(4, 0, 0) + }, + ), + structural_layer, + ) + } else { + empty.with_priority(structural_layer) + } + } else { + wall + }, + ); + } + + if bound_offset.x == bound_offset.y || profile.y == ceil_height { + // Support beams + return Some(log); + } else if !branch.attr.storey_fill.has_lower() && profile.y < ceil_height { + return Some(empty); + } else if !branch.attr.storey_fill.has_upper() { + return Some(empty); + } else { + let (frame_bounds, frame_borders) = if profile.y >= ceil_height { + ( + Aabr { + min: Vec2::new(-1, ceil_height + 2), + max: Vec2::new(1, ceil_height + 5), + }, + Vec2::new(1, 1), + ) + } else { + ( + Aabr { + min: Vec2::new(2, foundation_height + 2), + max: Vec2::new(width - 2, ceil_height - 2), + }, + Vec2::new(1, 0), + ) + }; + let window_bounds = Aabr { + min: (frame_bounds.min + frame_borders) + .map2(frame_bounds.center(), |a, b| a.min(b)), + max: (frame_bounds.max - frame_borders) + .map2(frame_bounds.center(), |a, b| a.max(b)), + }; + + // Window + if (frame_bounds.size() + 1).reduce_min() > 2 { + // Window frame is large enough for a window + let surface_pos = Vec2::new(bound_offset.x, profile.y); + if window_bounds.contains_point(surface_pos) { + return Some(end_window); + } else if frame_bounds.contains_point(surface_pos) { + return Some(log.with_priority(structural_layer)); + }; + } + + // Wall + return Some(if branch.attr.central_supports && profile.x == 0 { + // Support beams + log.with_priority(structural_layer) + } else { + wall + }); + } + } + + if dist < width { + // Internals + if profile.y == ceil_height { + if profile.x == 0 { + // Rafters + return Some(log); + } else if branch.attr.storey_fill.has_upper() { + // Ceiling + return Some(floor); + } + } else if (!branch.attr.storey_fill.has_lower() && profile.y < ceil_height) + || (!branch.attr.storey_fill.has_upper() && profile.y >= ceil_height) + { + return Some(empty); + } else { + return Some(internal); + } + } + + None + }; + + let mut cblock = empty; + + if let Some(block) = do_roof_wall( + profile, + width, + dist, + bound_offset, + roof_top, + branch.attr.mansard, + ) { + cblock = cblock.resolve_with(block); + } + + if let Pillar::Tower(tower_top) = branch.attr.pillar { + let profile = Vec2::new(center_offset.x.abs(), profile.y); + let dist = center_offset.map(|e| e.abs()).reduce_max(); + + if let Some(block) = do_roof_wall( + profile, + 4, + dist, + center_offset.map(|e| e.abs()), + tower_top, + branch.attr.mansard, + ) { + cblock = cblock.resolve_with(block); + } + } + + cblock + } +} diff --git a/world/src/site/settlement/building/archetype/keep.rs b/world/src/site/settlement/building/archetype/keep.rs new file mode 100644 index 0000000000..6354413547 --- /dev/null +++ b/world/src/site/settlement/building/archetype/keep.rs @@ -0,0 +1,81 @@ +use super::{super::skeleton::*, Archetype}; +use crate::site::BlockMask; +use common::{ + terrain::{Block, BlockKind}, + vol::Vox, +}; +use rand::prelude::*; +use vek::*; + +pub struct Keep; + +impl Archetype for Keep { + type Attr = (); + + fn generate(rng: &mut R) -> (Self, Skeleton) { + let len = rng.gen_range(-8, 12).max(0); + let skel = Skeleton { + offset: -rng.gen_range(0, len + 7).clamped(0, len), + ori: if rng.gen() { Ori::East } else { Ori::North }, + root: Branch { + len, + attr: Self::Attr::default(), + locus: 5 + rng.gen_range(0, 5), + border: 3, + children: (0..rng.gen_range(0, 4)) + .map(|_| { + ( + rng.gen_range(-5, len + 5).clamped(0, len.max(1) - 1), + Branch { + len: rng.gen_range(5, 12) * if rng.gen() { 1 } else { -1 }, + attr: Self::Attr::default(), + locus: 5 + rng.gen_range(0, 3), + border: 3, + children: Vec::new(), + }, + ) + }) + .collect(), + }, + }; + + (Self, skel) + } + + fn draw( + &self, + dist: i32, + bound_offset: Vec2, + _center_offset: Vec2, + z: i32, + _ori: Ori, + branch: &Branch, + ) -> BlockMask { + let profile = Vec2::new(bound_offset.x, z); + + let make_block = + |r, g, b| BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(r, g, b)), 2); + + let foundation = make_block(100, 100, 100); + let wall = make_block(75, 100, 125); + let roof = make_block(150, 120, 50); + let empty = BlockMask::new(Block::empty(), 2); + + let width = branch.locus; + let rampart_width = 5 + branch.locus; + let ceil_height = 16; + + if profile.y <= 1 - (dist - width - 1).max(0) && dist < width + 3 { + // Foundations + foundation + } else if profile.y == ceil_height && dist < rampart_width { + roof + } else if dist == rampart_width && profile.y >= ceil_height && profile.y < ceil_height + 4 { + wall + } else if dist == width && profile.y <= ceil_height { + wall + } else { + empty + } + } +} diff --git a/world/src/site/settlement/building/archetype/mod.rs b/world/src/site/settlement/building/archetype/mod.rs new file mode 100644 index 0000000000..89ca5dca24 --- /dev/null +++ b/world/src/site/settlement/building/archetype/mod.rs @@ -0,0 +1,24 @@ +pub mod house; +pub mod keep; + +use super::skeleton::*; +use crate::site::BlockMask; +use rand::prelude::*; +use vek::*; + +pub trait Archetype { + type Attr; + + fn generate(rng: &mut R) -> (Self, Skeleton) + where + Self: Sized; + fn draw( + &self, + dist: i32, + bound_offset: Vec2, + center_offset: Vec2, + z: i32, + ori: Ori, + branch: &Branch, + ) -> BlockMask; +} diff --git a/world/src/site/settlement/building/mod.rs b/world/src/site/settlement/building/mod.rs new file mode 100644 index 0000000000..7687bd4bf9 --- /dev/null +++ b/world/src/site/settlement/building/mod.rs @@ -0,0 +1,61 @@ +mod archetype; +mod skeleton; + +// Reexports +pub use self::archetype::Archetype; + +use self::skeleton::*; +use common::terrain::Block; +use rand::prelude::*; +use vek::*; + +pub type HouseBuilding = Building; + +pub struct Building { + skel: Skeleton, + archetype: A, + origin: Vec3, +} + +impl Building { + pub fn generate(rng: &mut impl Rng, origin: Vec3) -> Self + where + A: Sized, + { + let (archetype, skel) = A::generate(rng); + Self { + skel, + archetype, + origin, + } + } + + pub fn bounds_2d(&self) -> Aabr { + let b = self.skel.bounds(); + Aabr { + min: Vec2::from(self.origin) + b.min, + max: Vec2::from(self.origin) + b.max, + } + } + + pub fn bounds(&self) -> Aabb { + let aabr = self.bounds_2d(); + Aabb { + min: Vec3::from(aabr.min) + Vec3::unit_z() * (self.origin.z - 8), + max: Vec3::from(aabr.max) + Vec3::unit_z() * (self.origin.z + 32), + } + } + + pub fn sample(&self, pos: Vec3) -> Option { + let rpos = pos - self.origin; + self.skel + .sample_closest( + rpos.into(), + |dist, bound_offset, center_offset, ori, branch| { + self.archetype + .draw(dist, bound_offset, center_offset, rpos.z, ori, branch) + }, + ) + .finish() + } +} diff --git a/world/src/site/settlement/building/skeleton.rs b/world/src/site/settlement/building/skeleton.rs new file mode 100644 index 0000000000..4387a9e590 --- /dev/null +++ b/world/src/site/settlement/building/skeleton.rs @@ -0,0 +1,126 @@ +use crate::site::BlockMask; +use vek::*; + +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum Ori { + East, + North, +} + +impl Ori { + pub fn flip(self) -> Self { + match self { + Ori::East => Ori::North, + Ori::North => Ori::East, + } + } + + pub fn dir(self) -> Vec2 { + match self { + Ori::East => Vec2::unit_x(), + Ori::North => Vec2::unit_y(), + } + } +} + +pub struct Branch { + pub len: i32, + pub attr: T, + pub locus: i32, + pub border: i32, + pub children: Vec<(i32, Branch)>, +} + +impl Branch { + fn for_each<'a>( + &'a self, + node: Vec2, + ori: Ori, + is_child: bool, + parent_locus: i32, + f: &mut impl FnMut(Vec2, Ori, &'a Branch, bool, i32), + ) { + f(node, ori, self, is_child, parent_locus); + for (offset, child) in &self.children { + child.for_each(node + ori.dir() * *offset, ori.flip(), true, self.locus, f); + } + } +} + +pub struct Skeleton { + pub offset: i32, + pub ori: Ori, + pub root: Branch, +} + +impl Skeleton { + pub fn for_each<'a>(&'a self, mut f: impl FnMut(Vec2, Ori, &'a Branch, bool, i32)) { + self.root + .for_each(self.ori.dir() * self.offset, self.ori, false, 0, &mut f); + } + + pub fn bounds(&self) -> Aabr { + let mut bounds = Aabr::new_empty(self.ori.dir() * self.offset); + self.for_each(|node, ori, branch, _, _| { + let node2 = node + ori.dir() * branch.len; + + let a = node.map2(node2, |a, b| a.min(b)) - (branch.locus + branch.border); + let b = node.map2(node2, |a, b| a.max(b)) + (branch.locus + branch.border); + bounds.expand_to_contain_point(a); + bounds.expand_to_contain_point(b); + }); + bounds + } + + pub fn sample_closest( + &self, + pos: Vec2, + mut f: impl FnMut(i32, Vec2, Vec2, Ori, &Branch) -> BlockMask, + ) -> BlockMask { + let mut min = None::<(_, BlockMask)>; + self.for_each(|node, ori, branch, is_child, parent_locus| { + let node2 = node + ori.dir() * branch.len; + let node = node + + if is_child { + ori.dir() + * branch.len.signum() + * (branch.locus - parent_locus).clamped(0, branch.len.abs()) + } else { + Vec2::zero() + }; + let bounds = Aabr::new_empty(node).expanded_to_contain_point(node2); + let bound_offset = if ori == Ori::East { + Vec2::new( + node.y - pos.y, + pos.x - pos.x.clamped(bounds.min.x, bounds.max.x), + ) + } else { + Vec2::new( + node.x - pos.x, + pos.y - pos.y.clamped(bounds.min.y, bounds.max.y), + ) + } + .map(|e| e.abs()); + let center_offset = if ori == Ori::East { + Vec2::new(pos.y - bounds.center().y, pos.x - bounds.center().x) + } else { + Vec2::new(pos.x - bounds.center().x, pos.y - bounds.center().y) + }; + let dist = bound_offset.reduce_max(); + let dist_locus = dist - branch.locus; + if !is_child + || match ori { + Ori::East => (pos.x - node.x) * branch.len.signum() >= 0, + Ori::North => (pos.y - node.y) * branch.len.signum() >= 0, + } + || true + { + let new_bm = f(dist, bound_offset, center_offset, ori, branch); + min = min + .map(|(_, bm)| (dist_locus, bm.resolve_with(new_bm))) + .or(Some((dist_locus, new_bm))); + } + }); + min.map(|(_, bm)| bm).unwrap_or(BlockMask::nothing()) + } +} diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs new file mode 100644 index 0000000000..cba520fa10 --- /dev/null +++ b/world/src/site/settlement/mod.rs @@ -0,0 +1,1177 @@ +mod building; + +use self::building::HouseBuilding; +use super::SpawnRules; +use crate::{ + column::ColumnSample, + sim::WorldSim, + util::{RandomField, Sampler, StructureGen2d}, +}; +use common::{ + assets, + astar::Astar, + comp::{self, bird_medium, humanoid, quadruped_small}, + generation::{ChunkSupplement, EntityInfo}, + path::Path, + spiral::Spiral2d, + store::{Id, Store}, + terrain::{Block, BlockKind, TerrainChunkSize}, + vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol}, +}; +use hashbrown::{HashMap, HashSet}; +use rand::prelude::*; +use std::{collections::VecDeque, f32}; +use vek::*; + +#[allow(dead_code)] +pub fn gradient(line: [Vec2; 2]) -> f32 { + let r = (line[0].y - line[1].y) / (line[0].x - line[1].x); + if r.is_nan() { 100000.0 } else { r } +} + +#[allow(dead_code)] +pub fn intersect(a: [Vec2; 2], b: [Vec2; 2]) -> Option> { + let ma = gradient(a); + let mb = gradient(b); + + let ca = a[0].y - ma * a[0].x; + let cb = b[0].y - mb * b[0].x; + + if (ma - mb).abs() < 0.0001 || (ca - cb).abs() < 0.0001 { + None + } else { + let x = (cb - ca) / (ma - mb); + let y = ma * x + ca; + + Some(Vec2::new(x, y)) + } +} + +#[allow(dead_code)] +pub fn center_of(p: [Vec2; 3]) -> Vec2 { + let ma = -1.0 / gradient([p[0], p[1]]); + let mb = -1.0 / gradient([p[1], p[2]]); + + let pa = (p[0] + p[1]) * 0.5; + let pb = (p[1] + p[2]) * 0.5; + + let ca = pa.y - ma * pa.x; + let cb = pb.y - mb * pb.x; + + let x = (cb - ca) / (ma - mb); + let y = ma * x + ca; + + Vec2::new(x, y) +} + +impl WorldSim { + fn can_host_settlement(&self, pos: Vec2) -> bool { + self.get(pos) + .map(|chunk| !chunk.near_cliffs && !chunk.river.is_river() && !chunk.river.is_lake()) + .unwrap_or(false) + && self + .get_gradient_approx(pos) + .map(|grad| grad < 0.75) + .unwrap_or(false) + } +} + +const AREA_SIZE: u32 = 32; + +fn to_tile(e: i32) -> i32 { ((e as f32).div_euclid(AREA_SIZE as f32)).floor() as i32 } + +pub enum StructureKind { + House(HouseBuilding), +} + +pub struct Structure { + kind: StructureKind, +} + +impl Structure { + pub fn bounds_2d(&self) -> Aabr { + match &self.kind { + StructureKind::House(house) => house.bounds_2d(), + } + } +} + +pub struct Settlement { + seed: u32, + origin: Vec2, + land: Land, + farms: Store, + structures: Vec, + town: Option, + noise: RandomField, +} + +pub struct Town { + base_tile: Vec2, +} + +pub struct Farm { + #[allow(dead_code)] + base_tile: Vec2, +} + +pub struct GenCtx<'a, R: Rng> { + sim: Option<&'a WorldSim>, + rng: &'a mut R, +} + +impl Settlement { + pub fn generate(wpos: Vec2, sim: Option<&WorldSim>, rng: &mut impl Rng) -> Self { + let mut ctx = GenCtx { sim, rng }; + let mut this = Self { + seed: ctx.rng.gen(), + origin: wpos, + land: Land::new(ctx.rng), + farms: Store::default(), + structures: Vec::new(), + town: None, + noise: RandomField::new(ctx.rng.gen()), + }; + + if let Some(sim) = ctx.sim { + this.designate_from_world(sim, ctx.rng); + } + + //this.place_river(rng); + + this.place_farms(&mut ctx); + this.place_town(&mut ctx); + //this.place_paths(ctx.rng); + this.place_buildings(&mut ctx); + + this + } + + pub fn get_origin(&self) -> Vec2 { self.origin } + + /// Designate hazardous terrain based on world data + pub fn designate_from_world(&mut self, sim: &WorldSim, rng: &mut impl Rng) { + let tile_radius = self.radius() as i32 / AREA_SIZE as i32; + let hazard = self.land.hazard; + Spiral2d::new() + .take_while(|tile| tile.map(|e| e.abs()).reduce_max() < tile_radius) + .for_each(|tile| { + let wpos = self.origin + tile * AREA_SIZE as i32; + + if (0..4) + .map(|x| (0..4).map(move |y| Vec2::new(x, y))) + .flatten() + .any(|offs| { + let wpos = wpos + offs * AREA_SIZE as i32 / 2; + let cpos = wpos.map(|e| e.div_euclid(TerrainChunkSize::RECT_SIZE.x as i32)); + !sim.can_host_settlement(cpos) + }) + || rng.gen_range(0, 16) == 0 + // Randomly consider some tiles inaccessible + { + self.land.set(tile, hazard); + } + }) + } + + /// Testing only + pub fn place_river(&mut self, rng: &mut impl Rng) { + let river_dir = Vec2::new(rng.gen::() - 0.5, rng.gen::() - 0.5).normalized(); + let radius = 500.0 + rng.gen::().powf(2.0) * 1000.0; + let river = self.land.new_plot(Plot::Water); + let river_offs = Vec2::new(rng.gen_range(-3, 4), rng.gen_range(-3, 4)); + + for x in (0..100).map(|e| e as f32 / 100.0) { + let theta0 = x as f32 * f32::consts::PI * 2.0; + let theta1 = (x + 0.01) as f32 * f32::consts::PI * 2.0; + + let pos0 = (river_dir * radius + Vec2::new(theta0.sin(), theta0.cos()) * radius) + .map(|e| e.floor() as i32) + .map(to_tile) + + river_offs; + let pos1 = (river_dir * radius + Vec2::new(theta1.sin(), theta1.cos()) * radius) + .map(|e| e.floor() as i32) + .map(to_tile) + + river_offs; + + if pos0.magnitude_squared() > 15i32.pow(2) { + continue; + } + + if let Some(path) = self.land.find_path(pos0, pos1, |_, _| 1.0) { + for pos in path.iter().copied() { + self.land.set(pos, river); + } + } + } + } + + pub fn place_paths(&mut self, rng: &mut impl Rng) { + const PATH_COUNT: usize = 6; + + let mut dir = Vec2::zero(); + for _ in 0..PATH_COUNT { + dir = (Vec2::new(rng.gen::() - 0.5, rng.gen::() - 0.5) * 2.0 - dir) + .try_normalized() + .unwrap_or(Vec2::zero()); + let origin = dir.map(|e| (e * 100.0) as i32); + let origin = self + .land + .find_tile_near(origin, |plot| match plot { + Some(&Plot::Field { .. }) => true, + _ => false, + }) + .unwrap(); + + if let Some(path) = self.town.as_ref().and_then(|town| { + self.land + .find_path(origin, town.base_tile, |from, to| match (from, to) { + (_, Some(b)) if self.land.plot(b.plot) == &Plot::Dirt => 0.0, + (_, Some(b)) if self.land.plot(b.plot) == &Plot::Water => 20.0, + (_, Some(b)) if self.land.plot(b.plot) == &Plot::Hazard => 50.0, + (Some(a), Some(b)) if a.contains(WayKind::Wall) => { + if b.contains(WayKind::Wall) { + 1000.0 + } else { + 10.0 + } + }, + (Some(_), Some(_)) => 1.0, + _ => 1000.0, + }) + }) { + let path = path.iter().copied().collect::>(); + self.land.write_path(&path, WayKind::Path, |_| true, false); + } + } + } + + pub fn place_town(&mut self, ctx: &mut GenCtx) { + const PLOT_COUNT: usize = 3; + + let mut origin = Vec2::new(ctx.rng.gen_range(-2, 3), ctx.rng.gen_range(-2, 3)); + + for i in 0..PLOT_COUNT { + if let Some(base_tile) = self.land.find_tile_near(origin, |plot| match plot { + Some(Plot::Field { .. }) => true, + Some(Plot::Dirt) => true, + _ => false, + }) { + self.land + .plot_at_mut(base_tile) + .map(|plot| *plot = Plot::Town); + + if i == 0 { + self.town = Some(Town { base_tile }); + origin = base_tile; + } + } + } + + // Boundary wall + /* + let spokes = CARDINALS + .iter() + .filter_map(|dir| { + self.land.find_tile_dir(origin, *dir, |plot| match plot { + Some(Plot::Water) => false, + Some(Plot::Town) => false, + _ => true, + }) + }) + .collect::>(); + let mut wall_path = Vec::new(); + for i in 0..spokes.len() { + self.land + .find_path(spokes[i], spokes[(i + 1) % spokes.len()], |_, to| match to + .map(|to| self.land.plot(to.plot)) + { + Some(Plot::Hazard) => 200.0, + Some(Plot::Water) => 40.0, + Some(Plot::Town) => 10000.0, + _ => 10.0, + }) + .map(|path| wall_path.extend(path.iter().copied())); + } + let grass = self.land.new_plot(Plot::Grass); + let buildable = |plot: &Plot| match plot { + Plot::Water => false, + _ => true, + }; + for pos in wall_path.iter() { + if self.land.tile_at(*pos).is_none() { + self.land.set(*pos, grass); + } + if self.land.plot_at(*pos).copied().filter(buildable).is_some() { + self.land + .tile_at_mut(*pos) + .map(|tile| tile.tower = Some(Tower::Wall)); + } + } + if wall_path.len() > 0 { + wall_path.push(wall_path[0]); + } + self.land + .write_path(&wall_path, WayKind::Wall, buildable, true); + */ + } + + pub fn place_buildings(&mut self, ctx: &mut GenCtx) { + let town_center = if let Some(town) = self.town.as_ref() { + town.base_tile + } else { + return; + }; + + for tile in Spiral2d::new() + .map(|offs| town_center + offs) + .take(16usize.pow(2)) + { + // This is a stupid way to decide how to place buildings + for _ in 0..ctx.rng.gen_range(2, 5) { + for _ in 0..25 { + let house_pos = tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 / 2) + + Vec2::::zero().map(|_| { + ctx.rng + .gen_range(-(AREA_SIZE as i32) / 2, AREA_SIZE as i32 / 2) + }); + + let tile_pos = house_pos.map(|e| e.div_euclid(AREA_SIZE as i32)); + if !matches!(self.land.plot_at(tile_pos), Some(Plot::Town)) + || self + .land + .tile_at(tile_pos) + .map(|t| t.contains(WayKind::Path)) + .unwrap_or(true) + || ctx + .sim + .and_then(|sim| sim.get_nearest_path(self.origin + house_pos)) + .map(|(dist, _)| dist < 28.0) + .unwrap_or(false) + { + continue; + } + + let structure = Structure { + kind: StructureKind::House(HouseBuilding::generate( + ctx.rng, + Vec3::new( + house_pos.x, + house_pos.y, + ctx.sim + .and_then(|sim| sim.get_alt_approx(self.origin + house_pos)) + .unwrap_or(0.0) + .ceil() as i32, + ), + )), + }; + + let bounds = structure.bounds_2d(); + + // Check for collision with other structures + if self + .structures + .iter() + .any(|s| s.bounds_2d().collides_with_aabr(bounds)) + { + continue; + } + + self.structures.push(structure); + break; + } + } + } + } + + pub fn place_farms(&mut self, ctx: &mut GenCtx) { + const FARM_COUNT: usize = 6; + const FIELDS_PER_FARM: usize = 5; + + for _ in 0..FARM_COUNT { + if let Some(base_tile) = self + .land + .find_tile_near(Vec2::zero(), |plot| plot.is_none()) + { + // Farm + //let farmhouse = self.land.new_plot(Plot::Dirt); + //self.land.set(base_tile, farmhouse); + + // Farmhouses + // for _ in 0..ctx.rng.gen_range(1, 3) { + // let house_pos = base_tile.map(|e| e * AREA_SIZE as i32 + AREA_SIZE as i32 + // / 2) + Vec2::new(ctx.rng.gen_range(-16, 16), + // ctx.rng.gen_range(-16, 16)); + + // self.structures.push(Structure { + // kind: StructureKind::House(HouseBuilding::generate(ctx.rng, + // Vec3::new( house_pos.x, + // house_pos.y, + // ctx.sim + // .and_then(|sim| sim.get_alt_approx(self.origin + house_pos)) + // .unwrap_or(0.0) + // .ceil() as i32, + // ))), + // }); + // } + + // Fields + let farmland = self.farms.insert(Farm { base_tile }); + for _ in 0..FIELDS_PER_FARM { + self.place_field(farmland, base_tile, ctx.rng); + } + } + } + } + + pub fn place_field( + &mut self, + farm: Id, + origin: Vec2, + rng: &mut impl Rng, + ) -> Option> { + const MAX_FIELD_SIZE: usize = 24; + + if let Some(center) = self.land.find_tile_near(origin, |plot| plot.is_none()) { + let field = self.land.new_plot(Plot::Field { + farm, + seed: rng.gen(), + crop: match rng.gen_range(0, 8) { + 0 => Crop::Corn, + 1 => Crop::Wheat, + 2 => Crop::Cabbage, + 3 => Crop::Pumpkin, + 4 => Crop::Flax, + 5 => Crop::Carrot, + 6 => Crop::Tomato, + 7 => Crop::Radish, + _ => Crop::Sunflower, + }, + }); + let tiles = + self.land + .grow_from(center, rng.gen_range(5, MAX_FIELD_SIZE), rng, |plot| { + plot.is_none() + }); + for pos in tiles.into_iter() { + self.land.set(pos, field); + } + Some(field) + } else { + None + } + } + + pub fn radius(&self) -> f32 { 1200.0 } + + pub fn spawn_rules(&self, wpos: Vec2) -> SpawnRules { + SpawnRules { + trees: self + .land + .get_at_block(wpos - self.origin) + .plot + .map(|p| if let Plot::Hazard = p { true } else { false }) + .unwrap_or(true), + ..SpawnRules::default() + } + } + + pub fn apply_to<'a>( + &'a self, + wpos2d: Vec2, + mut get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, + vol: &mut (impl BaseVol + RectSizedVol + ReadVol + WriteVol), + ) { + for y in 0..vol.size_xy().y as i32 { + for x in 0..vol.size_xy().x as i32 { + let offs = Vec2::new(x, y); + + let wpos2d = wpos2d + offs; + let rpos = wpos2d - self.origin; + + // Sample terrain + let col_sample = if let Some(col_sample) = get_column(offs) { + col_sample + } else { + continue; + }; + let surface_z = col_sample.riverless_alt.floor() as i32; + + // Sample settlement + let sample = self.land.get_at_block(rpos); + + let noisy_color = |col: Rgb, factor: u32| { + let nz = self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, surface_z)); + col.map(|e| { + (e as u32 + nz % (factor * 2)) + .saturating_sub(factor) + .min(255) as u8 + }) + }; + + // Paths + if let Some((WayKind::Path, dist, nearest)) = sample.way { + let inset = -1; + + // Try to use the column at the centre of the path for sampling to make them + // flatter + let col = get_column(offs + (nearest.floor().map(|e| e as i32) - rpos)) + .unwrap_or(col_sample); + let (bridge_offset, depth) = if let Some(water_dist) = col.water_dist { + ( + ((water_dist.max(0.0) * 0.2).min(f32::consts::PI).cos() + 1.0) * 5.0, + ((1.0 - ((water_dist + 2.0) * 0.3).min(0.0).cos().abs()) + * (col.riverless_alt + 5.0 - col.alt).max(0.0) + * 1.75 + + 3.0) as i32, + ) + } else { + (0.0, 3) + }; + let surface_z = (col.riverless_alt + bridge_offset).floor() as i32; + + for z in inset - depth..inset { + let _ = vol.set( + Vec3::new(offs.x, offs.y, surface_z + z), + if bridge_offset >= 2.0 && dist >= 3.0 || z < inset - 1 { + Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 80, 100), 8)) + } else { + Block::new(BlockKind::Normal, noisy_color(Rgb::new(80, 50, 30), 8)) + }, + ); + } + let head_space = (8 - (dist * 0.25).powf(6.0).round() as i32).max(1); + for z in inset..inset + head_space { + let pos = Vec3::new(offs.x, offs.y, surface_z + z); + if vol.get(pos).unwrap().kind() != BlockKind::Water { + let _ = vol.set(pos, Block::empty()); + } + } + // Ground colour + } else { + let mut surface_block = None; + + let roll = + |seed, n| self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, seed * 5)) % n; + + let color = match sample.plot { + Some(Plot::Dirt) => Some(Rgb::new(90, 70, 50)), + Some(Plot::Grass) => Some(Rgb::new(100, 200, 0)), + Some(Plot::Water) => Some(Rgb::new(100, 150, 250)), + Some(Plot::Town) => { + if let Some((_, path_nearest)) = col_sample.path { + let path_dir = (path_nearest - wpos2d.map(|e| e as f32)) + .rotated_z(f32::consts::PI / 2.0) + .normalized(); + let is_lamp = if path_dir.x.abs() > path_dir.y.abs() { + wpos2d.x as f32 % 20.0 / path_dir.dot(Vec2::unit_y()).abs() + <= 1.0 + } else { + wpos2d.y as f32 % 20.0 / path_dir.dot(Vec2::unit_x()).abs() + <= 1.0 + }; + if (col_sample.path.map(|(dist, _)| dist > 6.0 && dist < 7.0).unwrap_or(false) && is_lamp) //roll(0, 50) == 0) + || roll(0, 2000) == 0 + { + surface_block = + Some(Block::new(BlockKind::StreetLamp, Rgb::white())); + } + } + + Some(Rgb::new(100, 90, 75).map2(Rgb::iota(), |e: u8, i: i32| { + e.saturating_add( + (self.noise.get(Vec3::new(wpos2d.x, wpos2d.y, i * 5)) % 1) + as u8, + ) + .saturating_sub(8) + })) + }, + Some(Plot::Field { seed, crop, .. }) => { + let furrow_dirs = [ + Vec2::new(1, 0), + Vec2::new(0, 1), + Vec2::new(1, 1), + Vec2::new(-1, 1), + ]; + let furrow_dir = furrow_dirs[*seed as usize % furrow_dirs.len()]; + let in_furrow = (wpos2d * furrow_dir).sum().rem_euclid(5) < 2; + + let dirt = Rgb::new(80, 55, 35).map(|e| { + e + (self.noise.get(Vec3::broadcast((seed % 4096 + 0) as i32)) % 32) + as u8 + }); + let mound = + Rgb::new(70, 80, 30).map(|e| e + roll(0, 8) as u8).map(|e| { + e + (self.noise.get(Vec3::broadcast((seed % 4096 + 1) as i32)) + % 32) as u8 + }); + + if in_furrow { + if roll(0, 5) == 0 { + surface_block = match crop { + Crop::Corn => Some(BlockKind::Corn), + Crop::Wheat if roll(1, 2) == 0 => { + Some(BlockKind::WheatYellow) + }, + Crop::Wheat => Some(BlockKind::WheatGreen), + Crop::Cabbage if roll(2, 2) == 0 => { + Some(BlockKind::Cabbage) + }, + Crop::Pumpkin if roll(3, 2) == 0 => { + Some(BlockKind::Pumpkin) + }, + Crop::Flax if roll(4, 2) == 0 => Some(BlockKind::Flax), + Crop::Carrot if roll(5, 2) == 0 => Some(BlockKind::Carrot), + Crop::Tomato if roll(6, 2) == 0 => Some(BlockKind::Tomato), + Crop::Radish if roll(7, 2) == 0 => Some(BlockKind::Radish), + Crop::Turnip if roll(8, 2) == 0 => Some(BlockKind::Turnip), + Crop::Sunflower => Some(BlockKind::Sunflower), + _ => None, + } + .or_else(|| { + if roll(9, 400) == 0 { + Some(BlockKind::Scarecrow) + } else { + None + } + }) + .map(|kind| Block::new(kind, Rgb::white())); + } + } else { + if roll(0, 20) == 0 { + surface_block = + Some(Block::new(BlockKind::ShortGrass, Rgb::white())); + } else if roll(1, 30) == 0 { + surface_block = + Some(Block::new(BlockKind::MediumGrass, Rgb::white())); + } + } + + Some(if in_furrow { dirt } else { mound }) + }, + _ => None, + }; + + if let Some(color) = color { + if col_sample.water_dist.map(|dist| dist > 2.0).unwrap_or(true) { + for z in -8..3 { + let pos = Vec3::new(offs.x, offs.y, surface_z + z); + + if let (0, Some(block)) = (z, surface_block) { + let _ = vol.set(pos, block); + } else if z >= 0 { + if vol.get(pos).unwrap().kind() != BlockKind::Water { + let _ = vol.set(pos, Block::empty()); + } + } else { + let _ = vol.set( + pos, + Block::new(BlockKind::Normal, noisy_color(color, 4)), + ); + } + } + } + } + } + + // Walls + if let Some((WayKind::Wall, dist, _)) = sample.way { + let color = Lerp::lerp( + Rgb::new(130i32, 100, 0), + Rgb::new(90, 70, 50), + (RandomField::new(0).get(wpos2d.into()) % 256) as f32 / 256.0, + ) + .map(|e| (e % 256) as u8); + + let z_offset = if let Some(water_dist) = col_sample.water_dist { + // Water gate + ((water_dist.max(0.0) * 0.45).min(f32::consts::PI).cos() + 1.0) * 4.0 + } else { + 0.0 + } as i32; + + for z in z_offset..12 { + if dist / WayKind::Wall.width() < ((1.0 - z as f32 / 12.0) * 2.0).min(1.0) { + let _ = vol.set( + Vec3::new(offs.x, offs.y, surface_z + z), + Block::new(BlockKind::Normal, color), + ); + } + } + } + + // Towers + if let Some((Tower::Wall, _pos)) = sample.tower { + for z in -2..16 { + let _ = vol.set( + Vec3::new(offs.x, offs.y, surface_z + z), + Block::new(BlockKind::Normal, Rgb::new(50, 50, 50)), + ); + } + } + } + } + + // Apply structures + for structure in &self.structures { + let bounds = structure.bounds_2d(); + + // Skip this structure if it's not near this chunk + if !bounds.collides_with_aabr(Aabr { + min: wpos2d - self.origin, + max: wpos2d - self.origin + vol.size_xy().map(|e| e as i32 + 1), + }) { + continue; + } + + match &structure.kind { + StructureKind::House(b) => { + let bounds = b.bounds(); + + for x in bounds.min.x..bounds.max.x + 1 { + for y in bounds.min.y..bounds.max.y + 1 { + let col = if let Some(col) = + get_column(self.origin + Vec2::new(x, y) - wpos2d) + { + col + } else { + continue; + }; + + for z in bounds.min.z.min(col.alt.floor() as i32 - 1)..bounds.max.z + 1 + { + let rpos = Vec3::new(x, y, z); + let wpos = Vec3::from(self.origin) + rpos; + let coffs = wpos - Vec3::from(wpos2d); + + if let Some(block) = b.sample(rpos) { + let _ = vol.set(coffs, block); + } + } + } + } + }, + } + } + } + + pub fn apply_supplement<'a>( + &'a self, + rng: &mut impl Rng, + wpos2d: Vec2, + mut get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, + supplement: &mut ChunkSupplement, + ) { + for y in 0..TerrainChunkSize::RECT_SIZE.y as i32 { + for x in 0..TerrainChunkSize::RECT_SIZE.x as i32 { + let offs = Vec2::new(x, y); + + let wpos2d = wpos2d + offs; + let rpos = wpos2d - self.origin; + + // Sample terrain + let col_sample = if let Some(col_sample) = get_column(offs) { + col_sample + } else { + continue; + }; + + let sample = self.land.get_at_block(rpos); + + let entity_wpos = Vec3::new(wpos2d.x as f32, wpos2d.y as f32, col_sample.alt + 3.0); + + if matches!(sample.plot, Some(Plot::Town)) + && RandomField::new(self.seed).chance(Vec3::from(wpos2d), 1.0 / (50.0 * 50.0)) + { + let entity = EntityInfo::at(entity_wpos) + .with_alignment(comp::Alignment::Npc) + .with_body(match rng.gen_range(0, 4) { + 0 => { + let species = match rng.gen_range(0, 3) { + 0 => quadruped_small::Species::Pig, + 1 => quadruped_small::Species::Sheep, + _ => quadruped_small::Species::Cat, + }; + + comp::Body::QuadrupedSmall(quadruped_small::Body::random_with( + rng, &species, + )) + }, + 1 => { + let species = match rng.gen_range(0, 4) { + 0 => bird_medium::Species::Duck, + 1 => bird_medium::Species::Chicken, + 2 => bird_medium::Species::Goose, + _ => bird_medium::Species::Peacock, + }; + + comp::Body::BirdMedium(bird_medium::Body::random_with( + rng, &species, + )) + }, + _ => comp::Body::Humanoid(humanoid::Body::random()), + }) + .do_if(rng.gen(), |entity| { + entity.with_main_tool(assets::load_expect_cloned( + match rng.gen_range(0, 6) { + 0 => "common.items.weapons.starter_axe", + 1 => "common.items.weapons.starter_sword", + 2 => "common.items.weapons.short_sword_0", + 3 => "common.items.weapons.hammer_1", + 4 => "common.items.weapons.starter_staff", + _ => "common.items.weapons.starter_bow", + }, + )) + }) + .with_automatic_name(); + + supplement.add_entity(entity); + } + } + } + } + + pub fn get_color(&self, pos: Vec2) -> Option> { + let sample = self.land.get_at_block(pos); + + // match sample.tower { + // Some((Tower::Wall, _)) => return Some(Rgb::new(50, 50, 50)), + // _ => {}, + // } + + // match sample.way { + // Some((WayKind::Path, _, _)) => return Some(Rgb::new(90, 70, 50)), + // Some((WayKind::Hedge, _, _)) => return Some(Rgb::new(0, 150, 0)), + // Some((WayKind::Wall, _, _)) => return Some(Rgb::new(60, 60, 60)), + // _ => {}, + // } + + match sample.plot { + Some(Plot::Dirt) => return Some(Rgb::new(90, 70, 50)), + Some(Plot::Grass) => return Some(Rgb::new(100, 200, 0)), + Some(Plot::Water) => return Some(Rgb::new(100, 150, 250)), + Some(Plot::Town) => { + return Some(Rgb::new(150, 110, 60).map2(Rgb::iota(), |e: u8, i: i32| { + e.saturating_add((self.noise.get(Vec3::new(pos.x, pos.y, i * 5)) % 16) as u8) + .saturating_sub(8) + })); + }, + Some(Plot::Field { seed, .. }) => { + let furrow_dirs = [ + Vec2::new(1, 0), + Vec2::new(0, 1), + Vec2::new(1, 1), + Vec2::new(-1, 1), + ]; + let furrow_dir = furrow_dirs[*seed as usize % furrow_dirs.len()]; + let furrow = (pos * furrow_dir).sum().rem_euclid(6) < 3; + return Some(Rgb::new( + if furrow { + 100 + } else { + 32 + seed.to_le_bytes()[0] % 64 + }, + 64 + seed.to_le_bytes()[1] % 128, + 16 + seed.to_le_bytes()[2] % 32, + )); + }, + _ => {}, + } + + None + } +} + +#[derive(Copy, Clone, PartialEq)] +pub enum Crop { + Corn, + Wheat, + Cabbage, + Pumpkin, + Flax, + Carrot, + Tomato, + Radish, + Turnip, + Sunflower, +} + +#[derive(Copy, Clone, PartialEq)] +pub enum Plot { + Hazard, + Dirt, + Grass, + Water, + Town, + Field { + farm: Id, + seed: u32, + crop: Crop, + }, +} + +const CARDINALS: [Vec2; 4] = [ + Vec2::new(0, 1), + Vec2::new(1, 0), + Vec2::new(0, -1), + Vec2::new(-1, 0), +]; + +#[derive(Copy, Clone, PartialEq)] +pub enum WayKind { + Path, + #[allow(dead_code)] + Wall, +} + +impl WayKind { + pub fn width(&self) -> f32 { + match self { + WayKind::Path => 4.0, + WayKind::Wall => 3.0, + } + } +} + +#[derive(Copy, Clone, PartialEq)] +pub enum Tower { + #[allow(dead_code)] + Wall, +} + +impl Tower { + pub fn radius(&self) -> f32 { + match self { + Tower::Wall => 6.0, + } + } +} + +pub struct Tile { + plot: Id, + ways: [Option; 4], + tower: Option, +} + +impl Tile { + pub fn contains(&self, kind: WayKind) -> bool { self.ways.iter().any(|way| way == &Some(kind)) } +} + +#[derive(Default)] +pub struct Sample<'a> { + plot: Option<&'a Plot>, + way: Option<(&'a WayKind, f32, Vec2)>, + tower: Option<(&'a Tower, Vec2)>, +} + +pub struct Land { + tiles: HashMap, Tile>, + plots: Store, + sampler_warp: StructureGen2d, + hazard: Id, +} + +impl Land { + pub fn new(rng: &mut impl Rng) -> Self { + let mut plots = Store::default(); + let hazard = plots.insert(Plot::Hazard); + Self { + tiles: HashMap::new(), + plots, + sampler_warp: StructureGen2d::new(rng.gen(), AREA_SIZE, AREA_SIZE * 2 / 5), + hazard, + } + } + + pub fn get_at_block(&self, pos: Vec2) -> Sample { + let mut sample = Sample::default(); + + let neighbors = self.sampler_warp.get(pos); + let closest = neighbors + .iter() + .min_by_key(|(center, _)| center.distance_squared(pos)) + .unwrap() + .0; + + let center_tile = self.tile_at(neighbors[4].0.map(to_tile)); + + if let Some(tower) = center_tile.and_then(|tile| tile.tower.as_ref()) { + if (neighbors[4].0.distance_squared(pos) as f32) < tower.radius().powf(2.0) { + sample.tower = Some((tower, neighbors[4].0)); + } + } + + for (i, _) in CARDINALS.iter().enumerate() { + let map = [1, 5, 7, 3]; + let line = LineSegment2 { + start: neighbors[4].0.map(|e| e as f32), + end: neighbors[map[i]].0.map(|e| e as f32), + }; + if let Some(way) = center_tile.and_then(|tile| tile.ways[i].as_ref()) { + let proj_point = line.projected_point(pos.map(|e| e as f32)); + let dist = proj_point.distance(pos.map(|e| e as f32)); + if dist < way.width() { + sample.way = sample + .way + .filter(|(_, d, _)| *d < dist) + .or(Some((way, dist, proj_point))); + } + } + } + + sample.plot = self.plot_at(closest.map(to_tile)); + + sample + } + + pub fn tile_at(&self, pos: Vec2) -> Option<&Tile> { self.tiles.get(&pos) } + + #[allow(dead_code)] + pub fn tile_at_mut(&mut self, pos: Vec2) -> Option<&mut Tile> { self.tiles.get_mut(&pos) } + + pub fn plot(&self, id: Id) -> &Plot { self.plots.get(id) } + + pub fn plot_at(&self, pos: Vec2) -> Option<&Plot> { + self.tiles.get(&pos).map(|tile| self.plots.get(tile.plot)) + } + + pub fn plot_at_mut(&mut self, pos: Vec2) -> Option<&mut Plot> { + self.tiles + .get(&pos) + .map(|tile| tile.plot) + .map(move |plot| self.plots.get_mut(plot)) + } + + pub fn set(&mut self, pos: Vec2, plot: Id) { + self.tiles.insert(pos, Tile { + plot, + ways: [None; 4], + tower: None, + }); + } + + fn find_tile_near( + &self, + origin: Vec2, + mut match_fn: impl FnMut(Option<&Plot>) -> bool, + ) -> Option> { + Spiral2d::new() + .map(|pos| origin + pos) + .find(|pos| match_fn(self.plot_at(*pos))) + } + + #[allow(dead_code)] + fn find_tile_dir( + &self, + origin: Vec2, + dir: Vec2, + mut match_fn: impl FnMut(Option<&Plot>) -> bool, + ) -> Option> { + (0..) + .map(|i| origin + dir * i) + .find(|pos| match_fn(self.plot_at(*pos))) + } + + fn find_path( + &self, + origin: Vec2, + dest: Vec2, + mut path_cost_fn: impl FnMut(Option<&Tile>, Option<&Tile>) -> f32, + ) -> Option>> { + let heuristic = |pos: &Vec2| (pos - dest).map(|e| e as f32).magnitude(); + let neighbors = |pos: &Vec2| { + let pos = *pos; + CARDINALS.iter().map(move |dir| pos + *dir) + }; + let transition = + |from: &Vec2, to: &Vec2| path_cost_fn(self.tile_at(*from), self.tile_at(*to)); + let satisfied = |pos: &Vec2| *pos == dest; + + Astar::new(250, origin, heuristic) + .poll(250, heuristic, neighbors, transition, satisfied) + .into_path() + } + + fn grow_from( + &self, + start: Vec2, + max_size: usize, + _rng: &mut impl Rng, + mut match_fn: impl FnMut(Option<&Plot>) -> bool, + ) -> HashSet> { + let mut open = VecDeque::new(); + open.push_back(start); + let mut closed = HashSet::new(); + + while open.len() + closed.len() < max_size { + let next_pos = if let Some(next_pos) = open.pop_front() { + closed.insert(next_pos); + next_pos + } else { + break; + }; + + let dirs = [ + Vec2::new(1, 0), + Vec2::new(-1, 0), + Vec2::new(0, 1), + Vec2::new(0, -1), + ]; + + for dir in dirs.iter() { + let neighbor = next_pos + dir; + if !closed.contains(&neighbor) && match_fn(self.plot_at(neighbor)) { + open.push_back(neighbor); + } + } + } + + closed.into_iter().chain(open.into_iter()).collect() + } + + fn write_path( + &mut self, + tiles: &[Vec2], + kind: WayKind, + mut permit_fn: impl FnMut(&Plot) -> bool, + overwrite: bool, + ) { + for tiles in tiles.windows(2) { + let dir = tiles[1] - tiles[0]; + let idx = if dir.y > 0 { + 1 + } else if dir.x > 0 { + 2 + } else if dir.y < 0 { + 3 + } else if dir.x < 0 { + 0 + } else { + continue; + }; + if self.tile_at(tiles[0]).is_none() { + self.set(tiles[0], self.hazard); + } + let plots = &self.plots; + + self.tiles + .get_mut(&tiles[1]) + .filter(|tile| permit_fn(plots.get(tile.plot))) + .map(|tile| { + if overwrite || tile.ways[(idx + 2) % 4].is_none() { + tile.ways[(idx + 2) % 4] = Some(kind); + } + }); + self.tiles + .get_mut(&tiles[0]) + .filter(|tile| permit_fn(plots.get(tile.plot))) + .map(|tile| { + if overwrite || tile.ways[idx].is_none() { + tile.ways[idx] = Some(kind); + } + }); + } + } + + pub fn new_plot(&mut self, plot: Plot) -> Id { self.plots.insert(plot) } +} diff --git a/world/src/util/grid.rs b/world/src/util/grid.rs index b6a421ceb0..7c57cc2050 100644 --- a/world/src/util/grid.rs +++ b/world/src/util/grid.rs @@ -5,8 +5,22 @@ pub struct Grid { size: Vec2, } -impl Grid { - pub fn new(default_cell: T, size: Vec2) -> Self { +impl Grid { + pub fn populate_from(size: Vec2, mut f: impl FnMut(Vec2) -> T) -> Self { + Self { + cells: (0..size.y) + .map(|y| (0..size.x).map(move |x| Vec2::new(x, y))) + .flatten() + .map(&mut f) + .collect(), + size, + } + } + + pub fn new(size: Vec2, default_cell: T) -> Self + where + T: Clone, + { Self { cells: vec![default_cell; size.product() as usize], size, diff --git a/world/src/util/mod.rs b/world/src/util/mod.rs index 489e3469b2..ce4dbd3971 100644 --- a/world/src/util/mod.rs +++ b/world/src/util/mod.rs @@ -19,3 +19,58 @@ pub use self::{ structure::StructureGen2d, unit_chooser::UnitChooser, }; + +use vek::*; + +pub fn attempt(max_iters: usize, mut f: impl FnMut() -> Option) -> Option { + (0..max_iters).find_map(|_| f()) +} + +pub const CARDINALS: [Vec2; 4] = [ + Vec2::new(0, 1), + Vec2::new(1, 0), + Vec2::new(0, -1), + Vec2::new(-1, 0), +]; + +pub const DIRS: [Vec2; 8] = [ + Vec2::new(1, 0), + Vec2::new(1, 1), + Vec2::new(0, 1), + Vec2::new(-1, 1), + Vec2::new(-1, 0), + Vec2::new(-1, -1), + Vec2::new(0, -1), + Vec2::new(1, -1), +]; + +pub const NEIGHBORS: [Vec2; 8] = [ + Vec2::new(1, 0), + Vec2::new(1, 1), + Vec2::new(0, 1), + Vec2::new(-1, 1), + Vec2::new(-1, 0), + Vec2::new(-1, -1), + Vec2::new(0, -1), + Vec2::new(1, -1), +]; + +pub const LOCALITY: [Vec2; 9] = [ + Vec2::new(0, 0), + Vec2::new(0, 1), + Vec2::new(1, 0), + Vec2::new(0, -1), + Vec2::new(-1, 0), + Vec2::new(1, 1), + Vec2::new(1, -1), + Vec2::new(-1, 1), + Vec2::new(-1, -1), +]; + +pub const CARDINAL_LOCALITY: [Vec2; 5] = [ + Vec2::new(0, 0), + Vec2::new(0, 1), + Vec2::new(1, 0), + Vec2::new(0, -1), + Vec2::new(-1, 0), +]; diff --git a/world/src/util/random.rs b/world/src/util/random.rs index 3a36c16fa9..f9d1afdcc8 100644 --- a/world/src/util/random.rs +++ b/world/src/util/random.rs @@ -8,6 +8,10 @@ pub struct RandomField { impl RandomField { pub const fn new(seed: u32) -> Self { Self { seed } } + + pub fn chance(&self, pos: Vec3, chance: f32) -> bool { + (self.get(pos) % (1 << 10)) as f32 / ((1 << 10) as f32) < chance + } } impl Sampler<'static> for RandomField { diff --git a/world/src/util/structure.rs b/world/src/util/structure.rs index 4868159d0b..48429f3e65 100644 --- a/world/src/util/structure.rs +++ b/world/src/util/structure.rs @@ -54,10 +54,14 @@ impl StructureGen2d { let pos = Vec3::from(center); ( center - + Vec2::new( - (x_field.get(pos) % spread_mul) as i32 - spread, - (y_field.get(pos) % spread_mul) as i32 - spread, - ), + + if spread_mul > 0 { + Vec2::new( + (x_field.get(pos) % spread_mul) as i32 - spread, + (y_field.get(pos) % spread_mul) as i32 - spread, + ) + } else { + Vec2::zero() + }, seed_field.get(pos), ) }